PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.1
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.1
1.6.5 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 All 48 releases
fluent-cart / app / Modules / StorageDrivers / S3 / S3.php

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

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