PluginProbe
Media Cloud Sync / 1.3.11
Media Cloud Sync v1.3.11
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 1.3.11, at includes/base/services/s3.php

1,222 lines 50.3 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 Exception;
13
14 class S3 {
15 private $assets_url;
16 private $version;
17 private $token;
18
19 protected $config;
20 protected $bucketConfig;
21 protected $settings;
22 protected $credentials;
23 protected $bucket_name;
24 protected $cdnConfig;
25
26 public $service = 's3';
27 public $s3Client = false;
28
29 /**
30 * Admin constructor.
31 * @since 1.0.0
32 */
33 public function __construct() {
34 $this->assets_url = WPMCS_ASSETS_URL;
35 $this->version = WPMCS_VERSION;
36 $this->token = WPMCS_TOKEN;
37
38 // Initialize setup
39 $this->init();
40 }
41
42 /**
43 * Initialise Client
44 */
45 public function init() {
46 $this->settings = Utils::get_settings();
47 $this->credentials = Utils::get_credentials();
48 $this->config = isset($this->credentials['config']) && !empty($this->credentials['config'])
49 ? $this->credentials['config']
50 : [];
51 $this->bucketConfig = isset($this->credentials['bucketConfig']) && !empty($this->credentials['bucketConfig'])
52 ? $this->credentials['bucketConfig']
53 : [];
54 $this->bucket_name = isset($this->bucketConfig['bucket_name']) && !empty($this->bucketConfig['bucket_name'])
55 ? $this->bucketConfig['bucket_name']
56 : '';
57 $this->cdnConfig = isset($this->credentials['cdn']) && !empty($this->credentials['cdn'])
58 ? $this->credentials['cdn']
59 : [];
60
61 if (
62 isset($this->config['region']) && !empty($this->config['region']) &&
63 isset($this->config['access_key']) && !empty($this->config['access_key']) &&
64 isset($this->config['secret_key']) && !empty($this->config['secret_key'])
65 ) {
66 $this->s3Client = new S3Client([
67 'version' => '2006-03-01',
68 'region' => $this->config['region'],
69 'use_accelerate_endpoint' => isset($this->bucketConfig['transfer_acceleration'])
70 ? $this->bucketConfig['transfer_acceleration'] : false,
71 'use_aws_shared_config_files' => false,
72 'credentials' => [
73 'key' => $this->config['access_key'],
74 'secret' => $this->config['secret_key'],
75 ],
76 ]);
77 }
78
79 }
80
81
82 /**
83 * Verify Credentials
84 * @since 1.0.0
85 * @return boolean
86 */
87 public function verifyCredentials($config = []) {
88 $region = isset($config['region']) ? $config['region'] : '';
89 $access_key = isset($config['access_key']) ? $config['access_key'] : '';
90 $secret_key = isset($config['secret_key']) ? $config['secret_key'] : '';
91
92 if (!empty($region) && !empty($access_key) && !empty($secret_key)) {
93 try {
94 $s3Client = new S3Client([
95 'version' => '2006-03-01',
96 'region' => $region,
97 'use_aws_shared_config_files' => false,
98 'credentials' => [
99 'key' => $access_key,
100 'secret' => $secret_key,
101 ],
102 ]);
103
104 $result = [
105 'success' => false,
106 'code' => 200,
107 'message' => esc_html__('Please check the authorization details', 'media-cloud-sync'),
108 ];
109
110 try {
111 $s3Client->listObjectsV2([
112 'Bucket' => $this->token . '_dummy-bucket-for-auth-check'
113 ]);
114
115 // If we reach here, the credentials are valid
116 $result = [
117 'success' => true,
118 'code' => 200,
119 'message' => esc_html__('Credentials are valid', 'media-cloud-sync'),
120 ];
121 } catch (AwsException $e) {
122 $code = $e->getAwsErrorCode();
123
124 $validErrors = [
125 'AccessDenied',
126 'NoSuchBucket',
127 'AllAccessDisabled',
128 'AuthorizationHeaderMalformed',
129 'PermanentRedirect',
130 'InvalidBucketName',
131 ];
132
133 if (in_array($code, $validErrors)) {
134 // If we reach here, the credentials are valid
135 $result = [
136 'success' => true,
137 'code' => 200,
138 'message' => esc_html__('Credentials are valid', 'media-cloud-sync'),
139 ];
140 }
141 }
142
143 if($result['success'] == false) {
144 return $result;
145 }
146
147 try {
148 $buckets = $s3Client->listBuckets();
149 $newBucketFormat = [];
150 if(isset($buckets['Buckets']) && !empty($buckets['Buckets'])){
151 foreach($buckets['Buckets'] as $bucket) {
152 if(isset($bucket['Name'])) {
153 $newBucketFormat[] = ['Name' => $bucket['Name'], 'CreationDate' => $bucket['CreationDate']];
154 }
155 }
156 }
157 $result['buckets_data']['buckets'] = $newBucketFormat;
158 $result['buckets_data']['message'] = esc_html__('Buckets listed successfully', 'media-cloud-sync');
159 $result['buckets_data']['status'] = true;
160 } catch (S3Exception $e) {
161 $result ['buckets_data']['buckets'] = [];
162 $result ['buckets_data']['message'] = esc_html__('Unable to list buckets, Please check the bucket listing permission', 'media-cloud-sync');
163 $result ['buckets_data']['status'] = false;
164 } catch (Exception $e) {
165 $result ['buckets_data']['buckets'] = [];
166 $result ['buckets_data']['message'] = esc_html__('Unable to list buckets, Please check the bucket listing permission', 'media-cloud-sync');
167 $result ['buckets_data']['status'] = false;
168 }
169 return $result;
170 }
171 catch (S3Exception $ex) {
172 return array('message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false);
173 } catch (Exception $ex) {
174 return array('message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false);
175 }
176 }
177 return array('message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync'), 'code' => 200, 'success' => false);
178 }
179
180 /**
181 * Verify Bucket Exist
182 * @since 1.0.0
183 * @return boolean
184 */
185 public function verifyBucketExist( $config = [], $bucketConfig = [] ) {
186 $region = isset($config['region']) ? $config['region'] : '';
187 $access_key = isset($config['access_key']) ? $config['access_key'] : '';
188 $secret_key = isset($config['secret_key']) ? $config['secret_key'] : '';
189 $bucket_name = isset($bucketConfig['bucket_name']) ? $bucketConfig['bucket_name'] : '';
190 $transfer_acceleration = isset($bucketConfig['transfer_acceleration']) ? $bucketConfig['transfer_acceleration'] : false;
191
192 if (!empty($region) && !empty($access_key) && !empty($secret_key) && !empty($bucket_name)) {
193 try {
194 $s3Client = new S3Client([
195 'version' => '2006-03-01',
196 'region' => $region,
197 'use_accelerate_endpoint' => $transfer_acceleration,
198 'use_aws_shared_config_files' => false,
199 'credentials' => [
200 'key' => $access_key,
201 'secret' => $secret_key,
202 ],
203 ]);
204
205 //get S3 object
206 $bucket_found = false;
207 try {
208 $s3Client->getObject([
209 'Bucket' => $bucket_name,
210 'Key' => $this->token . '_dummy-object-for-bucket-exist-check'
211 ]);
212 $bucket_found = true;
213 } catch (AwsException $e) {
214 $code = $e->getAwsErrorCode();
215 if ($code === 'NoSuchKey') {
216 $bucket_found = true;
217 }
218 }
219
220 if($bucket_found) {
221 return array('message' => esc_html__('Bucket exist', 'media-cloud-sync'), 'code' => 200, 'success' => true);
222 } else {
223 return array('message' => esc_html__("Bucket choosen does not exist / does not have read permission", 'media-cloud-sync'), 'code' => 200, 'success' => false);
224 }
225 }
226 catch (S3Exception $ex) {
227 return array('message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false);
228 } catch (Exception $ex) {
229 return array('message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false);
230 }
231 }
232 return array('message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync'), 'code' => 200, 'success' => false);
233 }
234
235 /**
236 * Create Bucket
237 * @since 1.0.0
238 * @return boolean
239 */
240 public function createBucket( $config = [], $bucketConfig = [] ) {
241 $region = isset($config['region']) ? $config['region'] : '';
242 $access_key = isset($config['access_key']) ? $config['access_key'] : '';
243 $secret_key = isset($config['secret_key']) ? $config['secret_key'] : '';
244 $bucket_name = isset($bucketConfig['bucket_name']) ? $bucketConfig['bucket_name'] : '';
245 $transfer_acceleration = isset($bucketConfig['transfer_acceleration']) ? $bucketConfig['transfer_acceleration'] : false;
246
247 if (!empty($region) && !empty($access_key) && !empty($secret_key) && !empty($bucket_name)) {
248 return ['message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync'), 'code' => 200, 'success' => false];
249 }
250
251 try {
252 $s3ClientConfig = [
253 'version' => '2006-03-01',
254 'region' => $region,
255 'use_aws_shared_config_files' => false,
256 'credentials' => [
257 'key' => $access_key,
258 'secret' => $secret_key,
259 ],
260 ];
261
262 $s3Client = new S3Client($s3ClientConfig);
263
264 // Create Bucket
265 if ($region === 'us-east-1') {
266 $s3Client->createBucket([
267 'Bucket' => $bucket_name,
268 ]);
269 } else {
270 $s3Client->createBucket([
271 'Bucket' => $bucket_name,
272 'CreateBucketConfiguration' => [
273 'LocationConstraint' => $region,
274 ],
275 ]);
276 }
277
278 try {
279 $this->blockPublicAccess($bucket_name, false, $s3Client);
280 $this->putBucketPolicy($bucket_name, $s3Client);
281 $this->changeBucketOwnership($bucket_name, $s3Client);
282 try {
283
284 $this->changeTransferAccilaration($bucket_name, $s3Client, $transfer_acceleration);
285
286 return [
287 'message' => esc_html__('Bucket created successfully. Choose bucket from list to select the bucket.', 'media-cloud-sync'),
288 'data' => [
289 'Name' => $bucket_name,
290 'CreationDate' => date('Y-m-d\TH:i:s\Z'),
291 ],
292 'code' => 200,
293 'success' => true,
294 ];
295 } catch (AwsException $ex) {
296 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];
297 }
298 } catch (AwsException $ex) {
299 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];
300 }
301 } catch (AwsException $ex) {
302 return ['message' => $ex->getAwsErrorMessage(), 'code' => 200, 'success' => false];
303 } catch (S3Exception $ex) {
304 return ['message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
305 } catch (Exception $ex) {
306 return ['message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
307 }
308 }
309
310 /**
311 * Check Bucket Write Permission
312 * @since 1.0.0
313 */
314 public function verifyObjectWritePermission( $config = [], $bucketConfig = [] ) {
315 $region = isset($config['region']) ? $config['region'] : '';
316 $access_key = isset($config['access_key']) ? $config['access_key'] : '';
317 $secret_key = isset($config['secret_key']) ? $config['secret_key'] : '';
318 $bucket_name = isset($bucketConfig['bucket_name']) ? $bucketConfig['bucket_name'] : '';
319 $transfer_acceleration = isset($bucketConfig['transfer_acceleration']) ? $bucketConfig['transfer_acceleration'] : false;
320
321 if (empty($region) || empty($access_key) || empty($secret_key) || empty($bucket_name)) {
322 return ['message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync'), 'code' => 200, 'success' => false];
323 }
324
325 try {
326 $s3ClientConfig = [
327 'version' => '2006-03-01',
328 'region' => $region,
329 'use_accelerate_endpoint' => $transfer_acceleration,
330 'use_aws_shared_config_files' => false,
331 'credentials' => [
332 'key' => $access_key,
333 'secret' => $secret_key,
334 ],
335 ];
336
337 $s3Client = new S3Client($s3ClientConfig);
338
339 $object_key = Utils::generate_object_key($this->token . '_dummy-object-for-bucket-permission-check', '');
340
341
342 // Create a dummy object to check write permission
343 $s3Client->putObject([
344 'Bucket' => $bucket_name,
345 'Key' => $object_key,
346 'Body' => 'This is a test object to check write permission.',
347 ]);
348 // Check if the object was created successfully
349 if ($this->exists($object_key, $bucket_name, $s3Client)) {
350 return ['message' => esc_html__('Bucket write permission verified successfully', 'media-cloud-sync'), 'code' => 200, 'success' => true];
351 } else {
352 return ['message' => esc_html__('Bucket write permission not verified', 'media-cloud-sync'), 'code' => 200, 'success' => false];
353 }
354
355 } catch (AwsException $ex) {
356 return ['message' => $ex->getAwsErrorMessage(), 'code' => 200, 'success' => false];
357 } catch (S3Exception $ex) {
358 return ['message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
359 } catch (Exception $ex) {
360 return ['message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
361 }
362 }
363
364
365
366 /**
367 * Check Bucket Delete Permission
368 * @since 1.0.0
369 */
370 public function verifyObjectDeletePermission( $config = [], $bucketConfig = [] ) {
371 $region = isset($config['region']) ? $config['region'] : '';
372 $access_key = isset($config['access_key']) ? $config['access_key'] : '';
373 $secret_key = isset($config['secret_key']) ? $config['secret_key'] : '';
374 $bucket_name = isset($bucketConfig['bucket_name']) ? $bucketConfig['bucket_name'] : '';
375 $transfer_acceleration = isset($bucketConfig['transfer_acceleration']) ? $bucketConfig['transfer_acceleration'] : false;
376
377 if (empty($region) || empty($access_key) || empty($secret_key) || empty($bucket_name)) {
378 return ['message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync'), 'code' => 200, 'success' => false];
379 }
380
381 try {
382 $s3ClientConfig = [
383 'version' => '2006-03-01',
384 'region' => $region,
385 'use_accelerate_endpoint' => $transfer_acceleration,
386 'use_aws_shared_config_files' => false,
387 'credentials' => [
388 'key' => $access_key,
389 'secret' => $secret_key,
390 ],
391 ];
392
393 $s3Client = new S3Client($s3ClientConfig);
394
395 $object_key = Utils::generate_object_key($this->token . '_dummy-object-for-bucket-permission-check', '');
396
397 // Create a dummy object to check dlete permission
398 $s3Client->deleteObject([
399 'Bucket' => $bucket_name,
400 'Key' => $object_key,
401 ]);
402
403 // Check if the object was created successfully
404 if (!$this->exists($object_key, $bucket_name, $s3Client)) {
405 return ['message' => esc_html__('Bucket delete permission verified successfully', 'media-cloud-sync'), 'code' => 200, 'success' => true];
406 } else {
407 return ['message' => esc_html__('Bucket delete permission not verified', 'media-cloud-sync'), 'code' => 200, 'success' => false];
408 }
409
410 } catch (AwsException $ex) {
411 return ['message' => $ex->getAwsErrorMessage(), 'code' => 200, 'success' => false];
412 } catch (S3Exception $ex) {
413 return ['message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
414 } catch (Exception $ex) {
415 return ['message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
416 }
417 }
418
419
420 /**
421 * Check Bucket Read Permission
422 * @since 1.2.4
423 */
424 public function verifyObjectReadPermission() {
425 $result = [
426 'status' => false,
427 'message' => '',
428 'lastChecked' => time(),
429 ];
430
431 if (empty($this->s3Client) || empty($this->bucket_name)) {
432 $result['message'] = esc_html__('Invalid Request', 'media-cloud-sync');
433 return ['message' => esc_html__('Invalid Request', 'media-cloud-sync'), 'code' => 200, 'success' => false, 'lastChecked' => time()];
434 }
435
436 try {
437 $object_key = Utils::generate_object_key($this->token . '_dummy-object-for-bucket-permission-check', '');
438
439 // Check if the object was created successfully
440 if (!$this->exists($object_key)) {
441 // Create a dummy object to check write permission
442 $this->s3Client->putObject([
443 'Bucket' => $this->bucket_name,
444 'Key' => $object_key,
445 'Body' => 'This is a test object to check permission.',
446 ]);
447 }
448
449
450 $url = $this->generate_file_url($object_key);
451 $cdn_url = Cdn::may_generate_cdn_url($url, $object_key);
452 $headers = @get_headers($cdn_url);
453 if (strpos($headers[0], '200') !== false) {
454 $result['status'] = true;
455 $result['message'] = esc_html__('Objects are accessible to Read', 'media-cloud-sync');
456 } else if (strpos($headers[0], '403') !== false) {
457 $result['status'] = false;
458 if($this->cdnConfig['service'] == $this->service) {
459 $result['message'] = esc_html__('Access Denied. Please check your bucket policy. Public Read Access is required.', 'media-cloud-sync');
460 } else {
461 $result['message'] = esc_html__('Access Denied. Please check your bucket policy', 'media-cloud-sync');
462 }
463 $result['message'] = esc_html__('Access Denied. Please check your bucket policy', 'media-cloud-sync');
464 } else if (strpos($headers[0], '404') !== false) {
465 $result['status'] = false;
466 $result['message'] = esc_html__('Object not found. Please check your bucket policy', 'media-cloud-sync');
467 } else if (strpos($headers[0], '500') !== false) {
468 $result['status'] = false;
469 $result['message'] = esc_html__('Internal Server error. Please check your bucket policy', 'media-cloud-sync');
470 } else {
471 $result['status'] = false;
472 $result['message'] = esc_html__('Objects are not accessible to read', 'media-cloud-sync');
473 }
474
475 $this->deleteSingle($object_key);
476 return [
477 'message' => $result['message'],
478 'code' => 200,
479 'success' => $result['status'],
480 'lastChecked' => $result['lastChecked'],
481 ];
482 } catch (AwsException $ex) {
483 $result['message'] = $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync');
484 return ['message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false, 'lastChecked' => time()];
485 } catch (S3Exception $ex) {
486 $result['message'] = $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync');
487 return ['message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false, 'lastChecked' => time()];
488 } catch (Exception $ex) {
489 $result['message'] = $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync');
490 return ['message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false, 'lastChecked' => time()];
491 }
492 }
493
494
495 /**
496 * get Bucket Security Settings
497 */
498 public function getBucketSecuritySettings( $config = [], $bucketConfig = [] ) {
499 $region = isset($config['region']) ? $config['region'] : '';
500 $access_key = isset($config['access_key']) ? $config['access_key'] : '';
501 $secret_key = isset($config['secret_key']) ? $config['secret_key'] : '';
502 $bucket_name = isset($bucketConfig['bucket_name']) ? $bucketConfig['bucket_name'] : '';
503 $transfer_acceleration = isset($bucketConfig['transfer_acceleration']) ? $bucketConfig['transfer_acceleration'] : false;
504
505 if (empty($region) || empty($access_key) || empty($secret_key) || empty($bucket_name)) {
506 return ['message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync'), 'code' => 200, 'success' => false];
507 }
508
509 $errors = [];
510 try {
511 $s3ClientConfig = [
512 'version' => '2006-03-01',
513 'region' => $region,
514 'use_accelerate_endpoint' => $transfer_acceleration,
515 'use_aws_shared_config_files' => false,
516 'credentials' => [
517 'key' => $access_key,
518 'secret' => $secret_key,
519 ],
520 ];
521
522 $s3Client = new S3Client($s3ClientConfig);
523
524 // Check public access block configuration
525 $security['block_public_access'] = false;
526 try {
527 $publicAccessBlock = $s3Client->getPublicAccessBlock([
528 'Bucket' => $bucket_name,
529 ]);
530
531 $publicAccessBlockConfig = $publicAccessBlock['PublicAccessBlockConfiguration'];
532 $security = [];
533 if (
534 $publicAccessBlockConfig['BlockPublicAcls'] &&
535 $publicAccessBlockConfig['IgnorePublicAcls'] &&
536 $publicAccessBlockConfig['BlockPublicPolicy'] &&
537 $publicAccessBlockConfig['RestrictPublicBuckets']
538 ) {
539 $security['block_public_access'] = true;
540 }
541 } catch (S3Exception $ex) {
542 // If the bucket does not have public access block configuration, we assume it is not blocked
543 $errors['block_public_access'] = $ex->getMessage();
544 } catch (Exception $ex) {
545 $errors['block_public_access'] = $ex->getMessage();
546 }
547
548
549 $security['object_ownership_enforced'] = false;
550 try {
551 $ownershipControls = $s3Client->getBucketOwnershipControls([
552 'Bucket' => $bucket_name,
553 ]);
554
555 $ownershipRule = $ownershipControls['OwnershipControls']['Rules'][0]['ObjectOwnership'];
556
557 if ($ownershipRule === 'BucketOwnerEnforced') {
558 $security['object_ownership_enforced'] = true;
559 }
560 } catch (S3Exception $ex) {
561 $errors['object_ownership_enforced'] = $ex->getMessage();
562 } catch (Exception $ex) {
563 $errors['object_ownership_enforced'] = $ex->getMessage();
564 }
565
566 return ['message' => '', 'code' => 200, 'success' => empty($errors), 'security' => $security, 'errors' => $errors];
567 } catch (AwsException $ex) {
568 return ['message' => $ex->getAwsErrorMessage(), 'code' => 200, 'success' => false];
569 } catch (S3Exception $ex) {
570 return ['message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
571 } catch (Exception $ex) {
572 return ['message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
573 }
574 }
575
576
577
578 /**
579 * Change Bucket Public Access
580 */
581
582 public function changePublicAccess( $config = [], $bucketConfig = [], $value = false ) {
583 $region = isset($config['region']) ? $config['region'] : '';
584 $access_key = isset($config['access_key']) ? $config['access_key'] : '';
585 $secret_key = isset($config['secret_key']) ? $config['secret_key'] : '';
586 $bucket_name = isset($bucketConfig['bucket_name']) ? $bucketConfig['bucket_name'] : '';
587 $transfer_acceleration = isset($bucketConfig['transfer_acceleration']) ? $bucketConfig['transfer_acceleration'] : false;
588
589 if (empty($region) || empty($access_key) || empty($secret_key) || empty($bucket_name)) {
590 return ['message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync'), 'code' => 200, 'success' => false];
591 }
592
593 try {
594 $s3ClientConfig = [
595 'version' => '2006-03-01',
596 'region' => $region,
597 'use_accelerate_endpoint' => $transfer_acceleration,
598 'use_aws_shared_config_files' => false,
599 'credentials' => [
600 'key' => $access_key,
601 'secret' => $secret_key,
602 ],
603 ];
604
605 $s3Client = new S3Client($s3ClientConfig);
606
607 try {
608 $result = $this->blockPublicAccess($bucket_name, $value, $s3Client);
609 return ['message' => '', 'code' => 200, 'success' => true, 'result' => $result];
610 } catch (AwsException $ex) {
611 return ['message' => $ex->getAwsErrorMessage(), 'code' => 200, 'success' => false];
612 } catch (S3Exception $ex) {
613 return ['message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
614 } catch (Exception $ex) {
615 return ['message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
616 }
617 } catch (AwsException $ex) {
618 return ['message' => $ex->getAwsErrorMessage(), 'code' => 200, 'success' => false];
619 } catch (S3Exception $ex) {
620 return ['message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
621 } catch (Exception $ex) {
622 return ['message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
623 }
624 }
625
626
627
628 /**
629 * Change Bucket Ownership
630 */
631
632 public function changeObjectOwnership( $config = [], $bucketConfig = [], $value = false ) {
633 $region = isset($config['region']) ? $config['region'] : '';
634 $access_key = isset($config['access_key']) ? $config['access_key'] : '';
635 $secret_key = isset($config['secret_key']) ? $config['secret_key'] : '';
636 $bucket_name = isset($bucketConfig['bucket_name']) ? $bucketConfig['bucket_name'] : '';
637 $transfer_acceleration = isset($bucketConfig['transfer_acceleration']) ? $bucketConfig['transfer_acceleration'] : false;
638
639 if (empty($region) || empty($access_key) || empty($secret_key) || empty($bucket_name)) {
640 return ['message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync'), 'code' => 200, 'success' => false];
641 }
642
643 try {
644 $s3ClientConfig = [
645 'version' => '2006-03-01',
646 'region' => $region,
647 'use_accelerate_endpoint' => $transfer_acceleration,
648 'use_aws_shared_config_files' => false,
649 'credentials' => [
650 'key' => $access_key,
651 'secret' => $secret_key,
652 ],
653 ];
654
655 $s3Client = new S3Client($s3ClientConfig);
656
657 $ownership = $value ? 'BucketOwnerEnforced' : 'BucketOwnerPreferred';
658
659 try {
660 $result = $this->changeBucketOwnership( $bucket_name, $s3Client, $ownership );
661 return ['message' => '', 'code' => 200, 'success' => true, 'result' => $result];
662 } catch (AwsException $ex) {
663 return ['message' => $ex->getAwsErrorMessage(), 'code' => 200, 'success' => false];
664 } catch (S3Exception $ex) {
665 return ['message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
666 } catch (Exception $ex) {
667 return ['message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
668 }
669
670 }
671 catch (AwsException $ex) {
672 return ['message' => $ex->getAwsErrorMessage(), 'code' => 200, 'success' => false];
673 } catch (S3Exception $ex) {
674 return ['message' => $ex->getAwsErrorMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
675 } catch (Exception $ex) {
676 return ['message' => $ex->getMessage() ?? esc_html__('Please check the authorization details', 'media-cloud-sync'), 'code' => 200, 'success' => false];
677 }
678 }
679
680
681
682
683 /**
684 * Block Public Access
685 * @since 1.0.0
686 */
687 private function blockPublicAccess($bucket, $block = true, $s3Client = false) {
688 if($s3Client == false) {
689 $s3Client = $this->s3Client;
690 }
691
692 if(empty($bucket)) return false;
693
694 try {
695 $s3Client->putPublicAccessBlock([
696 'Bucket' => $bucket,
697 'PublicAccessBlockConfiguration' => [
698 'BlockPublicPolicy' => $block,
699 'BlockPublicAcls' => $block,
700 'IgnorePublicAcls' => $block,
701 'RestrictPublicBuckets' => $block,
702 ]
703 ]);
704
705 return true;
706 } catch (AwsException $ex) {
707 return false;
708 }
709 }
710
711 /**
712 * Add Bucket Policy
713 */
714 private function putBucketPolicy($bucket, $s3Client = false) {
715 if($s3Client == false) {
716 $s3Client = $this->s3Client;
717 }
718
719 if(empty($bucket)) return false;
720
721 $policy = json_encode([
722 "Version" => "2012-10-17",
723 "Statement" => [
724 [
725 "Effect" => "Allow",
726 "Principal" => "*",
727 "Action" => [
728 "s3:DeleteObjectTagging",
729 "s3:ListBucketMultipartUploads",
730 "s3:DeleteObjectVersion",
731 "s3:ListBucket",
732 "s3:DeleteObjectVersionTagging",
733 "s3:GetBucketAcl",
734 "s3:ListMultipartUploadParts",
735 "s3:PutObject",
736 "s3:GetObjectAcl",
737 "s3:GetObject",
738 "s3:AbortMultipartUpload",
739 "s3:DeleteObject",
740 "s3:GetBucketLocation",
741 "s3:PutObjectAcl",
742 "s3:putBucketOwnershipControls",
743 "s3:putBucketPolicy"
744 ],
745 "Resource" => [
746 "arn:aws:s3:::$bucket/*",
747 "arn:aws:s3:::$bucket"
748 ]
749 ]
750 ]
751 ]);
752
753 try {
754 // Add bucket policy
755 $s3Client->putBucketPolicy(['Bucket' => $bucket, 'Policy' => $policy]);
756
757 return true;
758 } catch (AwsException $ex) {
759 return false;
760 } catch (S3Exception $ex) {
761 return false;
762 } catch (Exception $ex) {
763 return false;
764 }
765 }
766
767 /**
768 * Add Bucket Ownership
769 */
770 private function changeBucketOwnership($bucket, $s3Client = false, $ownership = 'BucketOwnerPreferred') {
771 if($s3Client == false) {
772 $s3Client = $this->s3Client;
773 }
774
775 if(empty($bucket)) return false;
776
777 try {
778 // Change object ownership ACL enabled
779 $s3Client->putBucketOwnershipControls([
780 'Bucket' => $bucket,
781 'OwnershipControls' => [
782 'Rules' => [['ObjectOwnership' => $ownership]],
783 ],
784 ]);
785
786 return true;
787 } catch (AwsException $ex) {
788 return false;
789 } catch (S3Exception $ex) {
790 return false;
791 } catch (Exception $ex) {
792 return false;
793 }
794 }
795
796 /**
797 * Change transfer accilaration
798 */
799 private function changeTransferAccilaration($bucket, $s3Client = false, $enable=false, $force = false) {
800 if($s3Client == false) {
801 $s3Client = $this->s3Client;
802 }
803
804 if(empty($bucket)) return false;
805 if(!$force && !$enable) return true;
806
807 // Check if the bucket already has transfer acceleration enabled
808 try {
809 $s3Client->putBucketAccelerateConfiguration([
810 'Bucket' => $bucket,
811 'AccelerateConfiguration' => [
812 'Status' => $enable ? 'Enabled' : 'Suspended'
813 ]
814 ]);
815 return true;
816 } catch (AwsException $ex) {
817 return false;
818 } catch (S3Exception $ex) {
819 return false;
820 } catch (Exception $ex) {
821 return false;
822 }
823 }
824
825 /**
826 * isConfigured Function To Identify the congfigurations are correct
827 * @since 1.0.0
828 */
829 public function isConfigured(){
830 if ($this->s3Client) {
831 try {
832 $this->s3Client->listObjectsV2([
833 'Bucket' => $this->token . '_dummy-bucket-for-auth-check'
834 ]);
835
836 // If we reach here, the credentials are valid
837 return true;
838 } catch (AwsException $ex) {
839 $code = $e->getAwsErrorCode();
840
841 $validErrors = [
842 'AccessDenied',
843 'NoSuchBucket',
844 'AllAccessDisabled',
845 'AuthorizationHeaderMalformed',
846 'PermanentRedirect',
847 'InvalidBucketName'
848 ];
849
850 if (in_array($code, $validErrors)) {
851 // If we reach here, the credentials are valid
852 return true;
853 } else {
854 return false;
855 }
856 } catch (S3Exception $ex) {
857 return false;
858 } catch (Exception $ex) {
859 return false;
860 }
861 }
862 return false;
863 }
864
865 /**
866 * Make Object Private
867 * @since 1.0.0
868 *
869 */
870 public function toPrivate($key) {
871 if(!$key) return false;
872 try {
873 $this->s3Client->putObjectAcl([
874 'Bucket' => $this->bucket_name,
875 'Key' => $key,
876 'ACL' => 'private'
877 ]);
878 return true;
879 } catch (AwsException $ex) {
880 return false;
881 }
882 return false;
883 }
884
885
886
887 /**
888 * Make Object Public
889 * @since 1.0.0
890 *
891 */
892 public function toPublic($key) {
893 if(!$key) return false;
894 try {
895 $this->s3Client->putObjectAcl([
896 'Bucket' => $this->bucket_name,
897 'Key' => $key,
898 'ACL' => 'public-read'
899 ]);
900 return true;
901 } catch (AwsException $ex) {
902 return false;
903 }
904 return false;
905 }
906
907
908
909 /**
910 * Check the object exist
911 * @since 1.1.8
912 */
913 public function exists($key, $bucket_name = '', $client = null) {
914 if(!$key) return false;
915 try {
916 $bucket_name = $bucket_name ? $bucket_name : $this->bucket_name;
917 $client = $client ?? $this->s3Client;
918 if($client->doesObjectExistV2( $bucket_name, $key)) {
919 return true;
920 }
921 } catch (AwsException $ex) {
922 return false;
923 } catch (S3Exception $ex) {
924 return false;
925 } catch (Exception $ex) {
926 return false;
927 }
928 }
929
930 /**
931 * Upload Single
932 * @since 1.0.0
933 * @return boolean
934 */
935 public function uploadSingle($absolute_source_path, $relative_source_path, $prefix='') {
936 $result = array();
937 if (
938 isset($absolute_source_path) && !empty($absolute_source_path) &&
939 isset($relative_source_path) && !empty($relative_source_path)
940 ) {
941 $file_name = wp_basename( $relative_source_path );
942 if ($file_name) {
943 $upload_path = Utils::generate_object_key($relative_source_path, $prefix);
944
945 // Decide Multipart upload or normal put object
946 if (filesize($absolute_source_path) <= Schema::getConstant('S3_MULTIPART_MIN_FILE_SIZE')) {
947 // Upload a publicly accessible file. The file size and type are determined by the SDK.
948 try {
949 $handle = fopen($absolute_source_path, 'rb');
950
951 $upload = $this->s3Client->putObject([
952 'Bucket' => $this->bucket_name,
953 'Key' => $upload_path,
954 'Body' => $handle,
955 ]);
956
957 if (is_resource($handle)) {
958 fclose($handle);
959 }
960
961 $result = array(
962 'success' => true,
963 'code' => 200,
964 'file_url' => $this->generate_file_url($upload_path),
965 'key' => $upload_path,
966 'message' => esc_html__('File Uploaded Successfully', 'media-cloud-sync')
967 );
968 } catch (AwsException $e) {
969 $result = array(
970 'success' => false,
971 'code' => 200,
972 'message' => $e->getMessage()
973 );
974 }
975 } else {
976 $multiUploader = new MultipartUploader($this->s3Client, $absolute_source_path, [
977 'bucket' => $this->bucket_name,
978 'key' => $upload_path,
979 ]);
980
981 try {
982 do {
983 try {
984 $uploaded = $multiUploader->upload();
985 } catch (MultipartUploadException $e) {
986 $multiUploader = new MultipartUploader($this->s3Client, $absolute_source_path, [
987 'state' => $e->getState(),
988 ]);
989 }
990 } while (!isset($uploaded));
991
992 if (isset($uploaded['ObjectURL']) && !empty($uploaded['ObjectURL'])) {
993 $result = array(
994 'success' => true,
995 'code' => 200,
996 'file_url' => $this->generate_file_url($upload_path),
997 'key' => $upload_path,
998 'message' => esc_html__('File Uploaded Successfully', 'media-cloud-sync')
999 );
1000 } else {
1001 $result = array(
1002 'success' => false,
1003 'code' => 200,
1004 'message' => esc_html__('Something happened while uploading to server', 'media-cloud-sync')
1005 );
1006 }
1007 } catch (MultipartUploadException $e) {
1008 $result = array(
1009 'success' => false,
1010 'code' => 200,
1011 'message' => $e->getMessage()
1012 );
1013 }
1014 }
1015 } else {
1016 $result = array(
1017 'success' => false,
1018 'code' => 200,
1019 'message' => esc_html__('Check the file you are trying to upload. Please try again', 'media-cloud-sync')
1020 );
1021 }
1022 } else {
1023 $result = array(
1024 'success' => false,
1025 'code' => 200,
1026 'message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync')
1027 );
1028 }
1029 return $result;
1030 }
1031
1032 /**
1033 * Save object to server
1034 * @since 1.0.0
1035 */
1036 public function object_to_server($key, $save_path) {
1037 try {
1038 $getObject = $this->s3Client->GetObject([
1039 'Bucket' => $this->bucket_name,
1040 'Key' => $key,
1041 'SaveAs' => $save_path
1042 ]);
1043 if (file_exists($save_path)) {
1044 return true;
1045 }
1046 } catch (AwsException $e) {
1047 return false;
1048 }
1049 return false;
1050 }
1051
1052
1053 /**
1054 * Copy object to new path
1055 * @since 1.3.4
1056 */
1057 public function copy_to_new_path($key, $new_path) {
1058 try {
1059 // Step 1: Verify object exists at old location
1060 if (!$this->exists($key)) {
1061 return [
1062 'message' => esc_html__('Original file not found' , 'media-cloud-sync'),
1063 'code' => 200,
1064 'success' => false
1065 ];
1066 }
1067 // Step 2: Copy object
1068 if (!$this->exists($new_path)) {
1069 $this->s3Client->copyObject([
1070 'Bucket' => $this->bucket_name,
1071 'CopySource' => "{$this->bucket_name}/{$key}",
1072 'Key' => $new_path,
1073 'MetadataDirective' => 'COPY',
1074 ]);
1075 }
1076
1077 // Step 3: Verify object exists at new location
1078 if ($this->exists($new_path)) {
1079 return [
1080 'success' => true,
1081 'code' => 200,
1082 'message' => esc_html__('File copied successfully', 'media-cloud-sync')
1083 ];
1084 }
1085 } catch (AwsException $e) {
1086 return [
1087 'success' => false,
1088 'code' => 200,
1089 'message' => $e->getMessage()
1090 ];
1091 }
1092 return [
1093 'success' => false,
1094 'code' => 200,
1095 'message' => esc_html__('Something went wrong', 'media-cloud-sync')
1096 ];
1097 }
1098
1099
1100 /**
1101 * Delete Single
1102 * @since 1.0.0
1103 * @return boolean
1104 */
1105 public function deleteSingle($key) {
1106 $result = array();
1107 if (isset($key) && !empty($key)) {
1108 try {
1109 $this->s3Client->deleteObject([
1110 'Bucket' => $this->bucket_name,
1111 'Key' => $key
1112 ]);
1113
1114 if (!$this->exists($key)) {
1115 $result = array(
1116 'success' => true,
1117 'code' => 200,
1118 'message' => esc_html__('Deleted Successfully', 'media-cloud-sync')
1119 );
1120 } else {
1121 $result = array(
1122 'success' => false,
1123 'code' => 200,
1124 'message' => esc_html__('File not deleted', 'media-cloud-sync')
1125 );
1126 }
1127 } catch (AwsException $e) {
1128 $result = array(
1129 'success' => false,
1130 'code' => 200,
1131 'message' => $e->getMessage()
1132 );
1133 }
1134 } else {
1135 $result = array(
1136 'success' => false,
1137 'code' => 200,
1138 'message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync')
1139 );
1140 }
1141 return $result;
1142 }
1143
1144 /**
1145 * get private URL
1146 * @since 1.0.0
1147 * @return boolean
1148 */
1149 public function get_private_url($key) {
1150 $result = array();
1151 if (isset($key) && !empty($key)) {
1152 try {
1153 $cmd = $this->s3Client->getCommand('GetObject', [
1154 'Bucket' => $this->bucket_name,
1155 'Key' => $key
1156 ]);
1157
1158 $expires = isset($this->settings['private_url_expire']) ? $this->settings['private_url_expire'] : 20;
1159
1160 $request = $this->s3Client->createPresignedRequest($cmd, sprintf('+%s minutes', $expires));
1161
1162 if ($privateUrl = (string)$request->getUri()) {
1163 $result = array(
1164 'success' => true,
1165 'code' => 200,
1166 'file_url' => $privateUrl,
1167 'message' => esc_html__('Got Private URL Successfully', 'media-cloud-sync')
1168 );
1169 } else {
1170 $result = array(
1171 'success' => false,
1172 'code' => 200,
1173 'message' => esc_html__('Error getting private URL', 'media-cloud-sync')
1174 );
1175 }
1176 } catch (AwsException $e) {
1177 $result = array(
1178 'success' => false,
1179 'code' => 200,
1180 'message' => $e->getMessage()
1181 );
1182 }
1183 } else {
1184 $result = array(
1185 'success' => false,
1186 'code' => 200,
1187 'message' => esc_html__('Insufficient Data. Please try again', 'media-cloud-sync')
1188 );
1189 }
1190 return $result;
1191 }
1192
1193 /**
1194 * Generate file URL
1195 */
1196 public function generate_file_url($key){
1197 $domain = $this->get_domain();
1198
1199 return apply_filters('wpmcs_generate_s3_file_url',
1200 $domain . '/' . $key,
1201 $domain, $key
1202 );
1203 }
1204
1205 /**
1206 * Is Provider URL
1207 * @since 1.3.6
1208 */
1209 public function is_provider_url($url) {
1210 $domain = $this->get_domain();
1211 return (strpos($url, $domain . '/') !== false);
1212 }
1213
1214 /**
1215 * Get domain URL
1216 */
1217 public function get_domain() {
1218 $region = isset($this->config['region']) ? $this->config['region'] : '';
1219 return "https://{$this->bucket_name}.s3.{$region}.amazonaws.com";
1220 }
1221
1222 }