├── psalm.xml.dist ├── LICENSE.md ├── CHANGELOG.md ├── .php_cs.dist.php ├── composer.json ├── README.md └── src └── GoogleCloudStorageAdapter.php /psalm.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) spatie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `flysystem-google-cloud-storage` will be documented in this file. 4 | 5 | ## 1.1.2 - 2023-07-20 6 | 7 | ### What's Changed 8 | 9 | - Moved mockery/mockery to the dev dependencies list in composer.json config by @alexandrmazur96 in https://github.com/spatie/flysystem-google-cloud-storage/pull/2 10 | 11 | ### New Contributors 12 | 13 | - @alexandrmazur96 made their first contribution in https://github.com/spatie/flysystem-google-cloud-storage/pull/2 14 | 15 | **Full Changelog**: https://github.com/spatie/flysystem-google-cloud-storage/compare/1.1.1...1.1.2 16 | 17 | ## 1.1.0 - 2022-04-28 18 | 19 | ## What's Changed 20 | 21 | - Add support for copying files with uniform ACL by @AlexVanderbist in https://github.com/spatie/flysystem-google-cloud-storage/pull/1 22 | 23 | ## New Contributors 24 | 25 | - @AlexVanderbist made their first contribution in https://github.com/spatie/flysystem-google-cloud-storage/pull/1 26 | 27 | **Full Changelog**: https://github.com/spatie/flysystem-google-cloud-storage/compare/1.0.0...1.1.0 28 | 29 | ## 1.0.0 - 2022-02-09 30 | 31 | **Full Changelog**: https://github.com/spatie/flysystem-google-cloud-storage/compare/0.0.3...1.0.0 32 | 33 | ## 1.0.0 - 202X-XX-XX 34 | 35 | - initial release 36 | -------------------------------------------------------------------------------- /.php_cs.dist.php: -------------------------------------------------------------------------------- 1 | in([ 5 | __DIR__ . '/src', 6 | __DIR__ . '/tests', 7 | ]) 8 | ->name('*.php') 9 | ->notName('*.blade.php') 10 | ->ignoreDotFiles(true) 11 | ->ignoreVCS(true); 12 | 13 | return (new PhpCsFixer\Config()) 14 | ->setRules([ 15 | '@PSR12' => true, 16 | 'array_syntax' => ['syntax' => 'short'], 17 | 'ordered_imports' => ['sort_algorithm' => 'alpha'], 18 | 'no_unused_imports' => true, 19 | 'not_operator_with_successor_space' => true, 20 | 'trailing_comma_in_multiline' => true, 21 | 'phpdoc_scalar' => true, 22 | 'unary_operator_spaces' => true, 23 | 'binary_operator_spaces' => true, 24 | 'blank_line_before_statement' => [ 25 | 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], 26 | ], 27 | 'phpdoc_single_line_var_spacing' => true, 28 | 'phpdoc_var_without_name' => true, 29 | 'class_attributes_separation' => [ 30 | 'elements' => [ 31 | 'method' => 'one', 32 | ], 33 | ], 34 | 'method_argument_space' => [ 35 | 'on_multiline' => 'ensure_fully_multiline', 36 | 'keep_multiple_spaces_after_comma' => true, 37 | ], 38 | 'single_trait_insert_per_statement' => true, 39 | ]) 40 | ->setFinder($finder); 41 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/flysystem-google-cloud-storage", 3 | "description": "Flysystem adapter for Google Cloud Storage", 4 | "keywords": [ 5 | "spatie", 6 | "flysystem-google-cloud-storage" 7 | ], 8 | "homepage": "https://github.com/spatie/flysystem-google-cloud-storage", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Alex Vanderbist", 13 | "email": "alex@spatie.be", 14 | "role": "Developer" 15 | } 16 | ], 17 | "require": { 18 | "php": "^8.0", 19 | "google/cloud-storage": "^1.24", 20 | "league/flysystem": "^1.1", 21 | "ext-fileinfo": "*" 22 | }, 23 | "require-dev": { 24 | "friendsofphp/php-cs-fixer": "^3.0", 25 | "mockery/mockery": "^1.4", 26 | "phpunit/phpunit": "^9.5", 27 | "spatie/ray": "^1.28", 28 | "vimeo/psalm": "^4.8" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "Spatie\\GoogleCloudStorageAdapter\\": "src" 33 | } 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "Spatie\\GoogleCloudStorageAdapter\\Tests\\": "tests" 38 | } 39 | }, 40 | "scripts": { 41 | "psalm": "vendor/bin/psalm", 42 | "test": "vendor/bin/phpunit", 43 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage", 44 | "format": "vendor/bin/php-cs-fixer fix --allow-risky=yes" 45 | }, 46 | "config": { 47 | "sort-packages": true 48 | }, 49 | "minimum-stability": "dev", 50 | "prefer-stable": true 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flysystem adapter for Google Cloud Storage 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/flysystem-google-cloud-storage.svg?style=flat-square)](https://packagist.org/packages/spatie/flysystem-google-cloud-storage) 4 | [![GitHub Tests Action Status](https://img.shields.io/github/workflow/status/spatie/flysystem-google-cloud-storage/Tests/main?label=tests)](https://github.com/spatie/flysystem-google-cloud-storage/actions?query=workflow%3ATests+branch%3Amain) 5 | [![GitHub Code Style Action Status](https://img.shields.io/github/workflow/status/spatie/flysystem-google-cloud-storage/Check%20&%20fix%20styling/main?label=code%20style)](https://github.com/spatie/flysystem-google-cloud-storage/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amain) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/flysystem-google-cloud-storage.svg?style=flat-square)](https://packagist.org/packages/spatie/flysystem-google-cloud-storage) 7 | 8 | This package contains a [Google Cloud Storage](https://cloud.google.com/storage) driver for [Flysystem v1](https://flysystem.thephpleague.com). 9 | 10 | (If you need Google Cloud Storage support on Flysystem v2 or above (or Laravel 9), this package is not for you) 11 | 12 | ### Notice 13 | 14 | This package is a fork from [superbalist/flysystem-google-cloud-storage](https://github.com/Superbalist/flysystem-google-cloud-storage). Changes include: 15 | 16 | - PHP 8 only 17 | - merged random open PRs from Superbalist's package 18 | 19 | 20 | ## Support us 21 | 22 | [](https://spatie.be/github-ad-click/flysystem-google-cloud-storage) 23 | 24 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source) 25 | . You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 26 | 27 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are 28 | using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received 29 | postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 30 | 31 | ## Installation 32 | 33 | You can install the package via composer: 34 | 35 | ```bash 36 | composer require spatie/flysystem-google-cloud-storage 37 | ``` 38 | 39 | ## Authentication 40 | 41 | Before you can use the package, you'll need to authenticate with Google. When possible, the credentials will be auto-loaded by the Google Cloud Client. 42 | 43 | 1. The client will first look at the GOOGLE_APPLICATION_CREDENTIALS env var. You can use `putenv('GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json');` to set the location of your credentials file. 44 | 2. The client will look for the credentials file at the following paths: 45 | - Windows: `%APPDATA%/gcloud/application_default_credentials.json` 46 | - MacOS/Unix: `$HOME/.config/gcloud/application_default_credentials.json` 47 | 3. If running in Google App Engine, Google Compute Engine or GKE, the built-in service account associated with the app, instance or cluster will be used. 48 | 49 | Using this in a Kubernetes cluster? Take a look at [Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity). 50 | 51 | ## Usage 52 | 53 | Here's an example that shows you have you can call the various functions to manipulate files on Google Cloud. 54 | 55 | ```php 56 | use Google\Cloud\Storage\StorageClient; 57 | use League\Flysystem\Filesystem; 58 | use Spatie\GoogleCloudStorageAdapter\GoogleCloudStorageAdapter; 59 | 60 | $storageClient = new StorageClient([ 61 | 'projectId' => 'your-project-id', 62 | // The credentials can manually be specified by passing in a keyFilePath. 63 | // 'keyFilePath' => '/path/to/service-account.json', 64 | ]); 65 | 66 | $bucket = $storageClient->bucket('your-bucket-name'); 67 | 68 | $adapter = new GoogleCloudStorageAdapter($storageClient, $bucket); 69 | 70 | $filesystem = new Filesystem($adapter); 71 | 72 | $filesystem->write('path/to/file.txt', 'contents'); 73 | $filesystem->update('path/to/file.txt', 'new contents'); 74 | $contents = $filesystem->read('path/to/file.txt'); 75 | $exists = $filesystem->has('path/to/file.txt'); 76 | $filesystem->delete('path/to/file.txt'); 77 | $filesystem->rename('filename.txt', 'newname.txt'); 78 | $filesystem->copy('filename.txt', 'duplicate.txt'); 79 | $filesystem->deleteDir('path/to/directory'); 80 | ``` 81 | 82 | See [https://flysystem.thephpleague.com/v1/docs/usage/filesystem-api/](https://flysystem.thephpleague.com/v1/docs/usage/filesystem-api/) for full list of available functionality 83 | 84 | ### Using a custom storage URI 85 | 86 | You can configure this adapter to use a custom storage URI. Read more about configuring a custom domain for Google Cloud Storage [here](https://cloud.google.com/storage/docs/request-endpoints#cname). 87 | 88 | When using a custom storage URI, the bucket name will not prepended to the file path: 89 | 90 | ```php 91 | use Google\Cloud\Storage\StorageClient; 92 | use League\Flysystem\Filesystem; 93 | use Spatie\GoogleCloudStorageAdapter\GoogleCloudStorageAdapter; 94 | 95 | $storageClient = new StorageClient([ 96 | 'projectId' => 'your-project-id', 97 | ]); 98 | $bucket = $storageClient->bucket('your-bucket-name'); 99 | $adapter = new GoogleCloudStorageAdapter($storageClient, $bucket); 100 | 101 | // URI defaults to "https://storage.googleapis.com": 102 | $filesystem = new Filesystem($adapter); 103 | $filesystem->getUrl('path/to/file.txt'); 104 | // "https://storage.googleapis.com/your-bucket-name/path/to/file.txt" 105 | 106 | // Using custom storage uri: 107 | $adapter->setStorageApiUri('http://example.com'); 108 | $filesystem = new Filesystem($adapter); 109 | $filesystem->getUrl('path/to/file.txt'); 110 | // "http://example.com/path/to/file.txt" 111 | 112 | // You can also prefix the file path if needed: 113 | $adapter->setStorageApiUri('http://example.com'); 114 | $adapter->setPathPrefix('extra-folder/another-folder/'); 115 | $filesystem = new Filesystem($adapter); 116 | $filesystem->getUrl('path/to/file.txt'); 117 | // "http://example.com/extra-folder/another-folder/path/to/file.txt" 118 | ``` 119 | 120 | ## Testing 121 | 122 | ```bash 123 | composer test 124 | ``` 125 | 126 | ## Changelog 127 | 128 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 129 | 130 | ## Contributing 131 | 132 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 133 | 134 | ## Security Vulnerabilities 135 | 136 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 137 | 138 | ## Credits 139 | 140 | - [Alex Vanderbist](https://github.com/alexvanderbist) 141 | - [All Contributors](../../contributors) 142 | - [Superbalist](https://github.com/Superbalist) for the initial package 143 | 144 | ## License 145 | 146 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 147 | -------------------------------------------------------------------------------- /src/GoogleCloudStorageAdapter.php: -------------------------------------------------------------------------------- 1 | storageClient = $storageClient; 34 | $this->bucket = $bucket; 35 | 36 | if ($pathPrefix) { 37 | $this->setPathPrefix($pathPrefix); 38 | } 39 | 40 | $this->storageApiUri = ($storageApiUri) ?: self::STORAGE_API_URI_DEFAULT; 41 | } 42 | 43 | public function getStorageClient(): StorageClient 44 | { 45 | return $this->storageClient; 46 | } 47 | 48 | public function getBucket(): Bucket 49 | { 50 | return $this->bucket; 51 | } 52 | 53 | public function setStorageApiUri(string $uri) 54 | { 55 | $this->storageApiUri = $uri; 56 | } 57 | 58 | public function getStorageApiUri(): string 59 | { 60 | return $this->storageApiUri; 61 | } 62 | 63 | public function write($path, $contents, Config $config): array 64 | { 65 | return $this->upload($path, $contents, $config); 66 | } 67 | 68 | public function writeStream($path, $resource, Config $config): array 69 | { 70 | return $this->upload($path, $resource, $config); 71 | } 72 | 73 | public function update($path, $contents, Config $config): array 74 | { 75 | return $this->upload($path, $contents, $config); 76 | } 77 | 78 | public function updateStream($path, $resource, Config $config): array 79 | { 80 | return $this->upload($path, $resource, $config); 81 | } 82 | 83 | protected function getOptionsFromConfig(Config $config): array 84 | { 85 | $options = []; 86 | 87 | if (! $this->uniformBucketLevelAccessEnabled()) { 88 | if ($visibility = $config->get('visibility')) { 89 | $options['predefinedAcl'] = $this->getPredefinedAclForVisibility($visibility); 90 | } else { 91 | // if a file is created without an acl, it isn't accessible via the console 92 | // we therefore default to private 93 | $options['predefinedAcl'] = $this->getPredefinedAclForVisibility(AdapterInterface::VISIBILITY_PRIVATE); 94 | } 95 | } 96 | 97 | if ($mimetype = $config->get('mimetype')) { 98 | $options['mimetype'] = $mimetype; 99 | $options['metadata']['contentType'] = $mimetype; 100 | } 101 | 102 | if ($metadata = $config->get('metadata')) { 103 | $options['metadata'] = $metadata; 104 | } 105 | 106 | if ($chunkSize = $config->get('chunkSize')) { 107 | $options['chunkSize'] = $chunkSize; 108 | } 109 | 110 | if ($uploadProgressCallback = $config->get('uploadProgressCallback')) { 111 | $options['uploadProgressCallback'] = $uploadProgressCallback; 112 | } 113 | 114 | return $options; 115 | } 116 | 117 | /** 118 | * @param string $path 119 | * @param string|resource $contents 120 | * @param Config $config 121 | * 122 | * @return array 123 | */ 124 | protected function upload(string $path, mixed $contents, Config $config): array 125 | { 126 | $path = $this->applyPathPrefix($path); 127 | 128 | $options = $this->getOptionsFromConfig($config); 129 | 130 | $options['name'] = $path; 131 | 132 | if (! $this->isDirectory($path)) { 133 | if (! isset($options['metadata']['contentType'])) { 134 | $options['metadata']['contentType'] = Util::guessMimeType($path, $contents); 135 | } 136 | } 137 | 138 | $object = $this->bucket->upload($contents, $options); 139 | 140 | return $this->normaliseObject($object); 141 | } 142 | 143 | protected function normaliseObject(StorageObject $object): array 144 | { 145 | $name = $this->removePathPrefix($object->name()); 146 | $info = $object->info(); 147 | 148 | $isDirectory = $this->isDirectory($name); 149 | if ($isDirectory) { 150 | $name = rtrim($name, '/'); 151 | } 152 | 153 | return [ 154 | 'type' => $isDirectory ? 'dir' : 'file', 155 | 'dirname' => Util::dirname($name), 156 | 'path' => $name, 157 | 'timestamp' => strtotime($info['updated']), 158 | 'mimetype' => $info['contentType'] ?? '', 159 | 'size' => $info['size'], 160 | ]; 161 | } 162 | 163 | public function rename($path, $newpath): bool 164 | { 165 | if (! $this->copy($path, $newpath)) { 166 | return false; 167 | } 168 | 169 | return $this->delete($path); 170 | } 171 | 172 | public function copy($path, $newpath): bool 173 | { 174 | $newpath = $this->applyPathPrefix($newpath); 175 | 176 | // we want the new file to have the same visibility as the original file 177 | $visibility = $this->getRawVisibility($path); 178 | 179 | $options = [ 180 | 'name' => $newpath, 181 | ]; 182 | 183 | if ($visibility) { 184 | $options['predefinedAcl'] = $this->getPredefinedAclForVisibility($visibility); 185 | } 186 | 187 | $this->getObject($path)->copy($this->bucket, $options); 188 | 189 | return true; 190 | } 191 | 192 | public function delete($path): bool 193 | { 194 | $this->getObject($path)->delete(); 195 | 196 | return true; 197 | } 198 | 199 | public function deleteDir($dirname): bool 200 | { 201 | $dirname = $this->normaliseDirectoryName($dirname); 202 | $objects = $this->listContents($dirname, true); 203 | 204 | // We first delete the file, so that we can delete 205 | // the empty folder at the end. 206 | uasort($objects, function ($_a, $b) { 207 | return $b['type'] === 'file' ? 1 : -1; 208 | }); 209 | 210 | // We remove all objects that should not be deleted. 211 | $filtered_objects = []; 212 | foreach ($objects as $object) { 213 | // normalise directories path 214 | if ($object['type'] === 'dir') { 215 | $object['path'] = $this->normaliseDirectoryName($object['path']); 216 | } 217 | 218 | if (strpos($object['path'], $dirname) !== false) { 219 | $filtered_objects[] = $object; 220 | } 221 | } 222 | 223 | // Execute deletion for each object (if it still exists at this point). 224 | foreach ($filtered_objects as $object) { 225 | if ($this->has($object['path'])) { 226 | $this->delete($object['path']); 227 | } 228 | } 229 | 230 | return true; 231 | } 232 | 233 | public function createDir($dirname, Config $config): array 234 | { 235 | return $this->upload($this->normaliseDirectoryName($dirname), '', $config); 236 | } 237 | 238 | protected function normaliseDirectoryName(string $dirname): string 239 | { 240 | return rtrim($dirname, '/') . '/'; 241 | } 242 | 243 | public function setVisibility($path, $visibility): array 244 | { 245 | $object = $this->getObject($path); 246 | 247 | if ($visibility === AdapterInterface::VISIBILITY_PRIVATE) { 248 | try { 249 | $object->acl()->delete('allUsers'); 250 | } catch (NotFoundException) { 251 | // Not actually an exception, no ACL to delete. 252 | } 253 | } elseif ($visibility === AdapterInterface::VISIBILITY_PUBLIC) { 254 | $object->acl()->add('allUsers', Acl::ROLE_READER); 255 | } 256 | 257 | $normalised = $this->normaliseObject($object); 258 | $normalised['visibility'] = $visibility; 259 | 260 | return $normalised; 261 | } 262 | 263 | public function has($path): bool 264 | { 265 | // Path has a `/` suffix. We are definitely checking for a directory. 266 | if ($this->isDirectory($path)) { 267 | return $this->getObject($path)->exists(); 268 | } 269 | 270 | // Path might be directory with missing `/` suffix. Add `/` suffix first to check. 271 | return $this->getObject($path . '/')->exists() 272 | || $this->getObject($path)->exists(); 273 | } 274 | 275 | public function read($path): array 276 | { 277 | $object = $this->getObject($path); 278 | $contents = $object->downloadAsString(); 279 | 280 | $data = $this->normaliseObject($object); 281 | $data['contents'] = $contents; 282 | 283 | return $data; 284 | } 285 | 286 | public function readStream($path): array 287 | { 288 | $object = $this->getObject($path); 289 | 290 | $data = $this->normaliseObject($object); 291 | $data['stream'] = StreamWrapper::getResource($object->downloadAsStream()); 292 | 293 | return $data; 294 | } 295 | 296 | /** 297 | * @param string $directory 298 | * @param bool $recursive 299 | * 300 | * @psalm-suppress TooManyTemplateParams 301 | * 302 | * @return array 303 | */ 304 | public function listContents($directory = '', $recursive = false): array 305 | { 306 | $directory = $this->applyPathPrefix($directory); 307 | 308 | $objects = $this->bucket->objects(['prefix' => $directory]); 309 | 310 | $normalised = []; 311 | foreach ($objects as $object) { 312 | $normalised[] = $this->normaliseObject($object); 313 | } 314 | 315 | return Util::emulateDirectories($normalised); 316 | } 317 | 318 | public function getMetadata($path): array 319 | { 320 | $object = $this->getObject($path); 321 | 322 | return $this->normaliseObject($object); 323 | } 324 | 325 | public function getSize($path): array 326 | { 327 | return $this->getMetadata($path); 328 | } 329 | 330 | public function getMimetype($path): array 331 | { 332 | return $this->getMetadata($path); 333 | } 334 | 335 | public function getTimestamp($path): array 336 | { 337 | return $this->getMetadata($path); 338 | } 339 | 340 | public function getVisibility($path): array 341 | { 342 | return [ 343 | 'visibility' => $this->getRawVisibility($path), 344 | ]; 345 | } 346 | 347 | /** 348 | * Return a public url to a file. 349 | * 350 | * Note: The file must have `AdapterInterface::VISIBILITY_PUBLIC` visibility. 351 | * 352 | * @param string $path 353 | * 354 | * @return string 355 | */ 356 | public function getUrl(string $path): string 357 | { 358 | $uri = rtrim($this->storageApiUri, '/'); 359 | $path = $this->applyPathPrefix($path); 360 | 361 | // Generating an uri with whitespaces or any other characters besides alphanumeric characters or "-_.~" will 362 | // not be RFC 3986 compliant. They will work in most browsers because they are automatically encoded but 363 | // may fail when passed to other software modules which are not doing automatic encoding. 364 | $path = implode('/', array_map('rawurlencode', explode('/', $path))); 365 | 366 | // Only prepend bucket name if no custom storage uri specified 367 | // Default: "https://storage.googleapis.com/{my_bucket}/{path_prefix}" 368 | // Custom: "https://example.com/{path_prefix}" 369 | if ($this->getStorageApiUri() === self::STORAGE_API_URI_DEFAULT) { 370 | $path = $this->bucket->name() . '/' . $path; 371 | } 372 | 373 | return $uri . '/' . $path; 374 | } 375 | 376 | public function getTemporaryUrl( 377 | string $path, 378 | \DateTimeInterface | int $expiration, 379 | array $options = [] 380 | ): string { 381 | $object = $this->getObject($path); 382 | 383 | $signedUrl = $object->signedUrl($expiration, $options); 384 | 385 | if ($this->getStorageApiUri() !== self::STORAGE_API_URI_DEFAULT) { 386 | [, $params] = explode('?', $signedUrl, 2); 387 | 388 | $signedUrl = $this->getUrl($path) . '?' . $params; 389 | } 390 | 391 | return $signedUrl; 392 | } 393 | 394 | protected function getRawVisibility(string $path): ?string 395 | { 396 | if ($this->uniformBucketLevelAccessEnabled()) { 397 | return null; 398 | } 399 | 400 | try { 401 | $acl = $this->getObject($path)->acl()->get(['entity' => 'allUsers']); 402 | 403 | return $acl['role'] === Acl::ROLE_READER 404 | ? AdapterInterface::VISIBILITY_PUBLIC 405 | : AdapterInterface::VISIBILITY_PRIVATE; 406 | } catch (NotFoundException $e) { 407 | // object may not have an acl entry, so handle that gracefully 408 | return AdapterInterface::VISIBILITY_PRIVATE; 409 | } 410 | } 411 | 412 | protected function getObject(string $path): StorageObject 413 | { 414 | $path = $this->applyPathPrefix($path); 415 | 416 | return $this->bucket->object($path); 417 | } 418 | 419 | protected function getPredefinedAclForVisibility(string $visibility): string 420 | { 421 | return $visibility === AdapterInterface::VISIBILITY_PUBLIC ? 'publicRead' : 'projectPrivate'; 422 | } 423 | 424 | protected function isDirectory(string $path): bool 425 | { 426 | return substr($path, -1) === '/'; 427 | } 428 | 429 | public function uniformBucketLevelAccessEnabled(): bool 430 | { 431 | return $this->bucket->info()['iamConfiguration']['uniformBucketLevelAccess']['enabled'] ?? false; 432 | } 433 | } 434 | --------------------------------------------------------------------------------