Passed
Push — master ( cbf6d3...d62b89 )
by Michiel
08:56
created

FileSystem   F

Complexity

Total Complexity 99

Size/Duplication

Total Lines 900
Duplicated Lines 0 %

Test Coverage

Coverage 47.96%

Importance

Changes 0
Metric Value
wmc 99
eloc 270
dl 0
loc 900
ccs 141
cts 294
cp 0.4796
rs 2
c 0
b 0
f 0

29 Methods

Rating   Name   Duplication   Size   Complexity  
A deleteOnExit() 0 3 1
A setLastModifiedTime() 0 9 2
A delete() 0 6 2
A checkAccess() 0 28 6
A copy() 0 20 3
A compare() 0 3 1
A getLastModifiedTime() 0 30 5
A rename() 0 11 2
A getLength() 0 13 2
A canDelete() 0 6 1
A createNewFile() 0 17 3
A createDirectory() 0 15 3
B copyr() 0 33 9
A deleteFilesOnExit() 0 4 2
A getFileSystem() 0 16 4
A canonicalize() 0 3 1
A symlink() 0 13 2
A umask() 0 15 2
C which() 0 51 15
A touch() 0 15 3
A chgrp() 0 7 2
C rmdir() 0 68 12
A compareMTimes() 0 24 5
A unlink() 0 8 2
A listContents() 0 7 1
A unlock() 0 8 2
A chown() 0 7 2
A lock() 0 8 2
A chmod() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like FileSystem often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FileSystem, and based on these observations, apply Extract Interface, too.

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

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

204
        /** @scrutinizer ignore-unhandled */ @clearstatcache();

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...
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...
205
206
207
        // Shouldn't this be $f->GetAbsolutePath() ?
208
        // And why doesn't GetAbsolutePath() work?
209
210 865
        $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 865
        if (!@file_exists($strPath) && !is_dir($strPath)) {
216 248
            $strPath = $f->getParent();
217 248
            if ($strPath === null || !is_dir($strPath)) {
218 52
                $strPath = Phing::getProperty("user.dir");
219
            }
220
            //$strPath = dirname($strPath);
221
        }
222
223 865
        if (!$write) {
224 865
            return (bool) @is_readable($strPath);
225
        }
226
227 188
        return (bool) @is_writable($strPath);
228
    }
229
230
    /**
231
     * Whether file can be deleted.
232
     *
233
     * @param  File $f
234
     * @return boolean
235
     */
236
    public function canDelete(File $f)
237
    {
238
        clearstatcache();
239
        $dir = dirname($f->getAbsolutePath());
240
241
        return @is_writable($dir);
242
    }
243
244
    /**
245
     * Return the time at which the file or directory denoted by the given
246
     * abstract pathname was last modified, or zero if it does not exist or
247
     * some other I/O error occurs.
248
     *
249
     * @param  File $f
250
     * @return int
251
     * @throws IOException
252
     */
253 55
    public function getLastModifiedTime(File $f)
254
    {
255 55
        if (!$f->exists()) {
256 5
            return 0;
257
        }
258
259 55
        @clearstatcache();
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for clearstatcache(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

259
        /** @scrutinizer ignore-unhandled */ @clearstatcache();

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...
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...
260 55
        error_clear_last();
261 55
        $strPath = (string) $f->getPath();
262
263 55
        if (@is_link($strPath)) {
264 2
            $stats = @lstat($strPath);
265
266 2
            if (!isset($stats['mtime'])) {
267
                $mtime = false;
268
            } else {
269 2
                $mtime = $stats['mtime'];
270
            }
271
        } else {
272 55
            $mtime = @filemtime($strPath);
273
        }
274
275 55
        if (false === $mtime) {
276
            $lastError = error_get_last();
277
            $errormsg = $lastError['message'] ?? 'unknown error';
278
            $msg = "FileSystem::getLastModifiedTime() FAILED. Can not get modified time of $strPath. $errormsg";
279
            throw new IOException($msg);
280
        }
281
282 55
        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
     * @param  File $f
291
     * @return int
292
     * @throws IOException
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 ($fs !== false) {
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
        throw new IOException($msg);
307
    }
308
309
    /* -- File operations -- */
310
311
    /**
312
     * Create a new empty file with the given pathname.  Return
313
     * true if the file was created and false if a
314
     * file or directory with the given pathname already exists.  Throw an
315
     * IOException if an I/O error occurs.
316
     *
317
     * @param  string $strPathname Path of the file to be created.
318
     * @return boolean
319
     * @throws IOException
320
     */
321 80
    public function createNewFile($strPathname)
322
    {
323 80
        if (@file_exists($strPathname)) {
324
            return false;
325
        }
326
327
        // Create new file
328 80
        $fp = @fopen($strPathname, "w");
329 80
        if ($fp === false) {
330 1
            $error = error_get_last();
331 1
            throw new IOException(
332 1
                "The file \"$strPathname\" could not be created: " . $error['message'] ?? 'unknown error'
333
            );
334
        }
335 79
        @fclose($fp);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for fclose(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

335
        /** @scrutinizer ignore-unhandled */ @fclose($fp);

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...
336
337 79
        return true;
338
    }
339
340
    /**
341
     * Delete the file or directory denoted by the given abstract pathname,
342
     * returning true if and only if the operation succeeds.
343
     *
344
     * @param  File    $f
345
     * @param  boolean $recursive
346
     * @throws IOException
347
     */
348 209
    public function delete(File $f, $recursive = false)
349
    {
350 209
        if ($f->isDirectory()) {
351 170
            $this->rmdir($f->getPath(), $recursive);
352
        } else {
353 160
            $this->unlink($f->getPath());
354
        }
355 209
    }
356
357
    /**
358
     * Arrange for the file or directory denoted by the given abstract
359
     * pathname to be deleted when Phing::shutdown is called, returning
360
     * true if and only if the operation succeeds.
361
     *
362
     * @param  File $f
363
     * @throws IOException
364
     */
365
    public function deleteOnExit(File $f)
366
    {
367
        self::$filesToDelete[] = $f;
368
    }
369
370 1
    public static function deleteFilesOnExit()
371
    {
372 1
        foreach (self::$filesToDelete as $file) {
373
            $file->delete();
374
        }
375 1
    }
376
377
    /**
378
     * Create a new directory denoted by the given abstract pathname,
379
     * returning true if and only if the operation succeeds.
380
     *
381
     * The behaviour is the same as of "mkdir" command in Linux.
382
     * Without $mode argument, the directory is created with permissions
383
     * corresponding to the umask setting.
384
     * If $mode argument is specified, umask setting is ignored and
385
     * the permissions are set according to the $mode argument using chmod().
386
     *
387
     * @param  File     $f
388
     * @param  int|null $mode
389
     * @return boolean
390
     */
391 170
    public function createDirectory(&$f, $mode = null)
392
    {
393 170
        if ($mode === null) {
394 167
            $return = @mkdir($f->getAbsolutePath());
395
        } else {
396
            // If the $mode is specified, mkdir() is called with the $mode
397
            // argument so that the new directory does not temporarily have
398
            // higher permissions than it should before chmod() is called.
399 11
            $return = @mkdir($f->getAbsolutePath(), $mode);
400 11
            if ($return) {
401 11
                chmod($f->getAbsolutePath(), $mode);
402
            }
403
        }
404
405 170
        return $return;
406
    }
407
408
    /**
409
     * Rename the file or directory denoted by the first abstract pathname to
410
     * the second abstract pathname, returning true if and only if
411
     * the operation succeeds.
412
     *
413
     * @param  File $f1 abstract source file
414
     * @param  File $f2 abstract destination file
415
     * @return void
416
     * @throws IOException if rename cannot be performed
417
     */
418 1
    public function rename(File $f1, File $f2)
419
    {
420 1
        error_clear_last();
421
        // get the canonical paths of the file to rename
422 1
        $src = $f1->getAbsolutePath();
423 1
        $dest = $f2->getAbsolutePath();
424 1
        if (false === @rename($src, $dest)) {
425
            $lastError = error_get_last();
426
            $errormsg = $lastError['message'] ?? 'unknown error';
427
            $msg = "Rename FAILED. Cannot rename $src to $dest. $errormsg";
428
            throw new IOException($msg);
429
        }
430 1
    }
431
432
    /**
433
     * Set the last-modified time of the file or directory denoted by the
434
     * given abstract pathname returning true if and only if the
435
     * operation succeeds.
436
     *
437
     * @param  File $f
438
     * @param  int  $time
439
     * @return void
440
     * @throws IOException
441
     */
442 78
    public function setLastModifiedTime(File $f, $time)
443
    {
444 78
        error_clear_last();
445 78
        $path = $f->getPath();
446 78
        $success = @touch($path, $time);
447 78
        if (!$success) {
448
            $lastError = error_get_last();
449
            $errormsg = $lastError['message'] ?? 'unknown error';
450
            throw new IOException("Could not touch '" . $path . "' due to: $errormsg");
451
        }
452 78
    }
453
454
    /* -- Basic infrastructure -- */
455
456
    /**
457
     * Compare two abstract pathnames lexicographically.
458
     *
459
     * @param  File $f1
460
     * @param  File $f2
461
     * @return int
462
     * @throws IOException
463
     */
464
    public function compare(File $f1, File $f2)
465
    {
466
        throw new IOException("compare() not implemented by local fs driver");
467
    }
468
469
    /**
470
     * Copy a file.
471
     *
472
     * @param File $src  Source path and name file to copy.
473
     * @param File $dest Destination path and name of new file.
474
     *
475
     * @return void
476
     *
477
     * @throws IOException if file cannot be copied.
478
     */
479 25
    public function copy(File $src, File $dest)
480
    {
481
        // Recursively copy a directory
482 25
        if ($src->isDirectory()) {
483
            $this->copyr($src->getAbsolutePath(), $dest->getAbsolutePath());
484
        }
485
486 25
        $srcPath = $src->getAbsolutePath();
487 25
        $destPath = $dest->getAbsolutePath();
488
489 25
        error_clear_last();
490 25
        if (false === @copy($srcPath, $destPath)) { // Copy FAILED. Log and return err.
491
            // Add error from php to end of log message. $errormsg.
492
            $lastError = error_get_last();
493
            $errormsg = $lastError['message'] ?? 'unknown error';
494
            $msg = "FileSystem::copy() FAILED. Cannot copy $srcPath to $destPath. $errormsg";
495
            throw new IOException($msg);
496
        }
497
498 25
        $dest->setMode($src->getMode());
499 25
    }
500
501
    /**
502
     * Copy a file, or recursively copy a folder and its contents
503
     *
504
     * @param string $source Source path
505
     * @param string $dest   Destination path
506
     *
507
     * @return  bool   Returns TRUE on success, FALSE on failure
508
     * @author  Aidan Lister <[email protected]>
509
     * @version 1.0.1
510
     * @link    http://aidanlister.com/repos/v/function.copyr.php
511
     */
512
    public function copyr($source, $dest)
513
    {
514
        // Check for symlinks
515
        if (is_link($source)) {
516
            return symlink(readlink($source), $dest);
517
        }
518
519
        // Simple copy for a file
520
        if (is_file($source)) {
521
            return copy($source, $dest);
522
        }
523
524
        // Make destination directory
525
        if (!is_dir($dest) && !mkdir($dest) && !is_dir($dest)) {
526
            return false;
527
        }
528
529
        // Loop through the folder
530
        $dir = dir($source);
531
        while (false !== $entry = $dir->read()) {
532
            // Skip pointers
533
            if ($entry == '.' || $entry == '..') {
534
                continue;
535
            }
536
537
            // Deep copy directories
538
            $this->copyr("$source/$entry", "$dest/$entry");
539
        }
540
541
        // Clean up
542
        $dir->close();
543
544
        return true;
545
    }
546
547
    /**
548
     * Change the ownership on a file or directory.
549
     *
550
     * @param string $pathname Path and name of file or directory.
551
     * @param string $user     The user name or number of the file or directory. See http://us.php.net/chown
552
     *
553
     * @return void
554
     *
555
     * @throws IOException if operation failed.
556
     */
557
    public function chown($pathname, $user)
558
    {
559
        error_clear_last();
560
        if (false === @chown($pathname, $user)) { // FAILED.
561
            $lastError = error_get_last();
562
            $errormsg = $lastError['message'] ?? 'unknown error';
563
            throw new IOException("FileSystem::chown() FAILED. Cannot chown $pathname. User $user $errormsg");
564
        }
565
    }
566
567
    /**
568
     * Change the group on a file or directory.
569
     *
570
     * @param string $pathname Path and name of file or directory.
571
     * @param string $group    The group of the file or directory. See http://us.php.net/chgrp
572
     *
573
     * @return void
574
     * @throws IOException if operation failed.
575
     */
576
    public function chgrp($pathname, $group)
577
    {
578
        error_clear_last();
579
        if (false === @chgrp($pathname, $group)) { // FAILED.
580
            $lastError = error_get_last();
581
            $errormsg = $lastError['message'] ?? 'unknown error';
582
            throw new IOException("FileSystem::chgrp() FAILED. Cannot chown $pathname. Group $group $errormsg");
583
        }
584
    }
585
586
    /**
587
     * Change the permissions on a file or directory.
588
     *
589
     * @param string $pathname Path and name of file or directory.
590
     * @param int    $mode     The mode (permissions) of the file or
591
     *                         directory. If using octal add leading
592
     *                         0. eg. 0777. Mode is affected by the
593
     *                         umask system setting.
594
     *
595
     * @return void
596
     * @throws IOException if operation failed.
597
     */
598 60
    public function chmod($pathname, $mode)
599
    {
600 60
        error_clear_last();
601 60
        $str_mode = decoct($mode); // Show octal in messages.
602 60
        if (false === @chmod($pathname, $mode)) { // FAILED.
603
            $lastError = error_get_last();
604
            $errormsg = $lastError['message'] ?? 'unknown error';
605
            throw new IOException("FileSystem::chmod() FAILED. Cannot chmod $pathname. Mode $str_mode $errormsg");
606
        }
607 60
    }
608
609
    /**
610
     * Locks a file and throws an Exception if this is not possible.
611
     *
612
     * @param  File $f
613
     * @return void
614
     * @throws IOException
615
     */
616
    public function lock(File $f)
617
    {
618
        $filename = $f->getPath();
619
        $fp = @fopen($filename, "w");
620
        $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

620
        $result = @flock(/** @scrutinizer ignore-type */ $fp, LOCK_EX);
Loading history...
621
        @fclose($fp);
1 ignored issue
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

621
        @fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
Security Best Practice introduced by
It seems like you do not handle an error condition for fclose(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

621
        /** @scrutinizer ignore-unhandled */ @fclose($fp);

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...
622
        if (!$result) {
623
            throw new IOException("Could not lock file '$filename'");
624
        }
625
    }
626
627
    /**
628
     * Unlocks a file and throws an IO Error if this is not possible.
629
     *
630
     * @param  File $f
631
     * @return void
632
     * @throws IOException
633
     */
634
    public function unlock(File $f)
635
    {
636
        $filename = $f->getPath();
637
        $fp = @fopen($filename, "w");
638
        $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

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

639
        fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
640
        if (!$result) {
641
            throw new IOException("Could not unlock file '$filename'");
642
        }
643
    }
644
645
    /**
646
     * Delete a file.
647
     *
648
     * @param string $file Path and/or name of file to delete.
649
     *
650
     * @return void
651
     * @throws IOException - if an error is encountered.
652
     */
653 163
    public function unlink($file)
654
    {
655 163
        error_clear_last();
656 163
        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
            throw new IOException($msg);
661
        }
662 163
    }
663
664
    /**
665
     * Symbolically link a file to another name.
666
     *
667
     * Currently symlink is not implemented on Windows. Don't use if the application is to be portable.
668
     *
669
     * @param  string $target Path and/or name of file to link.
670
     * @param  string $link   Path and/or name of link to be created.
671
     * @return void
672
     * @throws IOException
673
     */
674 15
    public function symlink($target, $link)
675
    {
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
            throw new IOException($msg);
687
        }
688 15
    }
689
690
    /**
691
     * Set the modification and access time on a file to the present time.
692
     *
693
     * @param  string $file Path and/or name of file to touch.
694
     * @param  int    $time
695
     * @return void
696
     * @throws Exception
697
     */
698
    public function touch($file, $time = null)
699
    {
700
        error_clear_last();
701
        if (null === $time) {
702
            $error = @touch($file);
703
        } else {
704
            $error = @touch($file, $time);
705
        }
706
707
        if (false === $error) { // FAILED.
708
            $lastError = error_get_last();
709
            $errormsg = $lastError['message'] ?? 'unknown error';
710
            // Add error from php to end of log message.
711
            $msg = "FileSystem::touch() FAILED. Cannot touch '$file'. $errormsg";
712
            throw new Exception($msg);
713
        }
714
    }
715
716
    /**
717
     * Delete an empty directory OR a directory and all of its contents.
718
     *
719
     * @param string $dir      Path and/or name of directory to delete.
720
     * @param bool   $children False: don't delete directory contents.
721
     *                         True: delete directory contents.
722
     *
723
     * @return void
724
     * @throws Exception
725
     */
726 170
    public function rmdir($dir, $children = false)
727
    {
728 170
        error_clear_last();
729
730
        // If children=FALSE only delete dir if empty.
731 170
        if (false === $children) {
732 169
            if (false === @rmdir($dir)) { // FAILED.
733
                $lastError = error_get_last();
734
                $errormsg = $lastError['message'] ?? 'unknown error';
735
                // Add error from php to end of log message.
736
                $msg = "FileSystem::rmdir() FAILED. Cannot rmdir $dir. $errormsg";
737 169
                throw new Exception($msg);
738
            }
739
        } else { // delete contents and dir.
740 2
            $handle = @opendir($dir);
741 2
            $lastError = error_get_last();
742 2
            $errormsg = $lastError['message'] ?? 'unknown error';
743
744 2
            if (false === $handle) { // Error.
745
                $msg = "FileSystem::rmdir() FAILED. Cannot opendir() $dir. $errormsg";
746
                throw new Exception($msg);
747
            }
748
            // Read from handle.
749
            // Don't error on readdir().
750 2
            while (false !== ($entry = @readdir($handle))) {
751 2
                if ($entry != '.' && $entry != '..') {
752
                    // Only add / if it isn't already the last char.
753
                    // This ONLY serves the purpose of making the Logger
754
                    // output look nice:)
755
756
                    if (strpos(strrev($dir), DIRECTORY_SEPARATOR) === 0) { // there is a /
757
                        $next_entry = $dir . $entry;
758
                    } else { // no /
759
                        $next_entry = $dir . DIRECTORY_SEPARATOR . $entry;
760
                    }
761
762
                    // NOTE: As of php 4.1.1 is_dir doesn't return FALSE it
763
                    // returns 0. So use == not ===.
764
765
                    // Don't error on is_dir()
766
                    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...
767
                        try {
768
                            $this->unlink($next_entry); // Delete.
769
                        } catch (Exception $e) {
770
                            $msg = "FileSystem::Rmdir() FAILED. Cannot FileSystem::Unlink() $next_entry. " . $e->getMessage();
771
                            throw new Exception($msg);
772
                        }
773
                    } else { // Is directory.
774
                        try {
775
                            $this->rmdir($next_entry, true); // Delete
776
                        } catch (Exception $e) {
777
                            $msg = "FileSystem::rmdir() FAILED. Cannot FileSystem::rmdir() $next_entry. " . $e->getMessage();
778
                            throw new Exception($msg);
779
                        }
780
                    }
781
                }
782
            }
783
784
            // Don't error on closedir()
785 2
            @closedir($handle);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for closedir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

785
            /** @scrutinizer ignore-unhandled */ @closedir($handle);

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...
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...
786
787 2
            error_clear_last();
788 2
            if (false === @rmdir($dir)) { // FAILED.
789
                // Add error from php to end of log message.
790
                $lastError = error_get_last();
791
                $errormsg = $lastError['message'] ?? 'unknown error';
792
                $msg = "FileSystem::rmdir() FAILED. Cannot rmdir $dir. $errormsg";
793
                throw new Exception($msg);
794
            }
795
        }
796 170
    }
797
798
    /**
799
     * Set the umask for file and directory creation.
800
     *
801
     * @param    Int $mode
802
     * @return   void
803
     * @throws   Exception
804
     * @internal param Int $mode . Permissions usually in ocatal. Use leading 0 for
805
     *                    octal. Number between 0 and 0777.
806
     */
807
    public function umask($mode)
808
    {
809
        error_clear_last();
810
        // CONSIDERME:
811
        // Throw a warning if mode is 0. PHP converts illegal octal numbers to
812
        // 0 so 0 might not be what the user intended.
813
814
        $str_mode = decoct($mode); // Show octal in messages.
815
816
        if (false === @umask($mode)) { // FAILED.
817
            $lastError = error_get_last();
818
            $errormsg = $lastError['message'] ?? 'unknown error';
819
            // Add error from php to end of log message.
820
            $msg = "FileSystem::Umask() FAILED. Value $str_mode. $errormsg";
821
            throw new Exception($msg);
822
        }
823
    }
824
825
    /**
826
     * Compare the modified time of two files.
827
     *
828
     * @param string $file1 Path and name of file1.
829
     * @param string $file2 Path and name of file2.
830
     *
831
     * @return int  1 if file1 is newer.
832
     *              -1 if file2 is newer.
833
     *              0 if files have the same time.
834
     *              Err object on failure.
835
     *
836
     * @throws Exception - if cannot get modified time of either file.
837
     */
838
    public function compareMTimes($file1, $file2)
839
    {
840
        $mtime1 = filemtime($file1);
841
        $mtime2 = filemtime($file2);
842
843
        if ($mtime1 === false) { // FAILED. Log and return err.
844
            // Add error from php to end of log message.
845
            $msg = "FileSystem::compareMTimes() FAILED. Cannot can not get modified time of $file1.";
846
            throw new Exception($msg);
847
        }
848
849
        if ($mtime2 === false) { // FAILED. Log and return err.
850
            // Add error from php to end of log message.
851
            $msg = "FileSystem::compareMTimes() FAILED. Cannot can not get modified time of $file2.";
852
            throw new Exception($msg);
853
        }
854
855
        // Worked. Log and return compare.
856
        // Compare mtimes.
857
        if ($mtime1 == $mtime2) {
858
            return 0;
859
        }
860
861
        return ($mtime1 < $mtime2) ? -1 : 1; // end compare
862
    }
863
864
    /**
865
     * returns the contents of a directory in an array
866
     *
867
     * @param  File $f
868
     * @return string[]
869
     */
870 222
    public function listContents(File $f)
871
    {
872 222
        return array_keys(
873 222
            iterator_to_array(
874 222
                new FilesystemIterator(
875 222
                    $f->getAbsolutePath(),
876 222
                    FilesystemIterator::KEY_AS_FILENAME
877
                )
878
            )
879
        );
880
    }
881
882
    /**
883
     * PHP implementation of the 'which' command.
884
     *
885
     * Used to retrieve/determine the full path for a command.
886
     *
887
     * @param string $executable Executable file to search for
888
     * @param mixed  $fallback   Default to fallback to.
889
     *
890
     * @return string Full path for the specified executable/command.
891
     */
892 6
    public function which($executable, $fallback = false)
893
    {
894 6
        if (is_string($executable)) {
0 ignored issues
show
introduced by
The condition is_string($executable) is always true.
Loading history...
895 5
            if (trim($executable) === '') {
896 5
                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...
897
            }
898
        } else {
899 1
            return $fallback;
900
        }
901 5
        if (basename($executable) === $executable) {
902 5
            $path = getenv("PATH");
903
        } else {
904
            $path = dirname($executable);
905
        }
906 5
        $dirSeparator = $this->getSeparator();
907 5
        $pathSeparator = $this->getPathSeparator();
908 5
        $elements = explode($pathSeparator, $path);
909 5
        $amount = count($elements);
910 5
        $fstype = Phing::getProperty('host.fstype');
911 5
        switch ($fstype) {
912 5
            case 'UNIX':
913 5
                for ($count = 0; $count < $amount; ++$count) {
914 5
                    $file = $elements[$count] . $dirSeparator . $executable;
915 5
                    if (file_exists($file) && is_executable($file)) {
916 3
                        return $file;
917
                    }
918
                }
919 2
                break;
920
            case 'WINDOWS':
921
                $exts = getenv('PATHEXT');
922
                if ($exts === false) {
923
                    $exts = ['.exe', '.bat', '.cmd', '.com'];
924
                } else {
925
                    $exts = explode($pathSeparator, $exts);
926
                }
927
                for ($count = 0; $count < $amount; $count++) {
928
                    foreach ($exts as $ext) {
929
                        $file = $elements[$count] . $dirSeparator . $executable . $ext;
930
                        // Not all of the extensions above need to be set executable on Windows for them to be executed.
931
                        // I'm sure there's a joke here somewhere.
932
                        if (file_exists($file)) {
933
                            return $file;
934
                        }
935
                    }
936
                }
937
                break;
938
        }
939 2
        if (file_exists($executable) && is_executable($executable)) {
940
            return $executable;
941
        }
942 2
        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...
943
    }
944
}
945