vendor/league/flysystem/src/Local/LocalFilesystemAdapter.php line 278

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace League\Flysystem\Local;
  4. use DirectoryIterator;
  5. use FilesystemIterator;
  6. use Generator;
  7. use League\Flysystem\ChecksumProvider;
  8. use League\Flysystem\Config;
  9. use League\Flysystem\DirectoryAttributes;
  10. use League\Flysystem\FileAttributes;
  11. use League\Flysystem\FilesystemAdapter;
  12. use League\Flysystem\PathPrefixer;
  13. use League\Flysystem\SymbolicLinkEncountered;
  14. use League\Flysystem\UnableToCopyFile;
  15. use League\Flysystem\UnableToCreateDirectory;
  16. use League\Flysystem\UnableToDeleteDirectory;
  17. use League\Flysystem\UnableToDeleteFile;
  18. use League\Flysystem\UnableToMoveFile;
  19. use League\Flysystem\UnableToProvideChecksum;
  20. use League\Flysystem\UnableToReadFile;
  21. use League\Flysystem\UnableToRetrieveMetadata;
  22. use League\Flysystem\UnableToSetVisibility;
  23. use League\Flysystem\UnableToWriteFile;
  24. use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
  25. use League\Flysystem\UnixVisibility\VisibilityConverter;
  26. use League\MimeTypeDetection\FinfoMimeTypeDetector;
  27. use League\MimeTypeDetection\MimeTypeDetector;
  28. use RecursiveDirectoryIterator;
  29. use RecursiveIteratorIterator;
  30. use SplFileInfo;
  31. use function chmod;
  32. use function clearstatcache;
  33. use function dirname;
  34. use function error_clear_last;
  35. use function error_get_last;
  36. use function file_exists;
  37. use function file_put_contents;
  38. use function hash_file;
  39. use function is_dir;
  40. use function is_file;
  41. use function mkdir;
  42. use function rename;
  43. use const DIRECTORY_SEPARATOR;
  44. use const LOCK_EX;
  45. class LocalFilesystemAdapter implements FilesystemAdapterChecksumProvider
  46. {
  47.     /**
  48.      * @var int
  49.      */
  50.     public const SKIP_LINKS 0001;
  51.     /**
  52.      * @var int
  53.      */
  54.     public const DISALLOW_LINKS 0002;
  55.     private PathPrefixer $prefixer;
  56.     private VisibilityConverter $visibility;
  57.     private MimeTypeDetector $mimeTypeDetector;
  58.     private string $rootLocation;
  59.     /**
  60.      * @var bool
  61.      */
  62.     private $rootLocationIsSetup false;
  63.     public function __construct(
  64.         string $location,
  65.         VisibilityConverter $visibility null,
  66.         private int $writeFlags LOCK_EX,
  67.         private int $linkHandling self::DISALLOW_LINKS,
  68.         MimeTypeDetector $mimeTypeDetector null,
  69.         bool $lazyRootCreation false,
  70.     ) {
  71.         $this->prefixer = new PathPrefixer($locationDIRECTORY_SEPARATOR);
  72.         $visibility ??= new PortableVisibilityConverter();
  73.         $this->visibility $visibility;
  74.         $this->rootLocation $location;
  75.         $this->mimeTypeDetector $mimeTypeDetector ?: new FallbackMimeTypeDetector(new FinfoMimeTypeDetector());
  76.         if ( ! $lazyRootCreation) {
  77.             $this->ensureRootDirectoryExists();
  78.         }
  79.     }
  80.     private function ensureRootDirectoryExists(): void
  81.     {
  82.         if ($this->rootLocationIsSetup) {
  83.             return;
  84.         }
  85.         $this->ensureDirectoryExists($this->rootLocation$this->visibility->defaultForDirectories());
  86.     }
  87.     public function write(string $pathstring $contentsConfig $config): void
  88.     {
  89.         $this->writeToFile($path$contents$config);
  90.     }
  91.     public function writeStream(string $path$contentsConfig $config): void
  92.     {
  93.         $this->writeToFile($path$contents$config);
  94.     }
  95.     /**
  96.      * @param resource|string $contents
  97.      */
  98.     private function writeToFile(string $path$contentsConfig $config): void
  99.     {
  100.         $prefixedLocation $this->prefixer->prefixPath($path);
  101.         $this->ensureRootDirectoryExists();
  102.         $this->ensureDirectoryExists(
  103.             dirname($prefixedLocation),
  104.             $this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY))
  105.         );
  106.         error_clear_last();
  107.         if (@file_put_contents($prefixedLocation$contents$this->writeFlags) === false) {
  108.             throw UnableToWriteFile::atLocation($patherror_get_last()['message'] ?? '');
  109.         }
  110.         if ($visibility $config->get(Config::OPTION_VISIBILITY)) {
  111.             $this->setVisibility($path, (string) $visibility);
  112.         }
  113.     }
  114.     public function delete(string $path): void
  115.     {
  116.         $location $this->prefixer->prefixPath($path);
  117.         if ( ! file_exists($location)) {
  118.             return;
  119.         }
  120.         error_clear_last();
  121.         if ( ! @unlink($location)) {
  122.             throw UnableToDeleteFile::atLocation($locationerror_get_last()['message'] ?? '');
  123.         }
  124.     }
  125.     public function deleteDirectory(string $prefix): void
  126.     {
  127.         $location $this->prefixer->prefixPath($prefix);
  128.         if ( ! is_dir($location)) {
  129.             return;
  130.         }
  131.         $contents $this->listDirectoryRecursively($locationRecursiveIteratorIterator::CHILD_FIRST);
  132.         /** @var SplFileInfo $file */
  133.         foreach ($contents as $file) {
  134.             if ( ! $this->deleteFileInfoObject($file)) {
  135.                 throw UnableToDeleteDirectory::atLocation($prefix"Unable to delete file at " $file->getPathname());
  136.             }
  137.         }
  138.         unset($contents);
  139.         if ( ! @rmdir($location)) {
  140.             throw UnableToDeleteDirectory::atLocation($prefixerror_get_last()['message'] ?? '');
  141.         }
  142.     }
  143.     private function listDirectoryRecursively(
  144.         string $path,
  145.         int $mode RecursiveIteratorIterator::SELF_FIRST
  146.     ): Generator {
  147.         if ( ! is_dir($path)) {
  148.             return;
  149.         }
  150.         yield from new RecursiveIteratorIterator(
  151.             new RecursiveDirectoryIterator($pathFilesystemIterator::SKIP_DOTS),
  152.             $mode
  153.         );
  154.     }
  155.     protected function deleteFileInfoObject(SplFileInfo $file): bool
  156.     {
  157.         switch ($file->getType()) {
  158.             case 'dir':
  159.                 return @rmdir((string) $file->getRealPath());
  160.             case 'link':
  161.                 return @unlink((string) $file->getPathname());
  162.             default:
  163.                 return @unlink((string) $file->getRealPath());
  164.         }
  165.     }
  166.     public function listContents(string $pathbool $deep): iterable
  167.     {
  168.         $location $this->prefixer->prefixPath($path);
  169.         if ( ! is_dir($location)) {
  170.             return;
  171.         }
  172.         /** @var SplFileInfo[] $iterator */
  173.         $iterator $deep $this->listDirectoryRecursively($location) : $this->listDirectory($location);
  174.         foreach ($iterator as $fileInfo) {
  175.             if ($fileInfo->isLink()) {
  176.                 if ($this->linkHandling self::SKIP_LINKS) {
  177.                     continue;
  178.                 }
  179.                 throw SymbolicLinkEncountered::atLocation($fileInfo->getPathname());
  180.             }
  181.             $path $this->prefixer->stripPrefix($fileInfo->getPathname());
  182.             $lastModified $fileInfo->getMTime();
  183.             $isDirectory $fileInfo->isDir();
  184.             $permissions octdec(substr(sprintf('%o'$fileInfo->getPerms()), -4));
  185.             $visibility $isDirectory $this->visibility->inverseForDirectory($permissions) : $this->visibility->inverseForFile($permissions);
  186.             yield $isDirectory ? new DirectoryAttributes(str_replace('\\''/'$path), $visibility$lastModified) : new FileAttributes(
  187.                 str_replace('\\''/'$path),
  188.                 $fileInfo->getSize(),
  189.                 $visibility,
  190.                 $lastModified
  191.             );
  192.         }
  193.     }
  194.     public function move(string $sourcestring $destinationConfig $config): void
  195.     {
  196.         $sourcePath $this->prefixer->prefixPath($source);
  197.         $destinationPath $this->prefixer->prefixPath($destination);
  198.         $this->ensureRootDirectoryExists();
  199.         $this->ensureDirectoryExists(
  200.             dirname($destinationPath),
  201.             $this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY))
  202.         );
  203.         if ( ! @rename($sourcePath$destinationPath)) {
  204.             throw UnableToMoveFile::fromLocationTo($sourcePath$destinationPath);
  205.         }
  206.     }
  207.     public function copy(string $sourcestring $destinationConfig $config): void
  208.     {
  209.         $sourcePath $this->prefixer->prefixPath($source);
  210.         $destinationPath $this->prefixer->prefixPath($destination);
  211.         $this->ensureRootDirectoryExists();
  212.         $this->ensureDirectoryExists(
  213.             dirname($destinationPath),
  214.             $this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY))
  215.         );
  216.         if ( ! @copy($sourcePath$destinationPath)) {
  217.             throw UnableToCopyFile::fromLocationTo($sourcePath$destinationPath);
  218.         }
  219.     }
  220.     public function read(string $path): string
  221.     {
  222.         $location $this->prefixer->prefixPath($path);
  223.         error_clear_last();
  224.         $contents = @file_get_contents($location);
  225.         if ($contents === false) {
  226.             throw UnableToReadFile::fromLocation($patherror_get_last()['message'] ?? '');
  227.         }
  228.         return $contents;
  229.     }
  230.     public function readStream(string $path)
  231.     {
  232.         $location $this->prefixer->prefixPath($path);
  233.         error_clear_last();
  234.         $contents = @fopen($location'rb');
  235.         if ($contents === false) {
  236.             throw UnableToReadFile::fromLocation($patherror_get_last()['message'] ?? '');
  237.         }
  238.         return $contents;
  239.     }
  240.     protected function ensureDirectoryExists(string $dirnameint $visibility): void
  241.     {
  242.         if (is_dir($dirname)) {
  243.             return;
  244.         }
  245.         error_clear_last();
  246.         if ( ! @mkdir($dirname$visibilitytrue)) {
  247.             $mkdirError error_get_last();
  248.         }
  249.         clearstatcache(true$dirname);
  250.         if ( ! is_dir($dirname)) {
  251.             $errorMessage = isset($mkdirError['message']) ? $mkdirError['message'] : '';
  252.             throw UnableToCreateDirectory::atLocation($dirname$errorMessage);
  253.         }
  254.     }
  255.     public function fileExists(string $location): bool
  256.     {
  257.         $location $this->prefixer->prefixPath($location);
  258.         return is_file($location);
  259.     }
  260.     public function directoryExists(string $location): bool
  261.     {
  262.         $location $this->prefixer->prefixPath($location);
  263.         return is_dir($location);
  264.     }
  265.     public function createDirectory(string $pathConfig $config): void
  266.     {
  267.         $this->ensureRootDirectoryExists();
  268.         $location $this->prefixer->prefixPath($path);
  269.         $visibility $config->get(Config::OPTION_VISIBILITY$config->get(Config::OPTION_DIRECTORY_VISIBILITY));
  270.         $permissions $this->resolveDirectoryVisibility($visibility);
  271.         if (is_dir($location)) {
  272.             $this->setPermissions($location$permissions);
  273.             return;
  274.         }
  275.         error_clear_last();
  276.         if ( ! @mkdir($location$permissionstrue)) {
  277.             throw UnableToCreateDirectory::atLocation($patherror_get_last()['message'] ?? '');
  278.         }
  279.     }
  280.     public function setVisibility(string $pathstring $visibility): void
  281.     {
  282.         $path $this->prefixer->prefixPath($path);
  283.         $visibility is_dir($path) ? $this->visibility->forDirectory($visibility) : $this->visibility->forFile(
  284.             $visibility
  285.         );
  286.         $this->setPermissions($path$visibility);
  287.     }
  288.     public function visibility(string $path): FileAttributes
  289.     {
  290.         $location $this->prefixer->prefixPath($path);
  291.         clearstatcache(false$location);
  292.         error_clear_last();
  293.         $fileperms = @fileperms($location);
  294.         if ($fileperms === false) {
  295.             throw UnableToRetrieveMetadata::visibility($patherror_get_last()['message'] ?? '');
  296.         }
  297.         $permissions $fileperms 0777;
  298.         $visibility $this->visibility->inverseForFile($permissions);
  299.         return new FileAttributes($pathnull$visibility);
  300.     }
  301.     private function resolveDirectoryVisibility(?string $visibility): int
  302.     {
  303.         return $visibility === null $this->visibility->defaultForDirectories() : $this->visibility->forDirectory(
  304.             $visibility
  305.         );
  306.     }
  307.     public function mimeType(string $path): FileAttributes
  308.     {
  309.         $location $this->prefixer->prefixPath($path);
  310.         error_clear_last();
  311.         if ( ! is_file($location)) {
  312.             throw UnableToRetrieveMetadata::mimeType($location'No such file exists.');
  313.         }
  314.         $mimeType $this->mimeTypeDetector->detectMimeTypeFromFile($location);
  315.         if ($mimeType === null) {
  316.             throw UnableToRetrieveMetadata::mimeType($patherror_get_last()['message'] ?? '');
  317.         }
  318.         return new FileAttributes($pathnullnullnull$mimeType);
  319.     }
  320.     public function lastModified(string $path): FileAttributes
  321.     {
  322.         $location $this->prefixer->prefixPath($path);
  323.         error_clear_last();
  324.         $lastModified = @filemtime($location);
  325.         if ($lastModified === false) {
  326.             throw UnableToRetrieveMetadata::lastModified($patherror_get_last()['message'] ?? '');
  327.         }
  328.         return new FileAttributes($pathnullnull$lastModified);
  329.     }
  330.     public function fileSize(string $path): FileAttributes
  331.     {
  332.         $location $this->prefixer->prefixPath($path);
  333.         error_clear_last();
  334.         if (is_file($location) && ($fileSize = @filesize($location)) !== false) {
  335.             return new FileAttributes($path$fileSize);
  336.         }
  337.         throw UnableToRetrieveMetadata::fileSize($patherror_get_last()['message'] ?? '');
  338.     }
  339.     public function checksum(string $pathConfig $config): string
  340.     {
  341.         $algo $config->get('checksum_algo''md5');
  342.         $location $this->prefixer->prefixPath($path);
  343.         error_clear_last();
  344.         $checksum = @hash_file($algo$location);
  345.         if ($checksum === false) {
  346.             throw new UnableToProvideChecksum(error_get_last()['message'] ?? ''$path);
  347.         }
  348.         return $checksum;
  349.     }
  350.     private function listDirectory(string $location): Generator
  351.     {
  352.         $iterator = new DirectoryIterator($location);
  353.         foreach ($iterator as $item) {
  354.             if ($item->isDot()) {
  355.                 continue;
  356.             }
  357.             yield $item;
  358.         }
  359.     }
  360.     private function setPermissions(string $locationint $visibility): void
  361.     {
  362.         error_clear_last();
  363.         if ( ! @chmod($location$visibility)) {
  364.             $extraMessage error_get_last()['message'] ?? '';
  365.             throw UnableToSetVisibility::atLocation($this->prefixer->stripPrefix($location), $extraMessage);
  366.         }
  367.     }
  368. }