FileSystem::delete()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 2
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 2
rs 10
c 0
b 0
f 0
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 963
    public static function getFileSystem()
86
    {
87 963
        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 962
        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 924
    public function canonicalize($strPath)
183
    {
184 924
        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 933
    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 933
        @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 933
        $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 933
        if (!@file_exists($strPath) && !is_dir($strPath)) {
216 277
            $strPath = $f->getParent();
217 277
            if (null === $strPath || !is_dir($strPath)) {
218 57
                $strPath = Phing::getProperty('user.dir');
219
            }
220
            //$strPath = dirname($strPath);
221
        }
222
223 933
        if (!$write) {
224 933
            return (bool) @is_readable($strPath);
225
        }
226
227 207
        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 60
    public function getLastModifiedTime(File $f)
253
    {
254 60
        if (!$f->exists()) {
255 6
            return 0;
256
        }
257
258 60
        @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 60
        error_clear_last();
260 60
        $strPath = (string) $f->getPath();
261
262 60
        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 60
            $mtime = @filemtime($strPath);
272
        }
273
274 60
        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 60
        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 85
    public function createNewFile($strPathname)
325
    {
326 85
        if (@file_exists($strPathname)) {
327
            return false;
328
        }
329
330
        // Create new file
331 85
        $fp = @fopen($strPathname, 'w');
332 85
        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 1
            );
338
        }
339 84
        @fclose($fp);
340
341 84
        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 233
    public function delete(File $f, $recursive = false)
353
    {
354 233
        if ($f->isDirectory()) {
355 187
            $this->rmdir($f->getPath(), $recursive);
356
        } else {
357 176
            $this->unlink($f->getPath());
358
        }
359
    }
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
    }
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 187
    public function createDirectory(&$f, $mode = null)
396
    {
397 187
        if (null === $mode) {
398
            try {
399 181
                $return = @mkdir($f->getAbsolutePath());
400
            } catch (\Throwable $t) {
401
                $return = false;
402
            }
403
        } else {
404
            // If the $mode is specified, mkdir() is called with the $mode
405
            // argument so that the new directory does not temporarily have
406
            // higher permissions than it should before chmod() is called.
407
            try {
408 16
                $return = @mkdir($f->getAbsolutePath(), $mode);
409
            } catch (\Throwable $t) {
410
                $return = false;
411
            }
412 16
            if ($return) {
413 16
                chmod($f->getAbsolutePath(), $mode);
414
            }
415
        }
416
417 187
        return $return;
418
    }
419
420
    /**
421
     * Rename the file or directory denoted by the first abstract pathname to
422
     * the second abstract pathname, returning true if and only if
423
     * the operation succeeds.
424
     *
425
     * @param File $f1 abstract source file
426
     * @param File $f2 abstract destination file
427
     *
428
     * @throws IOException if rename cannot be performed
429
     */
430 1
    public function rename(File $f1, File $f2)
431
    {
432 1
        error_clear_last();
433
        // get the canonical paths of the file to rename
434 1
        $src = $f1->getAbsolutePath();
435 1
        $dest = $f2->getAbsolutePath();
436 1
        if (false === @rename($src, $dest)) {
437
            $lastError = error_get_last();
438
            $errormsg = $lastError['message'] ?? 'unknown error';
439
            $msg = "Rename FAILED. Cannot rename {$src} to {$dest}. {$errormsg}";
440
441
            throw new IOException($msg);
442
        }
443
    }
444
445
    /**
446
     * Set the last-modified time of the file or directory denoted by the
447
     * given abstract pathname returning true if and only if the
448
     * operation succeeds.
449
     *
450
     * @param int $time
451
     *
452
     * @throws IOException
453
     */
454 83
    public function setLastModifiedTime(File $f, $time)
455
    {
456 83
        error_clear_last();
457 83
        $path = $f->getPath();
458 83
        $success = @touch($path, $time);
459 83
        if (!$success) {
460
            $lastError = error_get_last();
461
            $errormsg = $lastError['message'] ?? 'unknown error';
462
463
            throw new IOException("Could not touch '" . $path . "' due to: {$errormsg}");
464
        }
465
    }
466
467
    // -- Basic infrastructure --
468
469
    /**
470
     * Compare two abstract pathnames lexicographically.
471
     *
472
     * @throws IOException
473
     *
474
     * @return int
475
     */
476
    public function compare(File $f1, File $f2)
477
    {
478
        throw new IOException('compare() not implemented by local fs driver');
479
    }
480
481
    /**
482
     * Copy a file.
483
     *
484
     * @param File $src  source path and name file to copy
485
     * @param File $dest destination path and name of new file
486
     *
487
     * @throws IOException if file cannot be copied
488
     */
489 27
    public function copy(File $src, File $dest)
490
    {
491
        // Recursively copy a directory
492 27
        if ($src->isDirectory()) {
493
            $this->copyr($src->getAbsolutePath(), $dest->getAbsolutePath());
494
        }
495
496 27
        $srcPath = $src->getAbsolutePath();
497 27
        $destPath = $dest->getAbsolutePath();
498
499 27
        error_clear_last();
500 27
        if (false === @copy($srcPath, $destPath)) { // Copy FAILED. Log and return err.
501
            // Add error from php to end of log message. $errormsg.
502
            $lastError = error_get_last();
503
            $errormsg = $lastError['message'] ?? 'unknown error';
504
            $msg = "FileSystem::copy() FAILED. Cannot copy {$srcPath} to {$destPath}. {$errormsg}";
505
506
            throw new IOException($msg);
507
        }
508
509 27
        $dest->setMode($src->getMode());
510
    }
511
512
    /**
513
     * Copy a file, or recursively copy a folder and its contents.
514
     *
515
     * @param string $source Source path
516
     * @param string $dest   Destination path
517
     *
518
     * @return bool Returns TRUE on success, FALSE on failure
519
     *
520
     * @author  Aidan Lister <[email protected]>
521
     *
522
     * @version 1.0.1
523
     *
524
     * @see    http://aidanlister.com/repos/v/function.copyr.php
525
     */
526
    public function copyr($source, $dest)
527
    {
528
        // Check for symlinks
529
        if (is_link($source)) {
530
            return symlink(readlink($source), $dest);
531
        }
532
533
        // Simple copy for a file
534
        if (is_file($source)) {
535
            return copy($source, $dest);
536
        }
537
538
        // Make destination directory
539
        if (!is_dir($dest) && !mkdir($dest) && !is_dir($dest)) {
540
            return false;
541
        }
542
543
        // Loop through the folder
544
        $dir = dir($source);
545
        while (false !== $entry = $dir->read()) {
546
            // Skip pointers
547
            if ('.' == $entry || '..' == $entry) {
548
                continue;
549
            }
550
551
            // Deep copy directories
552
            $this->copyr("{$source}/{$entry}", "{$dest}/{$entry}");
553
        }
554
555
        // Clean up
556
        $dir->close();
557
558
        return true;
559
    }
560
561
    /**
562
     * Change the ownership on a file or directory.
563
     *
564
     * @param string $pathname path and name of file or directory
565
     * @param string $user     The user name or number of the file or directory. See http://us.php.net/chown
566
     *
567
     * @throws IOException if operation failed
568
     */
569 1
    public function chown($pathname, $user)
570
    {
571 1
        error_clear_last();
572 1
        if (false === @chown($pathname, $user)) { // FAILED.
573
            $lastError = error_get_last();
574
            $errormsg = $lastError['message'] ?? 'unknown error';
575
576
            throw new IOException("FileSystem::chown() FAILED. Cannot chown {$pathname}. User {$user} {$errormsg}");
577
        }
578
    }
579
580
    /**
581
     * Change the group on a file or directory.
582
     *
583
     * @param string $pathname path and name of file or directory
584
     * @param string $group    The group of the file or directory. See http://us.php.net/chgrp
585
     *
586
     * @throws IOException if operation failed
587
     */
588 1
    public function chgrp($pathname, $group)
589
    {
590 1
        error_clear_last();
591 1
        if (false === @chgrp($pathname, $group)) { // FAILED.
592
            $lastError = error_get_last();
593
            $errormsg = $lastError['message'] ?? 'unknown error';
594
595
            throw new IOException("FileSystem::chgrp() FAILED. Cannot chown {$pathname}. Group {$group} {$errormsg}");
596
        }
597
    }
598
599
    /**
600
     * Change the permissions on a file or directory.
601
     *
602
     * @param string $pathname path and name of file or directory
603
     * @param int    $mode     The mode (permissions) of the file or
604
     *                         directory. If using octal add leading
605
     *                         0. eg. 0777. Mode is affected by the
606
     *                         umask system setting.
607
     *
608
     * @throws IOException if operation failed
609
     */
610 67
    public function chmod($pathname, $mode)
611
    {
612 67
        error_clear_last();
613 67
        $str_mode = decoct($mode); // Show octal in messages.
614 67
        if (false === @chmod($pathname, $mode)) { // FAILED.
615
            $lastError = error_get_last();
616
            $errormsg = $lastError['message'] ?? 'unknown error';
617
618
            throw new IOException("FileSystem::chmod() FAILED. Cannot chmod {$pathname}. Mode {$str_mode} {$errormsg}");
619
        }
620
    }
621
622
    /**
623
     * Locks a file and throws an Exception if this is not possible.
624
     *
625
     * @throws IOException
626
     */
627
    public function lock(File $f)
628
    {
629
        $filename = $f->getPath();
630
        $fp = @fopen($filename, 'w');
631
        $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

631
        $result = @flock(/** @scrutinizer ignore-type */ $fp, LOCK_EX);
Loading history...
632
        @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

632
        @fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
633
        if (!$result) {
634
            throw new IOException("Could not lock file '{$filename}'");
635
        }
636
    }
637
638
    /**
639
     * Unlocks a file and throws an IO Error if this is not possible.
640
     *
641
     * @throws IOException
642
     */
643
    public function unlock(File $f)
644
    {
645
        $filename = $f->getPath();
646
        $fp = @fopen($filename, 'w');
647
        $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

647
        $result = @flock(/** @scrutinizer ignore-type */ $fp, LOCK_UN);
Loading history...
648
        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

648
        fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
649
        if (!$result) {
650
            throw new IOException("Could not unlock file '{$filename}'");
651
        }
652
    }
653
654
    /**
655
     * Delete a file.
656
     *
657
     * @param string $file path and/or name of file to delete
658
     *
659
     * @throws IOException - if an error is encountered
660
     */
661 180
    public function unlink($file)
662
    {
663 180
        error_clear_last();
664 180
        if (false === @unlink($file)) {
665
            $lastError = error_get_last();
666
            $errormsg = $lastError['message'] ?? 'unknown error';
667
            $msg = "FileSystem::unlink() FAILED. Cannot unlink '{$file}'. {$errormsg}";
668
669
            throw new IOException($msg);
670
        }
671
    }
672
673
    /**
674
     * Symbolically link a file to another name.
675
     *
676
     * Currently symlink is not implemented on Windows. Don't use if the application is to be portable.
677
     *
678
     * @param string $target path and/or name of file to link
679
     * @param string $link   path and/or name of link to be created
680
     *
681
     * @throws IOException
682
     */
683 15
    public function symlink($target, $link)
684
    {
685 15
        error_clear_last();
686
        // If Windows OS then symlink() will report it is not supported in
687
        // the build. Use this error instead of checking for Windows as the OS.
688
689 15
        if (false === @symlink($target, $link)) {
690
            $lastError = error_get_last();
691
            $errormsg = $lastError['message'] ?? 'unknown error';
692
            // Add error from php to end of log message.
693
            $msg = "FileSystem::Symlink() FAILED. Cannot symlink '{$target}' to '{$link}'. {$errormsg}";
694
695
            throw new IOException($msg);
696
        }
697
    }
698
699
    /**
700
     * Set the modification and access time on a file to the present time.
701
     *
702
     * @param string $file path and/or name of file to touch
703
     * @param int    $time
704
     *
705
     * @throws Exception
706
     */
707
    public function touch($file, $time = null)
708
    {
709
        error_clear_last();
710
        if (null === $time) {
711
            $error = @touch($file);
712
        } else {
713
            $error = @touch($file, $time);
714
        }
715
716
        if (false === $error) { // FAILED.
717
            $lastError = error_get_last();
718
            $errormsg = $lastError['message'] ?? 'unknown error';
719
            // Add error from php to end of log message.
720
            $msg = "FileSystem::touch() FAILED. Cannot touch '{$file}'. {$errormsg}";
721
722
            throw new Exception($msg);
723
        }
724
    }
725
726
    /**
727
     * Delete an empty directory OR a directory and all of its contents.
728
     *
729
     * @param string $dir      path and/or name of directory to delete
730
     * @param bool   $children False: don't delete directory contents.
731
     *                         True: delete directory contents.
732
     *
733
     * @throws Exception
734
     */
735 187
    public function rmdir($dir, $children = false)
736
    {
737 187
        error_clear_last();
738
739
        // If children=FALSE only delete dir if empty.
740 187
        if (false === $children) {
741 186
            if (false === @rmdir($dir)) { // FAILED.
742
                $lastError = error_get_last();
743
                $errormsg = $lastError['message'] ?? 'unknown error';
744
                // Add error from php to end of log message.
745
                $msg = "FileSystem::rmdir() FAILED. Cannot rmdir {$dir}. {$errormsg}";
746
747
                throw new Exception($msg);
748
            }
749
        } else { // delete contents and dir.
750 2
            $handle = @opendir($dir);
751 2
            $lastError = error_get_last();
752 2
            $errormsg = $lastError['message'] ?? 'unknown error';
753
754 2
            if (false === $handle) { // Error.
755
                $msg = "FileSystem::rmdir() FAILED. Cannot opendir() {$dir}. {$errormsg}";
756
757
                throw new Exception($msg);
758
            }
759
            // Read from handle.
760
            // Don't error on readdir().
761 2
            while (false !== ($entry = @readdir($handle))) {
762 2
                if ('.' != $entry && '..' != $entry) {
763
                    // Only add / if it isn't already the last char.
764
                    // This ONLY serves the purpose of making the Logger
765
                    // output look nice:)
766
767
                    if (0 === strpos(strrev($dir), DIRECTORY_SEPARATOR)) { // there is a /
768
                        $next_entry = $dir . $entry;
769
                    } else { // no /
770
                        $next_entry = $dir . DIRECTORY_SEPARATOR . $entry;
771
                    }
772
773
                    // NOTE: As of php 4.1.1 is_dir doesn't return FALSE it
774
                    // returns 0. So use == not ===.
775
776
                    // Don't error on is_dir()
777
                    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...
778
                        try {
779
                            $this->unlink($next_entry); // Delete.
780
                        } catch (Exception $e) {
781
                            $msg = "FileSystem::Rmdir() FAILED. Cannot FileSystem::Unlink() {$next_entry}. " . $e->getMessage();
782
783
                            throw new Exception($msg);
784
                        }
785
                    } else { // Is directory.
786
                        try {
787
                            $this->rmdir($next_entry, true); // Delete
788
                        } catch (Exception $e) {
789
                            $msg = "FileSystem::rmdir() FAILED. Cannot FileSystem::rmdir() {$next_entry}. " . $e->getMessage();
790
791
                            throw new Exception($msg);
792
                        }
793
                    }
794
                }
795
            }
796
797
            // Don't error on closedir()
798 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...
799
800 2
            error_clear_last();
801 2
            if (false === @rmdir($dir)) { // FAILED.
802
                // Add error from php to end of log message.
803
                $lastError = error_get_last();
804
                $errormsg = $lastError['message'] ?? 'unknown error';
805
                $msg = "FileSystem::rmdir() FAILED. Cannot rmdir {$dir}. {$errormsg}";
806
807
                throw new Exception($msg);
808
            }
809
        }
810
    }
811
812
    /**
813
     * Set the umask for file and directory creation.
814
     *
815
     * @param int $mode
816
     *
817
     * @throws Exception
818
     *
819
     * @internal param Int $mode . Permissions usually in ocatal. Use leading 0 for
820
     *                    octal. Number between 0 and 0777.
821
     */
822
    public function umask($mode)
823
    {
824
        error_clear_last();
825
        // CONSIDERME:
826
        // Throw a warning if mode is 0. PHP converts illegal octal numbers to
827
        // 0 so 0 might not be what the user intended.
828
829
        $str_mode = decoct($mode); // Show octal in messages.
830
831
        if (false === @umask($mode)) { // FAILED.
832
            $lastError = error_get_last();
833
            $errormsg = $lastError['message'] ?? 'unknown error';
834
            // Add error from php to end of log message.
835
            $msg = "FileSystem::Umask() FAILED. Value {$str_mode}. {$errormsg}";
836
837
            throw new Exception($msg);
838
        }
839
    }
840
841
    /**
842
     * Compare the modified time of two files.
843
     *
844
     * @param string $file1 path and name of file1
845
     * @param string $file2 path and name of file2
846
     *
847
     * @throws exception - if cannot get modified time of either file
848
     *
849
     * @return int 1 if file1 is newer.
850
     *             -1 if file2 is newer.
851
     *             0 if files have the same time.
852
     *             Err object on failure.
853
     */
854
    public function compareMTimes($file1, $file2)
855
    {
856
        $mtime1 = filemtime($file1);
857
        $mtime2 = filemtime($file2);
858
859
        if (false === $mtime1) { // FAILED. Log and return err.
860
            // Add error from php to end of log message.
861
            $msg = "FileSystem::compareMTimes() FAILED. Cannot can not get modified time of {$file1}.";
862
863
            throw new Exception($msg);
864
        }
865
866
        if (false === $mtime2) { // FAILED. Log and return err.
867
            // Add error from php to end of log message.
868
            $msg = "FileSystem::compareMTimes() FAILED. Cannot can not get modified time of {$file2}.";
869
870
            throw new Exception($msg);
871
        }
872
873
        // Worked. Log and return compare.
874
        // Compare mtimes.
875
        if ($mtime1 == $mtime2) {
876
            return 0;
877
        }
878
879
        return ($mtime1 < $mtime2) ? -1 : 1; // end compare
880
    }
881
882
    /**
883
     * returns the contents of a directory in an array.
884
     *
885
     * @return string[]
886
     */
887 236
    public function listContents(File $f)
888
    {
889 236
        return array_map('strval', array_keys(
890 236
            iterator_to_array(
891 236
                new FilesystemIterator(
892 236
                    $f->getAbsolutePath(),
893 236
                    FilesystemIterator::KEY_AS_FILENAME | FilesystemIterator::SKIP_DOTS
894 236
                )
895 236
            )
896 236
        ));
897
    }
898
899
    /**
900
     * PHP implementation of the 'which' command.
901
     *
902
     * Used to retrieve/determine the full path for a command.
903
     *
904
     * @param string $executable Executable file to search for
905
     * @param mixed  $fallback   default to fallback to
906
     *
907
     * @return string full path for the specified executable/command
908
     */
909 8
    public function which($executable, $fallback = false)
910
    {
911 8
        if (is_string($executable)) {
0 ignored issues
show
introduced by
The condition is_string($executable) is always true.
Loading history...
912 7
            if ('' === trim($executable)) {
913
                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...
914
            }
915
        } else {
916 1
            return $fallback;
917
        }
918 7
        if (basename($executable) === $executable) {
919 7
            $path = getenv('PATH');
920
        } else {
921
            $path = dirname($executable);
922
        }
923 7
        $dirSeparator = $this->getSeparator();
924 7
        $pathSeparator = $this->getPathSeparator();
925 7
        $elements = explode($pathSeparator, $path);
926 7
        $amount = count($elements);
927 7
        $fstype = Phing::getProperty('host.fstype');
928
929
        switch ($fstype) {
930 7
            case 'UNIX':
931 7
                for ($count = 0; $count < $amount; ++$count) {
932 7
                    $file = $elements[$count] . $dirSeparator . $executable;
933 7
                    if (file_exists($file) && is_executable($file)) {
934 4
                        return $file;
935
                    }
936
                }
937
938 3
                break;
939
940
            case 'WINDOWS':
941
                $exts = getenv('PATHEXT');
942
                if (false === $exts) {
943
                    $exts = ['.exe', '.bat', '.cmd', '.com'];
944
                } else {
945
                    $exts = explode($pathSeparator, $exts);
946
                }
947
                for ($count = 0; $count < $amount; ++$count) {
948
                    foreach ($exts as $ext) {
949
                        $file = $elements[$count] . $dirSeparator . $executable . $ext;
950
                        // Not all of the extensions above need to be set executable on Windows for them to be executed.
951
                        // I'm sure there's a joke here somewhere.
952
                        if (file_exists($file)) {
953
                            return $file;
954
                        }
955
                    }
956
                }
957
958
                break;
959
        }
960 3
        if (file_exists($executable) && is_executable($executable)) {
961
            return $executable;
962
        }
963
964 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...
965
    }
966
}
967