driver = $driver; } /** * Assert that the given file exists. * * @param string $path * @return void */ public function assertExists($path) { PHPUnit::assertTrue( $this->exists($path), "Unable to find a file at path [{$path}]." ); } /** * Assert that the given file does not exist. * * @param string $path * @return void */ public function assertMissing($path) { PHPUnit::assertFalse( $this->exists($path), "Found unexpected file at path [{$path}]." ); } /** * Determine if a file exists. * * @param string $path * @return bool */ public function exists($path) { return $this->driver->has($path); } /** * Get the full path for the file at the given "short" path. * * @param string $path * @return string */ public function path($path) { return $this->driver->getAdapter()->getPathPrefix().$path; } /** * Get the contents of a file. * * @param string $path * @return string * * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException */ public function get($path) { try { return $this->driver->read($path); } catch (FileNotFoundException $e) { throw new ContractFileNotFoundException($path, $e->getCode(), $e); } } /** * Create a streamed response for a given file. * * @param string $path * @param string|null $name * @param array|null $headers * @param string|null $disposition * @return \Symfony\Component\HttpFoundation\StreamedResponse */ public function response($path, $name = null, array $headers = [], $disposition = 'inline') { $response = new StreamedResponse; $disposition = $response->headers->makeDisposition($disposition, $name ?? basename($path)); $response->headers->replace($headers + [ 'Content-Type' => $this->mimeType($path), 'Content-Length' => $this->size($path), 'Content-Disposition' => $disposition, ]); $response->setCallback(function () use ($path) { $stream = $this->driver->readStream($path); fpassthru($stream); fclose($stream); }); return $response; } /** * Create a streamed download response for a given file. * * @param string $path * @param string|null $name * @param array|null $headers * @return \Symfony\Component\HttpFoundation\StreamedResponse */ public function download($path, $name = null, array $headers = []) { return $this->response($path, $name, $headers, 'attachment'); } /** * Write the contents of a file. * * @param string $path * @param string|resource $contents * @param mixed $options * @return bool */ public function put($path, $contents, $options = []) { $options = is_string($options) ? ['visibility' => $options] : (array) $options; // If the given contents is actually a file or uploaded file instance than we will // automatically store the file using a stream. This provides a convenient path // for the developer to store streams without managing them manually in code. if ($contents instanceof File || $contents instanceof UploadedFile) { return $this->putFile($path, $contents, $options); } return is_resource($contents) ? $this->driver->putStream($path, $contents, $options) : $this->driver->put($path, $contents, $options); } /** * Store the uploaded file on the disk. * * @param string $path * @param \Illuminate\Http\File|\Illuminate\Http\UploadedFile $file * @param array $options * @return string|false */ public function putFile($path, $file, $options = []) { return $this->putFileAs($path, $file, $file->hashName(), $options); } /** * Store the uploaded file on the disk with a given name. * * @param string $path * @param \Illuminate\Http\File|\Illuminate\Http\UploadedFile $file * @param string $name * @param array $options * @return string|false */ public function putFileAs($path, $file, $name, $options = []) { $stream = fopen($file->getRealPath(), 'r+'); // Next, we will format the path of the file and store the file using a stream since // they provide better performance than alternatives. Once we write the file this // stream will get closed automatically by us so the developer doesn't have to. $result = $this->put( $path = trim($path.'/'.$name, '/'), $stream, $options ); if (is_resource($stream)) { fclose($stream); } return $result ? $path : false; } /** * Get the visibility for the given path. * * @param string $path * @return string */ public function getVisibility($path) { if ($this->driver->getVisibility($path) == AdapterInterface::VISIBILITY_PUBLIC) { return FilesystemContract::VISIBILITY_PUBLIC; } return FilesystemContract::VISIBILITY_PRIVATE; } /** * Set the visibility for the given path. * * @param string $path * @param string $visibility * @return void */ public function setVisibility($path, $visibility) { return $this->driver->setVisibility($path, $this->parseVisibility($visibility)); } /** * Prepend to a file. * * @param string $path * @param string $data * @param string $separator * @return int */ public function prepend($path, $data, $separator = PHP_EOL) { if ($this->exists($path)) { return $this->put($path, $data.$separator.$this->get($path)); } return $this->put($path, $data); } /** * Append to a file. * * @param string $path * @param string $data * @param string $separator * @return int */ public function append($path, $data, $separator = PHP_EOL) { if ($this->exists($path)) { return $this->put($path, $this->get($path).$separator.$data); } return $this->put($path, $data); } /** * 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 (! $this->driver->delete($path)) { $success = false; } } catch (FileNotFoundException $e) { $success = false; } } return $success; } /** * Copy a file to a new location. * * @param string $from * @param string $to * @return bool */ public function copy($from, $to) { return $this->driver->copy($from, $to); } /** * Move a file to a new location. * * @param string $from * @param string $to * @return bool */ public function move($from, $to) { return $this->driver->rename($from, $to); } /** * Get the file size of a given file. * * @param string $path * @return int */ public function size($path) { return $this->driver->getSize($path); } /** * Get the mime-type of a given file. * * @param string $path * @return string|false */ public function mimeType($path) { return $this->driver->getMimetype($path); } /** * Get the file's last modification time. * * @param string $path * @return int */ public function lastModified($path) { return $this->driver->getTimestamp($path); } /** * Get the URL for the file at the given path. * * @param string $path * @return string */ public function url($path) { $adapter = $this->driver->getAdapter(); if ($adapter instanceof CachedAdapter) { $adapter = $adapter->getAdapter(); } if (method_exists($adapter, 'getUrl')) { return $adapter->getUrl($path); } elseif (method_exists($this->driver, 'getUrl')) { return $this->driver->getUrl($path); } elseif ($adapter instanceof AwsS3Adapter) { return $this->getAwsUrl($adapter, $path); } elseif ($adapter instanceof RackspaceAdapter) { return $this->getRackspaceUrl($adapter, $path); } elseif ($adapter instanceof LocalAdapter) { return $this->getLocalUrl($path); } else { throw new RuntimeException('This driver does not support retrieving URLs.'); } } /** * Get the URL for the file at the given path. * * @param \League\Flysystem\AwsS3v3\AwsS3Adapter $adapter * @param string $path * @return string */ protected function getAwsUrl($adapter, $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 (! is_null($url = $this->driver->getConfig()->get('url'))) { return $this->concatPathToUrl($url, $adapter->getPathPrefix().$path); } return $adapter->getClient()->getObjectUrl( $adapter->getBucket(), $adapter->getPathPrefix().$path ); } /** * Get the URL for the file at the given path. * * @param \League\Flysystem\Rackspace\RackspaceAdapter $adapter * @param string $path * @return string */ protected function getRackspaceUrl($adapter, $path) { return (string) $adapter->getContainer()->getObject($path)->getPublicUrl(); } /** * Get the URL for the file at the given path. * * @param string $path * @return string */ protected function getLocalUrl($path) { $config = $this->driver->getConfig(); // 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 ($config->has('url')) { return $this->concatPathToUrl($config->get('url'), $path); } $path = '/storage/'.$path; // If the path contains "storage/public", it probably means the developer is using // the default disk to generate the path instead of the "public" disk like they // are really supposed to use. We will remove the public from this path here. if (Str::contains($path, '/storage/public/')) { return Str::replaceFirst('/public/', '/', $path); } return $path; } /** * 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 = []) { $adapter = $this->driver->getAdapter(); if ($adapter instanceof CachedAdapter) { $adapter = $adapter->getAdapter(); } if (method_exists($adapter, 'getTemporaryUrl')) { return $adapter->getTemporaryUrl($path, $expiration, $options); } elseif ($adapter instanceof AwsS3Adapter) { return $this->getAwsTemporaryUrl($adapter, $path, $expiration, $options); } elseif ($adapter instanceof RackspaceAdapter) { return $this->getRackspaceTemporaryUrl($adapter, $path, $expiration, $options); } else { throw new RuntimeException('This driver does not support creating temporary URLs.'); } } /** * Get a temporary URL for the file at the given path. * * @param \League\Flysystem\AwsS3v3\AwsS3Adapter $adapter * @param string $path * @param \DateTimeInterface $expiration * @param array $options * @return string */ public function getAwsTemporaryUrl($adapter, $path, $expiration, $options) { $client = $adapter->getClient(); $command = $client->getCommand('GetObject', array_merge([ 'Bucket' => $adapter->getBucket(), 'Key' => $adapter->getPathPrefix().$path, ], $options)); return (string) $client->createPresignedRequest( $command, $expiration )->getUri(); } /** * Get a temporary URL for the file at the given path. * * @param \League\Flysystem\Rackspace\RackspaceAdapter $adapter * @param string $path * @param \DateTimeInterface $expiration * @param array $options * @return string */ public function getRackspaceTemporaryUrl($adapter, $path, $expiration, $options) { return $adapter->getContainer()->getObject($path)->getTemporaryUrl( Carbon::now()->diffInSeconds($expiration), $options['method'] ?? 'GET', $options['forcePublicUrl'] ?? true ); } /** * Concatenate a path to a URL. * * @param string $url * @param string $path * @return string */ protected function concatPathToUrl($url, $path) { return rtrim($url, '/').'/'.ltrim($path, '/'); } /** * Get an array of all files in a directory. * * @param string|null $directory * @param bool $recursive * @return array */ public function files($directory = null, $recursive = false) { $contents = $this->driver->listContents($directory, $recursive); return $this->filterContentsByType($contents, 'file'); } /** * Get all of the files from the given directory (recursive). * * @param string|null $directory * @return array */ public function allFiles($directory = null) { return $this->files($directory, true); } /** * Get all of the directories within a given directory. * * @param string|null $directory * @param bool $recursive * @return array */ public function directories($directory = null, $recursive = false) { $contents = $this->driver->listContents($directory, $recursive); return $this->filterContentsByType($contents, 'dir'); } /** * Get all (recursive) of the directories within a given directory. * * @param string|null $directory * @return array */ public function allDirectories($directory = null) { return $this->directories($directory, true); } /** * Create a directory. * * @param string $path * @return bool */ public function makeDirectory($path) { return $this->driver->createDir($path); } /** * Recursively delete a directory. * * @param string $directory * @return bool */ public function deleteDirectory($directory) { return $this->driver->deleteDir($directory); } /** * Flush the Flysystem cache. * * @return void */ public function flushCache() { $adapter = $this->driver->getAdapter(); if ($adapter instanceof CachedAdapter) { $adapter->getCache()->flush(); } } /** * Get the Flysystem driver. * * @return \League\Flysystem\FilesystemInterface */ public function getDriver() { return $this->driver; } /** * Filter directory contents by type. * * @param array $contents * @param string $type * @return array */ protected function filterContentsByType($contents, $type) { return Collection::make($contents) ->where('type', $type) ->pluck('path') ->values() ->all(); } /** * Parse the given visibility value. * * @param string|null $visibility * @return string|null * * @throws \InvalidArgumentException */ protected function parseVisibility($visibility) { if (is_null($visibility)) { return; } switch ($visibility) { case FilesystemContract::VISIBILITY_PUBLIC: return AdapterInterface::VISIBILITY_PUBLIC; case FilesystemContract::VISIBILITY_PRIVATE: return AdapterInterface::VISIBILITY_PRIVATE; } throw new InvalidArgumentException("Unknown visibility: {$visibility}"); } /** * Pass dynamic methods call onto Flysystem. * * @param string $method * @param array $parameters * @return mixed * * @throws \BadMethodCallException */ public function __call($method, array $parameters) { return call_user_func_array([$this->driver, $method], $parameters); } }