vendor/league/flysystem-local/LocalFilesystemAdapter.php line 423

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