Test Failed
Push — master ( 398493...d4ef72 )
by Michael
11:04
created

XoopsFolderHandler::messages()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Folder engine For XOOPS
4
 *
5
 * You may not change or alter any portion of this comment or credits
6
 * of supporting developers from this source code or any supporting source code
7
 * which is considered copyrighted (c) material of the original comment or credit authors.
8
 * This program is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
 *
12
 * @copyright       (c) 2005-2016 XOOPS Project (www.xoops.org)
13
 * @license             GNU GPL 2 (http://www.gnu.org/licenses/gpl-2.0.html)
14
 * @package             class
15
 * @subpackage          file
16
 * @since               2.3.0
17
 * @author              Taiwen Jiang <[email protected]>
18
 */
19
20
/**
21
 * Convenience class for handling directories.
22
 *
23
 * PHP versions 4 and 5
24
 *
25
 * CakePHP(tm) :  Rapid Development Framework <http://www.cakephp.org/>
26
 * Copyright 2005-2008, Cake Software Foundation, Inc.
27
 *                                     1785 E. Sahara Avenue, Suite 490-204
28
 *                                     Las Vegas, Nevada 89104
29
 *
30
 * Licensed under The MIT License
31
 * Redistributions of files must retain the above copyright notice.
32
 *
33
 * @filesource
34
 * @copyright  Copyright 2005-2008, Cake Software Foundation, Inc.
35
 * @link       http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
36
 * @package    cake
37
 * @subpackage cake.cake.libs
38
 * @since      CakePHP(tm) v 0.2.9
39
 * @modifiedby $LastChangedBy: beckmi $
40
 * @lastmodified $Date: 2015-06-06 17:59:41 -0400 (Sat, 06 Jun 2015) $
41
 * @license    http://www.opensource.org/licenses/mit-license.php The MIT License
42
 */
43
44
/**
45
 * Folder structure browser, lists folders and files.
46
 *
47
 * Long description for class
48
 *
49
 * @package    cake
50
 * @subpackage cake.cake.libs
51
 */
52
class XoopsFolderHandler
53
{
54
    /**
55
     * Path to Folder.
56
     *
57
     * @var string
58
     * @access public
59
     */
60
    public $path;
61
62
    /**
63
     * Sortedness.
64
     *
65
     * @var boolean
66
     * @access public
67
     */
68
    public $sort = false;
69
70
    /**
71
     * mode to be used on create.
72
     *
73
     * @var boolean
74
     * @access public
75
     */
76
    public $mode = '0755';
77
78
    /**
79
     * holds messages from last method.
80
     *
81
     * @var array
82
     * @access private
83
     */
84
    public $messages = array();
85
86
    /**
87
     * holds errors from last method.
88
     *
89
     * @var array
90
     * @access private
91
     */
92
    public $errors = false;
93
94
    /**
95
     * holds array of complete directory paths.
96
     *
97
     * @var array
98
     * @access private
99
     */
100
    public $directories;
101
102
    /**
103
     * holds array of complete file paths.
104
     *
105
     * @var array
106
     * @access private
107
     */
108
    public $files;
109
110
    /**
111
     * Constructor.
112
     *
113
     * @param bool|string $path   Path to folder
114
     * @param boolean     $create Create folder if not found
115
     * @param mixed       $mode   Mode (CHMOD) to apply to created folder, false to ignore
116
     */
117
    public function __construct($path = false, $create = true, $mode = false)
118
    {
119
        if (empty($path)) {
120
            $path = XOOPS_VAR_PATH . '/caches/xoops_cache';
121
        }
122
        if ($mode) {
123
            $this->mode = intval($mode, 8);
0 ignored issues
show
Documentation Bug introduced by
The property $mode was declared of type boolean, but intval($mode, 8) is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
124
        }
125
        if (!file_exists($path) && $create == true) {
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...
126
            $this->create($path, $this->mode);
127
        }
128
        if (!$this->isAbsolute($path)) {
129
            $path = realpath($path);
130
        }
131
        $this->cd($path);
132
    }
133
134
    /**
135
     * Return current path.
136
     *
137
     * @return string Current path
138
     * @access public
139
     */
140
    public function pwd()
141
    {
142
        return $this->path;
143
    }
144
145
    /**
146
     * Change directory to $desired_path.
147
     *
148
     * @param string $path Path to the directory to change to
149
     *
150
     * @return string The new path. Returns false on failure
151
     * @access   public
152
     */
153
    public function cd($path)
154
    {
155
        $path = $this->realpath($path);
156
        if (is_dir($path) && file_exists($path)) {
157
            return $this->path = $path;
158
        }
159
160
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
161
    }
162
163
    /**
164
     * Returns an array of the contents of the current directory, or false on failure.
165
     * The returned array holds two arrays: one of dirs and one of files.
166
     *
167
     * @param boolean $sort
168
     * @param mixed   $exceptions either an array or boolean true will no grab dot files
169
     *
170
     * @return mixed Contents of current directory as an array, false on failure
171
     * @access public
172
     */
173
    public function read($sort = true, $exceptions = false)
174
    {
175
        $dirs = $files = array();
176
        $dir  = opendir($this->path);
177
        if ($dir !== false) {
178
            while (false !== ($n = readdir($dir))) {
179
                $item = false;
180
                if (is_array($exceptions)) {
181
                    if (!in_array($n, $exceptions)) {
182
                        $item = $n;
183
                    }
184
                } else {
185
                    if ((!preg_match('/^\\.+$/', $n) && $exceptions === false) || ($exceptions === true && !preg_match('/^\\.(.*)$/', $n))) {
186
                        $item = $n;
187
                    }
188
                }
189
                if ($item !== false) {
190
                    if (is_dir($this->addPathElement($this->path, $item))) {
191
                        $dirs[] = $item;
192
                    } else {
193
                        $files[] = $item;
194
                    }
195
                }
196
            }
197
            if ($sort || $this->sort) {
198
                sort($dirs);
199
                sort($files);
200
            }
201
            closedir($dir);
202
        }
203
204
        return array(
205
            $dirs,
206
            $files);
207
    }
208
209
    /**
210
     * Returns an array of all matching files in current directory.
211
     *
212
     * @param string $regexp_pattern Preg_match pattern (Defaults to: .*)
213
     * @param bool   $sort
214
     *
215
     * @return array Files that match given pattern
216
     * @access   public
217
     */
218
    public function find($regexp_pattern = '.*', $sort = false)
219
    {
220
        $data = $this->read($sort);
221
        if (!is_array($data)) {
0 ignored issues
show
introduced by
The condition is_array($data) is always true.
Loading history...
222
            return array();
223
        }
224
        list($dirs, $files) = $data;
225
        $found = array();
226
        foreach ($files as $file) {
227
            if (preg_match("/^{$regexp_pattern}$/i", $file)) {
228
                $found[] = $file;
229
            }
230
        }
231
232
        return $found;
233
    }
234
235
    /**
236
     * Returns an array of all matching files in and below current directory.
237
     *
238
     * @param string $pattern Preg_match pattern (Defaults to: .*)
239
     * @param bool   $sort
240
     *
241
     * @return array Files matching $pattern
242
     * @access public
243
     */
244
    public function findRecursive($pattern = '.*', $sort = false)
245
    {
246
        $startsOn = $this->path;
247
        $out      = $this->_findRecursive($pattern, $sort);
248
        $this->cd($startsOn);
249
250
        return $out;
251
    }
252
253
    /**
254
     * Private helper function for findRecursive.
255
     *
256
     * @param string $pattern Pattern to match against
257
     * @param bool   $sort
258
     *
259
     * @return array Files matching pattern
260
     * @access private
261
     */
262
    public function _findRecursive($pattern, $sort = false)
263
    {
264
        list($dirs, $files) = $this->read($sort);
265
        $found = array();
266
        foreach ($files as $file) {
267
            if (preg_match("/^{$pattern}$/i", $file)) {
268
                $found[] = $this->addPathElement($this->path, $file);
269
            }
270
        }
271
        $start = $this->path;
272
        foreach ($dirs as $dir) {
273
            $this->cd($this->addPathElement($start, $dir));
274
            $found = array_merge($found, $this->findRecursive($pattern));
275
        }
276
277
        return $found;
278
    }
279
280
    /**
281
     * Returns true if given $path is a Windows path.
282
     *
283
     * @param string $path Path to check
284
     *
285
     * @return boolean true if windows path, false otherwise
286
     * @access public
287
     * @static
288
     */
289
    public function isWindowsPath($path)
290
    {
291
        if (preg_match('/^[A-Z]:\\\\/i', $path)) {
292
            return true;
293
        }
294
295
        return false;
296
    }
297
298
    /**
299
     * Returns true if given $path is an absolute path.
300
     *
301
     * @param string $path Path to check
302
     *
303
     * @return bool
304
     * @access public
305
     * @static
306
     */
307
    public function isAbsolute($path)
308
    {
309
        $match = preg_match('/^\\//', $path) || preg_match('/^[A-Z]:\\//i', $path);
310
311
        return $match;
312
    }
313
314
    /**
315
     * Returns a correct set of slashes for given $path. (\\ for Windows paths and / for other paths.)
316
     *
317
     * @param string $path Path to check
318
     *
319
     * @return string Set of slashes ("\\" or "/")
320
     * @access public
321
     * @static
322
     */
323
    public function normalizePath($path)
324
    {
325
        if (XoopsFolderHandler::isWindowsPath($path)) {
0 ignored issues
show
Bug Best Practice introduced by
The method XoopsFolderHandler::isWindowsPath() is not static, but was called statically. ( Ignorable by Annotation )

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

325
        if (XoopsFolderHandler::/** @scrutinizer ignore-call */ isWindowsPath($path)) {
Loading history...
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
326
            return '\\';
327
        }
328
329
        return '/';
330
    }
331
332
    /**
333
     * Returns a correct set of slashes for given $path. (\\ for Windows paths and / for other paths.)
334
     *
335
     * @param string $path Path to check
336
     *
337
     * @return string Set of slashes ("\\" or "/")
338
     * @access public
339
     * @static
340
     */
341
    public function correctSlashFor($path)
342
    {
343
        if (XoopsFolderHandler::isWindowsPath($path)) {
0 ignored issues
show
Bug Best Practice introduced by
The method XoopsFolderHandler::isWindowsPath() is not static, but was called statically. ( Ignorable by Annotation )

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

343
        if (XoopsFolderHandler::/** @scrutinizer ignore-call */ isWindowsPath($path)) {
Loading history...
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
344
            return '\\';
345
        }
346
347
        return '/';
348
    }
349
350
    /**
351
     * Returns $path with added terminating slash (corrected for Windows or other OS).
352
     *
353
     * @param string $path Path to check
354
     *
355
     * @return string Path with ending slash
356
     * @access public
357
     * @static
358
     */
359
    public function slashTerm($path)
360
    {
361
        if (XoopsFolderHandler::isSlashTerm($path)) {
0 ignored issues
show
Bug Best Practice introduced by
The method XoopsFolderHandler::isSlashTerm() is not static, but was called statically. ( Ignorable by Annotation )

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

361
        if (XoopsFolderHandler::/** @scrutinizer ignore-call */ isSlashTerm($path)) {
Loading history...
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
362
            return $path;
363
        }
364
365
        return $path . XoopsFolderHandler::correctSlashFor($path);
0 ignored issues
show
Bug Best Practice introduced by
The method XoopsFolderHandler::correctSlashFor() is not static, but was called statically. ( Ignorable by Annotation )

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

365
        return $path . XoopsFolderHandler::/** @scrutinizer ignore-call */ correctSlashFor($path);
Loading history...
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
366
    }
367
368
    /**
369
     * Returns $path with $element added, with correct slash in-between.
370
     *
371
     * @param string $path    Path
372
     * @param string $element Element to and at end of path
373
     *
374
     * @return string Combined path
375
     * @access public
376
     * @static
377
     */
378
    public function addPathElement($path, $element)
379
    {
380
        return $this->slashTerm($path) . $element;
381
    }
382
383
    /**
384
     * Returns true if the File is in a given XoopsPath.
385
     *
386
     * @param string $path
387
     *
388
     * @return bool
389
     * @access public
390
     */
391
    public function inXoopsPath($path = '')
392
    {
393
        $dir    = substr($this->slashTerm(XOOPS_ROOT_PATH), 0, -1);
394
        $newdir = $dir . $path;
395
396
        return $this->inPath($newdir);
397
    }
398
399
    /**
400
     * Returns true if the File is in given path.
401
     *
402
     * @param string $path
403
     * @param bool   $reverse
404
     *
405
     * @return bool
406
     * @access public
407
     */
408
    public function inPath($path = '', $reverse = false)
409
    {
410
        $dir     = $this->slashTerm($path);
411
        $current = $this->slashTerm($this->pwd());
412
        if (!$reverse) {
413
            $return = preg_match('/^(.*)' . preg_quote($dir, '/') . '(.*)/', $current);
414
        } else {
415
            $return = preg_match('/^(.*)' . preg_quote($current, '/') . '(.*)/', $dir);
416
        }
417
        if ($return == 1) {
418
            return true;
419
        } else {
420
            return false;
421
        }
422
    }
423
424
    /**
425
     * Change the mode on a directory structure recursively.
426
     *
427
     * @param string   $path       The path to chmod
428
     * @param bool|int $mode       octal value 0755
429
     * @param boolean  $recursive  chmod recursively
430
     * @param array    $exceptions array of files, directories to skip
431
     *
432
     * @return boolean Returns TRUE on success, FALSE on failure
433
     * @access public
434
     */
435
    public function chmod($path, $mode = false, $recursive = true, $exceptions = array())
436
    {
437
        if (!$mode) {
438
            $mode = $this->mode;
439
        }
440
        if ($recursive === false && is_dir($path)) {
441
            if (chmod($path, intval($mode, 8))) {
442
                $this->messages[] = sprintf('%s changed to %s', $path, $mode);
443
444
                return true;
445
            } else {
446
                $this->errors[] = sprintf('%s NOT changed to %s', $path, $mode);
447
448
                return false;
449
            }
450
        }
451
        if (is_dir($path)) {
452
            list($paths) = $this->tree($path);
453
            foreach ($paths as $key => $fullpath) {
454
                $check = explode('/', $fullpath);
455
                $count = count($check);
456
457
                if (in_array($check[$count - 1], $exceptions)) {
458
                    continue;
459
                }
460
461
                if (chmod($fullpath, intval($mode, 8))) {
462
                    $this->messages[] = sprintf('%s changed to %s', $fullpath, $mode);
463
                } else {
464
                    $this->errors[] = sprintf('%s NOT changed to %s', $fullpath, $mode);
465
                }
466
            }
467
            if (empty($this->errors)) {
468
                return true;
469
            }
470
        }
471
472
        return false;
473
    }
474
475
    /**
476
     * Returns an array of nested directories and files in each directory
477
     *
478
     * @param string  $path   the directory path to build the tree from
479
     * @param boolean $hidden return hidden files and directories
480
     * @param string  $type   either file or dir. null returns both files and directories
481
     *
482
     * @return mixed array of nested directories and files in each directory
483
     * @access public
484
     */
485
    public function tree($path, $hidden = true, $type = null)
486
    {
487
        $path              = rtrim($path, '/');
488
        $this->files       = array();
489
        $this->directories = array(
490
            $path);
491
        $directories       = array();
492
        while (count($this->directories)) {
493
            $dir = array_pop($this->directories);
494
            $this->_tree($dir, $hidden);
495
            $directories[] =  $dir;
496
        }
497
        if ($type === null) {
498
            return array(
499
                $directories,
500
                $this->files);
501
        }
502
        if ($type === 'dir') {
503
            return $directories;
504
        }
505
506
        return $this->files;
507
    }
508
509
    /**
510
     * Private method to list directories and files in each directory
511
     *
512
     * @param string $path
513
     * @param        $hidden
514
     *
515
     * @internal param $ $ = boolean $hidden
516
     * @access   private
517
     */
518
    public function _tree($path, $hidden)
519
    {
520
        if (is_dir($path)) {
521
            $dirHandle = opendir($path);
522
            while (false !== ($item = readdir($dirHandle))) {
0 ignored issues
show
Bug introduced by
It seems like $dirHandle can also be of type false; however, parameter $dir_handle of readdir() 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

522
            while (false !== ($item = readdir(/** @scrutinizer ignore-type */ $dirHandle))) {
Loading history...
523
                $found = false;
524
                if (($hidden === true && $item !== '.' && $item !== '..') || ($hidden === false && !preg_match('/^\\.(.*)$/', $item))) {
525
                    $found = $path . '/' . $item;
526
                }
527
                if ($found !== false) {
528
                    if (is_dir($found)) {
529
                        $this->directories[] =  $found;
530
                    } else {
531
                        $this->files[] =  $found;
532
                    }
533
                }
534
            }
535
            closedir($dirHandle);
0 ignored issues
show
Bug introduced by
It seems like $dirHandle can also be of type false; however, parameter $dir_handle of closedir() 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

535
            closedir(/** @scrutinizer ignore-type */ $dirHandle);
Loading history...
536
        }
537
    }
538
539
    /**
540
     * Create a directory structure recursively.
541
     *
542
     * @param string   $pathname The directory structure to create
543
     * @param bool|int $mode     octal value 0755
544
     *
545
     * @return boolean Returns TRUE on success, FALSE on failure
546
     * @access public
547
     */
548
    public function create($pathname, $mode = false)
549
    {
550
        if (is_dir($pathname) || empty($pathname)) {
551
            return true;
552
        }
553
        if (!$mode) {
554
            $mode = $this->mode;
555
        }
556
        if (is_file($pathname)) {
557
            $this->errors[] = sprintf('%s is a file', $pathname);
558
559
            return true;
560
        }
561
        $nextPathname = substr($pathname, 0, strrpos($pathname, '/'));
562
        if ($this->create($nextPathname, $mode)) {
563
            if (!file_exists($pathname)) {
564
                if (mkdir($pathname, intval($mode, 8))) {
565
                    $this->messages[] = sprintf('%s created', $pathname);
566
567
                    return true;
568
                } else {
569
                    $this->errors[] = sprintf('%s NOT created', $pathname);
570
571
                    return false;
572
                }
573
            }
574
        }
575
576
        return true;
577
    }
578
579
    /**
580
     * Returns the size in bytes of this Folder.
581
     *
582
     * @return int $size
583
     * @access   public
584
     */
585
    public function dirsize()
586
    {
587
        $size      = 0;
588
        $directory = $this->slashTerm($this->path);
589
        $stack     = array($directory);
590
        $count     = count($stack);
591
        for ($i = 0, $j = $count; $i < $j; ++$i) {
592
            if (is_file($stack[$i])) {
593
                $size += filesize($stack[$i]);
594
            } else {
595
                if (is_dir($stack[$i])) {
596
                    $dir = dir($stack[$i]);
597
                    if ($dir) {
598
                        while (false !== ($entry = $dir->read())) {
599
                            if ($entry === '.' || $entry === '..') {
600
                                continue;
601
                            }
602
                            $add = $stack[$i] . $entry;
603
                            if (is_dir($stack[$i] . $entry)) {
604
                                $add = $this->slashTerm($add);
605
                            }
606
                            $stack[] = $add;
607
                        }
608
                        $dir->close();
609
                    }
610
                }
611
            }
612
            $j = count($stack);
613
        }
614
615
        return $size;
616
    }
617
618
    /**
619
     * Recursively Remove directories if system allow.
620
     *
621
     * @param string $path Path of directory to delete
622
     *
623
     * @return boolean Success
624
     * @access public
625
     */
626
    public function delete($path)
627
    {
628
        $path = $this->slashTerm($path);
629
        if (is_dir($path) === true) {
630
            $files        = glob($path . '*', GLOB_NOSORT);
0 ignored issues
show
Unused Code introduced by
The assignment to $files is dead and can be removed.
Loading history...
631
            $normal_files = glob($path . '*');
632
            $hidden_files = glob($path . '\.?*');
633
            $files        = array_merge($normal_files, $hidden_files);
634
            if (is_array($files)) {
0 ignored issues
show
introduced by
The condition is_array($files) is always true.
Loading history...
635
                foreach ($files as $file) {
636
                    if (preg_match("/(\.|\.\.)$/", $file)) {
637
                        continue;
638
                    }
639
                    if (is_file($file) === true) {
640
                        if (unlink($file)) {
641
                            $this->messages[] = sprintf('%s removed', $path);
642
                        } else {
643
                            $this->errors[] = sprintf('%s NOT removed', $path);
644
                        }
645
                    } else {
646
                        if (is_dir($file) === true) {
647
                            if ($this->delete($file) === false) {
648
                                return false;
649
                            }
650
                        }
651
                    }
652
                }
653
            }
654
            $path = substr($path, 0, strlen($path) - 1);
655
            if (rmdir($path) === false) {
656
                $this->errors[] = sprintf('%s NOT removed', $path);
657
658
                return false;
659
            } else {
660
                $this->messages[] = sprintf('%s removed', $path);
661
            }
662
        }
663
664
        return true;
665
    }
666
667
    /**
668
     * Recursive directory copy.
669
     *
670
     * @param array|string $options (to, from, chmod, skip)
671
     *
672
     * @return bool
673
     * @access public
674
     */
675
    public function copy($options = array())
676
    {
677
        $to = null;
678
        if (is_string($options)) {
679
            $to      = $options;
680
            $options = array();
681
        }
682
        $options = array_merge(array(
683
                                   'to'   => $to,
684
                                   'from' => $this->path,
685
                                   'mode' => $this->mode,
686
                                   'skip' => array()), $options);
687
688
        $fromDir = $options['from'];
689
        $toDir   = $options['to'];
690
        $mode    = $options['mode'];
691
        if (!$this->cd($fromDir)) {
692
            $this->errors[] = sprintf('%s not found', $fromDir);
693
694
            return false;
695
        }
696
        if (!is_dir($toDir)) {
697
            mkdir($toDir, $mode);
698
        }
699
        if (!is_writable($toDir)) {
700
            $this->errors[] = sprintf('%s not writable', $toDir);
701
702
            return false;
703
        }
704
        $exceptions = array_merge(array(
705
                                      '.',
706
                                      '..',
707
                                      '.svn'), $options['skip']);
708
        $handle     = opendir($fromDir);
709
        if ($handle) {
0 ignored issues
show
introduced by
$handle is of type resource|false, thus it always evaluated to false.
Loading history...
710
            while (false !== ($item = readdir($handle))) {
711
                if (!in_array($item, $exceptions)) {
712
                    $from = $this->addPathElement($fromDir, $item);
713
                    $to   = $this->addPathElement($toDir, $item);
714
                    if (is_file($from)) {
715
                        if (copy($from, $to)) {
716
                            chmod($to, intval($mode, 8));
717
                            touch($to, filemtime($from));
718
                            $this->messages[] = sprintf('%s copied to %s', $from, $to);
719
                        } else {
720
                            $this->errors[] = sprintf('%s NOT copied to %s', $from, $to);
721
                        }
722
                    }
723
                    if (is_dir($from) && !file_exists($to)) {
724
                        if (mkdir($to, intval($mode, 8))) {
725
                            chmod($to, intval($mode, 8));
726
                            $this->messages[] = sprintf('%s created', $to);
727
                            $options          = array_merge($options, array(
728
                                                                        'to'   => $to,
729
                                                                        'from' => $from));
730
                            $this->copy($options);
731
                        } else {
732
                            $this->errors[] = sprintf('%s not created', $to);
733
                        }
734
                    }
735
                }
736
            }
737
            closedir($handle);
738
        } else {
739
            return false;
740
        }
741
        if (!empty($this->errors)) {
742
            return false;
743
        }
744
745
        return true;
746
    }
747
748
    /**
749
     * Recursive directory move.
750
     *
751
     * @param array|string $options (to, from, chmod, skip)
752
     *
753
     * @return boolean Success
754
     * @access public
755
     */
756
    public function move($options)
757
    {
758
        $to = null;
759
        if (is_string($options)) {
760
            $to      = $options;
761
            $options = (array)$options;
762
        }
763
        $options = array_merge(array(
764
                                   'to'   => $to,
765
                                   'from' => $this->path,
766
                                   'mode' => $this->mode,
767
                                   'skip' => array()), $options);
768
        if ($this->copy($options)) {
769
            if ($this->delete($options['from'])) {
770
                return $this->cd($options['to']);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->cd($options['to']) returns the type string which is incompatible with the documented return type boolean.
Loading history...
771
            }
772
        }
773
774
        return false;
775
    }
776
777
    /**
778
     * get messages from latest method
779
     *
780
     * @return array
781
     * @access public
782
     */
783
    public function messages()
784
    {
785
        return $this->messages;
786
    }
787
788
    /**
789
     * get error from latest method
790
     *
791
     * @return array
792
     * @access public
793
     */
794
    public function errors()
795
    {
796
        return $this->errors;
797
    }
798
799
    /**
800
     * Get the real path (taking ".." and such into account)
801
     *
802
     * @param string $path Path to resolve
803
     *
804
     * @return string The resolved path
805
     */
806
    public function realpath($path)
807
    {
808
        $path = trim($path);
809
        if (strpos($path, '..') === false) {
810
            if (!$this->isAbsolute($path)) {
811
                $path = $this->addPathElement($this->path, $path);
812
            }
813
814
            return $path;
815
        }
816
        $parts    = explode('/', $path);
817
        $newparts = array();
818
        $newpath  = $path{0} === '/' ? '/' : '';
819
        while (($part = array_shift($parts)) !== null) {
820
            if ($part === '.' || $part == '') {
821
                continue;
822
            }
823
            if ($part === '..') {
824
                if (count($newparts) > 0) {
825
                    array_pop($newparts);
826
                    continue;
827
                } else {
828
                    return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
829
                }
830
            }
831
            $newparts[] = $part;
832
        }
833
        $newpath .= implode('/', $newparts);
834
        if (strlen($path > 1) && $path{strlen($path) - 1} === '/') {
835
            $newpath .= '/';
836
        }
837
838
        return $newpath;
839
    }
840
841
    /**
842
     * Returns true if given $path ends in a slash (i.e. is slash-terminated).
843
     *
844
     * @param string $path Path to check
845
     *
846
     * @return boolean true if path ends with slash, false otherwise
847
     * @access public
848
     * @static
849
     */
850
    public function isSlashTerm($path)
851
    {
852
        if (preg_match('/[\/\\\]$/', $path)) {
853
            return true;
854
        }
855
856
        return false;
857
    }
858
}
859