PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.4
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.4
1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk 1.2.0 All 47 releases
fluent-cart / app / Modules / StorageDrivers / S3 / S3.php

S3.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.4, at app/Modules/StorageDrivers/S3/S3.php

783 lines 26.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Modules\StorageDrivers\S3;
4
5 use FluentCart\App\Helpers\Helper;
6 use FluentCart\App\Services\FileSystem\Drivers\S3\S3ConnectionVerify;
7 use FluentCart\App\Services\FileSystem\Drivers\S3\S3Driver;
8 use FluentCart\App\Services\FileSystem\Drivers\S3\S3InputValidator;
9 use FluentCart\App\Vite;
10 use FluentCart\App\Modules\StorageDrivers\BaseStorageDriver;
11 use FluentCart\Framework\Support\Arr;
12
13 class S3 extends BaseStorageDriver
14 {
15 /**
16 * title, slug, brandColor
17 */
18 public function __construct()
19 {
20 parent::__construct(
21 __('S3', 'fluent-cart'),
22 's3',
23 '#4f94d4'
24 );
25 }
26
27 public function registerHooks()
28 {
29 add_filter('fluent_cart/verify_driver_connect_info_' . $this->slug, [$this, 'verifyConnectInfo'], 10, 2);
30 add_filter('fluent_cart/get_dynamic_search_s3_bucket_list', [$this, 'getBucketList'], 10, 2);
31 }
32
33 public function getBucketList($buckets = [], $args = []): array
34 {
35 $driverSettings = Arr::get($args, 'driver_settings', []);
36 $query = strtolower(trim((string) Arr::get($args, 'query', '')));
37 $resolvedCredentials = $this->resolveCredentials($driverSettings);
38
39 if (empty($resolvedCredentials['access_key']) || empty($resolvedCredentials['secret_key'])) {
40 return [];
41 }
42
43 $bucketList = \FluentCart\App\Services\FileSystem\Drivers\S3\S3BucketList::get(
44 $resolvedCredentials['secret_key'],
45 $resolvedCredentials['access_key'],
46 'us-east-1',
47 $resolvedCredentials['session_token']
48 );
49
50 if (is_wp_error($bucketList)) {
51 return [];
52 }
53
54 if ($query !== '') {
55 $bucketList = array_values(array_filter($bucketList, function ($bucket) use ($query) {
56 return strpos(strtolower((string) $bucket), $query) !== false;
57 }));
58 }
59
60 $bucketList = array_slice($bucketList, 0, 50);
61
62 $buckets = [];
63 foreach ($bucketList as $bucket) {
64 $buckets[] = array(
65 "label" => $bucket,
66 "value" => $bucket,
67 );
68 }
69 return $buckets;
70 }
71
72 public function getLogo(): string
73 {
74 return Vite::getAssetUrl("images/storage-drivers/s3.svg");
75 }
76
77 public function getDarkLogo()
78 {
79 return Vite::getAssetUrl("images/storage-drivers/s3-dark.svg");
80 }
81
82 public function hasBucket(): bool
83 {
84 return true;
85 }
86
87 public function getEffectiveBucket(): string
88 {
89 return S3Settings::resolveEffectiveBucket($this->getSettings());
90 }
91
92 public function needsReconfigure(): bool
93 {
94 return S3Settings::resolveConfiguredBucket($this->getSettings()) === '';
95 }
96
97
98 public function getDescription()
99 {
100 return esc_html__('S3 bucket allows to configure storage options and others for efficient and secure cloud-based file storage', 'fluent-cart');
101 }
102
103 public function isEnabled(): bool
104 {
105 $settings = $this->getSettings();
106 $isActive = Arr::get($settings, 'is_active') === 'yes';
107 $hasSelectedBucket = S3Settings::resolveEffectiveBucket($settings) !== '';
108 return $isActive && $hasSelectedBucket;
109 }
110
111 public function getSettings()
112 {
113 return (new S3Settings())->get();
114 }
115
116 public function getSettingsTemplate(): ?string
117 {
118 $templatePath = dirname(__DIR__, 4) . '/app/Modules/StorageDrivers/S3/VueTemplates/SettingsPage.vue';
119
120 if (!file_exists($templatePath)) {
121 return null;
122 }
123
124 $template = file_get_contents($templatePath);
125
126 return $template ?: null;
127 }
128
129 public function getSettingsTemplatePayload(array $response = []): array
130 {
131 return array_merge(parent::getSettingsTemplatePayload($response), [
132 'connection_verify_endpoint' => 'settings/storage-drivers/verify-info',
133 'create_bucket_endpoint' => 'settings/storage-drivers/create-bucket',
134 'bucket_list_endpoint' => 'settings/storage-drivers/bucket-list',
135 'save_endpoint' => 'settings/storage-drivers',
136 'reset_endpoint' => 'settings/storage-drivers/reset',
137 'supports_template_mode' => true,
138 ]);
139 }
140
141 public function listBuckets(array $data, array $args = [])
142 {
143 $credentialError = $this->requireCredentials($data);
144 if ($credentialError) {
145 return $credentialError;
146 }
147
148 return $this->getBucketList([], [
149 'driver_settings' => $data,
150 'query' => Arr::get($args, 'query')
151 ]);
152 }
153
154 public function createBucket(array $data)
155 {
156 $credentialError = $this->requireCredentials($data);
157 if ($credentialError) {
158 return $credentialError;
159 }
160
161 $resolvedCredentials = $this->resolveCredentials($data);
162
163 if (empty($resolvedCredentials['access_key']) || empty($resolvedCredentials['secret_key'])) {
164 return new \WP_Error('invalid_credentials', __('Invalid credentials for bucket creation', 'fluent-cart'));
165 }
166
167 $newBucket = sanitize_text_field(Arr::get($data, 'new_bucket_name'));
168 $newBucket = strtolower($newBucket);
169 $newBucket = preg_replace('/[^a-z0-9\.-]/', '-', $newBucket);
170 $newBucket = preg_replace('/-+/', '-', $newBucket);
171 $newBucket = trim($newBucket, '-.');
172
173 $newRegion = sanitize_text_field(Arr::get($data, 'new_bucket_region', 'us-east-1')) ?: 'us-east-1';
174
175 if (!$newBucket) {
176 return new \WP_Error('invalid_bucket', __('Bucket name is required', 'fluent-cart'));
177 }
178
179 $validation = S3InputValidator::validateBucketAndRegion($newBucket, $newRegion);
180 if (is_wp_error($validation)) {
181 return $validation;
182 }
183
184 $creation = \FluentCart\App\Services\FileSystem\Drivers\S3\S3BucketCreator::create(
185 $resolvedCredentials['secret_key'],
186 $resolvedCredentials['access_key'],
187 $newBucket,
188 $newRegion,
189 $resolvedCredentials['session_token']
190 );
191
192 if (is_wp_error($creation)) {
193 return $creation;
194 }
195
196 $bucketCheck = S3ConnectionVerify::checkBucketExistence(
197 $resolvedCredentials['secret_key'],
198 $resolvedCredentials['access_key'],
199 $resolvedCredentials['session_token'],
200 $newBucket,
201 $newRegion
202 );
203
204 if (is_wp_error($bucketCheck)) {
205 return $bucketCheck;
206 }
207
208 $region = Arr::get($bucketCheck, 'region', $newRegion);
209
210 $securitySettings = S3ConnectionVerify::checkSecuritySettings(
211 $resolvedCredentials['secret_key'],
212 $resolvedCredentials['access_key'],
213 $resolvedCredentials['session_token'],
214 $newBucket,
215 $region
216 );
217
218 return [
219 'message' => __('Bucket created successfully', 'fluent-cart'),
220 'bucket' => $newBucket,
221 'region' => $region,
222 'connection_error' => '',
223 'block_public_access' => !empty($securitySettings['block_public_access']) ? 'yes' : 'no',
224 'object_ownership' => !empty($securitySettings['object_ownership']) ? 'yes' : 'no'
225 ];
226 }
227
228 public function updateSettings($data)
229 {
230 $settings = $this->getSettings();
231
232 if (!array_key_exists('auth_method', $data) && isset($settings['auth_method'])) {
233 $data['auth_method'] = $settings['auth_method'];
234 }
235
236 if (!array_key_exists('buckets', $data) && isset($settings['buckets'])) {
237 $data['buckets'] = $settings['buckets'];
238 }
239
240 $mergeKeys = array_diff($this->hiddenSettingKeys(), ['create_new_bucket', 'new_bucket_name', 'new_bucket_region']);
241 $credentialKeys = ['access_key', 'secret_key'];
242 $storedAuthMethod = Arr::get($settings, 'auth_method');
243
244 foreach ($mergeKeys as $key) {
245 $isCredential = in_array($key, $credentialKeys, true);
246
247 // In define mode getSettings() resolves credentials from the wp-config
248 // constants, so merging one here would copy it into the database and
249 // break revocation. Never fall back to a value we do not own.
250 if ($isCredential && $storedAuthMethod === 'define') {
251 continue;
252 }
253
254 // Credentials are stripped from the settings response, so an empty
255 // submitted value means "unchanged" and must fall back to storage.
256 $isMissing = $isCredential
257 ? empty($data[$key])
258 : !array_key_exists($key, $data);
259
260 if ($isMissing && isset($settings[$key])) {
261 $data[$key] = $settings[$key];
262 }
263 }
264
265 if (
266 Arr::get($data, 'is_active') === 'no' &&
267 !Arr::get($data, 'verify_only_credentials') &&
268 !Arr::get($data, 'check_bucket_exists')
269 ) {
270 $data['is_active'] = 'no';
271
272 if (!Arr::get($data, 'preserve_settings')) {
273 $resetSettings = $this->resetSettings();
274
275 return [
276 'data' => Arr::except($resetSettings, $this->hiddenSettingKeys()),
277 'message' => __('S3 settings have been reset successfully', 'fluent-cart'),
278 'shouldReload' => false
279 ];
280 }
281
282 if (!empty($data['bucket'])) {
283 $data['show_buckets'] = 'yes';
284 }
285
286 parent::updateSettings($this->prepareSettingsForStorage($data));
287
288 return [
289 'data' => Arr::except($data, $this->hiddenSettingKeys()),
290 'message' => __('S3 is deactivated successfully', 'fluent-cart'),
291 'shouldReload' => false
292 ];
293 }
294
295 $credentialError = $this->requireCredentials($data);
296 if ($credentialError) {
297 return $credentialError;
298 }
299
300 $resolvedCredentials = $this->resolveCredentials($data);
301 $userBlockPublicAccess = Arr::get($data, 'block_public_access');
302 $userObjectOwnership = Arr::get($data, 'object_ownership');
303 $verificationResult = true;
304
305 if (Arr::get($data, 'verify_only_credentials')) {
306 $verificationResult = $this->verifyConnectInfo($data);
307 if (is_wp_error($verificationResult)) {
308 return $verificationResult;
309 }
310 }
311
312 // A credentials-only step must not run a bucket check: on failure the
313 // branch below persists the stored settings and discards the credentials
314 // the user just entered. Mirrors the guards further down.
315 if (!empty($data['bucket']) && !Arr::get($data, 'verify_only_credentials')) {
316 $bucketValidation = S3InputValidator::validateBucket($data['bucket']);
317 if (is_wp_error($bucketValidation)) {
318 return $bucketValidation;
319 }
320
321 if (!empty($data['region'])) {
322 $regionValidation = S3InputValidator::validateRegion($data['region']);
323 if (is_wp_error($regionValidation)) {
324 return $regionValidation;
325 }
326 }
327
328 $region = Arr::get($data, 'region') ?: self::getBucketRegion($data['bucket']) ?: 'us-east-1';
329
330 $bucketCheck = S3ConnectionVerify::checkBucketExistence(
331 $resolvedCredentials['secret_key'],
332 $resolvedCredentials['access_key'],
333 $resolvedCredentials['session_token'],
334 $data['bucket'],
335 $region
336 );
337
338 if (is_wp_error($bucketCheck)) {
339 $errorSettings = $settings;
340 $errorSettings['connection_error'] = $bucketCheck->get_error_message();
341
342 parent::updateSettings($this->prepareSettingsForStorage($errorSettings));
343
344 return $bucketCheck;
345 }
346
347 $data['connection_error'] = '';
348
349 if (isset($bucketCheck['region']) && $bucketCheck['region'] !== Arr::get($data, 'region')) {
350 $data['region'] = $bucketCheck['region'];
351 }
352
353 $securitySettings = S3ConnectionVerify::checkSecuritySettings(
354 $resolvedCredentials['secret_key'],
355 $resolvedCredentials['access_key'],
356 $resolvedCredentials['session_token'],
357 $data['bucket'],
358 Arr::get($data, 'region') ?: $region
359 );
360
361 $bucketCheck['block_public_access'] = Arr::get($securitySettings, 'block_public_access');
362 $bucketCheck['object_ownership'] = Arr::get($securitySettings, 'object_ownership');
363 $verificationResult = $bucketCheck;
364 }
365
366 if (is_array($verificationResult) && array_key_exists('block_public_access', $verificationResult) && $userBlockPublicAccess === null) {
367 $data['block_public_access'] = $verificationResult['block_public_access'] ? 'yes' : 'no';
368 }
369
370 if (is_array($verificationResult) && array_key_exists('object_ownership', $verificationResult) && $userObjectOwnership === null) {
371 $data['object_ownership'] = $verificationResult['object_ownership'] ? 'yes' : 'no';
372 }
373
374 if (
375 !empty($data['bucket']) &&
376 Arr::get($data, 'is_active') === 'yes' &&
377 !Arr::get($data, 'verify_only_credentials') &&
378 !is_wp_error($verificationResult)
379 ) {
380 $bucketRegion = Arr::get($data, 'region') ?: self::getBucketRegion($data['bucket']) ?: 'us-east-1';
381
382 if ($userBlockPublicAccess !== null) {
383 $enableBlock = $this->isTrue($userBlockPublicAccess);
384 $syncResult = S3ConnectionVerify::updatePublicAccessBlock(
385 $resolvedCredentials['secret_key'],
386 $resolvedCredentials['access_key'],
387 $resolvedCredentials['session_token'],
388 $data['bucket'],
389 $enableBlock,
390 $bucketRegion
391 );
392
393 if (is_wp_error($syncResult)) {
394 return $syncResult;
395 }
396
397 $data['block_public_access'] = $enableBlock ? 'yes' : 'no';
398 }
399
400 if ($userObjectOwnership !== null) {
401 $enableOwnership = $this->isTrue($userObjectOwnership);
402 $syncResult = S3ConnectionVerify::updateObjectOwnership(
403 $resolvedCredentials['secret_key'],
404 $resolvedCredentials['access_key'],
405 $resolvedCredentials['session_token'],
406 $data['bucket'],
407 $enableOwnership,
408 $bucketRegion
409 );
410
411 if (is_wp_error($syncResult)) {
412 return $syncResult;
413 }
414
415 $data['object_ownership'] = $enableOwnership ? 'yes' : 'no';
416 }
417 }
418
419 if (Arr::get($data, 'verify_only_credentials') || !empty($data['bucket']) || Arr::get($data, 'is_active') === 'yes') {
420 $data['show_buckets'] = 'yes';
421 }
422
423 if (Arr::get($data, 'is_active') === 'yes') {
424 $data['is_active'] = 'yes';
425 $data['connection_error'] = '';
426 }
427
428 parent::updateSettings($this->prepareSettingsForStorage($data));
429
430 $shouldReload = Arr::get($data, 'is_active') === 'yes';
431
432 if (empty($message)) {
433 if (Arr::get($data, 'is_active') === 'yes') {
434 $message = __('Your s3 storage is activated successfully', 'fluent-cart');
435 } else {
436 $message = __('Settings saved successfully.', 'fluent-cart');
437 }
438 }
439
440
441 return [
442 'data' => Arr::except($data, $this->hiddenSettingKeys()),
443 'message' => $message,
444 'shouldReload' => $shouldReload
445 ];
446 }
447
448 private function requireCredentials(array $data)
449 {
450 $settings = $this->getSettings();
451 $authMethod = Arr::get($data, 'auth_method', Arr::get($settings, 'auth_method', 'db'));
452
453 if ($authMethod === 'define' && !S3Settings::hasDefinedCredentials()) {
454 return new \WP_Error(
455 'missing_credentials',
456 __('S3 credentials are not defined in wp-config.php. Please add them and try again.', 'fluent-cart')
457 );
458 }
459
460 return null;
461 }
462
463 private function resolveCredentials(array $data): array
464 {
465 $settings = $this->getSettings();
466 $authMethod = Arr::get($data, 'auth_method', Arr::get($settings, 'auth_method', 'db'));
467
468 if ($authMethod === 'define') {
469 $definedCredentials = S3Settings::getDefinedCredentials();
470
471 if (!empty($definedCredentials)) {
472 return [
473 'access_key' => Arr::get($definedCredentials, 'access_key', ''),
474 'secret_key' => Arr::get($definedCredentials, 'secret_key', ''),
475 'session_token' => null
476 ];
477 }
478 }
479
480 $accessKey = Arr::get($data, 'access_key');
481 if (empty($accessKey)) {
482 $accessKey = Arr::get($settings, 'access_key');
483 }
484
485 $secretKey = Arr::get($data, 'secret_key');
486 if (empty($secretKey)) {
487 $secretKey = Arr::get($settings, 'secret_key');
488 }
489
490 return [
491 'access_key' => $accessKey,
492 'secret_key' => $secretKey,
493 'session_token' => null
494 ];
495 }
496
497 public function resetSettings()
498 {
499 fluent_cart_update_option($this->driverHandler, []);
500 S3Settings::$cachedSettings = null;
501
502 return $this->getSettings();
503 }
504
505 private function prepareSettingsForStorage(array $settings): array
506 {
507 if (Arr::get($settings, 'auth_method') === 'define') {
508 unset($settings['access_key'], $settings['secret_key'], $settings['session_token']);
509 } else {
510 if (!empty($settings['access_key'])) {
511 $settings['access_key'] = Helper::encryptKey($settings['access_key']);
512 }
513
514 if (!empty($settings['secret_key'])) {
515 $settings['secret_key'] = Helper::encryptKey($settings['secret_key']);
516 }
517 }
518
519 unset($settings['create_new_bucket'], $settings['new_bucket_name'], $settings['new_bucket_region']);
520 unset($settings['verify_credentials'], $settings['verify_only_credentials'], $settings['preserve_settings'], $settings['check_bucket_exists']);
521
522 return $settings;
523 }
524
525 /**
526 * Verify Connect configuration
527 */
528 public function verifyConnectInfo(array $data, $args = [])
529 {
530 $credentialError = $this->requireCredentials($data);
531 if ($credentialError) {
532 return $credentialError;
533 }
534
535 $resolvedCredentials = $this->resolveCredentials($data);
536 $accessKey = Arr::get($resolvedCredentials, 'access_key');
537 $secretKey = Arr::get($resolvedCredentials, 'secret_key');
538 $sessionToken = Arr::get($resolvedCredentials, 'session_token');
539
540 if (empty($secretKey) || empty($accessKey)) {
541 return new \WP_Error('invalid_credentials', __('Invalid credentials', 'fluent-cart'));
542 }
543
544 $verifyOnlyCredentials = (bool) Arr::get($data, 'verify_only_credentials');
545 $bucket = '';
546 $region = 'us-east-1';
547
548 if (!$verifyOnlyCredentials) {
549 $bucket = Arr::get($data, 'bucket');
550
551 if ($bucket) {
552 $bucketValidation = S3InputValidator::validateBucket($bucket);
553 if (is_wp_error($bucketValidation)) {
554 return $bucketValidation;
555 }
556
557 $region = Arr::get($data, 'region');
558
559 if ($region) {
560 $regionValidation = S3InputValidator::validateRegion($region);
561 if (is_wp_error($regionValidation)) {
562 return $regionValidation;
563 }
564 }
565
566 if (!$region) {
567 $region = self::getBucketRegion($bucket);
568 }
569 }
570 }
571
572 if (!$region && $bucket) {
573 return new \WP_Error('missing_region', __('Region information is missing for the selected bucket.', 'fluent-cart'));
574 }
575
576 $result = S3ConnectionVerify::verify(
577 $secretKey,
578 $accessKey,
579 $sessionToken,
580 $bucket,
581 $region ?: 'us-east-1'
582 );
583
584 if (!is_wp_error($result)) {
585 $result['is_active'] = 'yes';
586 }
587
588 return $result;
589 }
590
591 public function fields(): array
592 {
593 $settings = $this->getSettings();
594 $showBucket = Arr::get($settings, 'show_buckets') === 'yes';
595 $usingDefineMode = Arr::get($settings, 'auth_method') === 'define';
596
597 $schema = [
598 'is_active' => [
599 'value' => '',
600 'label' => __('Enable s3 driver', 'fluent-cart'),
601 'type' => 'checkbox',
602 'attributes' => [
603 'disabled' => !$showBucket
604 ],
605 ],
606 'access_key' => [
607 'conditions' => [
608 [
609 'key' => 'is_active',
610 'operator' => '==',
611 'value' => 'yes'
612 ],
613 ],
614 'value' => '',
615 'label' => __('Access Key', 'fluent-cart'),
616 'type' => 'text',
617 'placeholder' => __('Enter access key', 'fluent-cart')
618 ],
619 'secret_key' => [
620 'conditions' => [
621 [
622 'key' => 'is_active',
623 'operator' => '==',
624 'value' => 'yes'
625 ],
626 ],
627 'value' => '',
628 'label' => __('Secret Key', 'fluent-cart'),
629 'type' => 'text',
630 'attributes' => [
631 'type' => 'password'
632 ],
633 'placeholder' => __('Enter secret key', 'fluent-cart')
634 ],
635 ];
636
637
638 if ($showBucket) {
639 $bucketList = Arr::get($settings, 'buckets', []);
640 $buckets = [];
641
642 foreach ($bucketList as $bucket) {
643 $buckets[] = array(
644 "label" => $bucket,
645 "value" => $bucket,
646 );
647 }
648
649 $schema['buckets'] = [
650 'conditions' => [
651 [
652 'key' => 'is_active',
653 'operator' => '==',
654 'value' => 'yes'
655 ],
656 ],
657 'value' => '',
658 'label' => __('Buckets', 'fluent-cart'),
659 'type' => 'remote_select',
660 'remote_key' => 's3_bucket_list',
661 'multiple' => true,
662 'options' => [],
663 'placeholder' => __('Select bucket', 'fluent-cart'),
664 'search_only_on_type' => false,
665 ];
666 }
667
668 if ($usingDefineMode) {
669 unset($schema['access_key'], $schema['secret_key']);
670 $schema['is_using_define_mode'] = [
671 'value' => Arr::get($settings, 'define_credentials_missing')
672 ? Arr::get($settings, 'define_credentials_missing_message')
673 : __('Using Define Mode', 'fluent-cart'),
674 'type' => 'html'
675 ];
676 }
677
678 return [
679 'view' => [
680 'title' => __('S3 Settings', 'fluent-cart'),
681 'type' => 'section',
682 'disable_nesting' => true,
683 'columns' => [
684 'default' => 1,
685 'md' => 1
686 ],
687 'schema' => $schema
688 ]
689 ];
690 }
691
692 public function getDriverClass(): string
693 {
694 return S3Driver::class;
695 }
696
697 public function hiddenSettingKeys(): array
698 {
699 return [
700 'show_buckets',
701 'access_key',
702 'secret_key',
703 'session_token',
704 'create_new_bucket',
705 'new_bucket_name',
706 'new_bucket_region',
707 'verify_credentials',
708 'verify_only_credentials',
709 'preserve_settings',
710 'check_bucket_exists',
711 ];
712 }
713
714 public static function getBucketRegion($bucket)
715 {
716 if (!S3InputValidator::isValidBucket($bucket)) {
717 return null;
718 }
719
720 $cacheKey = 'fct_s3_region';
721 $existingMeta = \FluentCart\App\Models\Meta::query()->where('meta_key', $cacheKey)->first();
722
723
724 if ($existingMeta) {
725 $region = Arr::get($existingMeta->meta_value, $bucket);
726 if ($region) {
727 return $region;
728 }
729 }
730
731 //$url = "https://{$bucket}.s3.amazonaws.com"; // The global endpoint
732 if (strpos($bucket, '.') !== false) {
733 // If bucket contains dot, use path-style (required for SSL)
734 $url = "https://s3.amazonaws.com/{$bucket}";
735 } else {
736 // Normal bucket: use virtual-hosted style
737 $url = "https://{$bucket}.s3.amazonaws.com";
738 }
739
740
741 $response = wp_remote_head($url);
742
743 if (is_wp_error($response)) {
744 return null;
745 }
746
747 $headers = wp_remote_retrieve_headers($response);
748
749 $region = Arr::get($headers, 'x-amz-bucket-region') ?? 'us-east-1';
750 if (!S3InputValidator::isValidRegion($region)) {
751 $region = 'us-east-1';
752 }
753
754 // 5. Save or update the Meta record
755 if ($existingMeta) {
756 $values = $existingMeta->meta_value;
757 $values[$bucket] = $region;
758 $existingMeta->meta_value = $values;
759 $existingMeta->save();
760 } else {
761 \FluentCart\App\Models\Meta::query()->create([
762 'meta_key' => $cacheKey,
763 'meta_value' => [
764 $bucket => $region
765 ],
766 ]);
767 }
768
769 return $region;
770 }
771
772 private function isTrue($value)
773 {
774 if (is_string($value)) {
775 $value = strtolower($value);
776
777 return in_array($value, ['yes', 'true', '1', 'on'], true);
778 }
779
780 return (bool) $value;
781 }
782 }
783