v1.0.0 initial release
This commit is contained in:
+140
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Filesystem;
|
||||
|
||||
use Aws\S3\S3Client;
|
||||
use Illuminate\Support\Traits\Conditionable;
|
||||
use League\Flysystem\AwsS3V3\AwsS3V3Adapter as S3Adapter;
|
||||
use League\Flysystem\FilesystemOperator;
|
||||
|
||||
class AwsS3V3Adapter extends FilesystemAdapter
|
||||
{
|
||||
use Conditionable;
|
||||
|
||||
/**
|
||||
* The AWS S3 client.
|
||||
*
|
||||
* @var \Aws\S3\S3Client
|
||||
*/
|
||||
protected $client;
|
||||
|
||||
/**
|
||||
* Create a new AwsS3V3FilesystemAdapter instance.
|
||||
*
|
||||
* @param \League\Flysystem\FilesystemOperator $driver
|
||||
* @param \League\Flysystem\AwsS3V3\AwsS3V3Adapter $adapter
|
||||
* @param array $config
|
||||
* @param \Aws\S3\S3Client $client
|
||||
*/
|
||||
public function __construct(FilesystemOperator $driver, S3Adapter $adapter, array $config, S3Client $client)
|
||||
{
|
||||
parent::__construct($driver, $adapter, $config);
|
||||
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL for the file at the given path.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function url($path)
|
||||
{
|
||||
// If an explicit base URL has been set on the disk configuration then we will use
|
||||
// it as the base URL instead of the default path. This allows the developer to
|
||||
// have full control over the base path for this filesystem's generated URLs.
|
||||
if (isset($this->config['url'])) {
|
||||
return $this->concatPathToUrl($this->config['url'], $this->prefixer->prefixPath($path));
|
||||
}
|
||||
|
||||
return $this->client->getObjectUrl(
|
||||
$this->config['bucket'], $this->prefixer->prefixPath($path)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if temporary URLs can be generated.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function providesTemporaryUrls()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a temporary URL for the file at the given path.
|
||||
*
|
||||
* @param string $path
|
||||
* @param \DateTimeInterface $expiration
|
||||
* @param array $options
|
||||
* @return string
|
||||
*/
|
||||
public function temporaryUrl($path, $expiration, array $options = [])
|
||||
{
|
||||
$command = $this->client->getCommand('GetObject', array_merge([
|
||||
'Bucket' => $this->config['bucket'],
|
||||
'Key' => $this->prefixer->prefixPath($path),
|
||||
], $options));
|
||||
|
||||
$uri = $this->client->createPresignedRequest(
|
||||
$command, $expiration, $options
|
||||
)->getUri();
|
||||
|
||||
// If an explicit base URL has been set on the disk configuration then we will use
|
||||
// it as the base URL instead of the default path. This allows the developer to
|
||||
// have full control over the base path for this filesystem's generated URLs.
|
||||
if (isset($this->config['temporary_url'])) {
|
||||
$uri = $this->replaceBaseUrl($uri, $this->config['temporary_url']);
|
||||
}
|
||||
|
||||
return (string) $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a temporary upload URL for the file at the given path.
|
||||
*
|
||||
* @param string $path
|
||||
* @param \DateTimeInterface $expiration
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
public function temporaryUploadUrl($path, $expiration, array $options = [])
|
||||
{
|
||||
$command = $this->client->getCommand('PutObject', array_merge([
|
||||
'Bucket' => $this->config['bucket'],
|
||||
'Key' => $this->prefixer->prefixPath($path),
|
||||
], $options));
|
||||
|
||||
$signedRequest = $this->client->createPresignedRequest(
|
||||
$command, $expiration, $options
|
||||
);
|
||||
|
||||
$uri = $signedRequest->getUri();
|
||||
|
||||
// If an explicit base URL has been set on the disk configuration then we will use
|
||||
// it as the base URL instead of the default path. This allows the developer to
|
||||
// have full control over the base path for this filesystem's generated URLs.
|
||||
if (isset($this->config['temporary_url'])) {
|
||||
$uri = $this->replaceBaseUrl($uri, $this->config['temporary_url']);
|
||||
}
|
||||
|
||||
return [
|
||||
'url' => (string) $uri,
|
||||
'headers' => $signedRequest->getHeaders(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying S3 client.
|
||||
*
|
||||
* @return \Aws\S3\S3Client
|
||||
*/
|
||||
public function getClient()
|
||||
{
|
||||
return $this->client;
|
||||
}
|
||||
}
|
||||
+796
@@ -0,0 +1,796 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Filesystem;
|
||||
|
||||
use ErrorException;
|
||||
use FilesystemIterator;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Illuminate\Support\LazyCollection;
|
||||
use Illuminate\Support\Traits\Conditionable;
|
||||
use Illuminate\Support\Traits\Macroable;
|
||||
use RuntimeException;
|
||||
use SplFileObject;
|
||||
use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\Mime\MimeTypes;
|
||||
|
||||
class Filesystem
|
||||
{
|
||||
use Conditionable, Macroable;
|
||||
|
||||
/**
|
||||
* Determine if a file or directory exists.
|
||||
*
|
||||
* @param string $path
|
||||
* @return bool
|
||||
*/
|
||||
public function exists($path)
|
||||
{
|
||||
return file_exists($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a file or directory is missing.
|
||||
*
|
||||
* @param string $path
|
||||
* @return bool
|
||||
*/
|
||||
public function missing($path)
|
||||
{
|
||||
return ! $this->exists($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contents of a file.
|
||||
*
|
||||
* @param string $path
|
||||
* @param bool $lock
|
||||
* @return string
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
|
||||
*/
|
||||
public function get($path, $lock = false)
|
||||
{
|
||||
if ($this->isFile($path)) {
|
||||
return $lock ? $this->sharedGet($path) : file_get_contents($path);
|
||||
}
|
||||
|
||||
throw new FileNotFoundException("File does not exist at path {$path}.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contents of a file as decoded JSON.
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $flags
|
||||
* @param bool $lock
|
||||
* @return array
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
|
||||
*/
|
||||
public function json($path, $flags = 0, $lock = false)
|
||||
{
|
||||
return json_decode($this->get($path, $lock), true, 512, $flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contents of a file with shared access.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public function sharedGet($path)
|
||||
{
|
||||
$contents = '';
|
||||
|
||||
$handle = fopen($path, 'rb');
|
||||
|
||||
if ($handle) {
|
||||
try {
|
||||
if (flock($handle, LOCK_SH)) {
|
||||
clearstatcache(true, $path);
|
||||
|
||||
$contents = fread($handle, $this->size($path) ?: 1);
|
||||
|
||||
flock($handle, LOCK_UN);
|
||||
}
|
||||
} finally {
|
||||
fclose($handle);
|
||||
}
|
||||
}
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the returned value of a file.
|
||||
*
|
||||
* @param string $path
|
||||
* @param array $data
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
|
||||
*/
|
||||
public function getRequire($path, array $data = [])
|
||||
{
|
||||
if ($this->isFile($path)) {
|
||||
$__path = $path;
|
||||
$__data = $data;
|
||||
|
||||
return (static function () use ($__path, $__data) {
|
||||
extract($__data, EXTR_SKIP);
|
||||
|
||||
return require $__path;
|
||||
})();
|
||||
}
|
||||
|
||||
throw new FileNotFoundException("File does not exist at path {$path}.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Require the given file once.
|
||||
*
|
||||
* @param string $path
|
||||
* @param array $data
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
|
||||
*/
|
||||
public function requireOnce($path, array $data = [])
|
||||
{
|
||||
if ($this->isFile($path)) {
|
||||
$__path = $path;
|
||||
$__data = $data;
|
||||
|
||||
return (static function () use ($__path, $__data) {
|
||||
extract($__data, EXTR_SKIP);
|
||||
|
||||
return require_once $__path;
|
||||
})();
|
||||
}
|
||||
|
||||
throw new FileNotFoundException("File does not exist at path {$path}.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contents of a file one line at a time.
|
||||
*
|
||||
* @param string $path
|
||||
* @return \Illuminate\Support\LazyCollection
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
|
||||
*/
|
||||
public function lines($path)
|
||||
{
|
||||
if (! $this->isFile($path)) {
|
||||
throw new FileNotFoundException(
|
||||
"File does not exist at path {$path}."
|
||||
);
|
||||
}
|
||||
|
||||
return new LazyCollection(function () use ($path) {
|
||||
$file = new SplFileObject($path);
|
||||
|
||||
$file->setFlags(SplFileObject::DROP_NEW_LINE);
|
||||
|
||||
while (! $file->eof()) {
|
||||
yield $file->fgets();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hash of the file at the given path.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $algorithm
|
||||
* @return string|false
|
||||
*/
|
||||
public function hash($path, $algorithm = 'md5')
|
||||
{
|
||||
return hash_file($algorithm, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the contents of a file.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $contents
|
||||
* @param bool $lock
|
||||
* @return int|bool
|
||||
*/
|
||||
public function put($path, $contents, $lock = false)
|
||||
{
|
||||
return file_put_contents($path, $contents, $lock ? LOCK_EX : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the contents of a file, replacing it atomically if it already exists.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $content
|
||||
* @param int|null $mode
|
||||
* @return void
|
||||
*/
|
||||
public function replace($path, $content, $mode = null)
|
||||
{
|
||||
// If the path already exists and is a symlink, get the real path...
|
||||
clearstatcache(true, $path);
|
||||
|
||||
$path = realpath($path) ?: $path;
|
||||
|
||||
$tempPath = tempnam(dirname($path), basename($path));
|
||||
|
||||
// Fix permissions of tempPath because `tempnam()` creates it with permissions set to 0600...
|
||||
if (! is_null($mode)) {
|
||||
chmod($tempPath, $mode);
|
||||
} else {
|
||||
chmod($tempPath, 0777 - umask());
|
||||
}
|
||||
|
||||
file_put_contents($tempPath, $content);
|
||||
|
||||
rename($tempPath, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a given string within a given file.
|
||||
*
|
||||
* @param array|string $search
|
||||
* @param array|string $replace
|
||||
* @param string $path
|
||||
* @return void
|
||||
*/
|
||||
public function replaceInFile($search, $replace, $path)
|
||||
{
|
||||
file_put_contents($path, str_replace($search, $replace, file_get_contents($path)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend to a file.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $data
|
||||
* @return int
|
||||
*/
|
||||
public function prepend($path, $data)
|
||||
{
|
||||
if ($this->exists($path)) {
|
||||
return $this->put($path, $data.$this->get($path));
|
||||
}
|
||||
|
||||
return $this->put($path, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append to a file.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $data
|
||||
* @param bool $lock
|
||||
* @return int
|
||||
*/
|
||||
public function append($path, $data, $lock = false)
|
||||
{
|
||||
return file_put_contents($path, $data, FILE_APPEND | ($lock ? LOCK_EX : 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set UNIX mode of a file or directory.
|
||||
*
|
||||
* @param string $path
|
||||
* @param int|null $mode
|
||||
* @return mixed
|
||||
*/
|
||||
public function chmod($path, $mode = null)
|
||||
{
|
||||
if ($mode) {
|
||||
return chmod($path, $mode);
|
||||
}
|
||||
|
||||
return substr(sprintf('%o', fileperms($path)), -4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the file at a given path.
|
||||
*
|
||||
* @param string|array $paths
|
||||
* @return bool
|
||||
*/
|
||||
public function delete($paths)
|
||||
{
|
||||
$paths = is_array($paths) ? $paths : func_get_args();
|
||||
|
||||
$success = true;
|
||||
|
||||
foreach ($paths as $path) {
|
||||
try {
|
||||
if (@unlink($path)) {
|
||||
clearstatcache(false, $path);
|
||||
} else {
|
||||
$success = false;
|
||||
}
|
||||
} catch (ErrorException) {
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a file to a new location.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $target
|
||||
* @return bool
|
||||
*/
|
||||
public function move($path, $target)
|
||||
{
|
||||
return rename($path, $target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a file to a new location.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $target
|
||||
* @return bool
|
||||
*/
|
||||
public function copy($path, $target)
|
||||
{
|
||||
return copy($path, $target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a symlink to the target file or directory. On Windows, a hard link is created if the target is a file.
|
||||
*
|
||||
* @param string $target
|
||||
* @param string $link
|
||||
* @return bool|null
|
||||
*/
|
||||
public function link($target, $link)
|
||||
{
|
||||
if (! windows_os()) {
|
||||
if (function_exists('symlink')) {
|
||||
return symlink($target, $link);
|
||||
} else {
|
||||
return exec('ln -s '.escapeshellarg($target).' '.escapeshellarg($link)) !== false;
|
||||
}
|
||||
}
|
||||
|
||||
$mode = $this->isDirectory($target) ? 'J' : 'H';
|
||||
|
||||
exec("mklink /{$mode} ".escapeshellarg($link).' '.escapeshellarg($target));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a relative symlink to the target file or directory.
|
||||
*
|
||||
* @param string $target
|
||||
* @param string $link
|
||||
* @return void
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function relativeLink($target, $link)
|
||||
{
|
||||
if (! class_exists(SymfonyFilesystem::class)) {
|
||||
throw new RuntimeException(
|
||||
'To enable support for relative links, please install the symfony/filesystem package.'
|
||||
);
|
||||
}
|
||||
|
||||
$relativeTarget = (new SymfonyFilesystem)->makePathRelative($target, dirname($link));
|
||||
|
||||
$this->link($this->isFile($target) ? rtrim($relativeTarget, '/') : $relativeTarget, $link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the file name from a file path.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public function name($path)
|
||||
{
|
||||
return pathinfo($path, PATHINFO_FILENAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the trailing name component from a file path.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public function basename($path)
|
||||
{
|
||||
return pathinfo($path, PATHINFO_BASENAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the parent directory from a file path.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public function dirname($path)
|
||||
{
|
||||
return pathinfo($path, PATHINFO_DIRNAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the file extension from a file path.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public function extension($path)
|
||||
{
|
||||
return pathinfo($path, PATHINFO_EXTENSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess the file extension from the mime-type of a given file.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string|null
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function guessExtension($path)
|
||||
{
|
||||
if (! class_exists(MimeTypes::class)) {
|
||||
throw new RuntimeException(
|
||||
'To enable support for guessing extensions, please install the symfony/mime package.'
|
||||
);
|
||||
}
|
||||
|
||||
return (new MimeTypes)->getExtensions($this->mimeType($path))[0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file type of a given file.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public function type($path)
|
||||
{
|
||||
return filetype($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mime-type of a given file.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string|false
|
||||
*/
|
||||
public function mimeType($path)
|
||||
{
|
||||
return finfo_file(finfo_open(FILEINFO_MIME_TYPE), $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file size of a given file.
|
||||
*
|
||||
* @param string $path
|
||||
* @return int
|
||||
*/
|
||||
public function size($path)
|
||||
{
|
||||
return filesize($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file's last modification time.
|
||||
*
|
||||
* @param string $path
|
||||
* @return int
|
||||
*/
|
||||
public function lastModified($path)
|
||||
{
|
||||
return filemtime($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given path is a directory.
|
||||
*
|
||||
* @param string $directory
|
||||
* @return bool
|
||||
*/
|
||||
public function isDirectory($directory)
|
||||
{
|
||||
return is_dir($directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given path is a directory that does not contain any other files or directories.
|
||||
*
|
||||
* @param string $directory
|
||||
* @param bool $ignoreDotFiles
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmptyDirectory($directory, $ignoreDotFiles = false)
|
||||
{
|
||||
return ! Finder::create()->ignoreDotFiles($ignoreDotFiles)->in($directory)->depth(0)->hasResults();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given path is readable.
|
||||
*
|
||||
* @param string $path
|
||||
* @return bool
|
||||
*/
|
||||
public function isReadable($path)
|
||||
{
|
||||
return is_readable($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given path is writable.
|
||||
*
|
||||
* @param string $path
|
||||
* @return bool
|
||||
*/
|
||||
public function isWritable($path)
|
||||
{
|
||||
return is_writable($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if two files are the same by comparing their hashes.
|
||||
*
|
||||
* @param string $firstFile
|
||||
* @param string $secondFile
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSameHash($firstFile, $secondFile)
|
||||
{
|
||||
$hash = @hash_file('xxh128', $firstFile);
|
||||
|
||||
return $hash && hash_equals($hash, (string) @hash_file('xxh128', $secondFile));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given path is a file.
|
||||
*
|
||||
* @param string $file
|
||||
* @return bool
|
||||
*/
|
||||
public function isFile($file)
|
||||
{
|
||||
return is_file($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find path names matching a given pattern.
|
||||
*
|
||||
* @param string $pattern
|
||||
* @param int $flags
|
||||
* @return array
|
||||
*/
|
||||
public function glob($pattern, $flags = 0)
|
||||
{
|
||||
return glob($pattern, $flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all files in a directory.
|
||||
*
|
||||
* @param string $directory
|
||||
* @param bool $hidden
|
||||
* @return \Symfony\Component\Finder\SplFileInfo[]
|
||||
*/
|
||||
public function files($directory, $hidden = false)
|
||||
{
|
||||
return iterator_to_array(
|
||||
Finder::create()->files()->ignoreDotFiles(! $hidden)->in($directory)->depth(0)->sortByName(),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the files from the given directory (recursive).
|
||||
*
|
||||
* @param string $directory
|
||||
* @param bool $hidden
|
||||
* @return \Symfony\Component\Finder\SplFileInfo[]
|
||||
*/
|
||||
public function allFiles($directory, $hidden = false)
|
||||
{
|
||||
return iterator_to_array(
|
||||
Finder::create()->files()->ignoreDotFiles(! $hidden)->in($directory)->sortByName(),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the directories within a given directory.
|
||||
*
|
||||
* @param string $directory
|
||||
* @return array
|
||||
*/
|
||||
public function directories($directory)
|
||||
{
|
||||
$directories = [];
|
||||
|
||||
foreach (Finder::create()->in($directory)->directories()->depth(0)->sortByName() as $dir) {
|
||||
$directories[] = $dir->getPathname();
|
||||
}
|
||||
|
||||
return $directories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure a directory exists.
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $mode
|
||||
* @param bool $recursive
|
||||
* @return void
|
||||
*/
|
||||
public function ensureDirectoryExists($path, $mode = 0755, $recursive = true)
|
||||
{
|
||||
if (! $this->isDirectory($path)) {
|
||||
$this->makeDirectory($path, $mode, $recursive);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a directory.
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $mode
|
||||
* @param bool $recursive
|
||||
* @param bool $force
|
||||
* @return bool
|
||||
*/
|
||||
public function makeDirectory($path, $mode = 0755, $recursive = false, $force = false)
|
||||
{
|
||||
if ($force) {
|
||||
return @mkdir($path, $mode, $recursive);
|
||||
}
|
||||
|
||||
return mkdir($path, $mode, $recursive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a directory.
|
||||
*
|
||||
* @param string $from
|
||||
* @param string $to
|
||||
* @param bool $overwrite
|
||||
* @return bool
|
||||
*/
|
||||
public function moveDirectory($from, $to, $overwrite = false)
|
||||
{
|
||||
if ($overwrite && $this->isDirectory($to) && ! $this->deleteDirectory($to)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return @rename($from, $to) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a directory from one location to another.
|
||||
*
|
||||
* @param string $directory
|
||||
* @param string $destination
|
||||
* @param int|null $options
|
||||
* @return bool
|
||||
*/
|
||||
public function copyDirectory($directory, $destination, $options = null)
|
||||
{
|
||||
if (! $this->isDirectory($directory)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$options = $options ?: FilesystemIterator::SKIP_DOTS;
|
||||
|
||||
// If the destination directory does not actually exist, we will go ahead and
|
||||
// create it recursively, which just gets the destination prepared to copy
|
||||
// the files over. Once we make the directory we'll proceed the copying.
|
||||
$this->ensureDirectoryExists($destination, 0777);
|
||||
|
||||
$items = new FilesystemIterator($directory, $options);
|
||||
|
||||
foreach ($items as $item) {
|
||||
// As we spin through items, we will check to see if the current file is actually
|
||||
// a directory or a file. When it is actually a directory we will need to call
|
||||
// back into this function recursively to keep copying these nested folders.
|
||||
$target = $destination.'/'.$item->getBasename();
|
||||
|
||||
if ($item->isDir()) {
|
||||
$path = $item->getPathname();
|
||||
|
||||
if (! $this->copyDirectory($path, $target, $options)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If the current items is just a regular file, we will just copy this to the new
|
||||
// location and keep looping. If for some reason the copy fails we'll bail out
|
||||
// and return false, so the developer is aware that the copy process failed.
|
||||
elseif (! $this->copy($item->getPathname(), $target)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively delete a directory.
|
||||
*
|
||||
* The directory itself may be optionally preserved.
|
||||
*
|
||||
* @param string $directory
|
||||
* @param bool $preserve
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteDirectory($directory, $preserve = false)
|
||||
{
|
||||
if (! $this->isDirectory($directory)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$items = new FilesystemIterator($directory);
|
||||
|
||||
foreach ($items as $item) {
|
||||
// If the item is a directory, we can just recurse into the function and
|
||||
// delete that sub-directory otherwise we'll just delete the file and
|
||||
// keep iterating through each file until the directory is cleaned.
|
||||
if ($item->isDir() && ! $item->isLink()) {
|
||||
$this->deleteDirectory($item->getPathname());
|
||||
}
|
||||
|
||||
// If the item is just a file, we can go ahead and delete it since we're
|
||||
// just looping through and waxing all of the files in this directory
|
||||
// and calling directories recursively, so we delete the real path.
|
||||
else {
|
||||
$this->delete($item->getPathname());
|
||||
}
|
||||
}
|
||||
|
||||
unset($items);
|
||||
|
||||
if (! $preserve) {
|
||||
@rmdir($directory);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all of the directories within a given directory.
|
||||
*
|
||||
* @param string $directory
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteDirectories($directory)
|
||||
{
|
||||
$allDirectories = $this->directories($directory);
|
||||
|
||||
if (! empty($allDirectories)) {
|
||||
foreach ($allDirectories as $directoryName) {
|
||||
$this->deleteDirectory($directoryName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty the specified directory of all files and folders.
|
||||
*
|
||||
* @param string $directory
|
||||
* @return bool
|
||||
*/
|
||||
public function cleanDirectory($directory)
|
||||
{
|
||||
return $this->deleteDirectory($directory, true);
|
||||
}
|
||||
}
|
||||
+1095
File diff suppressed because it is too large
Load Diff
+460
@@ -0,0 +1,460 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Filesystem;
|
||||
|
||||
use Aws\S3\S3Client;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Filesystem\Factory as FactoryContract;
|
||||
use Illuminate\Support\Arr;
|
||||
use InvalidArgumentException;
|
||||
use League\Flysystem\AwsS3V3\AwsS3V3Adapter as S3Adapter;
|
||||
use League\Flysystem\AwsS3V3\PortableVisibilityConverter as AwsS3PortableVisibilityConverter;
|
||||
use League\Flysystem\Filesystem as Flysystem;
|
||||
use League\Flysystem\FilesystemAdapter as FlysystemAdapter;
|
||||
use League\Flysystem\Ftp\FtpAdapter;
|
||||
use League\Flysystem\Ftp\FtpConnectionOptions;
|
||||
use League\Flysystem\Local\LocalFilesystemAdapter as LocalAdapter;
|
||||
use League\Flysystem\PathPrefixing\PathPrefixedAdapter;
|
||||
use League\Flysystem\PhpseclibV3\SftpAdapter;
|
||||
use League\Flysystem\PhpseclibV3\SftpConnectionProvider;
|
||||
use League\Flysystem\ReadOnly\ReadOnlyFilesystemAdapter;
|
||||
use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
|
||||
use League\Flysystem\Visibility;
|
||||
|
||||
/**
|
||||
* @mixin \Illuminate\Contracts\Filesystem\Filesystem
|
||||
* @mixin \Illuminate\Filesystem\FilesystemAdapter
|
||||
*/
|
||||
class FilesystemManager implements FactoryContract
|
||||
{
|
||||
/**
|
||||
* The application instance.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Foundation\Application
|
||||
*/
|
||||
protected $app;
|
||||
|
||||
/**
|
||||
* The array of resolved filesystem drivers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $disks = [];
|
||||
|
||||
/**
|
||||
* The registered custom driver creators.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $customCreators = [];
|
||||
|
||||
/**
|
||||
* Create a new filesystem manager instance.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Foundation\Application $app
|
||||
*/
|
||||
public function __construct($app)
|
||||
{
|
||||
$this->app = $app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a filesystem instance.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @return \Illuminate\Contracts\Filesystem\Filesystem
|
||||
*/
|
||||
public function drive($name = null)
|
||||
{
|
||||
return $this->disk($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a filesystem instance.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @return \Illuminate\Contracts\Filesystem\Filesystem
|
||||
*/
|
||||
public function disk($name = null)
|
||||
{
|
||||
$name = $name ?: $this->getDefaultDriver();
|
||||
|
||||
return $this->disks[$name] = $this->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a default cloud filesystem instance.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Filesystem\Cloud
|
||||
*/
|
||||
public function cloud()
|
||||
{
|
||||
$name = $this->getDefaultCloudDriver();
|
||||
|
||||
return $this->disks[$name] = $this->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an on-demand disk.
|
||||
*
|
||||
* @param string|array $config
|
||||
* @return \Illuminate\Contracts\Filesystem\Filesystem
|
||||
*/
|
||||
public function build($config)
|
||||
{
|
||||
return $this->resolve('ondemand', is_array($config) ? $config : [
|
||||
'driver' => 'local',
|
||||
'root' => $config,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to get the disk from the local cache.
|
||||
*
|
||||
* @param string $name
|
||||
* @return \Illuminate\Contracts\Filesystem\Filesystem
|
||||
*/
|
||||
protected function get($name)
|
||||
{
|
||||
return $this->disks[$name] ?? $this->resolve($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the given disk.
|
||||
*
|
||||
* @param string $name
|
||||
* @param array|null $config
|
||||
* @return \Illuminate\Contracts\Filesystem\Filesystem
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function resolve($name, $config = null)
|
||||
{
|
||||
$config ??= $this->getConfig($name);
|
||||
|
||||
if (empty($config['driver'])) {
|
||||
throw new InvalidArgumentException("Disk [{$name}] does not have a configured driver.");
|
||||
}
|
||||
|
||||
$driver = $config['driver'];
|
||||
|
||||
if (isset($this->customCreators[$driver])) {
|
||||
return $this->callCustomCreator($config);
|
||||
}
|
||||
|
||||
$driverMethod = 'create'.ucfirst($driver).'Driver';
|
||||
|
||||
if (! method_exists($this, $driverMethod)) {
|
||||
throw new InvalidArgumentException("Driver [{$driver}] is not supported.");
|
||||
}
|
||||
|
||||
return $this->{$driverMethod}($config, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a custom driver creator.
|
||||
*
|
||||
* @param array $config
|
||||
* @return \Illuminate\Contracts\Filesystem\Filesystem
|
||||
*/
|
||||
protected function callCustomCreator(array $config)
|
||||
{
|
||||
return $this->customCreators[$config['driver']]($this->app, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of the local driver.
|
||||
*
|
||||
* @param array $config
|
||||
* @param string $name
|
||||
* @return \Illuminate\Contracts\Filesystem\Filesystem
|
||||
*/
|
||||
public function createLocalDriver(array $config, string $name = 'local')
|
||||
{
|
||||
$visibility = PortableVisibilityConverter::fromArray(
|
||||
$config['permissions'] ?? [],
|
||||
$config['directory_visibility'] ?? $config['visibility'] ?? Visibility::PRIVATE
|
||||
);
|
||||
|
||||
$links = ($config['links'] ?? null) === 'skip'
|
||||
? LocalAdapter::SKIP_LINKS
|
||||
: LocalAdapter::DISALLOW_LINKS;
|
||||
|
||||
$adapter = new LocalAdapter(
|
||||
$config['root'], $visibility, $config['lock'] ?? LOCK_EX, $links
|
||||
);
|
||||
|
||||
return (new LocalFilesystemAdapter(
|
||||
$this->createFlysystem($adapter, $config), $adapter, $config
|
||||
))->diskName(
|
||||
$name
|
||||
)->shouldServeSignedUrls(
|
||||
$config['serve'] ?? false,
|
||||
fn () => $this->app['url'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of the ftp driver.
|
||||
*
|
||||
* @param array $config
|
||||
* @return \Illuminate\Contracts\Filesystem\Filesystem
|
||||
*/
|
||||
public function createFtpDriver(array $config)
|
||||
{
|
||||
if (! isset($config['root'])) {
|
||||
$config['root'] = '';
|
||||
}
|
||||
|
||||
$adapter = new FtpAdapter(FtpConnectionOptions::fromArray($config));
|
||||
|
||||
return new FilesystemAdapter($this->createFlysystem($adapter, $config), $adapter, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of the sftp driver.
|
||||
*
|
||||
* @param array $config
|
||||
* @return \Illuminate\Contracts\Filesystem\Filesystem
|
||||
*/
|
||||
public function createSftpDriver(array $config)
|
||||
{
|
||||
$provider = SftpConnectionProvider::fromArray($config);
|
||||
|
||||
$root = $config['root'] ?? '';
|
||||
|
||||
$visibility = PortableVisibilityConverter::fromArray(
|
||||
$config['permissions'] ?? []
|
||||
);
|
||||
|
||||
$adapter = new SftpAdapter($provider, $root, $visibility);
|
||||
|
||||
return new FilesystemAdapter($this->createFlysystem($adapter, $config), $adapter, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of the Amazon S3 driver.
|
||||
*
|
||||
* @param array $config
|
||||
* @return \Illuminate\Contracts\Filesystem\Cloud
|
||||
*/
|
||||
public function createS3Driver(array $config)
|
||||
{
|
||||
$s3Config = $this->formatS3Config($config);
|
||||
|
||||
$root = (string) ($s3Config['root'] ?? '');
|
||||
|
||||
$visibility = new AwsS3PortableVisibilityConverter(
|
||||
$config['visibility'] ?? Visibility::PUBLIC
|
||||
);
|
||||
|
||||
$streamReads = $s3Config['stream_reads'] ?? false;
|
||||
|
||||
$client = new S3Client($s3Config);
|
||||
|
||||
$adapter = new S3Adapter($client, $s3Config['bucket'], $root, $visibility, null, $config['options'] ?? [], $streamReads);
|
||||
|
||||
return new AwsS3V3Adapter(
|
||||
$this->createFlysystem($adapter, $config), $adapter, $s3Config, $client
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given S3 configuration with the default options.
|
||||
*
|
||||
* @param array $config
|
||||
* @return array
|
||||
*/
|
||||
protected function formatS3Config(array $config)
|
||||
{
|
||||
$config += ['version' => 'latest'];
|
||||
|
||||
if (! empty($config['key']) && ! empty($config['secret'])) {
|
||||
$config['credentials'] = Arr::only($config, ['key', 'secret']);
|
||||
|
||||
if (! empty($config['token'])) {
|
||||
$config['credentials']['token'] = $config['token'];
|
||||
}
|
||||
}
|
||||
|
||||
return Arr::except($config, ['token']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a scoped driver.
|
||||
*
|
||||
* @param array $config
|
||||
* @return \Illuminate\Contracts\Filesystem\Filesystem
|
||||
*/
|
||||
public function createScopedDriver(array $config)
|
||||
{
|
||||
if (empty($config['disk'])) {
|
||||
throw new InvalidArgumentException('Scoped disk is missing "disk" configuration option.');
|
||||
} elseif (empty($config['prefix'])) {
|
||||
throw new InvalidArgumentException('Scoped disk is missing "prefix" configuration option.');
|
||||
}
|
||||
|
||||
return $this->build(tap(
|
||||
is_string($config['disk']) ? $this->getConfig($config['disk']) : $config['disk'],
|
||||
function (&$parent) use ($config) {
|
||||
if (empty($parent['prefix'])) {
|
||||
$parent['prefix'] = $config['prefix'];
|
||||
} else {
|
||||
$separator = $parent['directory_separator'] ?? DIRECTORY_SEPARATOR;
|
||||
|
||||
$parentPrefix = rtrim($parent['prefix'], $separator);
|
||||
$scopedPrefix = ltrim($config['prefix'], $separator);
|
||||
|
||||
$parent['prefix'] = "{$parentPrefix}{$separator}{$scopedPrefix}";
|
||||
}
|
||||
|
||||
if (isset($config['visibility'])) {
|
||||
$parent['visibility'] = $config['visibility'];
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Flysystem instance with the given adapter.
|
||||
*
|
||||
* @param \League\Flysystem\FilesystemAdapter $adapter
|
||||
* @param array $config
|
||||
* @return \League\Flysystem\FilesystemOperator
|
||||
*/
|
||||
protected function createFlysystem(FlysystemAdapter $adapter, array $config)
|
||||
{
|
||||
if ($config['read-only'] ?? false === true) {
|
||||
$adapter = new ReadOnlyFilesystemAdapter($adapter);
|
||||
}
|
||||
|
||||
if (! empty($config['prefix'])) {
|
||||
$adapter = new PathPrefixedAdapter($adapter, $config['prefix']);
|
||||
}
|
||||
|
||||
if (str_contains($config['endpoint'] ?? '', 'r2.cloudflarestorage.com')) {
|
||||
$config['retain_visibility'] = false;
|
||||
}
|
||||
|
||||
return new Flysystem($adapter, Arr::only($config, [
|
||||
'directory_visibility',
|
||||
'disable_asserts',
|
||||
'retain_visibility',
|
||||
'temporary_url',
|
||||
'url',
|
||||
'visibility',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the given disk instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $disk
|
||||
* @return $this
|
||||
*/
|
||||
public function set($name, $disk)
|
||||
{
|
||||
$this->disks[$name] = $disk;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filesystem connection configuration.
|
||||
*
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
protected function getConfig($name)
|
||||
{
|
||||
return $this->app['config']["filesystems.disks.{$name}"] ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default driver name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDefaultDriver()
|
||||
{
|
||||
return $this->app['config']['filesystems.default'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default cloud driver name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDefaultCloudDriver()
|
||||
{
|
||||
return $this->app['config']['filesystems.cloud'] ?? 's3';
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the given disk instances.
|
||||
*
|
||||
* @param array|string $disk
|
||||
* @return $this
|
||||
*/
|
||||
public function forgetDisk($disk)
|
||||
{
|
||||
foreach ((array) $disk as $diskName) {
|
||||
unset($this->disks[$diskName]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect the given disk and remove from local cache.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @return void
|
||||
*/
|
||||
public function purge($name = null)
|
||||
{
|
||||
$name ??= $this->getDefaultDriver();
|
||||
|
||||
unset($this->disks[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom driver creator Closure.
|
||||
*
|
||||
* @param string $driver
|
||||
* @param \Closure $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function extend($driver, Closure $callback)
|
||||
{
|
||||
$this->customCreators[$driver] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the application instance used by the manager.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Foundation\Application $app
|
||||
* @return $this
|
||||
*/
|
||||
public function setApplication($app)
|
||||
{
|
||||
$this->app = $app;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically call the default driver instance.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $parameters)
|
||||
{
|
||||
return $this->disk()->$method(...$parameters);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Filesystem;
|
||||
|
||||
use Illuminate\Contracts\Foundation\CachesRoutes;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class FilesystemServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap the filesystem.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->serveFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the service provider.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->registerNativeFilesystem();
|
||||
$this->registerFlysystem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the native filesystem implementation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function registerNativeFilesystem()
|
||||
{
|
||||
$this->app->singleton('files', function () {
|
||||
return new Filesystem;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the driver based filesystem.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function registerFlysystem()
|
||||
{
|
||||
$this->registerManager();
|
||||
|
||||
$this->app->singleton('filesystem.disk', function ($app) {
|
||||
return $app['filesystem']->disk($this->getDefaultDriver());
|
||||
});
|
||||
|
||||
$this->app->singleton('filesystem.cloud', function ($app) {
|
||||
return $app['filesystem']->disk($this->getCloudDriver());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the filesystem manager.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function registerManager()
|
||||
{
|
||||
$this->app->singleton('filesystem', function ($app) {
|
||||
return new FilesystemManager($app);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register protected file serving.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function serveFiles()
|
||||
{
|
||||
if ($this->app instanceof CachesRoutes && $this->app->routesAreCached()) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->app['config']['filesystems.disks'] ?? [] as $disk => $config) {
|
||||
if (! $this->shouldServeFiles($config)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->app->booted(function ($app) use ($disk, $config) {
|
||||
$uri = isset($config['url'])
|
||||
? rtrim(parse_url($config['url'])['path'], '/')
|
||||
: '/storage';
|
||||
|
||||
$isProduction = $app->isProduction();
|
||||
|
||||
Route::get($uri.'/{path}', function (Request $request, string $path) use ($disk, $config, $isProduction) {
|
||||
return (new ServeFile(
|
||||
$disk,
|
||||
$config,
|
||||
$isProduction
|
||||
))($request, $path);
|
||||
})->where('path', '.*')->name('storage.'.$disk);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the disk is serveable.
|
||||
*
|
||||
* @param array $config
|
||||
* @return bool
|
||||
*/
|
||||
protected function shouldServeFiles(array $config)
|
||||
{
|
||||
return $config['driver'] === 'local' && ($config['serve'] ?? false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default file driver.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getDefaultDriver()
|
||||
{
|
||||
return $this->app['config']['filesystems.default'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default cloud based file driver.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getCloudDriver()
|
||||
{
|
||||
return $this->app['config']['filesystems.cloud'];
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Taylor Otwell
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Filesystem;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Support\Traits\Conditionable;
|
||||
use RuntimeException;
|
||||
|
||||
class LocalFilesystemAdapter extends FilesystemAdapter
|
||||
{
|
||||
use Conditionable;
|
||||
|
||||
/**
|
||||
* The name of the filesystem disk.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $disk;
|
||||
|
||||
/**
|
||||
* Indicates if signed URLs should serve corresponding files.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $shouldServeSignedUrls = false;
|
||||
|
||||
/**
|
||||
* The Closure that should be used to resolve the URL generator.
|
||||
*
|
||||
* @var \Closure
|
||||
*/
|
||||
protected $urlGeneratorResolver;
|
||||
|
||||
/**
|
||||
* Determine if temporary URLs can be generated.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function providesTemporaryUrls()
|
||||
{
|
||||
return $this->temporaryUrlCallback || (
|
||||
$this->shouldServeSignedUrls && $this->urlGeneratorResolver instanceof Closure
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a temporary URL for the file at the given path.
|
||||
*
|
||||
* @param string $path
|
||||
* @param \DateTimeInterface $expiration
|
||||
* @param array $options
|
||||
* @return string
|
||||
*/
|
||||
public function temporaryUrl($path, $expiration, array $options = [])
|
||||
{
|
||||
if ($this->temporaryUrlCallback) {
|
||||
return $this->temporaryUrlCallback->bindTo($this, static::class)(
|
||||
$path, $expiration, $options
|
||||
);
|
||||
}
|
||||
|
||||
if (! $this->providesTemporaryUrls()) {
|
||||
throw new RuntimeException('This driver does not support creating temporary URLs.');
|
||||
}
|
||||
|
||||
$url = call_user_func($this->urlGeneratorResolver);
|
||||
|
||||
return $url->to($url->temporarySignedRoute(
|
||||
'storage.'.$this->disk,
|
||||
$expiration,
|
||||
['path' => $path],
|
||||
absolute: false
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the name of the disk the adapter is managing.
|
||||
*
|
||||
* @param string $disk
|
||||
* @return $this
|
||||
*/
|
||||
public function diskName(string $disk)
|
||||
{
|
||||
$this->disk = $disk;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that signed URLs should serve the corresponding files.
|
||||
*
|
||||
* @param bool $serve
|
||||
* @param \Closure|null $urlGeneratorResolver
|
||||
* @return $this
|
||||
*/
|
||||
public function shouldServeSignedUrls(bool $serve = true, ?Closure $urlGeneratorResolver = null)
|
||||
{
|
||||
$this->shouldServeSignedUrls = $serve;
|
||||
$this->urlGeneratorResolver = $urlGeneratorResolver;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Filesystem;
|
||||
|
||||
use Illuminate\Contracts\Filesystem\LockTimeoutException;
|
||||
|
||||
class LockableFile
|
||||
{
|
||||
/**
|
||||
* The file resource.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $handle;
|
||||
|
||||
/**
|
||||
* The file path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* Indicates if the file is locked.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isLocked = false;
|
||||
|
||||
/**
|
||||
* Create a new File instance.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $mode
|
||||
*/
|
||||
public function __construct($path, $mode)
|
||||
{
|
||||
$this->path = $path;
|
||||
|
||||
$this->ensureDirectoryExists($path);
|
||||
$this->createResource($path, $mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the file's directory if necessary.
|
||||
*
|
||||
* @param string $path
|
||||
* @return void
|
||||
*/
|
||||
protected function ensureDirectoryExists($path)
|
||||
{
|
||||
if (! file_exists(dirname($path))) {
|
||||
@mkdir(dirname($path), 0777, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the file resource.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $mode
|
||||
* @return void
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function createResource($path, $mode)
|
||||
{
|
||||
$this->handle = fopen($path, $mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the file contents.
|
||||
*
|
||||
* @param int|null $length
|
||||
* @return string
|
||||
*/
|
||||
public function read($length = null)
|
||||
{
|
||||
clearstatcache(true, $this->path);
|
||||
|
||||
return fread($this->handle, $length ?? ($this->size() ?: 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file size.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function size()
|
||||
{
|
||||
return filesize($this->path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to the file.
|
||||
*
|
||||
* @param string $contents
|
||||
* @return $this
|
||||
*/
|
||||
public function write($contents)
|
||||
{
|
||||
fwrite($this->handle, $contents);
|
||||
|
||||
fflush($this->handle);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate the file.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function truncate()
|
||||
{
|
||||
rewind($this->handle);
|
||||
|
||||
ftruncate($this->handle, 0);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a shared lock on the file.
|
||||
*
|
||||
* @param bool $block
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Filesystem\LockTimeoutException
|
||||
*/
|
||||
public function getSharedLock($block = false)
|
||||
{
|
||||
if (! flock($this->handle, LOCK_SH | ($block ? 0 : LOCK_NB))) {
|
||||
throw new LockTimeoutException("Unable to acquire file lock at path [{$this->path}].");
|
||||
}
|
||||
|
||||
$this->isLocked = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an exclusive lock on the file.
|
||||
*
|
||||
* @param bool $block
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Filesystem\LockTimeoutException
|
||||
*/
|
||||
public function getExclusiveLock($block = false)
|
||||
{
|
||||
if (! flock($this->handle, LOCK_EX | ($block ? 0 : LOCK_NB))) {
|
||||
throw new LockTimeoutException("Unable to acquire file lock at path [{$this->path}].");
|
||||
}
|
||||
|
||||
$this->isLocked = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the lock on the file.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function releaseLock()
|
||||
{
|
||||
flock($this->handle, LOCK_UN);
|
||||
|
||||
$this->isLocked = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the file.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
if ($this->isLocked) {
|
||||
$this->releaseLock();
|
||||
}
|
||||
|
||||
return fclose($this->handle);
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Filesystem;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use League\Flysystem\PathTraversalDetected;
|
||||
|
||||
class ServeFile
|
||||
{
|
||||
/**
|
||||
* Create a new invokable controller to serve files.
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $disk,
|
||||
protected array $config,
|
||||
protected bool $isProduction,
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
*/
|
||||
public function __invoke(Request $request, string $path)
|
||||
{
|
||||
abort_unless(
|
||||
$this->hasValidSignature($request),
|
||||
$this->isProduction ? 404 : 403
|
||||
);
|
||||
try {
|
||||
abort_unless(Storage::disk($this->disk)->exists($path), 404);
|
||||
|
||||
$headers = [
|
||||
'Cache-Control' => 'no-store, no-cache, must-revalidate, max-age=0',
|
||||
'Content-Security-Policy' => "default-src 'none'; style-src 'unsafe-inline'; sandbox",
|
||||
];
|
||||
|
||||
return tap(
|
||||
Storage::disk($this->disk)->serve($request, $path, headers: $headers),
|
||||
function ($response) use ($headers) {
|
||||
if (! $response->headers->has('Content-Security-Policy')) {
|
||||
$response->headers->replace($headers);
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (PathTraversalDetected $e) {
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the request has a valid signature if applicable.
|
||||
*/
|
||||
protected function hasValidSignature(Request $request): bool
|
||||
{
|
||||
return ($this->config['visibility'] ?? 'private') === 'public' ||
|
||||
$request->hasValidRelativeSignature();
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "illuminate/filesystem",
|
||||
"description": "The Illuminate Filesystem package.",
|
||||
"license": "MIT",
|
||||
"homepage": "https://laravel.com",
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"illuminate/collections": "^12.0",
|
||||
"illuminate/contracts": "^12.0",
|
||||
"illuminate/macroable": "^12.0",
|
||||
"illuminate/support": "^12.0",
|
||||
"symfony/finder": "^7.2.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Illuminate\\Filesystem\\": ""
|
||||
},
|
||||
"files": [
|
||||
"functions.php"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "12.x-dev"
|
||||
}
|
||||
},
|
||||
"suggest": {
|
||||
"ext-fileinfo": "Required to use the Filesystem class.",
|
||||
"ext-ftp": "Required to use the Flysystem FTP driver.",
|
||||
"ext-hash": "Required to use the Filesystem class.",
|
||||
"illuminate/http": "Required for handling uploaded files (^12.0).",
|
||||
"league/flysystem": "Required to use the Flysystem local driver (^3.25.1).",
|
||||
"league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).",
|
||||
"league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).",
|
||||
"league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).",
|
||||
"psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).",
|
||||
"symfony/filesystem": "Required to enable support for relative symbolic links (^7.2).",
|
||||
"symfony/mime": "Required to enable support for guessing extensions (^7.2)."
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Filesystem;
|
||||
|
||||
if (! function_exists('Illuminate\Filesystem\join_paths')) {
|
||||
/**
|
||||
* Join the given paths together.
|
||||
*
|
||||
* @param string|null $basePath
|
||||
* @param string ...$paths
|
||||
* @return string
|
||||
*/
|
||||
function join_paths($basePath, ...$paths)
|
||||
{
|
||||
foreach ($paths as $index => $path) {
|
||||
if (empty($path) && $path !== '0') {
|
||||
unset($paths[$index]);
|
||||
} else {
|
||||
$paths[$index] = DIRECTORY_SEPARATOR.ltrim($path, DIRECTORY_SEPARATOR);
|
||||
}
|
||||
}
|
||||
|
||||
return $basePath.implode('', $paths);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user