PluginProbe
Media Cloud Sync / 1.3.10
Media Cloud Sync v1.3.10
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 / item.php

item.php in Media Cloud Sync 1.3.10, at includes/base/item.php

1,080 lines 35.0 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 class Item {
7 private static $instance = null;
8 private $assets_url;
9 private $version;
10 private $token;
11
12 protected $config;
13 protected $bucketConfig;
14 protected $settings;
15 protected $credentials;
16 protected $service;
17
18 protected $bucket_name;
19 protected $region = '';
20
21 /**
22 * Admin constructor.
23 * @since 1.0.0
24 */
25 public function __construct() {
26 $this->assets_url = WPMCS_ASSETS_URL;
27 $this->version = WPMCS_VERSION;
28 $this->token = WPMCS_TOKEN;
29
30 // Initialize setup
31 $this->init();
32 }
33
34 /**
35 * Initialize datas
36 */
37 public function init() {
38 $this->settings = Utils::get_settings();
39 $this->credentials = Utils::get_credentials();
40 $this->config = isset($this->credentials['config']) && !empty($this->credentials['config'])
41 ? $this->credentials['config']
42 : [];
43 $this->bucketConfig = isset($this->credentials['bucketConfig']) && !empty($this->credentials['bucketConfig'])
44 ? $this->credentials['bucketConfig']
45 : [];
46 $this->service = isset($this->credentials['service']) && !empty($this->credentials['service'])
47 ? $this->credentials['service']
48 : '';
49
50 if (isset($this->bucketConfig['bucket_name'])) {
51 $this->bucket_name = $this->bucketConfig['bucket_name'];
52 }
53 if (isset($this->config['region'])) {
54 $this->region = $this->config['region'];
55 }
56 }
57
58 /**
59 * Add Item In database
60 * @since 1.0.0
61 * @param
62 */
63 public function add(
64 $source_id,
65 $url,
66 $key,
67 $source_path,
68 $original_source_path = '',
69 $original_key = '',
70 $meta = array(),
71 $source_type = 'media_library',
72 $is_private = 0
73 ) {
74 global $wpdb;
75 $item_id = false;
76 $data = array(
77 'provider' => $this->service,
78 'region' => $this->region,
79 'storage' => $this->bucket_name,
80 'source_id' => $source_id,
81 'source_path' => $source_path,
82 'source_type' => $source_type,
83 'url' => $url,
84 'key' => $key,
85 'original_source_path' => $original_source_path,
86 'original_key' => $original_key,
87 'is_private' => $is_private,
88 'extra' => Utils::maybe_serialize($meta),
89 );
90
91 // Do some pre-update actions
92 $this->pre_update_item($source_id, $data, [], $source_type);
93
94 if ($wpdb->insert(Db::get_table_name(), $data)) {
95 $item_id = $wpdb->insert_id;
96 $data['id'] = $item_id;
97 Integration::update_meta($source_id, 'item', $data, false, false, $source_type);
98 Cache::update_item_cache($source_id.'_item_'.$source_type, $data);
99 Counter::add( 'uploaded', $source_type );
100 }
101
102 // Do some post-update actions
103 $this->post_update_item($source_id, $data, $source_type);
104
105 return $item_id;
106 }
107
108
109 /**
110 * Get Item From Db
111 */
112 public function get($source_id, $source_type = 'media_library') {
113 global $wpdb;
114 $item = Cache::get_item_cache($source_id.'_item_'.$source_type, false);
115 if($item === false) {
116 $item = Integration::get_meta($source_id, 'item', false, false, false, $source_type);
117 if($item == false){
118 $item_table = Db::get_table_name();
119 $query = "SELECT * FROM {$item_table}
120 WHERE source_id = %d
121 AND source_type = %s
122 AND provider = %s
123 AND storage = %s";
124
125 if(!empty($this->region)) {
126 $query .= " AND region = %s";
127 $query = $wpdb->prepare($query, $source_id, $source_type, $this->service, $this->bucket_name, $this->region);
128 } else {
129 $query = $wpdb->prepare($query, $source_id, $source_type, $this->service, $this->bucket_name);
130 }
131
132 $item = $wpdb->get_row( $query, ARRAY_A);
133
134 if($wpdb->last_error || null === $item || !(isset($item) && !empty($item))) {
135 Cache::update_item_cache($source_id.'_item_'.$source_type, '');
136 return false;
137 }
138
139 Integration::update_meta($source_id, 'item', $item, false, false, $source_type);
140 }
141 // Update cache
142 Cache::update_item_cache($source_id.'_item_'.$source_type, $item == false ? '' : $item);
143 }
144
145 /**
146 * Filter to modify item data when retrieved
147 * @param array|false $item
148 * @param int $source_id
149 * @param string $source_type
150 * @since 1.3.5
151 */
152 return apply_filters( 'wpmcs_get_item' , !empty($item) ? $item : false, $source_id, $source_type );
153 }
154
155 /**
156 * Function to delete item from data base
157 * @since 1.0.0
158 */
159 public function delete($source_id, $source_type = 'media_library'){
160 global $wpdb;
161 if (isset($source_id) && !Utils::is_empty($source_id)) {
162 // Delete attachments by item from cloud
163 $item = $this->get($source_id, $source_type);
164 $this->delete_attachments_by_item($item);
165
166 $where = array(
167 'source_id' => $source_id,
168 'source_type' => $source_type,
169 'provider' => $this->service,
170 'storage' => $this->bucket_name,
171 );
172 if(!empty($this->region)) {
173 $where['region'] = $this->region;
174 }
175 $rows = $wpdb->delete( Db::get_table_name(), $where );
176 if ($wpdb->last_error || false === $rows) {
177 return false;
178 }
179 Integration::delete_meta($source_id, 'item', false, $source_type);
180 Cache::delete_item_cache($source_id.'_item_'.$source_type);
181 Counter::remove( 'uploaded', $source_type );
182
183 // Remove logs by media ID & source type
184 Logger::instance()->remove_log_by_media_id($source_id, $source_type);
185
186 return true;
187 }
188 return false;
189 }
190
191
192 /**
193 * Function to update item in data base
194 * @since 1.0.0
195 */
196 public function update($source_id, $data, $source_type = 'media_library') {
197 global $wpdb;
198 if (isset($source_id) && !Utils::is_empty($source_id)) {
199 $data['provider'] = $this->service;
200 $data['storage'] = $this->bucket_name;
201 $data['region'] = $this->region;
202
203 $old_item = $this->get($source_id, $source_type);
204
205 // update data from $old_item if not exists in $data
206 if (isset($old_item) && !empty($old_item)) {
207 foreach ($old_item as $key => $value) {
208 if (!isset($data[$key])) {
209 $data[$key] = $value;
210 }
211 }
212 }
213
214 // Do some pre-update actions
215 $this->pre_update_item($source_id, $data, $old_item, $source_type);
216
217 $where = array(
218 'source_id' => $source_id,
219 'source_type' => $source_type,
220 'provider' => $this->service,
221 'storage' => $this->bucket_name,
222 );
223 if(!empty($this->region)) {
224 $where['region'] = $this->region;
225 }
226 $rows = $wpdb->update(Db::get_table_name(), $data, $where);
227 if ($wpdb->last_error || false === $rows) {
228 return false;
229 }
230
231 Integration::delete_meta($source_id, 'item', false, $source_type);
232 Cache::delete_item_cache($source_id.'_item_'.$source_type);
233
234 // Reset cache
235 $this->get($source_id, $source_type);
236 // Do some post-update actions
237 $this->post_update_item($source_id, $data, $source_type);
238
239 return true;
240 }
241 return false;
242 }
243
244 /**
245 * Get extra values of item from database
246 * @since 1.0.0
247 * @param
248 */
249 public function get_extras($source_id, $field = false, $source_type = 'media_library'){
250 $data = $this->get($source_id, $source_type);
251 if ($data) {
252 if (isset($data['extra']) && !empty($data['extra'])) {
253
254 if(!Utils::is_empty($field)) {
255 $extras = Utils::maybe_unserialize($data['extra']);
256 return isset($extras[$field]) && !empty($extras[$field]) ? $extras[$field] : false;
257 }
258
259 return Utils::maybe_unserialize($data['extra']);
260 }
261 }
262 return false;
263 }
264
265
266 /**
267 * Get column of item from database
268 * @since 1.0.0
269 * @param
270 */
271 public function get_field($source_id, $field, $source_type = 'media_library'){
272 $data = $this->get($source_id, $source_type);
273 if ($data) {
274 if (isset($data[$field]) && !empty($data[$field])) {
275 return $data[$field];
276 }
277 }
278 return false;
279 }
280
281
282 /**
283 * Get backup values of item from database
284 * @since 1.0.0
285 * @param
286 */
287 public function get_backup($source_id, $source_type = 'media_library'){
288 $data = $this->get_extras($source_id, false, $source_type);
289 if ($data) {
290 if (isset($data['backup']) && !empty($data['backup'])) {
291 return Utils::maybe_unserialize($data['backup']);
292 }
293 }
294 return false;
295 }
296
297
298 /**
299 * Checking Item that is served by provider And Is rewrite URL is enabled
300 * @since 1.0.0
301 * @param
302 */
303 public function is_available_from_provider($attachment_id, $check_rewrite = true, $source_type = 'media_library') {
304 $item = $this->get($attachment_id, $source_type);
305 if(Utils::is_empty($item)) {
306 return false;
307 }
308
309 if (
310 $item['provider'] == $this->service &&
311 $item['storage'] == $this->bucket_name &&
312 $item['source_type'] == $source_type
313 ) {
314 if(
315 ($check_rewrite && (isset($this->settings['rewrite_url']) && $this->settings['rewrite_url'])) ||
316 !$check_rewrite
317 ) {
318 return true;
319 }
320 }
321 return false;
322 }
323
324 /**
325 * Get items by source paths
326 *
327 * @param array|string $paths
328 * @param bool $exact_match Use exact paths or greedy match
329 * @param bool $first_only Return only the first matched item
330 *
331 * @return array
332 */
333 public function get_items_by_paths( $paths, $exact_match = true, $first_only = false, $type = 'source' ) {
334 global $wpdb;
335
336 if ( ! is_array( $paths ) && is_string( $paths ) && ! empty( $paths ) ) {
337 $paths = [ $paths ];
338 }
339
340 if ( Utils::is_empty( $paths ) ) {
341 return [];
342 }
343
344 /**
345 * Field => Index mapping
346 * field_name => index_name
347 */
348 switch ( $type ) {
349 case 'key':
350 $fields = [
351 'key' => 'uidx_key',
352 'original_key' => 'uidx_original_key',
353 ];
354 break;
355
356 default:
357 $fields = [
358 'source_path' => 'uidx_source_path',
359 'original_source_path' => 'uidx_original_source_path',
360 ];
361 break;
362 }
363
364 if ( empty( $fields ) ) {
365 return [];
366 }
367
368 // Normalize & deduplicate
369 $paths = array_unique( array_map( 'esc_sql', $paths ) );
370
371 $table = Db::get_table_name();
372
373 // Build USE INDEX clause from field map
374 $index_list = implode( ', ', array_values( $fields ) );
375
376 $sql = "
377 SELECT DISTINCT source_id, source_type
378 FROM {$table} USE INDEX ({$index_list})
379 WHERE provider = %s
380 AND storage = %s
381 ";
382
383 $params = [
384 $this->service,
385 $this->bucket_name,
386 ];
387
388 // Optional region
389 if ( ! empty( $this->region ) ) {
390 $sql .= " AND region = %s";
391 $params[] = $this->region;
392 }
393
394 /**
395 * Path conditions
396 */
397 $conditions = [];
398
399 if ( $exact_match ) {
400 $in = "'" . implode( "','", $paths ) . "'";
401
402 foreach ( array_keys( $fields ) as $column ) {
403 $conditions[] = "`{$column}` IN ({$in})";
404 }
405 } else {
406 foreach ( $paths as $path ) {
407 $ext = pathinfo( $path, PATHINFO_EXTENSION );
408 $base = $ext
409 ? substr_replace( $path, '%', -strlen( $ext ) - 1 )
410 : $path . '%';
411
412 foreach ( array_keys( $fields ) as $column ) {
413 $conditions[] = "`{$column}` LIKE '{$base}'";
414 }
415 }
416 }
417
418 if ( ! empty( $conditions ) ) {
419 $sql .= " AND ( " . implode( ' OR ', $conditions ) . " )";
420 }
421
422 // First only
423 if ( $first_only ) {
424 $sql .= " ORDER BY source_id ASC LIMIT 1";
425 }
426
427 $prepared = $wpdb->prepare( $sql, $params );
428 $results = $wpdb->get_results( $prepared, ARRAY_A );
429
430 if ( $wpdb->last_error || empty( $results ) ) {
431 return [];
432 }
433
434 // Hydration
435 if ( $first_only ) {
436 return $this->get(
437 (int) $results[0]['source_id'],
438 $results[0]['source_type']
439 );
440 }
441
442 $items = [];
443
444 foreach ( $results as $row ) {
445 $item = $this->get(
446 (int) $row['source_id'],
447 $row['source_type']
448 );
449
450 if ( ! Utils::is_empty( $item ) ) {
451 $items[] = $item;
452 }
453 }
454
455 return $items;
456 }
457
458
459 /**
460 * Get similar existing files by source path prefix
461 * Searches in source_path and original_source_path
462 *
463 * @since 1.0.0
464 */
465 public function get_similar_files_by_path( $path ) {
466 global $wpdb;
467
468 if ( Utils::is_empty( $path ) ) {
469 return false;
470 }
471
472 $table = Db::get_table_name();
473 $like = $wpdb->esc_like( $path ) . '%';
474
475 $base_where = "
476 provider = %s
477 AND storage = %s
478 " . ( ! empty( $this->region ) ? "AND region = %s" : '' );
479
480 $params = [ $this->service, $this->bucket_name ];
481 if ( ! empty( $this->region ) ) {
482 $params[] = $this->region;
483 }
484
485 $sql = "
486 (
487 SELECT source_path
488 FROM {$table} USE INDEX (idx_source_path_provider)
489 WHERE {$base_where}
490 AND source_path LIKE %s
491 )
492 UNION DISTINCT
493 (
494 SELECT original_source_path AS source_path
495 FROM {$table} USE INDEX (uidx_original_source_path)
496 WHERE {$base_where}
497 AND original_source_path LIKE %s
498 )
499 ";
500
501 $params = array_merge( $params, [ $like ], $params, [ $like ] );
502
503 $results = $wpdb->get_results(
504 $wpdb->prepare( $sql, $params ),
505 ARRAY_A
506 );
507
508 return ( $wpdb->last_error || empty( $results ) )
509 ? false
510 : array_column( $results, 'source_path' );
511 }
512
513
514 /**
515 * Get service url of item from database
516 * @since 1.0.0
517 * @param
518 */
519 public function get_url($source_id, $size = 'full', $source_type = 'media_library'){
520 if ($data = $this->get($source_id, $source_type)) {
521 $extras = $this->get_extras($source_id, false, $source_type) ?: [];
522 $key = '';
523 $url = '';
524 switch($size) {
525 case 'full':
526 $key = $data['key'];
527 break;
528 case 'original':
529 if( isset($data['original_key']) && !empty($data['original_key']) ) {
530 $key = $data['original_key'];
531 }
532 break;
533 default:
534 if(
535 isset($extras) && !empty($extras) &&
536 isset($extras['sizes']) && !empty($extras['sizes']) &&
537 isset($extras['sizes'][$size]) && !empty($extras['sizes'][$size])
538 ) {
539 $key = $extras['sizes'][$size]['key'];
540 }
541 }
542
543 if(!empty($key)){
544 if (isset($data['is_private']) && $data['is_private']) {
545 $privateUrl = Integration::get_meta( $source_id, 'private_url_'.$size, false, false, false, $source_type );
546 if ($privateUrl === false) {
547 $new_url = Service::instance()->get_private_url($key);
548
549 if (!Utils::is_empty($new_url)) {
550 $privateUrl = Cdn::may_generate_cdn_url($new_url, $key);
551 $expireMinutes = (int)(isset($this->settings['private_url_expire']) && !empty($this->settings['private_url_expire']))
552 ? $this->settings['private_url_expire']
553 : 20;
554 $expireSeconds = $expireMinutes * 60;
555
556 Integration::update_meta($source_id, 'private_url_'.$size, $privateUrl, false, $expireSeconds, $source_type);
557 }
558 }
559 return $privateUrl;
560 } else {
561 $url = Service::instance()->get_url($key);
562 if(!Utils::is_empty($url)) {
563 return Cdn::may_generate_cdn_url($url, $key);
564 }
565 }
566 }
567 }
568 return false;
569 }
570
571
572
573 /**
574 * Move file to server, given item id and size
575 * If $all is true, it will move all files to server
576 * If $backup is true, it will move backup file to server
577 * @param int $source_id source id of item
578 * @param string $size size of the file, default is full
579 * @param string $source_type source type of item, default is media_library
580 * @param bool $all if true, it will move all files to server
581 * @param bool $backup if true, it will move backup file to server
582 * @return array an array of server file paths
583 */
584 public function moveToServer($source_id, $size = 'full', $source_type = 'media_library', $all = false, $backup = false){
585 $server_files = [];
586 $server_file = false;
587 $source_id = (int)$source_id;
588 $item = $this->get($source_id, $source_type);
589
590 // Remove log if exists before move to server
591 Logger::instance()->remove_log('restore_to_server', $source_id, $source_type);
592
593 if ( isset($item) && !empty($item) ) {
594 $files = $this->moveToServerByItem($item, $size, $all);
595 if (isset($files) && !empty($files)) {
596 $server_files = $all ? array_merge($server_files, $files) : $files;
597 }
598 }
599 if( $all && $backup ) {
600 $backupItem = $this->get_backup($source_id, $source_type);
601 if (isset($backupItem) && !empty($backupItem)) {
602 $files = $this->moveToServerByItem($backupItem, $size, $all);
603 if (isset($files) && !empty($files)) {
604 $server_files['backup'] = $files;
605 }
606 }
607 }
608 return $server_files;
609 }
610
611
612 /**
613 * Copy back an item from the service to the server
614 *
615 * @since 1.0.0
616 * @param array $item
617 * @param string $size
618 * @param bool $all
619 * @return array|string
620 */
621 public function moveToServerByItem( $item = [], $size = 'full', $all = false ) {
622 $source_id = (int) ( $item['source_id'] ?? 0 );
623 // Validate source ID
624 if( $source_id <= 0 ) {
625 return false;
626 }
627
628 $source_type = $item['source_type'] ?? 'media_library';
629 $extras = ! empty( $item['extra'] ) ? Utils::maybe_unserialize( $item['extra'] ) : [];
630
631 // Build file map once
632 $files = [
633 'full' => [
634 'key' => $item['key'] ?? null,
635 'path' => $item['source_path'] ?? null,
636 ],
637 'original' => [
638 'key' => $item['original_key'] ?? null,
639 'path' => $item['original_source_path'] ?? null,
640 ],
641 ];
642
643 if ( ! empty( $extras['sizes'] ) ) {
644 foreach ( $extras['sizes'] as $name => $data ) {
645 $files[ $name ] = [
646 'key' => $data['key'] ?? null,
647 'path' => $data['source_path'] ?? null,
648 ];
649 }
650 }
651
652 // ALL files
653 if ( $all ) {
654 $results = [];
655
656 foreach ( $files as $label => $data ) {
657 if ( $file = $this->move_to_server_by_key_and_path(
658 $data['key'],
659 $data['path'],
660 $source_id,
661 $source_type
662 ) ) {
663 $results[ $label ] = $file;
664 }
665 }
666
667 return $results;
668 }
669
670 // SINGLE file
671 if ( isset( $files[ $size ] ) ) {
672 return $this->move_to_server_by_key_and_path(
673 $files[ $size ]['key'],
674 $files[ $size ]['path'],
675 $source_id,
676 $source_type
677 );
678 }
679
680 return false;
681 }
682
683
684
685 /**
686 * Get service path of item from database by source url
687 * @since 1.0.0
688 * @param int $source_id
689 * @param string $file
690 * @param string $source_type
691 * @return bool
692 */
693 public function moveToServerBySourcePath( $source_id, $file, $source_type = 'media_library' ) {
694 $source_id = (int) $source_id;
695
696 $item = $this->get( $source_id, $source_type );
697 if ( Utils::is_empty( $item ) ) {
698 return false;
699 }
700
701 $source_path = Utils::get_attachment_source_path( $file );
702 if ( empty( $source_path ) ) {
703 return false;
704 }
705
706 // 1. Check main file
707 if (
708 isset( $item['source_path'] ) &&
709 ! empty( $item['source_path'] ) &&
710 $item['source_path'] === $source_path &&
711 $this->move_to_server_by_key_and_path(
712 $item['key'] ?? null,
713 $item['source_path'],
714 $source_id,
715 $source_type
716 )
717 ) {
718 return true;
719 }
720
721
722 // 2. Check original
723 if (
724 isset( $item['original_source_path'] ) && ! empty( $item['original_source_path'] ) &&
725 $item['original_source_path'] === $source_path &&
726 $this->move_to_server_by_key_and_path(
727 $item['original_key'] ?? null,
728 $item['original_source_path'],
729 $source_id,
730 $source_type
731 )
732 ) {
733 return true;
734 }
735
736 $extras = $this->get_extras( $source_id, false, $source_type ) ?: [];
737
738 // 3. Check sizes
739 if ( ! empty( $extras['sizes'] ) ) {
740 foreach ( $extras['sizes'] as $size ) {
741 if (
742 isset( $size['source_path'] ) &&
743 ! empty( $size['source_path'] ) &&
744 $size['source_path'] === $source_path &&
745 $this->move_to_server_by_key_and_path(
746 $size['key'] ?? null,
747 $size['source_path'],
748 $source_id,
749 $source_type
750 )
751 ) {
752 return true;
753 }
754 }
755 }
756
757 return false;
758 }
759
760 /**
761 * Copy back a file from the service to the server
762 *
763 * @param string $key
764 * @param string $relative_path
765 * @param int $source_id
766 * @param string $source_type
767 *
768 * @return string|false
769 */
770 protected function move_to_server_by_key_and_path( $key, $relative_path, $source_id = 0, $source_type = 'media_library' ) {
771 if ( empty( $key ) || empty( $relative_path ) ) {
772 return false;
773 }
774
775 $upload_dir = wp_get_upload_dir();
776 $file = trailingslashit( $upload_dir['basedir'] ) . $relative_path;
777
778 if ( file_exists( $file ) ) {
779 return $file;
780 }
781
782 if ( Service::instance()->object_to_server( $key, $file ) ) {
783 return $file;
784 }
785
786 Logger::instance()->add_log( 'restore_to_server', $source_id, $source_type, [
787 'message' => __( 'The file could not be copied to the server. Please try again.', 'media-cloud-sync' ),
788 'file' => $key,
789 'code' => 404,
790 ] );
791
792 return false;
793 }
794
795
796 /**
797 * Move original file to server
798 *
799 * @param int $source_id
800 * @param string $source_type
801 * @return bool
802 */
803 public function moveOriginalToServer($source_id, $source_type = 'media_library') {
804 $data = $this->get($source_id, $source_type);
805 if ($data) {
806 $size = 'full';
807 if (
808 isset($data['original_source_path']) && !empty($data['original_source_path']) &&
809 isset($data['original_key']) && !empty($data['original_key'])
810 ) {
811 $size = 'original';
812 }
813 return $this->moveToServer($source_id, $size, $source_type);
814 }
815 return false;
816 }
817
818
819 /**
820 * Delete media item
821 */
822 public function delete_attachments_by_item($item, $delete_backup = true) {
823 $upload_dir = wp_get_upload_dir();
824
825 if (isset($item['extra']) && !empty($item['extra'])) {
826 $extras = Utils::maybe_unserialize($item['extra']);
827 if (
828 isset($extras) && !empty($extras) &&
829 isset($extras['sizes']) && !empty($extras['sizes'])
830 ) {
831 foreach ($extras['sizes'] as $sub_image) {
832 if (isset($sub_image['key']) && !empty($sub_image['key'])) {
833 Service::instance()->deleteSingle($sub_image['key']);
834 }
835 }
836 }
837
838 if (
839 isset($extras) && !empty($extras) &&
840 isset($extras['backup']) && !empty($extras['backup']) &&
841 $delete_backup
842 ) {
843 $backup = Utils::maybe_unserialize($extras['backup']);
844 if (isset($backup) && !empty($backup)) {
845 $this->delete_attachments_by_item($backup, false);
846 }
847 }
848 }
849
850 if (
851 isset($item['original_key']) && !empty($item['original_key'])
852 ) {
853 Service::instance()->deleteSingle($item['original_key']);
854 }
855
856 if (isset($item['key']) && !empty($item['key'])) {
857 Service::instance()->deleteSingle($item['key']);
858 }
859 }
860
861
862 /**
863 * Delete Cloud Files by Keys
864 * @since 1.3.6
865 */
866 public function delete_cloud_files_by_keys( $keys = [] ) {
867 if (Utils::is_empty($keys) || !is_array($keys)) {
868 return false;
869 }
870
871 foreach ($keys as $key) {
872 Service::instance()->deleteSingle( $key );
873 }
874 return true;
875 }
876
877
878
879 /**
880 * Pre-update item actions
881 * @since 1.2.13
882 * @param int $source_id
883 * @param array $data
884 * @param string $source_type
885 */
886 public function pre_update_item($source_id, $new_item, $old_item = [], $source_type = 'media_library') {
887 // Hook for pre-update actions
888 do_action('wpmcs_pre_update_item', $source_id, $new_item, $old_item, $source_type);
889
890 // Additional filter to modify files to be removed from server if needed
891 $files_to_remove = apply_filters('wpmcs_pre_update_item_additional_files_to_remove_from_server', [], $source_id, $new_item, $old_item, $source_type);
892
893 // Delete files if any
894 if (!Utils::is_empty($files_to_remove)) {
895 $this->may_be_delete_server_files_by_source_paths($files_to_remove);
896 }
897 }
898
899
900 /**
901 * Post-update item actions
902 * @since 1.2.13
903 * @param int $source_id
904 * @param array $data
905 * @param string $source_type
906 * This function is called after an item has been updated in the database.
907 * It triggers a WordPress action hook 'wpmcs_post_update_item' to allow other functions to hook into this event.
908 * After that, it calls may_be_delete_server_files_by_id to potentially delete server files associated with the item.
909 *
910 * @example
911 * $item = Item::instance();
912 * $item->post_update_item(123, $data, 'media_library');
913 *
914 * This example will trigger the post-update actions for the item with ID 123.
915 * It will execute any functions hooked to 'wpmcs_post_update_item' and may delete server files if the settings allow it.
916 */
917 public function post_update_item($source_id, $data, $source_type = 'media_library') {
918 // Hook for post-update actions
919 do_action('wpmcs_post_update_item', $source_id, $data, $source_type);
920
921 // May be delete server files
922 $this->may_be_delete_server_files_by_id($source_id, $source_type, true, true);
923 }
924
925
926 public function may_be_delete_server_files_by_source_paths($source_paths) {
927 if (Utils::is_empty($source_paths) || !is_array($source_paths)) {
928 return false;
929 }
930
931 if( !( isset($this->settings['remove_from_server']) && $this->settings['remove_from_server'] ) ) {
932 return false;
933 }
934
935 foreach ($source_paths as $path) {
936 if(file_exists($path)) {
937 wp_delete_file($path, true);
938 }
939 }
940 return true;
941 }
942
943 /**
944 * Function to remove media from server by id
945 * @param int $attachment_id
946 * @param string $source_type
947 * @param bool $delete_main_file
948 * @param bool $delete_backup
949 *
950 * This function checks if the item exists and if the setting to remove from server is enabled.
951 * If so, it deletes the main file and any backup files associated with the item.
952 * It also checks if the item has any extra data, and if so, it attempts to delete the backup files if specified.
953 * Finally, it deletes the main file associated with the item.
954 *
955 * @since 1.2.13
956 * @return bool Returns true if the deletion process was initiated, false otherwise.
957 *
958 * @throws \Exception If the item does not exist or if the removal from server setting is not enabled.
959 *
960 * @example
961 * $item = Item::instance();
962 * $item->may_be_delete_server_files_by_id(123, 'media_library', true, true);
963 *
964 * This example will attempt to delete the server files for the attachment with ID 123,
965 * including the main file and any backup files, if the settings allow it.
966 *
967 * @see Item::may_be_delete_server_files_by_item() for the function that actually performs the deletion.
968 */
969 public function may_be_delete_server_files_by_id($attachment_id, $source_type = 'media_library', $delete_main_file=false, $delete_backup = false) {
970 $item = $this->get($attachment_id, $source_type);
971 if(Utils::is_empty($item)) {
972 return false;
973 }
974
975 if( !( isset($this->settings['remove_from_server']) && $this->settings['remove_from_server'] ) ) {
976 return false;
977 }
978
979 if (isset($item['extra']) && !empty($item['extra'])) {
980 $extras = Utils::maybe_unserialize($item['extra']);
981 if (
982 isset($extras) && !empty($extras) &&
983 isset($extras['backup']) && !empty($extras['backup']) &&
984 $delete_backup
985 ) {
986 $backup = Utils::maybe_unserialize($extras['backup']);
987 if (isset($backup) && !empty($backup)) {
988 $this->may_be_delete_server_files_by_item($backup, $delete_main_file);
989 }
990 }
991 }
992
993 $this->may_be_delete_server_files_by_item($item, $delete_main_file, $delete_backup);
994
995 return true;
996 }
997
998 /**
999 * Function to remove media from server by item
1000 */
1001 public function may_be_delete_server_files_by_item( $item, $delete_main_file=false ) {
1002 if(Utils::is_empty($item)) {
1003 return false;
1004 }
1005
1006 if( !( isset($this->settings['remove_from_server']) && $this->settings['remove_from_server'] ) ) {
1007 return false;
1008 }
1009
1010 return $this->delete_server_files_by_item( $item, $delete_main_file );
1011 }
1012
1013
1014 /**
1015 * Function to remove media from server by item
1016 */
1017 public function delete_server_files_by_item( $item, $delete_main_file=false ) {
1018 $upload_dir = wp_get_upload_dir();
1019 $has_original = false;
1020 $files_to_remove = array();
1021
1022 $file_path = trailingslashit($upload_dir['basedir']) . $item['source_path'];
1023
1024 if (isset($item['extra']) && !empty($item['extra'])) {
1025 $extras = Utils::maybe_unserialize($item['extra']);
1026 if (
1027 isset($extras) && !empty($extras) &&
1028 isset($extras['sizes']) && !empty($extras['sizes'])
1029 ) {
1030 foreach ($extras['sizes'] as $sub_image) {
1031 if (isset($sub_image['source_path']) && !empty($sub_image['source_path'])) {
1032 $file = trailingslashit($upload_dir['basedir']) . $sub_image['source_path'];
1033 if(file_exists($file)) {
1034 $files_to_remove[] = $file;
1035 }
1036 }
1037 }
1038 }
1039 }
1040 if (
1041 isset($item['original_source_path']) && !empty($item['original_source_path'])
1042 ) {
1043 $has_original = true;
1044 $file = trailingslashit($upload_dir['basedir']).$item['original_source_path'];
1045 if(file_exists($file) && $delete_main_file) {
1046 $files_to_remove[] = $file;
1047 }
1048 }
1049 if(file_exists($file_path)) {
1050 if ($has_original || (!$has_original && $delete_main_file)) {
1051 $files_to_remove[] = $file_path;
1052 }
1053 }
1054
1055
1056 $files_to_remove = apply_filters('wpmcs_files_to_remove_from_server', array_unique($files_to_remove), $item['source_id'], $item);
1057
1058 if (!Utils::is_empty($files_to_remove)) {
1059 foreach ($files_to_remove as $file) {
1060 wp_delete_file($file, true);
1061 }
1062 }
1063
1064 return true;
1065 }
1066
1067 /**
1068 * Ensures only one instance of Class is loaded or can be loaded.
1069 *
1070 * @return Item Class instance
1071 * @since 1.0.0
1072 * @static
1073 */
1074 public static function instance(){
1075 if (is_null(self::$instance)) {
1076 self::$instance = new self();
1077 }
1078 return self::$instance;
1079 }
1080 }