Completed
Push — master ( d90c8b...86899b )
by Siad
16:39
created

FileSystem::getLastModifiedTime()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 30
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5.5359

Importance

Changes 0
Metric Value
cc 5
eloc 19
nc 7
nop 1
dl 0
loc 30
ccs 13
cts 18
cp 0.7221
crap 5.5359
rs 9.3222
c 0
b 0
f 0
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
/**
21
 * This is an abstract class for platform specific filesystem implementations
22
 * you have to implement each method in the platform specific filesystem implementation
23
 * classes Your local filesytem implementation must extend this class.
24
 * You should also use this class as a template to write your local implementation
25
 * Some native PHP filesystem specific methods are abstracted here as well. Anyway
26
 * you _must_ always use this methods via a PhingFile object (that by nature uses the
27
 * *FileSystem drivers to access the real filesystem via this class using natives.
28
 *
29
 * FIXME:
30
 *  - Error handling reduced to min fallthrough runtime exceptions
31
 *    more precise errorhandling is done by the PhingFile class
32
 *
33
 * @author Charlie Killian <[email protected]>
34
 * @author Hans Lellelid <[email protected]>
35
 *
36
 * @package phing.system.io
37
 */
38
abstract class FileSystem
39
{
40
41
    /**
42
     * @var int
43
     */
44
    const BA_EXISTS = 0x01;
45
46
    /**
47
     * @var int
48
     */
49
    const BA_REGULAR = 0x02;
50
51
    /**
52
     * @var int
53
     */
54
    const BA_DIRECTORY = 0x04;
55
56
    /**
57
     * @var int
58
     */
59
    const BA_HIDDEN = 0x08;
60
61
    /**
62
     * Instance for getFileSystem() method.
63
     *
64
     * @var FileSystem
65
     */
66
    private static $fs;
67
68
    /**
69
     * Static method to return the FileSystem singelton representing
70
     * this platform's local filesystem driver.
71
     *
72
     * @return FileSystem
73
     * @throws IOException
74
     */
75 780
    public static function getFileSystem()
76
    {
77 780
        if (self::$fs === null) {
78 6
            switch (Phing::getProperty('host.fstype')) {
79 6
                case 'UNIX':
80 4
                    self::$fs = new UnixFileSystem();
81 4
                    break;
82 2
                case 'WINDOWS':
83 1
                    self::$fs = new WindowsFileSystem();
84 1
                    break;
85
                default:
86 1
                    throw new IOException("Host uses unsupported filesystem, unable to proceed");
87
            }
88
        }
89
90 779
        return self::$fs;
91
    }
92
93
    /* -- Normalization and construction -- */
94
95
    /**
96
     * Return the local filesystem's name-separator character.
97
     */
98
    abstract public function getSeparator();
99
100
    /**
101
     * Return the local filesystem's path-separator character.
102
     */
103
    abstract public function getPathSeparator();
104
105
    /**
106
     * Convert the given pathname string to normal form.  If the string is
107
     * already in normal form then it is simply returned.
108
     *
109
     * @param string $strPath
110
     */
111
    abstract public function normalize($strPath);
112
113
    /**
114
     * Compute the length of this pathname string's prefix.  The pathname
115
     * string must be in normal form.
116
     *
117
     * @param string $pathname
118
     */
119
    abstract public function prefixLength($pathname);
120
121
    /**
122
     * Resolve the child pathname string against the parent.
123
     * Both strings must be in normal form, and the result
124
     * will be a string in normal form.
125
     *
126
     * @param string $parent
127
     * @param string $child
128
     */
129
    abstract public function resolve($parent, $child);
130
131
    /**
132
     * Resolve the given abstract pathname into absolute form.  Invoked by the
133
     * getAbsolutePath and getCanonicalPath methods in the PhingFile class.
134
     *
135
     * @param PhingFile $f
136
     */
137
    abstract public function resolveFile(PhingFile $f);
138
139
    /**
140
     * Return the parent pathname string to be used when the parent-directory
141
     * argument in one of the two-argument PhingFile constructors is the empty
142
     * pathname.
143
     */
144
    abstract public function getDefaultParent();
145
146
    /**
147
     * Post-process the given URI path string if necessary.  This is used on
148
     * win32, e.g., to transform "/c:/foo" into "c:/foo".  The path string
149
     * still has slash separators; code in the PhingFile class will translate them
150
     * after this method returns.
151
     *
152
     * @param string $path
153
     */
154
    abstract public function fromURIPath($path);
155
156
    /* -- Path operations -- */
157
158
    /**
159
     * Tell whether or not the given abstract pathname is absolute.
160
     *
161
     * @param PhingFile $f
162
     */
163
    abstract public function isAbsolute(PhingFile $f);
164
165
    /**
166
     * canonicalize filename by checking on disk
167
     *
168
     * @param  string $strPath
169
     * @return mixed  Canonical path or false if the file doesn't exist.
170
     */
171 763
    public function canonicalize($strPath)
172
    {
173 763
        return @realpath($strPath);
174
    }
175
176
    /* -- Attribute accessors -- */
177
178
    /**
179
     * Return the simple boolean attributes for the file or directory denoted
180
     * by the given abstract pathname, or zero if it does not exist or some
181
     * other I/O error occurs.
182
     *
183
     * @param  PhingFile $f
184
     * @throws IOException
185
     */
186
    public function getBooleanAttributes($f)
187
    {
188
        throw new IOException("getBooleanAttributes() not implemented by fs driver");
189
    }
190
191
    /**
192
     * Check whether the file or directory denoted by the given abstract
193
     * pathname may be accessed by this process.  If the second argument is
194
     * false, then a check for read access is made; if the second
195
     * argument is true, then a check for write (not read-write)
196
     * access is made.  Return false if access is denied or an I/O error
197
     * occurs.
198
     *
199
     * @param  PhingFile $f
200
     * @param  boolean $write
201
     * @return bool
202
     */
203 766
    public function checkAccess(PhingFile $f, $write = false)
204
    {
205
        // we clear stat cache, its expensive to look up from scratch,
206
        // but we need to be sure
207 766
        @clearstatcache();
1 ignored issue
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...
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

207
        /** @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...
208
209
210
        // Shouldn't this be $f->GetAbsolutePath() ?
211
        // And why doesn't GetAbsolutePath() work?
212
213 766
        $strPath = (string) $f->getPath();
214
215
        // FIXME
216
        // if file object does denote a file that yet not existst
217
        // path rights are checked
218 766
        if (!@file_exists($strPath) && !is_dir($strPath)) {
219 171
            $strPath = $f->getParent();
220 171
            if ($strPath === null || !is_dir($strPath)) {
221 42
                $strPath = Phing::getProperty("user.dir");
222
            }
223
            //$strPath = dirname($strPath);
224
        }
225
226 766
        if (!$write) {
227 766
            return (bool) @is_readable($strPath);
228
        }
229
230 136
        return (bool) @is_writable($strPath);
231
    }
232
233
    /**
234
     * Whether file can be deleted.
235
     *
236
     * @param  PhingFile $f
237
     * @return boolean
238
     */
239
    public function canDelete(PhingFile $f)
240
    {
241
        clearstatcache();
242
        $dir = dirname($f->getAbsolutePath());
243
244
        return @is_writable($dir);
245
    }
246
247
    /**
248
     * Return the time at which the file or directory denoted by the given
249
     * abstract pathname was last modified, or zero if it does not exist or
250
     * some other I/O error occurs.
251
     *
252
     * @param  PhingFile $f
253
     * @return int
254
     * @throws IOException
255
     */
256 38
    public function getLastModifiedTime(PhingFile $f)
257
    {
258 38
        if (!$f->exists()) {
259 6
            return 0;
260
        }
261
262 38
        @clearstatcache();
1 ignored issue
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...
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

262
        /** @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...
263 38
        error_clear_last();
264 38
        $strPath = (string) $f->getPath();
265
266 38
        if (@is_link($strPath)) {
267 2
            $stats = @lstat($strPath);
268
269 2
            if (!isset($stats['mtime'])) {
270
                $mtime = false;
271
            } else {
272 2
                $mtime = $stats['mtime'];
273
            }
274
        } else {
275 38
            $mtime = @filemtime($strPath);
276
        }
277
278 38
        if (false === $mtime) {
279
            $lastError = error_get_last();
280
            $errormsg = $lastError['message'];
281
            $msg = "FileSystem::getLastModifiedTime() FAILED. Can not get modified time of $strPath. $errormsg";
282
            throw new IOException($msg);
283
        }
284
285 38
        return (int) $mtime;
286
    }
287
288
    /**
289
     * Return the length in bytes of the file denoted by the given abstract
290
     * pathname, or zero if it does not exist, is a directory, or some other
291
     * I/O error occurs.
292
     *
293
     * @param  PhingFile $f
294
     * @throws IOException
295
     * @return int
296
     */
297 14
    public function getLength(PhingFile $f)
298
    {
299 14
        error_clear_last();
300 14
        $strPath = (string) $f->getAbsolutePath();
301 14
        $fs = filesize((string) $strPath);
302 14
        if ($fs !== false) {
303 14
            return $fs;
304
        }
305
306
        $lastError = error_get_last();
307
        $errormsg = $lastError['message'];
308
        $msg = "FileSystem::Read() FAILED. Cannot get filesize of $strPath. $errormsg";
309
        throw new IOException($msg);
310
    }
311
312
    /* -- File operations -- */
313
314
    /**
315
     * Create a new empty file with the given pathname.  Return
316
     * true if the file was created and false if a
317
     * file or directory with the given pathname already exists.  Throw an
318
     * IOException if an I/O error occurs.
319
     *
320
     * @param  string $strPathname Path of the file to be created.
321
     * @throws IOException
322
     * @return boolean
323
     */
324 61
    public function createNewFile($strPathname)
325
    {
326 61
        if (@file_exists($strPathname)) {
327
            return false;
328
        }
329
330
        // Create new file
331 61
        $fp = @fopen($strPathname, "w");
332 61
        if ($fp === false) {
333 1
            $error = error_get_last();
334 1
            throw new IOException("The file \"$strPathname\" could not be created: " . $error['message']);
335
        }
336 60
        @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

336
        /** @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...
337
338 60
        return true;
339
    }
340
341
    /**
342
     * Delete the file or directory denoted by the given abstract pathname,
343
     * returning true if and only if the operation succeeds.
344
     *
345
     * @param  PhingFile $f
346
     * @param  boolean $recursive
347
     * @throws IOException
348
     */
349 142
    public function delete(PhingFile $f, $recursive = false)
350
    {
351 142
        if ($f->isDirectory()) {
352 116
            $this->rmdir($f->getPath(), $recursive);
353
        } else {
354 117
            $this->unlink($f->getPath());
355
        }
356 142
    }
357
358
    /**
359
     * Arrange for the file or directory denoted by the given abstract
360
     * pathname to be deleted when Phing::shutdown is called, returning
361
     * true if and only if the operation succeeds.
362
     *
363
     * @param  PhingFile $f
364
     * @throws IOException
365
     */
366
    public function deleteOnExit($f)
0 ignored issues
show
Unused Code introduced by
The parameter $f is not used and could be removed. ( Ignorable by Annotation )

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

366
    public function deleteOnExit(/** @scrutinizer ignore-unused */ $f)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
367
    {
368
        throw new IOException("deleteOnExit() not implemented by local fs driver");
369
    }
370
371
    /**
372
     * Create a new directory denoted by the given abstract pathname,
373
     * returning true if and only if the operation succeeds.
374
     *
375
     * NOTE: umask() is reset to 0 while executing mkdir(), and restored afterwards
376
     *
377
     * @param  PhingFile $f
378
     * @param  int $mode
379
     * @return boolean
380
     */
381 116
    public function createDirectory(&$f, $mode = 0755)
382
    {
383 116
        $old_umask = umask(0);
384 116
        $return = @mkdir($f->getAbsolutePath(), $mode);
385 116
        umask($old_umask);
386
387 116
        return $return;
388
    }
389
390
    /**
391
     * Rename the file or directory denoted by the first abstract pathname to
392
     * the second abstract pathname, returning true if and only if
393
     * the operation succeeds.
394
     *
395
     * @param  PhingFile $f1 abstract source file
396
     * @param  PhingFile $f2 abstract destination file
397
     * @return void
398
     * @throws IOException if rename cannot be performed
399
     */
400 1
    public function rename(PhingFile $f1, PhingFile $f2)
401
    {
402 1
        error_clear_last();
403
        // get the canonical paths of the file to rename
404 1
        $src = $f1->getAbsolutePath();
405 1
        $dest = $f2->getAbsolutePath();
406 1
        if (false === @rename($src, $dest)) {
407
            $lastError = error_get_last();
408
            $errormsg = $lastError['message'];
409
            $msg = "Rename FAILED. Cannot rename $src to $dest. $errormsg";
410
            throw new IOException($msg);
411
        }
412 1
    }
413
414
    /**
415
     * Set the last-modified time of the file or directory denoted by the
416
     * given abstract pathname returning true if and only if the
417
     * operation succeeds.
418
     *
419
     * @param  PhingFile $f
420
     * @param  int $time
421
     * @return void
422
     * @throws IOException
423
     */
424 65
    public function setLastModifiedTime(PhingFile $f, $time)
425
    {
426 65
        error_clear_last();
427 65
        $path = $f->getPath();
428 65
        $success = @touch($path, $time);
429 65
        if (!$success) {
430
            $lastError = error_get_last();
431
            $errormsg = $lastError['message'];
432
            throw new IOException("Could not touch '" . $path . "' due to: $errormsg");
433
        }
434 65
    }
435
436
    /**
437
     * Mark the file or directory denoted by the given abstract pathname as
438
     * read-only, returning <code>true</code> if and only if the operation
439
     * succeeds.
440
     *
441
     * @param  PhingFile $f
442
     * @throws IOException
443
     */
444
    public function setReadOnly($f)
445
    {
446
        throw new IOException("setReadonly() not implemented by local fs driver");
447
    }
448
449
    /* -- Filesystem interface -- */
450
451
    /**
452
     * List the available filesystem roots, return array of PhingFile objects
453
     *
454
     * @throws IOException
455
     * @return PhingFile[]
456
     */
457
    public function listRoots()
458
    {
459
        throw new IOException("listRoots() not implemented by local fs driver");
460
    }
461
462
    /* -- Basic infrastructure -- */
463
464
    /**
465
     * Compare two abstract pathnames lexicographically.
466
     *
467
     * @param  PhingFile $f1
468
     * @param  PhingFile $f2
469
     * @throws IOException
470
     * @return int
471
     */
472
    public function compare(PhingFile $f1, PhingFile $f2)
473
    {
474
        throw new IOException("compare() not implemented by local fs driver");
475
    }
476
477
    /**
478
     * Copy a file.
479
     *
480
     * @param PhingFile $src Source path and name file to copy.
481
     * @param PhingFile $dest Destination path and name of new file.
482
     *
483
     * @return void
484
     *
485
     * @throws IOException if file cannot be copied.
486
     */
487 10
    public function copy(PhingFile $src, PhingFile $dest)
488
    {
489
        // Recursively copy a directory
490 10
        if ($src->isDirectory()) {
491
            $this->copyr($src->getAbsolutePath(), $dest->getAbsolutePath());
492
        }
493
494 10
        $srcPath = $src->getAbsolutePath();
495 10
        $destPath = $dest->getAbsolutePath();
496
497 10
        error_clear_last();
498 10
        if (false === @copy($srcPath, $destPath)) { // Copy FAILED. Log and return err.
499
            // Add error from php to end of log message. $errormsg.
500
            $lastError = error_get_last();
501
            $errormsg = $lastError['message'];
502
            $msg = "FileSystem::copy() FAILED. Cannot copy $srcPath to $destPath. $errormsg";
503
            throw new IOException($msg);
504
        }
505
506 10
        $dest->setMode($src->getMode());
507 10
    }
508
509
    /**
510
     * Copy a file, or recursively copy a folder and its contents
511
     *
512
     * @author  Aidan Lister <[email protected]>
513
     * @version 1.0.1
514
     * @link    http://aidanlister.com/repos/v/function.copyr.php
515
     *
516
     * @param string $source Source path
517
     * @param string $dest Destination path
518
     *
519
     * @return bool   Returns TRUE on success, FALSE on failure
520
     */
521
    public function copyr($source, $dest)
522
    {
523
        // Check for symlinks
524
        if (is_link($source)) {
525
            return symlink(readlink($source), $dest);
526
        }
527
528
        // Simple copy for a file
529
        if (is_file($source)) {
530
            return copy($source, $dest);
531
        }
532
533
        // Make destination directory
534
        if (!is_dir($dest) && !mkdir($dest) && !is_dir($dest)) {
535
            return false;
536
        }
537
538
        // Loop through the folder
539
        $dir = dir($source);
540
        while (false !== $entry = $dir->read()) {
541
            // Skip pointers
542
            if ($entry == '.' || $entry == '..') {
543
                continue;
544
            }
545
546
            // Deep copy directories
547
            $this->copyr("$source/$entry", "$dest/$entry");
548
        }
549
550
        // Clean up
551
        $dir->close();
552
553
        return true;
554
    }
555
556
    /**
557
     * Change the ownership on a file or directory.
558
     *
559
     * @param string $pathname Path and name of file or directory.
560
     * @param string $user The user name or number of the file or directory. See http://us.php.net/chown
561
     *
562
     * @return void
563
     *
564
     * @throws IOException if operation failed.
565
     */
566
    public function chown($pathname, $user)
567
    {
568
        error_clear_last();
569
        if (false === @chown($pathname, $user)) { // FAILED.
570
            $lastError = error_get_last();
571
            $errormsg = $lastError['message'];
572
            $msg = "FileSystem::chown() FAILED. Cannot chown $pathname. User $user." . (isset($errormsg) ? ' ' . $errormsg : "");
573
            throw new IOException($msg);
574
        }
575
    }
576
577
    /**
578
     * Change the group on a file or directory.
579
     *
580
     * @param string $pathname Path and name of file or directory.
581
     * @param string $group The group of the file or directory. See http://us.php.net/chgrp
582
     *
583
     * @return void
584
     * @throws IOException if operation failed.
585
     */
586
    public function chgrp($pathname, $group)
587
    {
588
        error_clear_last();
589
        if (false === @chgrp($pathname, $group)) { // FAILED.
590
            $lastError = error_get_last();
591
            $errormsg = $lastError['message'];
592
            $msg = "FileSystem::chgrp() FAILED. Cannot chown $pathname. Group $group." . (isset($errormsg) ? ' ' . $errormsg : "");
593
            throw new IOException($msg);
594
        }
595
    }
596
597
    /**
598
     * Change the permissions on a file or directory.
599
     *
600
     * @param string $pathname Path and name of file or directory.
601
     * @param int $mode The mode (permissions) of the file or
602
     *                         directory. If using octal add leading 0. eg. 0777.
603
     *                         Mode is affected by the umask system setting.
604
     *
605
     * @return void
606
     * @throws IOException if operation failed.
607
     */
608 39
    public function chmod($pathname, $mode)
609
    {
610 39
        error_clear_last();
611 39
        $str_mode = decoct($mode); // Show octal in messages.
612 39
        if (false === @chmod($pathname, $mode)) { // FAILED.
613
            $lastError = error_get_last();
614
            $errormsg = $lastError['message'];
615
            $msg = "FileSystem::chmod() FAILED. Cannot chmod $pathname. Mode $str_mode." . (isset($errormsg) ? ' ' . $errormsg : "");
616
            throw new IOException($msg);
617
        }
618 39
    }
619
620
    /**
621
     * Locks a file and throws an Exception if this is not possible.
622
     *
623
     * @param  PhingFile $f
624
     * @return void
625
     * @throws IOException
626
     */
627
    public function lock(PhingFile $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 $handle 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);
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

632
        /** @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...
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle 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
     * @param  PhingFile $f
642
     * @throws IOException
643
     * @return void
644
     */
645
    public function unlock(PhingFile $f)
646
    {
647
        $filename = $f->getPath();
648
        $fp = @fopen($filename, "w");
649
        $result = @flock($fp, LOCK_UN);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle 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

649
        $result = @flock(/** @scrutinizer ignore-type */ $fp, LOCK_UN);
Loading history...
650
        fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle 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

650
        fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
651
        if (!$result) {
652
            throw new IOException("Could not unlock file '$filename'");
653
        }
654
    }
655
656
    /**
657
     * Delete a file.
658
     *
659
     * @param string $file Path and/or name of file to delete.
660
     *
661
     * @return void
662
     * @throws IOException - if an error is encountered.
663
     */
664 120
    public function unlink($file)
665
    {
666 120
        error_clear_last();
667 120
        if (false === @unlink($file)) {
668
            $lastError = error_get_last();
669
            $errormsg = $lastError['message'];
670
            $msg = "FileSystem::unlink() FAILED. Cannot unlink '$file'. $errormsg";
671
            throw new IOException($msg);
672
        }
673 120
    }
674
675
    /**
676
     * Symbolically link a file to another name.
677
     *
678
     * Currently symlink is not implemented on Windows. Don't use if the application is to be portable.
679
     *
680
     * @param  string $target Path and/or name of file to link.
681
     * @param  string $link Path and/or name of link to be created.
682
     * @throws IOException
683
     * @return void
684
     */
685 15
    public function symlink($target, $link)
686
    {
687
688 15
        error_clear_last();
689
        // If Windows OS then symlink() will report it is not supported in
690
        // the build. Use this error instead of checking for Windows as the OS.
691
692 15
        if (false === @symlink($target, $link)) {
693
            $lastError = error_get_last();
694
            $errormsg = $lastError['message'];
695
            // Add error from php to end of log message.
696
            $msg = "FileSystem::Symlink() FAILED. Cannot symlink '$target' to '$link'. $errormsg";
697
            throw new IOException($msg);
698
        }
699 15
    }
700
701
    /**
702
     * Set the modification and access time on a file to the present time.
703
     *
704
     * @param  string $file Path and/or name of file to touch.
705
     * @param  int $time
706
     * @throws Exception
707
     * @return void
708
     */
709
    public function touch($file, $time = null)
710
    {
711
        error_clear_last();
712
        if (null === $time) {
713
            $error = @touch($file);
714
        } else {
715
            $error = @touch($file, $time);
716
        }
717
718
        if (false === $error) { // FAILED.
719
            $lastError = error_get_last();
720
            $errormsg = $lastError['message'];
721
            // Add error from php to end of log message.
722
            $msg = "FileSystem::touch() FAILED. Cannot touch '$file'. $errormsg";
723
            throw new Exception($msg);
724
        }
725
    }
726
727
    /**
728
     * Delete an empty directory OR a directory and all of its contents.
729
     *
730
     * @param string $dir Path and/or name of directory to delete.
731
     * @param bool $children False: don't delete directory contents.
732
     *                         True: delete directory contents.
733
     *
734
     * @throws Exception
735
     *
736
     * @return void
737
     */
738 116
    public function rmdir($dir, $children = false)
739
    {
740 116
        error_clear_last();
741
742
        // If children=FALSE only delete dir if empty.
743 116
        if (false === $children) {
744 115
            if (false === @rmdir($dir)) { // FAILED.
745
                $lastError = error_get_last();
746
                $errormsg = $lastError['message'];
747
                // Add error from php to end of log message.
748
                $msg = "FileSystem::rmdir() FAILED. Cannot rmdir $dir. $errormsg";
749
                throw new Exception($msg);
750
            }
751
        } else { // delete contents and dir.
752 2
            $handle = @opendir($dir);
753 2
            $lastError = error_get_last();
754 2
            $errormsg = $lastError['message'];
755
756 2
            if (false === $handle) { // Error.
757
                $msg = "FileSystem::rmdir() FAILED. Cannot opendir() $dir. $errormsg";
758
                throw new Exception($msg);
759
            }
760
            // Read from handle.
761
            // Don't error on readdir().
762 2
            while (false !== ($entry = @readdir($handle))) {
763 2
                if ($entry != '.' && $entry != '..') {
764
                    // Only add / if it isn't already the last char.
765
                    // This ONLY serves the purpose of making the Logger
766
                    // output look nice:)
767
768
                    if (strpos(strrev($dir), DIRECTORY_SEPARATOR) === 0) { // there is a /
769
                        $next_entry = $dir . $entry;
770
                    } else { // no /
771
                        $next_entry = $dir . DIRECTORY_SEPARATOR . $entry;
772
                    }
773
774
                    // NOTE: As of php 4.1.1 is_dir doesn't return FALSE it
775
                    // returns 0. So use == not ===.
776
777
                    // Don't error on is_dir()
778
                    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...
779
                        try {
780
                            $this->unlink($next_entry); // Delete.
781
                        } catch (Exception $e) {
782
                            $msg = "FileSystem::Rmdir() FAILED. Cannot FileSystem::Unlink() $next_entry. " . $e->getMessage();
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
                            throw new Exception($msg);
791
                        }
792
                    }
793
                }
794
            }
795
796
            // Don't error on closedir()
797 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

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