Overview

Namespaces

  • Liberty
  • None

Classes

  • Liberty\BigInteger
  • Liberty\Block
  • Liberty\Blockchain
  • Liberty\Collection
  • Liberty\CollectionFileList
  • Liberty\File
  • Liberty\Folder
  • Liberty\INI
  • Liberty\LECDSA
  • Liberty\Onecrypt
  • Liberty\SSV
  • Liberty\Text
  • Liberty\Transaction
  • Liberty\Wallet
  • Liberty\WebPeer

Functions

  • __autoload
  • Overview
  • Namespace
  • Class
  1: <?php
  2: 
  3: namespace Liberty;
  4: 
  5: use DirectoryIterator;
  6: use Exception;
  7: use InvalidArgumentException;
  8: use RecursiveDirectoryIterator;
  9: use RecursiveIteratorIterator;
 10: 
 11: /**
 12:  * Folder structure browser, lists folders and files.
 13:  * 
 14:  * Provides an Object interface for Common directory related tasks.
 15:  * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
 16:  *
 17:  * Licensed under The MIT License
 18:  * For full copyright and license information, please see the LICENSE.txt
 19:  * Redistributions of files must retain the above copyright notice.
 20:  *
 21:  * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
 22:  * @link          http://book.cakephp.org/3.0/en/core-libraries/file-folder.html#folder-api
 23:  * @since         0.2.9
 24:  * @license       http://www.opensource.org/licenses/mit-license.php MIT License
 25:  */
 26: 
 27: 
 28: 
 29: 
 30: class Folder
 31: {
 32:     /**
 33:      * Default scheme for Folder::copy
 34:      * Recursively merges subfolders with the same name
 35:      *
 36:      * @var string
 37:      */
 38:     const MERGE = 'merge';
 39: 
 40: 
 41: 
 42:     /**
 43:      * Overwrite scheme for Folder::copy
 44:      * subfolders with the same name will be replaced
 45:      *
 46:      * @var string
 47:      */
 48:     const OVERWRITE = 'overwrite';
 49: 
 50: 
 51: 
 52:     /**
 53:      * Skip scheme for Folder::copy
 54:      * if a subfolder with the same name exists it will be skipped
 55:      *
 56:      * @var string
 57:      */
 58:     const SKIP = 'skip';
 59: 
 60: 
 61: 
 62:     /**
 63:      * Sort mode by name
 64:      *
 65:      * @var string
 66:      */
 67:     const SORT_NAME = 'name';
 68: 
 69: 
 70: 
 71:     /**
 72:      * Sort mode by time
 73:      *
 74:      * @var string
 75:      */
 76:     const SORT_TIME = 'time';
 77: 
 78: 
 79: 
 80:     /**
 81:      * Path to Folder.
 82:      *
 83:      * @var string
 84:      */
 85:     public $path;
 86: 
 87: 
 88: 
 89:     /**
 90:      * Sortedness. Whether or not list results
 91:      * should be sorted by name.
 92:      *
 93:      * @var bool
 94:      */
 95:     public $sort = false;
 96: 
 97: 
 98: 
 99:     /**
100:      * Mode to be used on create. Does nothing on windows platforms.
101:      *
102:      * @var int
103:      * http://book.cakephp.org/3.0/en/core-libraries/file-folder.html#Cake\Filesystem\Folder::$mode
104:      */
105:     public $mode = 0755;
106: 
107: 
108: 
109:     /**
110:      * Functions array to be called depending on the sort type chosen.
111:      */
112:     protected $_fsorts = [
113:         self::SORT_NAME => 'getPathname',
114:         self::SORT_TIME => 'getCTime'
115:     ];
116: 
117: 
118: 
119:     /**
120:      * Holds messages from last method.
121:      *
122:      * @var array
123:      */
124:     protected $_messages = [];
125: 
126: 
127: 
128:     /**
129:      * Holds errors from last method.
130:      *
131:      * @var array
132:      */
133:     protected $_errors = [];
134: 
135: 
136: 
137:     /**
138:      * Holds array of complete directory paths.
139:      *
140:      * @var array
141:      */
142:     protected $_directories;
143: 
144: 
145: 
146:     /**
147:      * Holds array of complete file paths.
148:      *
149:      * @var array
150:      */
151:     protected $_files;
152: 
153: 
154: 
155:     /**
156:      * Constructor.
157:      *
158:      * @param string|null $path Path to folder
159:      * @param bool $create Create folder if not found
160:      * @param int|bool $mode Mode (CHMOD) to apply to created folder, false to ignore
161:      */
162:     public function __construct($path = null, $create = false, $mode = false)
163:     {
164:         if (empty($path)) {
165:             $path = TMP;
166:         }
167:         if ($mode) {
168:             $this->mode = $mode;
169:         }
170: 
171:         if (!file_exists($path) && $create === true) {
172:             $this->create($path, $this->mode);
173:         }
174:         if (!Folder::isAbsolute($path)) {
175:             $path = realpath($path);
176:         }
177:         if (!empty($path)) {
178:             $this->cd($path);
179:         }
180:     }
181: 
182:     /**
183:      * Return current path.
184:      *
185:      * @return string Current path
186:      */
187:     public function pwd()
188:     {
189:         return $this->path;
190:     }
191: 
192:     /**
193:      * Change directory to $path.
194:      *
195:      * @param string $path Path to the directory to change to
196:      * @return string|bool The new path. Returns false on failure
197:      */
198:     public function cd($path)
199:     {
200:         $path = $this->realpath($path);
201:         if (is_dir($path)) {
202:             return $this->path = $path;
203:         }
204: 
205:         return false;
206:     }
207: 
208:     /**
209:      * Returns an array of the contents of the current directory.
210:      * The returned array holds two arrays: One of directories and one of files.
211:      *
212:      * @param string|bool $sort Whether you want the results sorted, set this and the sort property
213:      *   to false to get unsorted results.
214:      * @param array|bool $exceptions Either an array or boolean true will not grab dot files
215:      * @param bool $fullPath True returns the full path
216:      * @return array Contents of current directory as an array, an empty array on failure
217:      */
218:     public function read($sort = self::SORT_NAME, $exceptions = false, $fullPath = false)
219:     {
220:         $dirs = $files = [];
221: 
222:         if (!$this->pwd()) {
223:             return [$dirs, $files];
224:         }
225:         if (is_array($exceptions)) {
226:             $exceptions = array_flip($exceptions);
227:         }
228:         $skipHidden = isset($exceptions['.']) || $exceptions === true;
229: 
230:         try {
231:             $iterator = new DirectoryIterator($this->path);
232:         } catch (Exception $e) {
233:             return [$dirs, $files];
234:         }
235: 
236:         if (!is_bool($sort) && isset($this->_fsorts[$sort])) {
237:             $methodName = $this->_fsorts[$sort];
238:         } else {
239:             $methodName = $this->_fsorts[self::SORT_NAME];
240:         }
241: 
242:         foreach ($iterator as $item) {
243:             if ($item->isDot()) {
244:                 continue;
245:             }
246:             $name = $item->getFilename();
247:             if ($skipHidden && $name[0] === '.' || isset($exceptions[$name])) {
248:                 continue;
249:             }
250:             if ($fullPath) {
251:                 $name = $item->getPathname();
252:             }
253: 
254:             if ($item->isDir()) {
255:                 $dirs[$item->{$methodName}()][] = $name;
256:             } else {
257:                 $files[$item->{$methodName}()][] = $name;
258:             }
259:         }
260: 
261:         if ($sort || $this->sort) {
262:             ksort($dirs);
263:             ksort($files);
264:         }
265: 
266:         if ($dirs) {
267:             $dirs = array_merge(...array_values($dirs));
268:         }
269: 
270:         if ($files) {
271:             $files = array_merge(...array_values($files));
272:         }
273: 
274:         return [$dirs, $files];
275:     }
276: 
277:     /**
278:      * Returns an array of all matching files in current directory.
279:      *
280:      * @param string $regexpPattern Preg_match pattern (Defaults to: .*)
281:      * @param bool $sort Whether results should be sorted.
282:      * @return array Files that match given pattern
283:      */
284:     public function find($regexpPattern = '.*', $sort = false)
285:     {
286:         list(, $files) = $this->read($sort);
287: 
288:         return array_values(preg_grep('/^' . $regexpPattern . '$/i', $files));
289:     }
290: 
291:     /**
292:      * Returns an array of all matching files in and below current directory.
293:      *
294:      * @param string $pattern Preg_match pattern (Defaults to: .*)
295:      * @param bool $sort Whether results should be sorted.
296:      * @return array Files matching $pattern
297:      */
298:     public function findRecursive($pattern = '.*', $sort = false)
299:     {
300:         if (!$this->pwd()) {
301:             return [];
302:         }
303:         $startsOn = $this->path;
304:         $out = $this->_findRecursive($pattern, $sort);
305:         $this->cd($startsOn);
306: 
307:         return $out;
308:     }
309: 
310:     /**
311:      * Private helper function for findRecursive.
312:      *
313:      * @param string $pattern Pattern to match against
314:      * @param bool $sort Whether results should be sorted.
315:      * @return array Files matching pattern
316:      */
317:     protected function _findRecursive($pattern, $sort = false)
318:     {
319:         list($dirs, $files) = $this->read($sort);
320:         $found = [];
321: 
322:         foreach ($files as $file) {
323:             if (preg_match('/^' . $pattern . '$/i', $file)) {
324:                 $found[] = Folder::addPathElement($this->path, $file);
325:             }
326:         }
327:         $start = $this->path;
328: 
329:         foreach ($dirs as $dir) {
330:             $this->cd(Folder::addPathElement($start, $dir));
331:             $found = array_merge($found, $this->findRecursive($pattern, $sort));
332:         }
333: 
334:         return $found;
335:     }
336: 
337:     /**
338:      * Returns true if given $path is a Windows path.
339:      *
340:      * @param string $path Path to check
341:      * @return bool true if windows path, false otherwise
342:      */
343:     public static function isWindowsPath($path)
344:     {
345:         return (preg_match('/^[A-Z]:\\\\/i', $path) || substr($path, 0, 2) === '\\\\');
346:     }
347: 
348:     /**
349:      * Returns true if given $path is an absolute path.
350:      *
351:      * @param string $path Path to check
352:      * @return bool true if path is absolute.
353:      */
354:     public static function isAbsolute($path)
355:     {
356:         if (empty($path)) {
357:             return false;
358:         }
359: 
360:         return $path[0] === '/' ||
361:             preg_match('/^[A-Z]:\\\\/i', $path) ||
362:             substr($path, 0, 2) === '\\\\' ||
363:             self::isRegisteredStreamWrapper($path);
364:     }
365: 
366:     /**
367:      * Returns true if given $path is a registered stream wrapper.
368:      *
369:      * @param string $path Path to check
370:      * @return bool True if path is registered stream wrapper.
371:      */
372:     public static function isRegisteredStreamWrapper($path)
373:     {
374:         return preg_match('/^[A-Z]+(?=:\/\/)/i', $path, $matches) &&
375:             in_array($matches[0], stream_get_wrappers());
376:     }
377: 
378:     /**
379:      * Returns a correct set of slashes for given $path. (\\ for Windows paths and / for other paths.)
380:      *
381:      * @param string $path Path to check
382:      * @return string Set of slashes ("\\" or "/")
383:      */
384:     public static function normalizePath($path)
385:     {
386:         return Folder::correctSlashFor($path);
387:     }
388: 
389:     /**
390:      * Returns a correct set of slashes for given $path. (\\ for Windows paths and / for other paths.)
391:      *
392:      * @param string $path Path to check
393:      * @return string Set of slashes ("\\" or "/")
394:      */
395:     public static function correctSlashFor($path)
396:     {
397:         return Folder::isWindowsPath($path) ? '\\' : '/';
398:     }
399: 
400:     /**
401:      * Returns $path with added terminating slash (corrected for Windows or other OS).
402:      *
403:      * @param string $path Path to check
404:      * @return string Path with ending slash
405:      */
406:     public static function slashTerm($path)
407:     {
408:         if (Folder::isSlashTerm($path)) {
409:             return $path;
410:         }
411: 
412:         return $path . Folder::correctSlashFor($path);
413:     }
414: 
415:     /**
416:      * Returns $path with $element added, with correct slash in-between.
417:      *
418:      * @param string $path Path
419:      * @param string|array $element Element to add at end of path
420:      * @return string Combined path
421:      */
422:     public static function addPathElement($path, $element)
423:     {
424:         $element = (array)$element;
425:         array_unshift($element, rtrim($path, DIRECTORY_SEPARATOR));
426: 
427:         return implode(DIRECTORY_SEPARATOR, $element);
428:     }
429: 
430: 
431: 
432:     /**
433:      * Returns true if the Folder is in the given path.
434:      *
435:      * @param string $path The absolute path to check that the current `pwd()` resides within.
436:      * @param bool $reverse Reverse the search, check if the given `$path` resides within the current `pwd()`.
437:      * @return bool
438:      * @throws \InvalidArgumentException When the given `$path` argument is not an absolute path.
439:      */
440:     public function inPath($path, $reverse = false)
441:     {
442:         if (!Folder::isAbsolute($path)) {
443:             throw new InvalidArgumentException('The $path argument is expected to be an absolute path.');
444:         }
445: 
446:         $dir = Folder::slashTerm($path);
447:         $current = Folder::slashTerm($this->pwd());
448: 
449:         if (!$reverse) {
450:             $return = preg_match('/^' . preg_quote($dir, '/') . '(.*)/', $current);
451:         } else {
452:             $return = preg_match('/^' . preg_quote($current, '/') . '(.*)/', $dir);
453:         }
454: 
455:         return (bool)$return;
456:     }
457: 
458:     /**
459:      * Change the mode on a directory structure recursively. This includes changing the mode on files as well.
460:      *
461:      * @param string $path The path to chmod.
462:      * @param int|bool $mode Octal value, e.g. 0755.
463:      * @param bool $recursive Chmod recursively, set to false to only change the current directory.
464:      * @param array $exceptions Array of files, directories to skip.
465:      * @return bool Success.
466:      */
467:     public function chmod($path, $mode = false, $recursive = true, array $exceptions = [])
468:     {
469:         if (!$mode) {
470:             $mode = $this->mode;
471:         }
472: 
473:         if ($recursive === false && is_dir($path)) {
474:             //@codingStandardsIgnoreStart
475:             if (@chmod($path, intval($mode, 8))) {
476:                 //@codingStandardsIgnoreEnd
477:                 $this->_messages[] = sprintf('%s changed to %s', $path, $mode);
478: 
479:                 return true;
480:             }
481: 
482:             $this->_errors[] = sprintf('%s NOT changed to %s', $path, $mode);
483: 
484:             return false;
485:         }
486: 
487:         if (is_dir($path)) {
488:             $paths = $this->tree($path);
489: 
490:             foreach ($paths as $type) {
491:                 foreach ($type as $fullpath) {
492:                     $check = explode(DIRECTORY_SEPARATOR, $fullpath);
493:                     $count = count($check);
494: 
495:                     if (in_array($check[$count - 1], $exceptions)) {
496:                         continue;
497:                     }
498: 
499:                     //@codingStandardsIgnoreStart
500:                     if (@chmod($fullpath, intval($mode, 8))) {
501:                         //@codingStandardsIgnoreEnd
502:                         $this->_messages[] = sprintf('%s changed to %s', $fullpath, $mode);
503:                     } else {
504:                         $this->_errors[] = sprintf('%s NOT changed to %s', $fullpath, $mode);
505:                     }
506:                 }
507:             }
508: 
509:             if (empty($this->_errors)) {
510:                 return true;
511:             }
512:         }
513: 
514:         return false;
515:     }
516: 
517:     /**
518:      * Returns an array of subdirectories for the provided or current path.
519:      *
520:      * @param string|null $path The directory path to get subdirectories for.
521:      * @param bool $fullPath Whether to return the full path or only the directory name.
522:      * @return array Array of subdirectories for the provided or current path.
523:      */
524:     public function subdirectories($path = null, $fullPath = true)
525:     {
526:         if (!$path) {
527:             $path = $this->path;
528:         }
529:         $subdirectories = [];
530: 
531:         try {
532:             $iterator = new DirectoryIterator($path);
533:         } catch (Exception $e) {
534:             return [];
535:         }
536: 
537:         foreach ($iterator as $item) {
538:             if (!$item->isDir() || $item->isDot()) {
539:                 continue;
540:             }
541:             $subdirectories[] = $fullPath ? $item->getRealPath() : $item->getFilename();
542:         }
543: 
544:         return $subdirectories;
545:     }
546: 
547:     /**
548:      * Returns an array of nested directories and files in each directory
549:      *
550:      * @param string|null $path the directory path to build the tree from
551:      * @param array|bool $exceptions Either an array of files/folder to exclude
552:      *   or boolean true to not grab dot files/folders
553:      * @param string|null $type either 'file' or 'dir'. Null returns both files and directories
554:      * @return array Array of nested directories and files in each directory
555:      */
556:     public function tree($path = null, $exceptions = false, $type = null)
557:     {
558:         if (!$path) {
559:             $path = $this->path;
560:         }
561:         $files = [];
562:         $directories = [$path];
563: 
564:         if (is_array($exceptions)) {
565:             $exceptions = array_flip($exceptions);
566:         }
567:         $skipHidden = false;
568:         if ($exceptions === true) {
569:             $skipHidden = true;
570:         } elseif (isset($exceptions['.'])) {
571:             $skipHidden = true;
572:             unset($exceptions['.']);
573:         }
574: 
575:         try {
576:             $directory = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::KEY_AS_PATHNAME | RecursiveDirectoryIterator::CURRENT_AS_SELF);
577:             $iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
578:         } catch (Exception $e) {
579:             if ($type === null) {
580:                 return [[], []];
581:             }
582: 
583:             return [];
584:         }
585: 
586:         foreach ($iterator as $itemPath => $fsIterator) {
587:             if ($skipHidden) {
588:                 $subPathName = $fsIterator->getSubPathname();
589:                 if ($subPathName{0} === '.' || strpos($subPathName, DIRECTORY_SEPARATOR . '.') !== false) {
590:                     continue;
591:                 }
592:             }
593:             $item = $fsIterator->current();
594:             if (!empty($exceptions) && isset($exceptions[$item->getFilename()])) {
595:                 continue;
596:             }
597: 
598:             if ($item->isFile()) {
599:                 $files[] = $itemPath;
600:             } elseif ($item->isDir() && !$item->isDot()) {
601:                 $directories[] = $itemPath;
602:             }
603:         }
604:         if ($type === null) {
605:             return [$directories, $files];
606:         }
607:         if ($type === 'dir') {
608:             return $directories;
609:         }
610: 
611:         return $files;
612:     }
613: 
614:     /**
615:      * Create a directory structure recursively.
616:      *
617:      * Can be used to create deep path structures like `/foo/bar/baz/shoe/horn`
618:      *
619:      * @param string $pathname The directory structure to create. Either an absolute or relative
620:      *   path. If the path is relative and exists in the process' cwd it will not be created.
621:      *   Otherwise relative paths will be prefixed with the current pwd().
622:      * @param int|bool $mode octal value 0755
623:      * @return bool Returns TRUE on success, FALSE on failure
624:      */
625:     public function create($pathname, $mode = false)
626:     {
627:         if (is_dir($pathname) || empty($pathname)) {
628:             return true;
629:         }
630: 
631:         if (!self::isAbsolute($pathname)) {
632:             $pathname = self::addPathElement($this->pwd(), $pathname);
633:         }
634: 
635:         if (!$mode) {
636:             $mode = $this->mode;
637:         }
638: 
639:         if (is_file($pathname)) {
640:             $this->_errors[] = sprintf('%s is a file', $pathname);
641: 
642:             return false;
643:         }
644:         $pathname = rtrim($pathname, DIRECTORY_SEPARATOR);
645:         $nextPathname = substr($pathname, 0, strrpos($pathname, DIRECTORY_SEPARATOR));
646: 
647:         if ($this->create($nextPathname, $mode)) {
648:             if (!file_exists($pathname)) {
649:                 $old = umask(0);
650:                 if (mkdir($pathname, $mode, true)) {
651:                     umask($old);
652:                     $this->_messages[] = sprintf('%s created', $pathname);
653: 
654:                     return true;
655:                 }
656:                 umask($old);
657:                 $this->_errors[] = sprintf('%s NOT created', $pathname);
658: 
659:                 return false;
660:             }
661:         }
662: 
663:         return false;
664:     }
665: 
666:     /**
667:      * Returns the size in bytes of this Folder and its contents.
668:      *
669:      * @return int size in bytes of current folder
670:      */
671:     public function dirsize()
672:     {
673:         $size = 0;
674:         $directory = Folder::slashTerm($this->path);
675:         $stack = [$directory];
676:         $count = count($stack);
677:         for ($i = 0, $j = $count; $i < $j; ++$i) {
678:             if (is_file($stack[$i])) {
679:                 $size += filesize($stack[$i]);
680:             } elseif (is_dir($stack[$i])) {
681:                 $dir = dir($stack[$i]);
682:                 if ($dir) {
683:                     while (($entry = $dir->read()) !== false) {
684:                         if ($entry === '.' || $entry === '..') {
685:                             continue;
686:                         }
687:                         $add = $stack[$i] . $entry;
688: 
689:                         if (is_dir($stack[$i] . $entry)) {
690:                             $add = Folder::slashTerm($add);
691:                         }
692:                         $stack[] = $add;
693:                     }
694:                     $dir->close();
695:                 }
696:             }
697:             $j = count($stack);
698:         }
699: 
700:         return $size;
701:     }
702: 
703:     /**
704:      * Recursively Remove directories if the system allows.
705:      *
706:      * @param string|null $path Path of directory to delete
707:      * @return bool Success
708:      */
709:     public function delete($path = null)
710:     {
711:         if (!$path) {
712:             $path = $this->pwd();
713:         }
714:         if (!$path) {
715:             return false;
716:         }
717:         $path = Folder::slashTerm($path);
718:         if (is_dir($path)) {
719:             try {
720:                 $directory = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::CURRENT_AS_SELF);
721:                 $iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::CHILD_FIRST);
722:             } catch (Exception $e) {
723:                 return false;
724:             }
725: 
726:             foreach ($iterator as $item) {
727:                 $filePath = $item->getPathname();
728:                 if ($item->isFile() || $item->isLink()) {
729:                     //@codingStandardsIgnoreStart
730:                     if (@unlink($filePath)) {
731:                         //@codingStandardsIgnoreEnd
732:                         $this->_messages[] = sprintf('%s removed', $filePath);
733:                     } else {
734:                         $this->_errors[] = sprintf('%s NOT removed', $filePath);
735:                     }
736:                 } elseif ($item->isDir() && !$item->isDot()) {
737:                     //@codingStandardsIgnoreStart
738:                     if (@rmdir($filePath)) {
739:                         //@codingStandardsIgnoreEnd
740:                         $this->_messages[] = sprintf('%s removed', $filePath);
741:                     } else {
742:                         $this->_errors[] = sprintf('%s NOT removed', $filePath);
743: 
744:                         return false;
745:                     }
746:                 }
747:             }
748: 
749:             $path = rtrim($path, DIRECTORY_SEPARATOR);
750:             //@codingStandardsIgnoreStart
751:             if (@rmdir($path)) {
752:                 //@codingStandardsIgnoreEnd
753:                 $this->_messages[] = sprintf('%s removed', $path);
754:             } else {
755:                 $this->_errors[] = sprintf('%s NOT removed', $path);
756: 
757:                 return false;
758:             }
759:         }
760: 
761:         return true;
762:     }
763: 
764:     /**
765:      * Recursive directory copy.
766:      *
767:      * ### Options
768:      *
769:      * - `to` The directory to copy to.
770:      * - `from` The directory to copy from, this will cause a cd() to occur, changing the results of pwd().
771:      * - `mode` The mode to copy the files/directories with as integer, e.g. 0775.
772:      * - `skip` Files/directories to skip.
773:      * - `scheme` Folder::MERGE, Folder::OVERWRITE, Folder::SKIP
774:      * - `recursive` Whether to copy recursively or not (default: true - recursive)
775:      *
776:      * @param array|string $options Either an array of options (see above) or a string of the destination directory.
777:      * @return bool Success.
778:      */
779:     public function copy($options)
780:     {
781:         if (!$this->pwd()) {
782:             return false;
783:         }
784:         $to = null;
785:         if (is_string($options)) {
786:             $to = $options;
787:             $options = [];
788:         }
789:         $options += [
790:             'to' => $to,
791:             'from' => $this->path,
792:             'mode' => $this->mode,
793:             'skip' => [],
794:             'scheme' => Folder::MERGE,
795:             'recursive' => true
796:         ];
797: 
798:         $fromDir = $options['from'];
799:         $toDir = $options['to'];
800:         $mode = $options['mode'];
801: 
802:         if (!$this->cd($fromDir)) {
803:             $this->_errors[] = sprintf('%s not found', $fromDir);
804: 
805:             return false;
806:         }
807: 
808:         if (!is_dir($toDir)) {
809:             $this->create($toDir, $mode);
810:         }
811: 
812:         if (!is_writable($toDir)) {
813:             $this->_errors[] = sprintf('%s not writable', $toDir);
814: 
815:             return false;
816:         }
817: 
818:         $exceptions = array_merge(['.', '..', '.svn'], $options['skip']);
819:         //@codingStandardsIgnoreStart
820:         if ($handle = @opendir($fromDir)) {
821:             //@codingStandardsIgnoreEnd
822:             while (($item = readdir($handle)) !== false) {
823:                 $to = Folder::addPathElement($toDir, $item);
824:                 if (($options['scheme'] != Folder::SKIP || !is_dir($to)) && !in_array($item, $exceptions)) {
825:                     $from = Folder::addPathElement($fromDir, $item);
826:                     if (is_file($from) && (!is_file($to) || $options['scheme'] != Folder::SKIP)) {
827:                         if (copy($from, $to)) {
828:                             chmod($to, intval($mode, 8));
829:                             touch($to, filemtime($from));
830:                             $this->_messages[] = sprintf('%s copied to %s', $from, $to);
831:                         } else {
832:                             $this->_errors[] = sprintf('%s NOT copied to %s', $from, $to);
833:                         }
834:                     }
835: 
836:                     if (is_dir($from) && file_exists($to) && $options['scheme'] === Folder::OVERWRITE) {
837:                         $this->delete($to);
838:                     }
839: 
840:                     if (is_dir($from) && $options['recursive'] === false) {
841:                         continue;
842:                     }
843: 
844:                     if (is_dir($from) && !file_exists($to)) {
845:                         $old = umask(0);
846:                         if (mkdir($to, $mode, true)) {
847:                             umask($old);
848:                             $old = umask(0);
849:                             chmod($to, $mode);
850:                             umask($old);
851:                             $this->_messages[] = sprintf('%s created', $to);
852:                             $options = ['to' => $to, 'from' => $from] + $options;
853:                             $this->copy($options);
854:                         } else {
855:                             $this->_errors[] = sprintf('%s not created', $to);
856:                         }
857:                     } elseif (is_dir($from) && $options['scheme'] === Folder::MERGE) {
858:                         $options = ['to' => $to, 'from' => $from] + $options;
859:                         $this->copy($options);
860:                     }
861:                 }
862:             }
863:             closedir($handle);
864:         } else {
865:             return false;
866:         }
867: 
868:         return empty($this->_errors);
869:     }
870: 
871:     /**
872:      * Recursive directory move.
873:      *
874:      * ### Options
875:      *
876:      * - `to` The directory to copy to.
877:      * - `from` The directory to copy from, this will cause a cd() to occur, changing the results of pwd().
878:      * - `chmod` The mode to copy the files/directories with.
879:      * - `skip` Files/directories to skip.
880:      * - `scheme` Folder::MERGE, Folder::OVERWRITE, Folder::SKIP
881:      * - `recursive` Whether to copy recursively or not (default: true - recursive)
882:      *
883:      * @param array|string $options (to, from, chmod, skip, scheme)
884:      * @return bool Success
885:      */
886:     public function move($options)
887:     {
888:         $to = null;
889:         if (is_string($options)) {
890:             $to = $options;
891:             $options = (array)$options;
892:         }
893:         $options += ['to' => $to, 'from' => $this->path, 'mode' => $this->mode, 'skip' => [], 'recursive' => true];
894: 
895:         if ($this->copy($options) && $this->delete($options['from'])) {
896:             return (bool)$this->cd($options['to']);
897:         }
898: 
899:         return false;
900:     }
901: 
902:     /**
903:      * get messages from latest method
904:      *
905:      * @param bool $reset Reset message stack after reading
906:      * @return array
907:      */
908:     public function messages($reset = true)
909:     {
910:         $messages = $this->_messages;
911:         if ($reset) {
912:             $this->_messages = [];
913:         }
914: 
915:         return $messages;
916:     }
917: 
918:     /**
919:      * get error from latest method
920:      *
921:      * @param bool $reset Reset error stack after reading
922:      * @return array
923:      */
924:     public function errors($reset = true)
925:     {
926:         $errors = $this->_errors;
927:         if ($reset) {
928:             $this->_errors = [];
929:         }
930: 
931:         return $errors;
932:     }
933: 
934:     /**
935:      * Get the real path (taking ".." and such into account)
936:      *
937:      * @param string $path Path to resolve
938:      * @return string|bool The resolved path
939:      */
940:     public function realpath($path)
941:     {
942:         if (strpos($path, '..') === false) {
943:             if (!Folder::isAbsolute($path)) {
944:                 $path = Folder::addPathElement($this->path, $path);
945:             }
946: 
947:             return $path;
948:         }
949:         $path = str_replace('/', DIRECTORY_SEPARATOR, trim($path));
950:         $parts = explode(DIRECTORY_SEPARATOR, $path);
951:         $newparts = [];
952:         $newpath = '';
953:         if ($path[0] === DIRECTORY_SEPARATOR) {
954:             $newpath = DIRECTORY_SEPARATOR;
955:         }
956: 
957:         while (($part = array_shift($parts)) !== null) {
958:             if ($part === '.' || $part === '') {
959:                 continue;
960:             }
961:             if ($part === '..') {
962:                 if (!empty($newparts)) {
963:                     array_pop($newparts);
964:                     continue;
965:                 }
966: 
967:                 return false;
968:             }
969:             $newparts[] = $part;
970:         }
971:         $newpath .= implode(DIRECTORY_SEPARATOR, $newparts);
972: 
973:         return Folder::slashTerm($newpath);
974:     }
975: 
976:     /**
977:      * Returns true if given $path ends in a slash (i.e. is slash-terminated).
978:      *
979:      * @param string $path Path to check
980:      * @return bool true if path ends with slash, false otherwise
981:      */
982:     public static function isSlashTerm($path)
983:     {
984:         $lastChar = $path[strlen($path) - 1];
985: 
986:         return $lastChar === '/' || $lastChar === '\\';
987:     }
988: }
989: 
API documentation generated by ApiGen