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

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