├── 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 | [](https://packagist.org/packages/spatie/flysystem-google-cloud-storage)
4 | [](https://github.com/spatie/flysystem-google-cloud-storage/actions?query=workflow%3ATests+branch%3Amain)
5 | [](https://github.com/spatie/flysystem-google-cloud-storage/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amain)
6 | [](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 |
--------------------------------------------------------------------------------