PluginProbe
Media Cloud Sync / trunk
Media Cloud Sync vtrunk
1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 All 34 releases
media-cloud-sync / includes / base / services / s3.php

s3.php in Media Cloud Sync trunk, at includes/base/services/s3.php

1,420 lines 58.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Dudlewebs\WPMCS;
3
4 defined('ABSPATH') || exit;
5
6 // Libraries
7 use Dudlewebs\WPMCS\s3\Aws\S3\S3Client;
8 use Dudlewebs\WPMCS\s3\Aws\Exception\AwsException;
9 use Dudlewebs\WPMCS\s3\Aws\S3\Exception\S3Exception;
10 use Dudlewebs\WPMCS\s3\Aws\S3\MultipartUploader;
11 use Dudlewebs\WPMCS\s3\Aws\Exception\MultipartUploadException;
12 use Dudlewebs\WPMCS\s3\Aws\S3\ObjectUploader;
13 use Dudlewebs\WPMCS\s3\Aws\Command;
14 use Exception;
15
16 class S3 {
17 private $assets_url;
18 private $version;
19 private $token;
20
21 protected $config;
22 protected $bucketConfig;
23 protected $settings;
24 protected $credentials;
25 protected $bucket_name;
26 protected $cdnConfig;
27
28 public $service = 's3';
29 public $s3Client = false;
30
31 /**
32 * Admin constructor.
33 * @since 1.0.0
34 */
35 public function __construct($credentials = null) {
36 $this->assets_url = WPMCS_ASSETS_URL;
37 $this->version = WPMCS_VERSION;
38 $this->token = WPMCS_TOKEN;
39
40 // Initialize setup
41 $this->init($credentials);
42 }
43
44 /**
45 * Initialise Client
46 *
47 * @param array|null $credentials Optional explicit credentials; falls back to
48 * Utils::get_credentials() when omitted.
49 */
50 public function init($credentials = null) {
51 $this->settings = Utils::get_settings();
52 $this->credentials = $credentials !== null ? $credentials : Utils::get_credentials();
53 $this->config = isset($this->credentials['config']) && !empty($this->credentials['config'])
54 ? $this->credentials['config']
55 : [];
56 $this->bucketConfig = isset($this->credentials['bucketConfig']) && !empty($this->credentials['bucketConfig'])
57 ? $this->credentials['bucketConfig']
58 : [];
59 $this->bucket_name = isset($this->bucketConfig['bucket_name']) && !empty($this->bucketConfig['bucket_name'])
60 ? $this->bucketConfig['bucket_name']
61 : '';
62 $this->cdnConfig = isset($this->credentials['cdn']) && !empty($this->credentials['cdn'])
63 ? $this->credentials['cdn']
64 : [];
65
66 if (
67 isset($this->config['region']) && !empty($this->config['region']) &&
68 isset($this->config['access_key']) && !empty($this->config['access_key']) &&
69 isset($this->config['secret_key']) && !empty($this->config['secret_key'])
70 ) {
71 $this->s3Client = new S3Client([
72 'version' => '2006-03-01',
73 'region' => $this->config['region'],
74 'use_accelerate_endpoint' => isset($this->bucketConfig['transfer_acceleration'])
75 ? $this->bucketConfig['transfer_acceleration'] : false,
76 'use_aws_shared_config_files' => false,
77 'credentials' => [
78 'key' => $this->config['access_key'],
79 'secret' => $this->config['secret_key'],
80 ],
81 ]);
82 }
83
84 }
85
86
87 /**
88 * Verify Credentials
89 * @since 1.0.0
90 * @return boolean
91 */
92 public function verifyCredentials($config = []) {
93 $region = isset($config['region']) ? $config['region'] : '';
94 $access_key = isset($config['access_key']) ? $config['access_key'] : '';
95 $secret_key = isset($config['secret_key']) ? $config['secret_key'] : '';
96
97 if (!Service::has_missing_fields([$region, $access_key, $secret_key])) {
98 try {
99 $s3Client = new S3Client([
100 'version' => '2006-03-01',
101 'region' => $region,
102 'use_aws_shared_config_files' => false,
103 'credentials' => [
104 'key' => $access_key,
105 'secret' => $secret_key,
106 ],
107 ]);
108
109 $result = [
110 'success' => false,
111 'code' => 200,
112 'message' => esc_html__('Please check the authorization details', 'media-cloud-sync'),
113 ];
114
115 try {
116 $s3Client->listObjectsV2([
117 'Bucket' => $this->token . '_dummy-bucket-for-auth-check'
118 ]);
119
120 // If we reach here, the credentials are valid
121 $result = [
122 'success' => true,
123 'code' => 200,
124 'message' => esc_html__('Credentials are valid', 'media-cloud-sync'),
125 ];
126 } catch (AwsException $e) {
127 $code = $e->getAwsErrorCode();
128
129 $validErrors = [
130 'AccessDenied',
131 'NoSuchBucket',
132 'AllAccessDisabled',
133 'AuthorizationHeaderMalformed',
134 'PermanentRedirect',
135 'InvalidBucketName',
136 ];
137
138 if (in_array($code, $validErrors)) {
139 // If we reach here, the credentials are valid
140 $result = [
141 'success' => true,
142 'code' => 200,
143 'message' => esc_html__('Credentials are valid', 'media-cloud-sync'),
144 ];
145 }
146 }
147
148 if($result['success'] == false) {
149 return $result;
150 }
151
152 try {
153 $buckets = $s3Client->listBuckets();
154 $newBucketFormat = [];
155 if(isset($buckets['Buckets']) && !empty($buckets['Buckets'])){
156 foreach($buckets['Buckets'] as $bucket) {
157 if(isset($bucket['Name'])) {
158 $newBucketFormat[] = ['Name' => $bucket['Name'], 'CreationDate' => $bucket['CreationDate']];
159 }
160 }
161 }
162 $result['buckets_data']['buckets'] = $newBucketFormat;
163 $result['buckets_data']['message'] = esc_html__('Buckets listed successfully', 'media-cloud-sync');
164 $result['buckets_data']['status'] = true;
165 } catch (S3Exception $e) {
166 $result ['buckets_data']['buckets'] = [];
167 $result ['buckets_data']['message'] = esc_html__('Unable to list buckets, Please check the bucket listing permission', 'media-cloud-sync');
168 $result ['buckets_data']['status'] = false;
169 } catch (Exception $e) {
170 $result ['buckets_data']['buckets'] = [];
171 $result ['buckets_data']['message'] = esc_html__('Unable to list buckets, Please check the bucket listing permission', 'media-cloud-sync');
172 $result ['buckets_data']['status'] = false;
173 }
174 return $result;
175 }
176 catch (S3Exception $ex) {
177 return array('message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false);
178 } catch (Exception $ex) {
179 return array('message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false);
180 }
181 }
182 return array('message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync'), 'code' => 200, 'success' => false);
183 }
184
185 /**
186 * Verify Bucket Exist
187 * @since 1.0.0
188 * @return boolean
189 */
190 public function verifyBucketExist( $config = [], $bucketConfig = [] ) {
191 $region = isset($config['region']) ? $config['region'] : '';
192 $access_key = isset($config['access_key']) ? $config['access_key'] : '';
193 $secret_key = isset($config['secret_key']) ? $config['secret_key'] : '';
194 $bucket_name = isset($bucketConfig['bucket_name']) ? $bucketConfig['bucket_name'] : '';
195 $transfer_acceleration = isset($bucketConfig['transfer_acceleration']) ? $bucketConfig['transfer_acceleration'] : false;
196
197 if (!Service::has_missing_fields([$region, $access_key, $secret_key, $bucket_name])) {
198 try {
199 $s3Client = new S3Client([
200 'version' => '2006-03-01',
201 'region' => $region,
202 'use_accelerate_endpoint' => $transfer_acceleration,
203 'use_aws_shared_config_files' => false,
204 'credentials' => [
205 'key' => $access_key,
206 'secret' => $secret_key,
207 ],
208 ]);
209
210 //get S3 object
211 $bucket_found = false;
212 try {
213 $s3Client->getObject([
214 'Bucket' => $bucket_name,
215 'Key' => $this->token . '_dummy-object-for-bucket-exist-check'
216 ]);
217 $bucket_found = true;
218 } catch (AwsException $e) {
219 $code = $e->getAwsErrorCode();
220 if ($code === 'NoSuchKey') {
221 $bucket_found = true;
222 }
223 }
224
225 if($bucket_found) {
226 return array('message' => esc_html__('Bucket exist', 'media-cloud-sync'), 'code' => 200, 'success' => true);
227 } else {
228 return array('message' => esc_html__("Bucket choosen does not exist / does not have read permission", 'media-cloud-sync'), 'code' => 200, 'success' => false);
229 }
230 }
231 catch (S3Exception $ex) {
232 return array('message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false);
233 } catch (Exception $ex) {
234 return array('message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false);
235 }
236 }
237 return array('message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync'), 'code' => 200, 'success' => false);
238 }
239
240 /**
241 * Create Bucket
242 * @since 1.0.0
243 * @return boolean
244 */
245 public function createBucket( $config = [], $bucketConfig = [] ) {
246 $region = isset($config['region']) ? $config['region'] : '';
247 $access_key = isset($config['access_key']) ? $config['access_key'] : '';
248 $secret_key = isset($config['secret_key']) ? $config['secret_key'] : '';
249 $bucket_name = isset($bucketConfig['bucket_name']) ? $bucketConfig['bucket_name'] : '';
250 $transfer_acceleration = isset($bucketConfig['transfer_acceleration']) ? $bucketConfig['transfer_acceleration'] : false;
251
252 if (Service::has_missing_fields([$region, $access_key, $secret_key, $bucket_name])) {
253 return ['message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync'), 'code' => 200, 'success' => false];
254 }
255
256 try {
257 $s3ClientConfig = [
258 'version' => '2006-03-01',
259 'region' => $region,
260 'use_aws_shared_config_files' => false,
261 'credentials' => [
262 'key' => $access_key,
263 'secret' => $secret_key,
264 ],
265 ];
266
267 $s3Client = new S3Client($s3ClientConfig);
268
269 // Create Bucket
270 if ($region === 'us-east-1') {
271 $s3Client->createBucket([
272 'Bucket' => $bucket_name,
273 ]);
274 } else {
275 $s3Client->createBucket([
276 'Bucket' => $bucket_name,
277 'CreateBucketConfiguration' => [
278 'LocationConstraint' => $region,
279 ],
280 ]);
281 }
282
283 try {
284 $this->blockPublicAccess($bucket_name, false, $s3Client);
285 $this->putBucketPolicy($bucket_name, $s3Client);
286 $this->changeBucketOwnership($bucket_name, $s3Client);
287 try {
288
289 $this->changeTransferAccilaration($bucket_name, $s3Client, $transfer_acceleration);
290
291 return [
292 'message' => esc_html__('Bucket created successfully. Choose bucket from list to select the bucket.', 'media-cloud-sync'),
293 'data' => [
294 'Name' => $bucket_name,
295 'CreationDate' => date('Y-m-d\TH:i:s\Z'),
296 ],
297 'code' => 200,
298 'success' => true,
299 ];
300 } catch (AwsException $ex) {
301 return ['message' => esc_html__('Bucket created and made public. But the following error happened while setting the transfer accilaration,', 'media-cloud-sync') . ' ' . $ex->getAwsErrorMessage(), 'code' => 200, 'success' => false];
302 }
303 } catch (AwsException $ex) {
304 return ['message' => esc_html__('Bucket created. But the following error happened while setting the public access,', 'media-cloud-sync') . ' ' . $ex->getAwsErrorMessage(), 'code' => 200, 'success' => false];
305 }
306 } catch (AwsException $ex) {
307 return ['message' => $ex->getAwsErrorMessage(), 'code' => 200, 'success' => false];
308 } catch (S3Exception $ex) {
309 return ['message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
310 } catch (Exception $ex) {
311 return ['message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
312 }
313 }
314
315 /**
316 * Check Bucket Write Permission
317 * @since 1.0.0
318 */
319 public function verifyObjectWritePermission( $config = [], $bucketConfig = [] ) {
320 $region = isset($config['region']) ? $config['region'] : '';
321 $access_key = isset($config['access_key']) ? $config['access_key'] : '';
322 $secret_key = isset($config['secret_key']) ? $config['secret_key'] : '';
323 $bucket_name = isset($bucketConfig['bucket_name']) ? $bucketConfig['bucket_name'] : '';
324 $transfer_acceleration = isset($bucketConfig['transfer_acceleration']) ? $bucketConfig['transfer_acceleration'] : false;
325
326 if (Service::has_missing_fields([$region, $access_key, $secret_key, $bucket_name])) {
327 return ['message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync'), 'code' => 200, 'success' => false];
328 }
329
330 try {
331 $s3ClientConfig = [
332 'version' => '2006-03-01',
333 'region' => $region,
334 'use_accelerate_endpoint' => $transfer_acceleration,
335 'use_aws_shared_config_files' => false,
336 'credentials' => [
337 'key' => $access_key,
338 'secret' => $secret_key,
339 ],
340 ];
341
342 $s3Client = new S3Client($s3ClientConfig);
343
344 $object_key = Utils::get_permission_check_object_key();
345
346
347 // Create a dummy object to check write permission
348 $s3Client->putObject([
349 'Bucket' => $bucket_name,
350 'Key' => $object_key,
351 'Body' => 'This is a test object to check write permission.',
352 ]);
353 // Check if the object was created successfully
354 if ($this->exists($object_key, $bucket_name, $s3Client)) {
355 return ['message' => esc_html__('Bucket write permission verified successfully', 'media-cloud-sync'), 'code' => 200, 'success' => true];
356 } else {
357 return ['message' => esc_html__('Bucket write permission not verified', 'media-cloud-sync'), 'code' => 200, 'success' => false];
358 }
359
360 } catch (AwsException $ex) {
361 return ['message' => $ex->getAwsErrorMessage(), 'code' => 200, 'success' => false];
362 } catch (S3Exception $ex) {
363 return ['message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
364 } catch (Exception $ex) {
365 return ['message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
366 }
367 }
368
369
370
371 /**
372 * Check Bucket Delete Permission
373 * @since 1.0.0
374 */
375 public function verifyObjectDeletePermission( $config = [], $bucketConfig = [] ) {
376 $region = isset($config['region']) ? $config['region'] : '';
377 $access_key = isset($config['access_key']) ? $config['access_key'] : '';
378 $secret_key = isset($config['secret_key']) ? $config['secret_key'] : '';
379 $bucket_name = isset($bucketConfig['bucket_name']) ? $bucketConfig['bucket_name'] : '';
380 $transfer_acceleration = isset($bucketConfig['transfer_acceleration']) ? $bucketConfig['transfer_acceleration'] : false;
381
382 if (Service::has_missing_fields([$region, $access_key, $secret_key, $bucket_name])) {
383 return ['message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync'), 'code' => 200, 'success' => false];
384 }
385
386 try {
387 $s3ClientConfig = [
388 'version' => '2006-03-01',
389 'region' => $region,
390 'use_accelerate_endpoint' => $transfer_acceleration,
391 'use_aws_shared_config_files' => false,
392 'credentials' => [
393 'key' => $access_key,
394 'secret' => $secret_key,
395 ],
396 ];
397
398 $s3Client = new S3Client($s3ClientConfig);
399
400 $object_key = Utils::get_permission_check_object_key();
401
402 // Create a dummy object to check dlete permission
403 $s3Client->deleteObject([
404 'Bucket' => $bucket_name,
405 'Key' => $object_key,
406 ]);
407
408 // Check if the object was created successfully
409 if (!$this->exists($object_key, $bucket_name, $s3Client)) {
410 return ['message' => esc_html__('Bucket delete permission verified successfully', 'media-cloud-sync'), 'code' => 200, 'success' => true];
411 } else {
412 return ['message' => esc_html__('Bucket delete permission not verified', 'media-cloud-sync'), 'code' => 200, 'success' => false];
413 }
414
415 } catch (AwsException $ex) {
416 return ['message' => $ex->getAwsErrorMessage(), 'code' => 200, 'success' => false];
417 } catch (S3Exception $ex) {
418 return ['message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
419 } catch (Exception $ex) {
420 return ['message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
421 }
422 }
423
424
425 /**
426 * Check Bucket Read Permission
427 * @since 1.2.4
428 */
429 public function verifyObjectReadPermission() {
430 $result = [
431 'status' => false,
432 'message' => '',
433 'lastChecked' => time(),
434 ];
435
436 if (Service::has_missing_fields([$this->s3Client, $this->bucket_name])) {
437 $result['message'] = esc_html__('Invalid Request', 'media-cloud-sync');
438 return ['message' => esc_html__('Invalid Request', 'media-cloud-sync'), 'code' => 200, 'success' => false, 'lastChecked' => time()];
439 }
440
441 try {
442 $object_key = Utils::get_permission_check_object_key();
443
444 // Check if the object was created successfully
445 if (!$this->exists($object_key)) {
446 // Create a dummy object to check write permission
447 $this->s3Client->putObject([
448 'Bucket' => $this->bucket_name,
449 'Key' => $object_key,
450 'Body' => 'This is a test object to check permission.',
451 'ContentType' => 'text/plain',
452 'CacheControl' => 'no-cache, no-store, must-revalidate',
453 ]);
454 }
455
456
457 $url = $this->generate_file_url($object_key);
458 $cdn_url = Cdn::may_generate_cdn_url($url, $object_key);
459 // Never trust a cached response for this fixed, predictable URL — a stale cached
460 // error would otherwise keep failing the check long after real access is fine.
461 $no_cache_context = stream_context_create(['http' => ['header' => "Cache-Control: no-cache\r\nPragma: no-cache\r\n"]]);
462 $headers = @get_headers($cdn_url, false, $no_cache_context);
463 $status_code = (is_array($headers) && !empty($headers[0]) && preg_match('/\s(\d{3})\s/', $headers[0], $matches))
464 ? (int) $matches[1]
465 : 0;
466
467 if ($status_code === 200) {
468 $result['status'] = true;
469 $result['message'] = esc_html__('Objects are accessible to Read', 'media-cloud-sync');
470 } else if ($status_code === 403) {
471 $result['status'] = false;
472 if(isset($this->cdnConfig['service']) && $this->cdnConfig['service'] == $this->service) {
473 $result['message'] = esc_html__('Access Denied. Please check your bucket policy. Public Read Access is required.', 'media-cloud-sync');
474 } else {
475 $result['message'] = esc_html__('Access Denied. Please check your bucket policy', 'media-cloud-sync');
476 }
477 } else if ($status_code === 404) {
478 $result['status'] = false;
479 $result['message'] = esc_html__('Object not found. Please check your bucket policy', 'media-cloud-sync');
480 } else if ($status_code === 500) {
481 $result['status'] = false;
482 $result['message'] = esc_html__('Internal Server error. Please check your bucket policy', 'media-cloud-sync');
483 } else {
484 $result['status'] = false;
485 $result['message'] = esc_html__('Objects are not accessible to read', 'media-cloud-sync');
486 }
487
488 $this->deleteSingle($object_key);
489 return [
490 'message' => $result['message'],
491 'code' => 200,
492 'success' => $result['status'],
493 'lastChecked' => $result['lastChecked'],
494 ];
495 } catch (AwsException $ex) {
496 $result['message'] = $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync');
497 return ['message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false, 'lastChecked' => time()];
498 } catch (S3Exception $ex) {
499 $result['message'] = $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync');
500 return ['message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false, 'lastChecked' => time()];
501 } catch (Exception $ex) {
502 $result['message'] = $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync');
503 return ['message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false, 'lastChecked' => time()];
504 }
505 }
506
507
508 /**
509 * get Bucket Security Settings
510 */
511 public function getBucketSecuritySettings( $config = [], $bucketConfig = [] ) {
512 $region = isset($config['region']) ? $config['region'] : '';
513 $access_key = isset($config['access_key']) ? $config['access_key'] : '';
514 $secret_key = isset($config['secret_key']) ? $config['secret_key'] : '';
515 $bucket_name = isset($bucketConfig['bucket_name']) ? $bucketConfig['bucket_name'] : '';
516 $transfer_acceleration = isset($bucketConfig['transfer_acceleration']) ? $bucketConfig['transfer_acceleration'] : false;
517
518 if (empty($region) || empty($access_key) || empty($secret_key) || empty($bucket_name)) {
519 return ['message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync'), 'code' => 200, 'success' => false];
520 }
521
522 $errors = [];
523 try {
524 $s3ClientConfig = [
525 'version' => '2006-03-01',
526 'region' => $region,
527 'use_accelerate_endpoint' => $transfer_acceleration,
528 'use_aws_shared_config_files' => false,
529 'credentials' => [
530 'key' => $access_key,
531 'secret' => $secret_key,
532 ],
533 ];
534
535 $s3Client = new S3Client($s3ClientConfig);
536
537 // Check public access block configuration
538 $security['block_public_access'] = false;
539 try {
540 $publicAccessBlock = $s3Client->getPublicAccessBlock([
541 'Bucket' => $bucket_name,
542 ]);
543
544 $publicAccessBlockConfig = $publicAccessBlock['PublicAccessBlockConfiguration'];
545 if (
546 $publicAccessBlockConfig['BlockPublicAcls'] &&
547 $publicAccessBlockConfig['IgnorePublicAcls'] &&
548 $publicAccessBlockConfig['BlockPublicPolicy'] &&
549 $publicAccessBlockConfig['RestrictPublicBuckets']
550 ) {
551 $security['block_public_access'] = true;
552 }
553 } catch (S3Exception $ex) {
554 // If the bucket does not have public access block configuration, we assume it is not blocked
555 $errors['block_public_access'] = $ex->getMessage();
556 } catch (Exception $ex) {
557 $errors['block_public_access'] = $ex->getMessage();
558 }
559
560
561 $security['object_ownership_enforced'] = false;
562 try {
563 $ownershipControls = $s3Client->getBucketOwnershipControls([
564 'Bucket' => $bucket_name,
565 ]);
566
567 $ownershipRule = $ownershipControls['OwnershipControls']['Rules'][0]['ObjectOwnership'];
568
569 if ($ownershipRule === 'BucketOwnerEnforced') {
570 $security['object_ownership_enforced'] = true;
571 }
572 } catch (S3Exception $ex) {
573 $errors['object_ownership_enforced'] = $ex->getMessage();
574 } catch (Exception $ex) {
575 $errors['object_ownership_enforced'] = $ex->getMessage();
576 }
577
578 return ['message' => '', 'code' => 200, 'success' => empty($errors), 'security' => $security, 'errors' => $errors];
579 } catch (AwsException $ex) {
580 return ['message' => $ex->getAwsErrorMessage(), 'code' => 200, 'success' => false];
581 } catch (S3Exception $ex) {
582 return ['message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
583 } catch (Exception $ex) {
584 return ['message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
585 }
586 }
587
588
589
590 /**
591 * Change Bucket Public Access
592 */
593
594 public function changePublicAccess( $config = [], $bucketConfig = [], $value = false ) {
595 $region = isset($config['region']) ? $config['region'] : '';
596 $access_key = isset($config['access_key']) ? $config['access_key'] : '';
597 $secret_key = isset($config['secret_key']) ? $config['secret_key'] : '';
598 $bucket_name = isset($bucketConfig['bucket_name']) ? $bucketConfig['bucket_name'] : '';
599 $transfer_acceleration = isset($bucketConfig['transfer_acceleration']) ? $bucketConfig['transfer_acceleration'] : false;
600
601 if (empty($region) || empty($access_key) || empty($secret_key) || empty($bucket_name)) {
602 return ['message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync'), 'code' => 200, 'success' => false];
603 }
604
605 try {
606 $s3ClientConfig = [
607 'version' => '2006-03-01',
608 'region' => $region,
609 'use_accelerate_endpoint' => $transfer_acceleration,
610 'use_aws_shared_config_files' => false,
611 'credentials' => [
612 'key' => $access_key,
613 'secret' => $secret_key,
614 ],
615 ];
616
617 $s3Client = new S3Client($s3ClientConfig);
618
619 try {
620 $result = $this->blockPublicAccess($bucket_name, $value, $s3Client);
621 return ['message' => '', 'code' => 200, 'success' => true, 'result' => $result];
622 } catch (AwsException $ex) {
623 return ['message' => $ex->getAwsErrorMessage(), 'code' => 200, 'success' => false];
624 } catch (S3Exception $ex) {
625 return ['message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
626 } catch (Exception $ex) {
627 return ['message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
628 }
629 } catch (AwsException $ex) {
630 return ['message' => $ex->getAwsErrorMessage(), 'code' => 200, 'success' => false];
631 } catch (S3Exception $ex) {
632 return ['message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
633 } catch (Exception $ex) {
634 return ['message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
635 }
636 }
637
638
639
640 /**
641 * Change Bucket Ownership
642 */
643
644 public function changeObjectOwnership( $config = [], $bucketConfig = [], $value = false ) {
645 $region = isset($config['region']) ? $config['region'] : '';
646 $access_key = isset($config['access_key']) ? $config['access_key'] : '';
647 $secret_key = isset($config['secret_key']) ? $config['secret_key'] : '';
648 $bucket_name = isset($bucketConfig['bucket_name']) ? $bucketConfig['bucket_name'] : '';
649 $transfer_acceleration = isset($bucketConfig['transfer_acceleration']) ? $bucketConfig['transfer_acceleration'] : false;
650
651 if (empty($region) || empty($access_key) || empty($secret_key) || empty($bucket_name)) {
652 return ['message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync'), 'code' => 200, 'success' => false];
653 }
654
655 try {
656 $s3ClientConfig = [
657 'version' => '2006-03-01',
658 'region' => $region,
659 'use_accelerate_endpoint' => $transfer_acceleration,
660 'use_aws_shared_config_files' => false,
661 'credentials' => [
662 'key' => $access_key,
663 'secret' => $secret_key,
664 ],
665 ];
666
667 $s3Client = new S3Client($s3ClientConfig);
668
669 $ownership = $value ? 'BucketOwnerEnforced' : 'BucketOwnerPreferred';
670
671 try {
672 $result = $this->changeBucketOwnership( $bucket_name, $s3Client, $ownership );
673 return ['message' => '', 'code' => 200, 'success' => true, 'result' => $result];
674 } catch (AwsException $ex) {
675 return ['message' => $ex->getAwsErrorMessage(), 'code' => 200, 'success' => false];
676 } catch (S3Exception $ex) {
677 return ['message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
678 } catch (Exception $ex) {
679 return ['message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
680 }
681
682 }
683 catch (AwsException $ex) {
684 return ['message' => $ex->getAwsErrorMessage(), 'code' => 200, 'success' => false];
685 } catch (S3Exception $ex) {
686 return ['message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
687 } catch (Exception $ex) {
688 return ['message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
689 }
690 }
691
692
693
694
695 /**
696 * Block Public Access
697 * @since 1.0.0
698 */
699 private function blockPublicAccess($bucket, $block = true, $s3Client = false) {
700 if($s3Client == false) {
701 $s3Client = $this->s3Client;
702 }
703
704 if(empty($bucket)) return false;
705
706 try {
707 $s3Client->putPublicAccessBlock([
708 'Bucket' => $bucket,
709 'PublicAccessBlockConfiguration' => [
710 'BlockPublicPolicy' => $block,
711 'BlockPublicAcls' => $block,
712 'IgnorePublicAcls' => $block,
713 'RestrictPublicBuckets' => $block,
714 ]
715 ]);
716
717 return true;
718 } catch (AwsException $ex) {
719 return false;
720 }
721 }
722
723 /**
724 * Add Bucket Policy
725 */
726 private function putBucketPolicy($bucket, $s3Client = false) {
727 if($s3Client == false) {
728 $s3Client = $this->s3Client;
729 }
730
731 if(empty($bucket)) return false;
732
733 $policy = json_encode([
734 "Version" => "2012-10-17",
735 "Statement" => [
736 [
737 "Effect" => "Allow",
738 "Principal" => "*",
739 "Action" => [
740 "s3:DeleteObjectTagging",
741 "s3:ListBucketMultipartUploads",
742 "s3:DeleteObjectVersion",
743 "s3:ListBucket",
744 "s3:DeleteObjectVersionTagging",
745 "s3:GetBucketAcl",
746 "s3:ListMultipartUploadParts",
747 "s3:PutObject",
748 "s3:GetObjectAcl",
749 "s3:GetObject",
750 "s3:AbortMultipartUpload",
751 "s3:DeleteObject",
752 "s3:GetBucketLocation",
753 "s3:PutObjectAcl",
754 "s3:putBucketOwnershipControls",
755 "s3:putBucketPolicy"
756 ],
757 "Resource" => [
758 "arn:aws:s3:::$bucket/*",
759 "arn:aws:s3:::$bucket"
760 ]
761 ]
762 ]
763 ]);
764
765 try {
766 // Add bucket policy
767 $s3Client->putBucketPolicy(['Bucket' => $bucket, 'Policy' => $policy]);
768
769 return true;
770 } catch (AwsException $ex) {
771 return false;
772 } catch (S3Exception $ex) {
773 return false;
774 } catch (Exception $ex) {
775 return false;
776 }
777 }
778
779 /**
780 * Add Bucket Ownership
781 */
782 private function changeBucketOwnership($bucket, $s3Client = false, $ownership = 'BucketOwnerPreferred') {
783 if($s3Client == false) {
784 $s3Client = $this->s3Client;
785 }
786
787 if(empty($bucket)) return false;
788
789 try {
790 // Change object ownership ACL enabled
791 $s3Client->putBucketOwnershipControls([
792 'Bucket' => $bucket,
793 'OwnershipControls' => [
794 'Rules' => [['ObjectOwnership' => $ownership]],
795 ],
796 ]);
797
798 return true;
799 } catch (AwsException $ex) {
800 return false;
801 } catch (S3Exception $ex) {
802 return false;
803 } catch (Exception $ex) {
804 return false;
805 }
806 }
807
808 /**
809 * Change transfer accilaration
810 */
811 private function changeTransferAccilaration($bucket, $s3Client = false, $enable=false, $force = false) {
812 if($s3Client == false) {
813 $s3Client = $this->s3Client;
814 }
815
816 if(empty($bucket)) return false;
817 if(!$force && !$enable) return true;
818
819 // Check if the bucket already has transfer acceleration enabled
820 try {
821 $s3Client->putBucketAccelerateConfiguration([
822 'Bucket' => $bucket,
823 'AccelerateConfiguration' => [
824 'Status' => $enable ? 'Enabled' : 'Suspended'
825 ]
826 ]);
827 return true;
828 } catch (AwsException $ex) {
829 return false;
830 } catch (S3Exception $ex) {
831 return false;
832 } catch (Exception $ex) {
833 return false;
834 }
835 }
836
837 /**
838 * isConfigured Function To Identify the congfigurations are correct
839 * @since 1.0.0
840 */
841 public function isConfigured(){
842 if ($this->s3Client) {
843 try {
844 $this->s3Client->listObjectsV2([
845 'Bucket' => $this->token . '_dummy-bucket-for-auth-check'
846 ]);
847
848 // If we reach here, the credentials are valid
849 return true;
850 } catch (AwsException $ex) {
851 $code = $ex->getAwsErrorCode();
852
853 $validErrors = [
854 'AccessDenied',
855 'NoSuchBucket',
856 'AllAccessDisabled',
857 'AuthorizationHeaderMalformed',
858 'PermanentRedirect',
859 'InvalidBucketName'
860 ];
861
862 if (in_array($code, $validErrors)) {
863 // If we reach here, the credentials are valid
864 return true;
865 } else {
866 return false;
867 }
868 } catch (S3Exception $ex) {
869 return false;
870 } catch (Exception $ex) {
871 return false;
872 }
873 }
874 return false;
875 }
876
877 /**
878 * Make Object Private
879 * @since 1.0.0
880 *
881 */
882 public function toPrivate($key) {
883 if(!$key) return false;
884 if(!$this->s3Client) return false;
885 try {
886 $this->s3Client->putObjectAcl([
887 'Bucket' => $this->bucket_name,
888 'Key' => $key,
889 'ACL' => 'private'
890 ]);
891 return true;
892 } catch (AwsException $ex) {
893 return false;
894 }
895 }
896
897
898
899 /**
900 * Make Object Public
901 * @since 1.0.0
902 *
903 */
904 public function toPublic($key) {
905 if(!$key) return false;
906 if(!$this->s3Client) return false;
907 try {
908 $this->s3Client->putObjectAcl([
909 'Bucket' => $this->bucket_name,
910 'Key' => $key,
911 'ACL' => 'public-read'
912 ]);
913 return true;
914 } catch (AwsException $ex) {
915 return false;
916 }
917 }
918
919
920
921 /**
922 * Check the object exist
923 * @since 1.1.8
924 */
925 public function exists($key, $bucket_name = '', $client = null) {
926 if(!$key) return false;
927 try {
928 $bucket_name = $bucket_name ? $bucket_name : $this->bucket_name;
929 $client = $client ?? $this->s3Client;
930 if($client->doesObjectExistV2( $bucket_name, $key)) {
931 return true;
932 }
933 return false;
934 } catch (AwsException $ex) {
935 return false;
936 } catch (S3Exception $ex) {
937 return false;
938 } catch (Exception $ex) {
939 return false;
940 }
941 }
942
943 /**
944 * List Objects — $delimiter = null gives a flat/recursive listing instead of one folder level.
945 * @since 1.3.13
946 */
947 public function listObjects($prefix = '', $continuationToken = null, $maxKeys = 1000, $delimiter = '/') {
948 if (!$this->s3Client) {
949 return ['success' => false, 'code' => 200, 'message' => esc_html__('Client not configured', 'media-cloud-sync'), 'folders' => [], 'objects' => [], 'next_token' => null];
950 }
951 try {
952 $params = ['Bucket' => $this->bucket_name, 'MaxKeys' => $maxKeys];
953 if (!empty($delimiter)) {
954 $params['Delimiter'] = $delimiter;
955 }
956 if (!empty($prefix)) {
957 $params['Prefix'] = $prefix;
958 }
959 if (!empty($continuationToken)) {
960 $params['ContinuationToken'] = $continuationToken;
961 }
962
963 $result = $this->s3Client->listObjectsV2($params);
964 $folders = [];
965 foreach (($result['CommonPrefixes'] ?? []) as $common) {
966 $folders[] = $common['Prefix'];
967 }
968 $objects = [];
969 foreach (($result['Contents'] ?? []) as $object) {
970 if ($object['Key'] === $prefix) {
971 continue; // the folder placeholder object itself, not a file
972 }
973 $objects[] = [
974 'key' => $object['Key'],
975 'size' => (int) $object['Size'],
976 'last_modified' => $object['LastModified'] ? $object['LastModified']->format(DATE_ATOM) : '',
977 ];
978 }
979
980 return [
981 'success' => true,
982 'code' => 200,
983 'message' => '',
984 'folders' => $folders,
985 'objects' => $objects,
986 'next_token' => !empty($result['IsTruncated']) ? ($result['NextContinuationToken'] ?? null) : null,
987 ];
988 } catch (AwsException $e) {
989 return ['success' => false, 'code' => 200, 'message' => $e->getMessage(), 'folders' => [], 'objects' => [], 'next_token' => null];
990 } catch (Exception $e) {
991 return ['success' => false, 'code' => 200, 'message' => $e->getMessage(), 'folders' => [], 'objects' => [], 'next_token' => null];
992 }
993 }
994
995 /**
996 * Upload Single
997 * @since 1.0.0
998 * @return boolean
999 */
1000 public function uploadSingle($absolute_source_path, $relative_source_path, $prefix='') {
1001 if (
1002 isset($absolute_source_path) && !empty($absolute_source_path) &&
1003 isset($relative_source_path) && !empty($relative_source_path)
1004 ) {
1005 $file_name = wp_basename( $relative_source_path );
1006 if ($file_name) {
1007 $upload_path = Utils::generate_object_key($relative_source_path, $prefix);
1008 return $this->execute_upload($absolute_source_path, $upload_path);
1009 }
1010 return [
1011 'success' => false,
1012 'code' => 200,
1013 'message' => esc_html__('Check the file you are trying to upload. Please try again', 'media-cloud-sync')
1014 ];
1015 }
1016 return [
1017 'success' => false,
1018 'code' => 200,
1019 'message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync')
1020 ];
1021 }
1022
1023 /**
1024 * Upload a local file to an exact destination key (no Utils::generate_object_key() derivation).
1025 * @since 1.4.0
1026 */
1027 public function uploadObjectAtKey($absolute_source_path, $key) {
1028 return $this->execute_upload($absolute_source_path, $key);
1029 }
1030
1031 /**
1032 * Build an unexecuted ObjectUploader (single PUT or multipart, decided internally by the
1033 * SDK, using this plugin's own multipart threshold rather than the SDK's 16MB default).
1034 * ACL is stripped via before_* hooks — this plugin's model is bucket-level, not per-object,
1035 * and an explicit `ACL: null` still serializes to an empty x-amz-acl header otherwise.
1036 * $options is threaded straight into the SDK (e.g. 'state' => UploadState to resume a
1037 * previously-failed multipart attempt).
1038 * @since 1.4.0
1039 */
1040 private function build_object_uploader($absolute_source_path, $key, $options = []) {
1041 $handle = fopen($absolute_source_path, 'rb');
1042 $params = [];
1043 $cache_control = Utils::get_cache_control_header();
1044 if ($cache_control) {
1045 $params['CacheControl'] = $cache_control;
1046 }
1047 $options += [
1048 'mup_threshold' => Schema::getConstant('S3_MULTIPART_MIN_FILE_SIZE'),
1049 'params' => $params,
1050 'before_initiate' => function ($params) { return $this->strip_acl($params); },
1051 'before_upload' => function ($params) { return $this->strip_acl($params); },
1052 'before_complete' => function ($params) { return $this->strip_acl($params); },
1053 ];
1054 return new ObjectUploader($this->s3Client, $this->bucket_name, $key, $handle, null, $options);
1055 }
1056
1057 // Mutate in place, not a clone — the SDK's before_* hooks call this and discard the
1058 // return value, relying on the same Command object being modified.
1059 private function strip_acl($params) {
1060 if ($params instanceof Command && $params->hasParam('ACL')) {
1061 unset($params['ACL']);
1062 } elseif (is_array($params) && isset($params['ACL'])) {
1063 unset($params['ACL']);
1064 }
1065 return $params;
1066 }
1067
1068 /**
1069 * Run an ObjectUploader synchronously and normalize the result shape. Retries up to
1070 * 3 attempts on MultipartUploadException, resuming from the failed attempt's saved
1071 * state rather than restarting the whole upload — same retry contract uploadSingle()
1072 * had before the ObjectUploader swap.
1073 * @since 1.4.0
1074 */
1075 private function execute_upload($absolute_source_path, $key) {
1076 $max_attempts = 3;
1077 $attempt = 0;
1078 $options = [];
1079
1080 while (true) {
1081 $attempt++;
1082 try {
1083 $this->build_object_uploader($absolute_source_path, $key, $options)->upload();
1084 return [
1085 'success' => true,
1086 'code' => 200,
1087 'file_url' => $this->generate_file_url($key),
1088 'key' => $key,
1089 'message' => esc_html__('File Uploaded Successfully', 'media-cloud-sync')
1090 ];
1091 } catch (MultipartUploadException $e) {
1092 if ($attempt >= $max_attempts) {
1093 return [
1094 'success' => false,
1095 'code' => 200,
1096 'message' => $e->getMessage()
1097 ];
1098 }
1099 $options = ['state' => $e->getState()];
1100 } catch (AwsException $e) {
1101 return [
1102 'success' => false,
1103 'code' => 200,
1104 'message' => $e->getMessage()
1105 ];
1106 } catch (Exception $e) {
1107 return [
1108 'success' => false,
1109 'code' => 200,
1110 'message' => $e->getMessage()
1111 ];
1112 }
1113 }
1114 }
1115
1116 /**
1117 * Save object to server
1118 * @since 1.0.0
1119 */
1120 public function object_to_server($key, $save_path) {
1121 if(!$this->s3Client) return false;
1122 try {
1123 $getObject = $this->s3Client->GetObject([
1124 'Bucket' => $this->bucket_name,
1125 'Key' => $key,
1126 'SaveAs' => $save_path
1127 ]);
1128 if (file_exists($save_path)) {
1129 return true;
1130 }
1131 } catch (AwsException $e) {
1132 return false;
1133 }
1134 return false;
1135 }
1136
1137 /**
1138 * Object bytes in memory, no local file — for callers (e.g. zip download) that need
1139 * the content itself rather than a copy on the server's filesystem.
1140 * @since 1.3.13
1141 */
1142 public function get_object_content($key) {
1143 if(!$this->s3Client) return false;
1144 try {
1145 $result = $this->s3Client->GetObject([
1146 'Bucket' => $this->bucket_name,
1147 'Key' => $key,
1148 ]);
1149 return (string) $result['Body'];
1150 } catch (AwsException $e) {
1151 return false;
1152 }
1153 }
1154
1155 /**
1156 * Deletes the live object, then best-effort purges every historical version too — a
1157 * plain deleteSingle() on a versioned bucket only adds a delete marker, leaving prior
1158 * versions (and the storage they use) behind at the old key. The live delete happens
1159 * unconditionally first: not every S3-compatible endpoint supports ListObjectVersions
1160 * (confirmed missing on Cloudflare R2, a live 501 "NotImplemented"), and the object must
1161 * still end up gone either way.
1162 * @since 1.3.14
1163 */
1164 public function purge_all_versions($key) {
1165 if (!$this->s3Client) {
1166 return ['success' => false, 'code' => 200, 'message' => esc_html__('Client not configured', 'media-cloud-sync')];
1167 }
1168
1169 try {
1170 $this->s3Client->deleteObject([
1171 'Bucket' => $this->bucket_name,
1172 'Key' => $key,
1173 ]);
1174 } catch (AwsException $e) {
1175 return ['success' => false, 'code' => 200, 'message' => $e->getMessage()];
1176 }
1177
1178 // Best-effort only from here — providers that don't support version listing simply
1179 // skip this part; the live object above is already gone regardless.
1180 try {
1181 $objects = [];
1182 $marker = null;
1183 do {
1184 $args = ['Bucket' => $this->bucket_name, 'Prefix' => $key];
1185 if ($marker) {
1186 $args['KeyMarker'] = $marker['key'];
1187 $args['VersionIdMarker'] = $marker['version'];
1188 }
1189 $result = $this->s3Client->listObjectVersions($args);
1190 foreach (array_merge($result['Versions'] ?? [], $result['DeleteMarkers'] ?? []) as $version) {
1191 if (($version['Key'] ?? null) === $key) {
1192 $objects[] = ['Key' => $key, 'VersionId' => $version['VersionId']];
1193 }
1194 }
1195 $marker = !empty($result['IsTruncated'])
1196 ? ['key' => $result['NextKeyMarker'], 'version' => $result['NextVersionIdMarker']]
1197 : null;
1198 } while ($marker);
1199
1200 foreach (array_chunk($objects, 1000) as $chunk) {
1201 $this->s3Client->deleteObjects([
1202 'Bucket' => $this->bucket_name,
1203 'Delete' => ['Objects' => $chunk],
1204 ]);
1205 }
1206 } catch (AwsException $e) {
1207 // Version history cleanup unsupported/failed — not fatal, live object is gone.
1208 }
1209
1210 return ['success' => true, 'code' => 200, 'message' => esc_html__('Purged Successfully', 'media-cloud-sync')];
1211 }
1212
1213
1214 /**
1215 * Copy object to new path
1216 * @since 1.3.4
1217 */
1218 // Trusts copyObject()'s own success/failure rather than pre/post-verifying with extra
1219 // exists() HEAD requests — each one is a full network round-trip, and with move/copy
1220 // processing keys sequentially, three extra round-trips per file adds up fast on a
1221 // folder with many files. copyObject() itself throws (caught below) if the source is
1222 // missing or the copy otherwise fails, so nothing is lost by not checking first.
1223 public function copy_to_new_path($key, $new_path) {
1224 if (!$this->s3Client) {
1225 return [
1226 'message' => esc_html__('Client not configured', 'media-cloud-sync'),
1227 'code' => 200,
1228 'success' => false
1229 ];
1230 }
1231 try {
1232 $this->s3Client->copyObject([
1233 'Bucket' => $this->bucket_name,
1234 'CopySource' => "{$this->bucket_name}/{$key}",
1235 'Key' => $new_path,
1236 'MetadataDirective' => 'COPY',
1237 ]);
1238 return [
1239 'success' => true,
1240 'code' => 200,
1241 'message' => esc_html__('File copied successfully', 'media-cloud-sync')
1242 ];
1243 } catch (AwsException $e) {
1244 return [
1245 'success' => false,
1246 'code' => 200,
1247 'message' => $e->getMessage()
1248 ];
1249 }
1250 }
1251
1252 // Like copy_to_new_path() but into an explicit (possibly different) bucket — needs write
1253 // access there too, so callers should fall back to download+upload on failure.
1254 public function copy_to_bucket($key, $new_key, $dest_bucket) {
1255 if (!$this->s3Client) {
1256 return [
1257 'message' => esc_html__('Client not configured', 'media-cloud-sync'),
1258 'code' => 200,
1259 'success' => false
1260 ];
1261 }
1262 try {
1263 $this->s3Client->copyObject([
1264 'Bucket' => $dest_bucket,
1265 'CopySource' => "{$this->bucket_name}/{$key}",
1266 'Key' => $new_key,
1267 'MetadataDirective' => 'COPY',
1268 ]);
1269 return [
1270 'success' => true,
1271 'code' => 200,
1272 'message' => esc_html__('File copied successfully', 'media-cloud-sync')
1273 ];
1274 } catch (AwsException $e) {
1275 return [
1276 'success' => false,
1277 'code' => 200,
1278 'message' => $e->getMessage()
1279 ];
1280 }
1281 }
1282
1283
1284 /**
1285 * Delete Single
1286 * @since 1.0.0
1287 * @return boolean
1288 */
1289 public function deleteSingle($key) {
1290 $result = array();
1291 if (!$this->s3Client) {
1292 return array(
1293 'success' => false,
1294 'code' => 200,
1295 'message' => esc_html__('Client not configured', 'media-cloud-sync')
1296 );
1297 }
1298 if (isset($key) && !empty($key)) {
1299 try {
1300 $this->s3Client->deleteObject([
1301 'Bucket' => $this->bucket_name,
1302 'Key' => $key
1303 ]);
1304
1305 if (!$this->exists($key)) {
1306 $result = array(
1307 'success' => true,
1308 'code' => 200,
1309 'message' => esc_html__('Deleted Successfully', 'media-cloud-sync')
1310 );
1311 } else {
1312 $result = array(
1313 'success' => false,
1314 'code' => 200,
1315 'message' => esc_html__('File not deleted', 'media-cloud-sync')
1316 );
1317 }
1318 } catch (AwsException $e) {
1319 $result = array(
1320 'success' => false,
1321 'code' => 200,
1322 'message' => $e->getMessage()
1323 );
1324 }
1325 } else {
1326 $result = array(
1327 'success' => false,
1328 'code' => 200,
1329 'message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync')
1330 );
1331 }
1332 return $result;
1333 }
1334
1335 /**
1336 * get private URL
1337 * @since 1.0.0
1338 * @return boolean
1339 */
1340 public function get_private_url($key) {
1341 $result = array();
1342 if (!$this->s3Client) {
1343 return array(
1344 'success' => false,
1345 'code' => 200,
1346 'message' => esc_html__('Client not configured', 'media-cloud-sync')
1347 );
1348 }
1349 if (isset($key) && !empty($key)) {
1350 try {
1351 $cmd = $this->s3Client->getCommand('GetObject', [
1352 'Bucket' => $this->bucket_name,
1353 'Key' => $key
1354 ]);
1355
1356 $expires = isset($this->settings['private_url_expire']) ? $this->settings['private_url_expire'] : 20;
1357
1358 $request = $this->s3Client->createPresignedRequest($cmd, sprintf('+%s minutes', $expires));
1359
1360 if ($privateUrl = (string)$request->getUri()) {
1361 $result = array(
1362 'success' => true,
1363 'code' => 200,
1364 'file_url' => $privateUrl,
1365 'message' => esc_html__('Got Private URL Successfully', 'media-cloud-sync')
1366 );
1367 } else {
1368 $result = array(
1369 'success' => false,
1370 'code' => 200,
1371 'message' => esc_html__('Error getting private URL', 'media-cloud-sync')
1372 );
1373 }
1374 } catch (AwsException $e) {
1375 $result = array(
1376 'success' => false,
1377 'code' => 200,
1378 'message' => $e->getMessage()
1379 );
1380 }
1381 } else {
1382 $result = array(
1383 'success' => false,
1384 'code' => 200,
1385 'message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync')
1386 );
1387 }
1388 return $result;
1389 }
1390
1391 /**
1392 * Generate file URL
1393 */
1394 public function generate_file_url($key){
1395 $domain = $this->get_domain();
1396
1397 return apply_filters('wpmcs_generate_s3_file_url',
1398 $domain . '/' . $key,
1399 $domain, $key
1400 );
1401 }
1402
1403 /**
1404 * Is Provider URL
1405 * @since 1.3.6
1406 */
1407 public function is_provider_url($url) {
1408 $domain = $this->get_domain();
1409 return (strpos($url, $domain . '/') !== false);
1410 }
1411
1412 /**
1413 * Get domain URL
1414 */
1415 public function get_domain() {
1416 $region = isset($this->config['region']) ? $this->config['region'] : '';
1417 return "https://{$this->bucket_name}.s3.{$region}.amazonaws.com";
1418 }
1419
1420 }