PluginProbe
Media Cloud Sync / 1.4.1
Media Cloud Sync v1.4.1
1.4.1 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 All 35 releases
← All changes | includes/base/item.php +997 -175 1.0.11.4.1 View file →
@@ -18,8 +18,17 @@
18 18 protected $bucket_name;
19 19 protected $region = '';
20 20
21 21 /**
22 + * Absolute paths restored from cloud (by any integration) pending removal
23 + * again — fed into the pre-update pipeline if a save happens this request,
24 + * with a shutdown fallback otherwise. See track_restored_for_cleanup().
25 + * @since 1.4.0
26 + */
27 + protected $pending_restored_files = [];
28 + protected $pending_cleanup_hooked = false;
29 +
30 + /**
22 31 * Admin constructor.
23 32 * @since 1.0.0
24 33 */
25 34 public function __construct() {
@@ -44,16 +53,12 @@
44 53 ? $this->credentials['bucketConfig']
45 54 : [];
46 55 $this->service = isset($this->credentials['service']) && !empty($this->credentials['service'])
47 56 ? $this->credentials['service']
48 - : [];
57 + : '';
49 58
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 - }
59 + $this->bucket_name = isset($this->bucketConfig['bucket_name']) ? $this->bucketConfig['bucket_name'] : '';
60 + $this->region = isset($this->config['region']) ? $this->config['region'] : '';
56 61 }
57 62
58 63 /**
59 64 * Add Item In database
@@ -64,35 +69,75 @@
64 69 $source_id,
65 70 $url,
66 71 $key,
67 72 $source_path,
68 - $meta,
73 + $original_source_path = '',
74 + $original_key = '',
75 + $meta = array(),
69 76 $source_type = 'media_library',
70 77 $is_private = 0
71 78 ) {
72 79 global $wpdb;
73 80 $item_id = false;
74 -
75 81 $data = array(
76 - 'provider' => $this->service,
77 - 'region' => $this->region,
78 - 'storage' => $this->bucket_name,
79 - 'source_id' => $source_id,
80 - 'source_path' => $source_path,
81 - 'source_type' => $source_type,
82 - 'url' => $url,
83 - 'key' => $key,
84 - 'is_private' => $is_private,
85 - 'extra' => maybe_serialize($meta),
82 + 'provider' => $this->service,
83 + 'region' => $this->region,
84 + 'storage' => $this->bucket_name,
85 + 'source_id' => $source_id,
86 + 'source_path' => $source_path,
87 + 'source_type' => $source_type,
88 + 'url' => $url,
89 + 'key' => $key,
90 + 'original_source_path' => $original_source_path,
91 + 'original_key' => $original_key,
92 + 'is_private' => $is_private,
93 + 'extra' => Utils::maybe_serialize($meta),
86 94 );
87 - if ($wpdb->insert(WPMCS_ITEM_TABLE, $data)) {
88 - $item_id = $wpdb->insert_id;
95 +
96 + // Do some pre-update actions
97 + $this->pre_update_item($source_id, $data, [], $source_type);
98 +
99 + // Upsert instead of a plain insert — two independent triggers (this plugin's bulk
100 + // sync, WordPress's own metadata hook, Imagify's re-sync) can land on the same
101 + // attachment around the same time; this converges on one row instead of erroring.
102 + $table = Db::get_table_name();
103 + $inserted = $wpdb->query($wpdb->prepare(
104 + "INSERT INTO {$table}
105 + (provider, region, storage, source_id, source_path, source_type, url, `key`, original_source_path, original_key, is_private, extra)
106 + VALUES (%s, %s, %s, %d, %s, %s, %s, %s, %s, %s, %d, %s)
107 + ON DUPLICATE KEY UPDATE
108 + id = LAST_INSERT_ID(id),
109 + source_path = VALUES(source_path),
110 + url = VALUES(url),
111 + `key` = VALUES(`key`),
112 + original_source_path = VALUES(original_source_path),
113 + original_key = VALUES(original_key),
114 + is_private = VALUES(is_private),
115 + extra = VALUES(extra)",
116 + $data['provider'], $data['region'], $data['storage'], $data['source_id'], $data['source_path'],
117 + $data['source_type'], $data['url'], $data['key'], $data['original_source_path'], $data['original_key'],
118 + $data['is_private'], $data['extra']
119 + ));
120 +
121 + if ($inserted !== false) {
122 + // rows_affected: 1 = new row, 2 = existing row updated. Capture before any
123 + // other query on $wpdb overwrites it.
124 + $is_new_row = (int) $wpdb->rows_affected === 1;
125 +
126 + $item_id = (int) $wpdb->insert_id;
89 127 $data['id'] = $item_id;
90 - Utils::update_meta($source_id, 'item', $data);
91 - Cache::update_item_cache($source_id.'_item', $data);
92 - Counter::add( 'uploaded' );
128 + Integration::update_meta($source_id, 'item', $data, false, false, $source_type);
129 + Cache::update_item_cache($source_id.'_item_'.$source_type, $data);
130 +
131 + // Only count a genuinely new item, not a duplicate-collision update.
132 + if ($is_new_row) {
133 + Counter::add( 'uploaded', $source_type );
134 + }
93 135 }
94 136
137 + // Do some post-update actions
138 + $this->post_update_item($source_id, $data, $source_type);
139 +
95 140 return $item_id;
96 141 }
97 142
98 143
@@ -100,26 +145,47 @@
100 145 * Get Item From Db
101 146 */
102 147 public function get($source_id, $source_type = 'media_library') {
103 148 global $wpdb;
104 - $item = Cache::get_item_cache($source_id.'_item', false);
105 - if($item == false) {
106 - $item = Utils::get_meta($source_id, 'item', false);
149 + $item = Cache::get_item_cache($source_id.'_item_'.$source_type, false);
150 + if($item === false) {
151 + $item = Integration::get_meta($source_id, 'item', false, false, false, $source_type);
107 152 if($item == false){
108 - $item = $wpdb->get_row("SELECT * FROM ".WPMCS_ITEM_TABLE." WHERE source_id = $source_id AND source_type='$source_type'", ARRAY_A);
153 + $item_table = Db::get_table_name();
154 + $query = "SELECT * FROM {$item_table}
155 + WHERE source_id = %d
156 + AND source_type = %s
157 + AND provider = %s
158 + AND storage = %s";
159 +
160 + if(!empty($this->region)) {
161 + $query .= " AND region = %s";
162 + $query = $wpdb->prepare($query, $source_id, $source_type, $this->service, $this->bucket_name, $this->region);
163 + } else {
164 + $query = $wpdb->prepare($query, $source_id, $source_type, $this->service, $this->bucket_name);
165 + }
166 +
167 + $item = $wpdb->get_row( $query, ARRAY_A);
109 168
110 169 if($wpdb->last_error || null === $item || !(isset($item) && !empty($item))) {
170 + Cache::update_item_cache($source_id.'_item_'.$source_type, '');
111 171 return false;
112 172 }
113 173
114 - Utils::update_meta($source_id, 'item', $item);
115 - Cache::update_item_cache($source_id.'_item', $item);
116 - } else {
117 - Cache::update_item_cache($source_id.'_item', $item);
118 - }
174 + Integration::update_meta($source_id, 'item', $item, false, false, $source_type);
175 + }
176 + // Update cache
177 + Cache::update_item_cache($source_id.'_item_'.$source_type, $item == false ? '' : $item);
119 178 }
120 179
121 - return $item;
180 + /**
181 + * Filter to modify item data when retrieved
182 + * @param array|false $item
183 + * @param int $source_id
184 + * @param string $source_type
185 + * @since 1.3.5
186 + */
187 + return apply_filters( 'wpmcs_get_item' , !empty($item) ? $item : false, $source_id, $source_type );
122 188 }
123 189
124 190 /**
125 191 * Function to delete item from data base
@@ -124,18 +190,35 @@
124 190 /**
125 191 * Function to delete item from data base
126 192 * @since 1.0.0
127 193 */
128 - public function delete($source_id){
194 + public function delete($source_id, $source_type = 'media_library'){
129 195 global $wpdb;
130 - if (isset($source_id) && !empty($source_id)) {
131 - $rows = $wpdb->delete(WPMCS_ITEM_TABLE, array('source_id' => $source_id));
196 + if (isset($source_id) && !Utils::is_empty($source_id)) {
197 + // Delete attachments by item from cloud
198 + $item = $this->get($source_id, $source_type);
199 + $this->delete_attachments_by_item($item);
200 +
201 + $where = array(
202 + 'source_id' => $source_id,
203 + 'source_type' => $source_type,
204 + 'provider' => $this->service,
205 + 'storage' => $this->bucket_name,
206 + );
207 + if(!empty($this->region)) {
208 + $where['region'] = $this->region;
209 + }
210 + $rows = $wpdb->delete( Db::get_table_name(), $where );
132 211 if ($wpdb->last_error || false === $rows) {
133 212 return false;
134 213 }
135 - Utils::delete_meta($source_id, 'item');
136 - Cache::delete_item_cache($source_id.'_item');
137 - Counter::remove( 'uploaded' );
214 + Integration::delete_meta($source_id, 'item', false, $source_type);
215 + Cache::delete_item_cache($source_id.'_item_'.$source_type);
216 + Counter::remove( 'uploaded', $source_type );
217 +
218 + // Remove logs by media ID & source type
219 + Logger::instance()->remove_log_by_media_id($source_id, $source_type);
220 +
138 221 return true;
139 222 }
140 223 return false;
141 224 }
@@ -143,27 +226,85 @@
143 226
144 227 /**
145 228 * Function to update item in data base
146 229 * @since 1.0.0
230 + * @param array|null $target_identity Optional ['provider'=>, 'storage'=>, 'region'=>] to
231 + * move the row to a different connection's identity.
232 + * The WHERE clause still uses this instance's own
233 + * (source) binding to locate the row — only the SET
234 + * values change. @since 1.4.0
147 235 */
148 - public function update($source_id, $data){
236 + public function update($source_id, $data, $source_type = 'media_library', $target_identity = null) {
149 237 global $wpdb;
150 - if (isset($source_id) && !empty($source_id)) {
238 + if (isset($source_id) && !Utils::is_empty($source_id)) {
239 + $data['provider'] = $target_identity['provider'] ?? $this->service;
240 + $data['storage'] = $target_identity['storage'] ?? $this->bucket_name;
241 + $data['region'] = $target_identity['region'] ?? $this->region;
151 242
152 - $data['provider'] = $this->service;
153 - $data['region'] = $this->region;
154 - $data['storage'] = $this->bucket_name;
243 + $old_item = $this->get($source_id, $source_type);
155 244
156 - $rows = $wpdb->update(WPMCS_ITEM_TABLE, $data, array('source_id' => $source_id));
245 + // update data from $old_item if not exists in $data
246 + if (isset($old_item) && !empty($old_item)) {
247 + foreach ($old_item as $key => $value) {
248 + if (!isset($data[$key])) {
249 + $data[$key] = $value;
250 + }
251 + }
252 + }
253 +
254 + // Do some pre-update actions
255 + $this->pre_update_item($source_id, $data, $old_item, $source_type);
256 +
257 + // Moving to a different identity (e.g. bucket_to_bucket migration) can collide
258 + // with a stale row already sitting at that target identity — e.g. an interrupted
259 + // earlier migration attempt, or the same item independently tracked under a
260 + // connection this site used previously. uidx_item_source is UNIQUE on
261 + // (source_id, source_type, provider, storage, region), so the UPDATE below would
262 + // otherwise fail outright. The row being updated here is the current, live one;
263 + // a pre-existing row already at the target is stale by definition — clear it
264 + // first rather than letting the whole update silently fail.
265 + $is_identity_move = $target_identity !== null && (
266 + $data['provider'] !== $this->service ||
267 + $data['storage'] !== $this->bucket_name ||
268 + $data['region'] !== $this->region
269 + );
270 + if ($is_identity_move) {
271 + $target_where = array(
272 + 'source_id' => $source_id,
273 + 'source_type' => $source_type,
274 + 'provider' => $data['provider'],
275 + 'storage' => $data['storage'],
276 + 'region' => $data['region'],
277 + );
278 + $wpdb->delete(Db::get_table_name(), $target_where);
279 + }
280 +
281 + $where = array(
282 + 'source_id' => $source_id,
283 + 'source_type' => $source_type,
284 + 'provider' => $this->service,
285 + 'storage' => $this->bucket_name,
286 + );
287 + if(!empty($this->region)) {
288 + $where['region'] = $this->region;
289 + }
290 + $rows = $wpdb->update(Db::get_table_name(), $data, $where);
157 291 if ($wpdb->last_error || false === $rows) {
158 292 return false;
159 293 }
160 294
161 - Utils::delete_meta($source_id, 'item');
162 - Cache::delete_item_cache($source_id.'_item');
295 + Integration::delete_meta($source_id, 'item', false, $source_type);
296 + Cache::delete_item_cache($source_id.'_item_'.$source_type);
163 297
164 - // Reset cache
165 - $current_data = $this->get($source_id);
298 + if ($target_identity === null) {
299 + // Reset cache — skipped when moving to a different identity: this instance's
300 + // get() would search under the now-stale source identity and cache a false
301 + // negative; the delete_item_cache() above is sufficient on its own there.
302 + $this->get($source_id, $source_type);
303 + }
304 + // Do some post-update actions
305 + $this->post_update_item($source_id, $data, $source_type);
306 +
166 307 return true;
167 308 }
168 309 return false;
169 310 }
@@ -172,19 +313,19 @@
172 313 * Get extra values of item from database
173 314 * @since 1.0.0
174 315 * @param
175 316 */
176 - public function get_extras($source_id, $field = false){
177 - $data = $this->get($source_id);
317 + public function get_extras($source_id, $field = false, $source_type = 'media_library'){
318 + $data = $this->get($source_id, $source_type);
178 319 if ($data) {
179 320 if (isset($data['extra']) && !empty($data['extra'])) {
180 321
181 322 if(!Utils::is_empty($field)) {
182 - $extras = unserialize($data['extra']);
323 + $extras = Utils::maybe_unserialize($data['extra']);
183 324 return isset($extras[$field]) && !empty($extras[$field]) ? $extras[$field] : false;
184 325 }
185 326
186 - return unserialize($data['extra']);
327 + return Utils::maybe_unserialize($data['extra']);
187 328 }
188 329 }
189 330 return false;
190 331 }
@@ -194,10 +335,10 @@
194 335 * Get column of item from database
195 336 * @since 1.0.0
196 337 * @param
197 338 */
198 - public function get_field($source_id, $field){
199 - $data = $this->get($source_id);
339 + public function get_field($source_id, $field, $source_type = 'media_library'){
340 + $data = $this->get($source_id, $source_type);
200 341 if ($data) {
201 342 if (isset($data[$field]) && !empty($data[$field])) {
202 343 return $data[$field];
203 344 }
@@ -210,13 +351,13 @@
210 351 * Get backup values of item from database
211 352 * @since 1.0.0
212 353 * @param
213 354 */
214 - public function get_backup($source_id){
215 - $data = $this->get_extras($source_id);
355 + public function get_backup($source_id, $source_type = 'media_library'){
356 + $data = $this->get_extras($source_id, false, $source_type);
216 357 if ($data) {
217 358 if (isset($data['backup']) && !empty($data['backup'])) {
218 - return unserialize($data['backup']);
359 + return Utils::maybe_unserialize($data['backup']);
219 360 }
220 361 }
221 362 return false;
222 363 }
@@ -226,83 +367,223 @@
226 367 * Checking Item that is served by provider And Is rewrite URL is enabled
227 368 * @since 1.0.0
228 369 * @param
229 370 */
230 - public function is_available_from_provider($attachment_id, $check_rewrite = true){
231 - $item = $this->get($attachment_id);
371 + public function is_available_from_provider($attachment_id, $check_rewrite = true, $source_type = 'media_library') {
372 + $item = $this->get($attachment_id, $source_type);
232 373 if(Utils::is_empty($item)) {
233 374 return false;
234 375 }
235 376
236 - if ($item['provider'] == $this->service) {
377 + if (
378 + $item['provider'] == $this->service &&
379 + $item['storage'] == $this->bucket_name &&
380 + $item['source_type'] == $source_type
381 + ) {
237 382 if(
238 383 ($check_rewrite && (isset($this->settings['rewrite_url']) && $this->settings['rewrite_url'])) ||
239 384 !$check_rewrite
240 385 ) {
386 + if ($check_rewrite) {
387 + return (bool) apply_filters('wpmcs_is_available_from_provider', true, $attachment_id, $source_type);
388 + }
241 389 return true;
242 - }
390 + }
243 391 }
244 392 return false;
245 393 }
246 394
247 395 /**
248 - * Get items by paths
396 + * Get items by source paths
397 + *
398 + * @param array|string $paths
399 + * @param bool $exact_match Use exact paths or greedy match
400 + * @param bool $first_only Return only the first matched item
401 + *
402 + * @return array
249 403 */
250 - public function get_items_by_source_paths( $paths = [] ) {
404 + public function get_items_by_paths( $paths, $exact_match = true, $first_only = false, $type = 'source' ) {
251 405 global $wpdb;
252 406
253 - $items = [];
407 + if ( ! is_array( $paths ) && is_string( $paths ) && ! empty( $paths ) ) {
408 + $paths = [ $paths ];
409 + }
254 410
255 - if (!Utils::is_empty($paths)) {
256 - // Ensure paths are properly sanitized and ready for the query
257 - $like_conditions = array();
258 - foreach ($paths as $path) {
259 - // Prepare the SQL conditions for exact match in source_path and partial match in extras column
260 - $like_conditions[] = $wpdb->prepare("(source_path = %s OR extra LIKE %s)", $path, '%' . $wpdb->esc_like($path) . '%');
261 - }
411 + if ( Utils::is_empty( $paths ) ) {
412 + return [];
413 + }
262 414
263 - // Combine the conditions with OR
264 - $where_clause = implode(' OR ', $like_conditions);
415 + /**
416 + * Field => Index mapping
417 + * field_name => index_name
418 + */
419 + switch ( $type ) {
420 + case 'key':
421 + $fields = [
422 + 'key' => 'uidx_key',
423 + 'original_key' => 'uidx_original_key',
424 + ];
425 + break;
265 426
266 - // Execute the SQL query to fetch source_id instead of source_path
267 - $sql = "SELECT DISTINCT source_id FROM " . WPMCS_ITEM_TABLE . " WHERE " . $where_clause;
268 - $results = $wpdb->get_results($sql, ARRAY_A);
427 + default:
428 + $fields = [
429 + 'source_path' => 'uidx_source_path',
430 + 'original_source_path' => 'uidx_original_source_path',
431 + ];
432 + break;
433 + }
269 434
270 - if ($wpdb->last_error || Utils::is_empty($results)) {
271 - return [];
435 + if ( empty( $fields ) ) {
436 + return [];
437 + }
438 +
439 + // Normalize & deduplicate
440 + $paths = array_unique( $paths );
441 +
442 + $table = Db::get_table_name();
443 +
444 + // Build USE INDEX clause from field map
445 + $index_list = implode( ', ', array_values( $fields ) );
446 +
447 + $sql = "
448 + SELECT DISTINCT source_id, source_type
449 + FROM {$table} USE INDEX ({$index_list})
450 + WHERE provider = %s
451 + AND storage = %s
452 + ";
453 +
454 + $params = [
455 + $this->service,
456 + $this->bucket_name,
457 + ];
458 +
459 + // Optional region
460 + if ( ! empty( $this->region ) ) {
461 + $sql .= " AND region = %s";
462 + $params[] = $this->region;
463 + }
464 +
465 + /**
466 + * Path conditions
467 + */
468 + $conditions = [];
469 +
470 + if ( $exact_match ) {
471 + $placeholders = implode( ',', array_fill( 0, count( $paths ), '%s' ) );
472 +
473 + foreach ( array_keys( $fields ) as $column ) {
474 + $conditions[] = "`{$column}` IN ({$placeholders})";
475 + foreach ( $paths as $path ) {
476 + $params[] = $path;
477 + }
272 478 }
479 + } else {
480 + foreach ( $paths as $path ) {
481 + $ext = pathinfo( $path, PATHINFO_EXTENSION );
482 + $base = $ext
483 + ? substr_replace( $path, '%', -strlen( $ext ) - 1 )
484 + : $path . '%';
273 485
274 - $source_ids = array();
275 - foreach ($results as $row) {
276 - $item = $this->get((int)$row['source_id']);
277 - if(!Utils::is_empty($item)) {
278 - $items[] = $item;
486 + foreach ( array_keys( $fields ) as $column ) {
487 + $conditions[] = "`{$column}` LIKE %s";
488 + $params[] = $base;
279 489 }
280 490 }
281 - return $items;
282 491 }
492 +
493 + if ( ! empty( $conditions ) ) {
494 + $sql .= " AND ( " . implode( ' OR ', $conditions ) . " )";
495 + }
496 +
497 + // First only
498 + if ( $first_only ) {
499 + $sql .= " ORDER BY source_id ASC LIMIT 1";
500 + }
501 +
502 + $prepared = $wpdb->prepare( $sql, $params );
503 + $results = $wpdb->get_results( $prepared, ARRAY_A );
504 +
505 + if ( $wpdb->last_error || empty( $results ) ) {
506 + return [];
507 + }
508 +
509 + // Hydration
510 + if ( $first_only ) {
511 + return $this->get(
512 + (int) $results[0]['source_id'],
513 + $results[0]['source_type']
514 + );
515 + }
516 +
517 + $items = [];
518 +
519 + foreach ( $results as $row ) {
520 + $item = $this->get(
521 + (int) $row['source_id'],
522 + $row['source_type']
523 + );
524 +
525 + if ( ! Utils::is_empty( $item ) ) {
526 + $items[] = $item;
527 + }
528 + }
529 +
283 530 return $items;
284 531 }
285 532
286 533
287 534 /**
288 - * Get similar existing files like origin path
535 + * Get similar existing files by source path prefix
536 + * Searches in source_path and original_source_path
537 + *
289 538 * @since 1.0.0
290 539 */
291 - public function get_similar_files_by_path($path){
540 + public function get_similar_files_by_path( $path ) {
292 541 global $wpdb;
293 - if (isset($path) && !empty($path)) {
294 - $results = $wpdb->get_results("SELECT source_path FROM " . WPMCS_ITEM_TABLE . " WHERE source_path LIKE '$path%'", ARRAY_A);
295 - if ($wpdb->last_error || null === $results || !(isset($results) && !empty($results))) {
296 - return false;
297 - }
298 - $source_paths = array();
299 - foreach ($results as $row) {
300 - $source_paths[] = $row['source_path'];
301 - }
302 - return $source_paths;
542 +
543 + if ( Utils::is_empty( $path ) ) {
544 + return false;
303 545 }
304 - return false;
546 +
547 + $table = Db::get_table_name();
548 + $like = $wpdb->esc_like( $path ) . '%';
549 +
550 + $base_where = "
551 + provider = %s
552 + AND storage = %s
553 + " . ( ! empty( $this->region ) ? "AND region = %s" : '' );
554 +
555 + $params = [ $this->service, $this->bucket_name ];
556 + if ( ! empty( $this->region ) ) {
557 + $params[] = $this->region;
558 + }
559 +
560 + $sql = "
561 + (
562 + SELECT source_path
563 + FROM {$table} USE INDEX (idx_source_path_provider)
564 + WHERE {$base_where}
565 + AND source_path LIKE %s
566 + )
567 + UNION DISTINCT
568 + (
569 + SELECT original_source_path AS source_path
570 + FROM {$table} USE INDEX (uidx_original_source_path)
571 + WHERE {$base_where}
572 + AND original_source_path LIKE %s
573 + )
574 + ";
575 +
576 + $params = array_merge( $params, [ $like ], $params, [ $like ] );
577 +
578 + $results = $wpdb->get_results(
579 + $wpdb->prepare( $sql, $params ),
580 + ARRAY_A
581 + );
582 +
583 + return ( $wpdb->last_error || empty( $results ) )
584 + ? false
585 + : array_column( $results, 'source_path' );
305 586 }
306 587
307 588
308 589 /**
@@ -309,27 +590,23 @@
309 590 * Get service url of item from database
310 591 * @since 1.0.0
311 592 * @param
312 593 */
313 - public function get_url($source_id, $size = 'full'){
314 - if ($data = $this->get($source_id)) {
315 - $extras = $this->get_extras($source_id);
316 - $key = '';
317 - $url = '';
594 + public function get_url($source_id, $size = 'full', $source_type = 'media_library'){
595 + if ($data = $this->get($source_id, $source_type)) {
596 + $key = '';
318 597 switch($size) {
319 598 case 'full':
320 599 $key = $data['key'];
321 - $url = $data['url'];
322 600 break;
323 601 case 'original':
324 - if(
325 - isset($extras) && !empty($extras) &&
326 - isset($extras['original']) && !empty($extras['original'])
327 - ) {
328 - $key = $extras['original']['key'];
329 - $url = $extras['original']['url'];
602 + if( isset($data['original_key']) && !empty($data['original_key']) ) {
603 + $key = $data['original_key'];
330 604 }
605 + break;
331 606 default:
607 + // Only named sizes need extras — skip fetching them for 'full'/'original'.
608 + $extras = $this->get_extras($source_id, false, $source_type) ?: [];
332 609 if(
333 610 isset($extras) && !empty($extras) &&
334 611 isset($extras['sizes']) && !empty($extras['sizes']) &&
335 612 isset($extras['sizes'][$size]) && !empty($extras['sizes'][$size])
@@ -334,35 +611,36 @@
334 611 isset($extras['sizes']) && !empty($extras['sizes']) &&
335 612 isset($extras['sizes'][$size]) && !empty($extras['sizes'][$size])
336 613 ) {
337 614 $key = $extras['sizes'][$size]['key'];
338 - $url = $extras['sizes'][$size]['url'];
339 615 }
340 616 }
341 617
342 618 if(!empty($key)){
343 - if (
344 - isset($this->settings['enable_presigned']) && $this->settings['enable_presigned'] &&
345 - isset($this->settings['presigned_expire']) && !empty($this->settings['presigned_expire'])
346 - ) {
347 - $preSignedUrl = Utils::get_meta( $source_id, 'presigned_url_'.$size );
348 - if ($preSignedUrl === false) {
349 - global $wpmcsService;
350 - $new_url = $wpmcsService->get_presigned_url($key);
619 + if (isset($data['is_private']) && $data['is_private']) {
620 + $privateUrl = Integration::get_meta( $source_id, 'private_url_'.$size, false, false, false, $source_type );
621 + if ($privateUrl === false) {
622 + $new_url = Service::instance()->get_private_url($key);
351 623
352 624 if (!Utils::is_empty($new_url)) {
353 - $preSignedUrl = Cdn::may_generate_cdn_url($new_url, $key);
354 - $expireMinutes = (int)(isset($this->settings['presigned_expire']) && !empty($this->settings['presigned_expire']))
355 - ? $this->settings['presigned_expire']
625 + // No hook (Pro inactive, or the current delivery provider hasn't
626 + // implemented one) means passthrough — same URL, unmodified. Real
627 + // per-CDN rewriting (e.g. CloudFront signed URLs) is a Pro concern.
628 + $privateUrl = apply_filters( 'wpmcs_generate_private_url', $new_url, $key );
629 + $expireMinutes = (int)(isset($this->settings['private_url_expire']) && !empty($this->settings['private_url_expire']))
630 + ? $this->settings['private_url_expire']
356 631 : 20;
357 632 $expireSeconds = $expireMinutes * 60;
358 633
359 - Utils::update_meta($source_id, 'presigned_url_'.$size, $preSignedUrl, false, $expireSeconds);
634 + Integration::update_meta($source_id, 'private_url_'.$size, $privateUrl, false, $expireSeconds, $source_type);
360 635 }
361 636 }
362 - return $preSignedUrl;
637 + return $privateUrl;
363 638 } else {
364 - return Cdn::may_generate_cdn_url($url, $key);
639 + $url = Service::instance()->get_url($key);
640 + if(!Utils::is_empty($url)) {
641 + return Cdn::may_generate_cdn_url($url, $key);
642 + }
365 643 }
366 644 }
367 645 }
368 646 return false;
@@ -368,71 +646,615 @@
368 646 return false;
369 647 }
370 648
371 649
372 - /**
373 - * Get service path of item from database
374 - * @since 1.0.0
375 - * @param
650 +
651 + /**
652 + * Move file to server, given item id and size
653 + * If $all is true, it will move all files to server
654 + * If $backup is true, it will move backup file to server
655 + * @param int $source_id source id of item
656 + * @param string $size size of the file, default is full
657 + * @param string $source_type source type of item, default is media_library
658 + * @param bool $all if true, it will move all files to server
659 + * @param bool $backup if true, it will move backup file to server
660 + * @param string $log_type error-log bucket to write to on failure — lets a caller other
661 + * than the "Restore to Server" job (e.g. "Remove from Cloud",
662 + * which also restores as a safety step) attribute failures to
663 + * its own error list instead of Restore to Server's.
664 + * @return array an array of server file paths
376 665 */
377 - public function moveToServer($source_id, $size = 'full', $all = false){
378 - $server_files = array();
666 + public function moveToServer($source_id, $size = 'full', $source_type = 'media_library', $all = false, $backup = false, $log_type = 'restore_to_server'){
667 + $server_files = [];
379 668 $server_file = false;
380 - $upload_dir = wp_get_upload_dir();
381 669 $source_id = (int)$source_id;
670 + $item = $this->get($source_id, $source_type);
382 671
383 - if ($data = $this->get($source_id)) {
384 - global $wpmcsService;
385 - if($all) {
672 + // Remove log if exists before move to server
673 + Logger::instance()->remove_log($log_type, $source_id, $source_type);
674 +
675 + if ( isset($item) && !empty($item) ) {
676 + $files = $this->moveToServerByItem($item, $size, $all, $log_type);
677 + if (isset($files) && !empty($files)) {
678 + $server_files = $all ? array_merge($server_files, $files) : $files;
679 + }
680 + }
681 + if( $all && $backup ) {
682 + $backupItem = $this->get_backup($source_id, $source_type);
683 + if (isset($backupItem) && !empty($backupItem)) {
684 + $files = $this->moveToServerByItem($backupItem, $size, $all, $log_type);
685 + if (isset($files) && !empty($files)) {
686 + $server_files['backup'] = $files;
687 + }
688 + }
689 + }
690 + return $server_files;
691 + }
692 +
693 +
694 + /**
695 + * Copy back an item from the service to the server
696 + *
697 + * @since 1.0.0
698 + * @param array $item
699 + * @param string $size
700 + * @param bool $all
701 + * @param string $log_type error-log bucket to write to on failure
702 + * @return array|string
703 + */
704 + public function moveToServerByItem( $item = [], $size = 'full', $all = false, $log_type = 'restore_to_server' ) {
705 + $source_id = (int) ( $item['source_id'] ?? 0 );
706 + // Validate source ID
707 + if( $source_id <= 0 ) {
708 + return false;
709 + }
710 +
711 + $source_type = $item['source_type'] ?? 'media_library';
712 + $extras = ! empty( $item['extra'] ) ? Utils::maybe_unserialize( $item['extra'] ) : [];
713 +
714 + // Build file map once
715 + $files = [
716 + 'full' => [
717 + 'key' => $item['key'] ?? null,
718 + 'path' => $item['source_path'] ?? null,
719 + ],
720 + 'original' => [
721 + 'key' => $item['original_key'] ?? null,
722 + 'path' => $item['original_source_path'] ?? null,
723 + ],
724 + ];
725 +
726 + if ( ! empty( $extras['sizes'] ) ) {
727 + foreach ( $extras['sizes'] as $name => $data ) {
728 + $files[ $name ] = [
729 + 'key' => $data['key'] ?? null,
730 + 'path' => $data['source_path'] ?? null,
731 + ];
732 + }
733 + }
734 +
735 + // ALL files
736 + if ( $all ) {
737 + $results = [];
738 +
739 + foreach ( $files as $label => $data ) {
740 + if ( $file = $this->move_to_server_by_key_and_path(
741 + $data['key'],
742 + $data['path'],
743 + $source_id,
744 + $source_type,
745 + $log_type
746 + ) ) {
747 + $results[ $label ] = $file;
748 + }
749 + }
750 +
751 + return $results;
752 + }
753 +
754 + // SINGLE file
755 + if ( isset( $files[ $size ] ) ) {
756 + return $this->move_to_server_by_key_and_path(
757 + $files[ $size ]['key'],
758 + $files[ $size ]['path'],
759 + $source_id,
760 + $source_type,
761 + $log_type
762 + );
763 + }
764 +
765 + return false;
766 + }
767 +
768 + /**
769 + * Whether a moveToServer(..., $all=true, $backup=true) result actually restored
770 + * everything this item is expected to have (every size, the original if present, and
771 + * the backup entry if one exists) — moveToServer()'s return silently drops any single
772 + * file that failed, so a plain non-empty check on it isn't enough to safely delete the
773 + * cloud copies afterward.
774 + * @since 1.4.1
775 + */
776 + public function verify_full_restore( $source_id, $source_type, $moved ) {
777 + $row = $this->get( $source_id, $source_type );
778 + if ( empty( $row ) ) {
779 + return false;
780 + }
781 +
782 + $expected = $this->expected_restore_labels( $row );
783 + $restored = array_diff( array_keys( (array) $moved ), [ 'backup' ] );
784 + if ( ! empty( array_diff( $expected, $restored ) ) ) {
785 + return false;
786 + }
787 +
788 + $backup_item = $this->get_backup( $source_id, $source_type );
789 + if ( empty( $backup_item ) ) {
790 + return true;
791 + }
792 +
793 + $expected_backup = $this->expected_restore_labels( $backup_item );
794 + $restored_backup = ! empty( $moved['backup'] ) ? array_keys( $moved['backup'] ) : [];
795 + return empty( array_diff( $expected_backup, $restored_backup ) );
796 + }
797 +
798 + /**
799 + * File labels (full, original, each named size) a given item row is expected to have.
800 + */
801 + private function expected_restore_labels( $item_row ) {
802 + $expected = [ 'full' ];
803 + if ( ! empty( $item_row['original_key'] ) || ! empty( $item_row['original_source_path'] ) ) {
804 + $expected[] = 'original';
805 + }
806 +
807 + $extras = ! empty( $item_row['extra'] ) ? Utils::maybe_unserialize( $item_row['extra'] ) : [];
808 + if ( ! empty( $extras['sizes'] ) ) {
809 + $expected = array_merge( $expected, array_keys( $extras['sizes'] ) );
810 + }
811 +
812 + return $expected;
813 + }
814 +
815 +
816 +
817 + /**
818 + * Get service path of item from database by source url
819 + * @since 1.0.0
820 + * @param int $source_id
821 + * @param string $file
822 + * @param string $source_type
823 + * @return bool
824 + */
825 + public function moveToServerBySourcePath( $source_id, $file, $source_type = 'media_library' ) {
826 + $source_id = (int) $source_id;
827 +
828 + $item = $this->get( $source_id, $source_type );
829 + if ( Utils::is_empty( $item ) ) {
830 + return false;
831 + }
832 +
833 + $source_path = Utils::get_attachment_source_path( $file );
834 + if ( empty( $source_path ) ) {
835 + return false;
836 + }
837 +
838 + // 1. Check main file
839 + if (
840 + isset( $item['source_path'] ) &&
841 + ! empty( $item['source_path'] ) &&
842 + $item['source_path'] === $source_path &&
843 + $this->move_to_server_by_key_and_path(
844 + $item['key'] ?? null,
845 + $item['source_path'],
846 + $source_id,
847 + $source_type
848 + )
849 + ) {
850 + return true;
851 + }
852 +
853 +
854 + // 2. Check original
855 + if (
856 + isset( $item['original_source_path'] ) && ! empty( $item['original_source_path'] ) &&
857 + $item['original_source_path'] === $source_path &&
858 + $this->move_to_server_by_key_and_path(
859 + $item['original_key'] ?? null,
860 + $item['original_source_path'],
861 + $source_id,
862 + $source_type
863 + )
864 + ) {
865 + return true;
866 + }
867 +
868 + $extras = $this->get_extras( $source_id, false, $source_type ) ?: [];
869 +
870 + // 3. Check sizes
871 + if ( ! empty( $extras['sizes'] ) ) {
872 + foreach ( $extras['sizes'] as $size ) {
386 873 if (
387 - isset($data['source_path']) && !empty($data['source_path']) &&
388 - isset($data['key']) && !empty($data['key'])
874 + isset( $size['source_path'] ) &&
875 + ! empty( $size['source_path'] ) &&
876 + $size['source_path'] === $source_path &&
877 + $this->move_to_server_by_key_and_path(
878 + $size['key'] ?? null,
879 + $size['source_path'],
880 + $source_id,
881 + $source_type
882 + )
389 883 ) {
390 - $file_path = trailingslashit($upload_dir['basedir']) . $data['source_path'];
391 - if(file_exists($file_path) || $wpmcsService->object_to_server($data['key'], $file_path)) {
392 - $server_files['full'] = $file_path;
884 + return true;
885 + }
886 + }
887 + }
888 +
889 + return false;
890 + }
891 +
892 + /**
893 + * Copy back a file from the service to the server
894 + *
895 + * @param string $key
896 + * @param string $relative_path
897 + * @param int $source_id
898 + * @param string $source_type
899 + * @param string $log_type error-log bucket to write to on failure
900 + *
901 + * @return string|false
902 + */
903 + protected function move_to_server_by_key_and_path( $key, $relative_path, $source_id = 0, $source_type = 'media_library', $log_type = 'restore_to_server' ) {
904 + if ( empty( $key ) || empty( $relative_path ) ) {
905 + return false;
906 + }
907 +
908 + $upload_dir = wp_get_upload_dir();
909 + $file = trailingslashit( $upload_dir['basedir'] ) . $relative_path;
910 +
911 + if ( file_exists( $file ) ) {
912 + return $file;
913 + }
914 +
915 + // Checked on disk rather than trusting the return value alone — at least one
916 + // provider (Cloudflare R2) has been observed writing the file successfully while
917 + // still reporting failure (an SDK-level error thrown after the save completes).
918 + Service::instance()->object_to_server( $key, $file );
919 +
920 + if ( file_exists( $file ) ) {
921 + return $file;
922 + }
923 +
924 + Logger::instance()->add_log( $log_type, $source_id, $source_type, [
925 + 'message' => __( 'The file could not be copied to the server. Please try again.', 'media-cloud-sync' ),
926 + 'file' => $key,
927 + 'code' => 404,
928 + ] );
929 +
930 + return false;
931 + }
932 +
933 +
934 + /**
935 + * Move original file to server
936 + *
937 + * @param int $source_id
938 + * @param string $source_type
939 + * @return bool
940 + */
941 + public function moveOriginalToServer($source_id, $source_type = 'media_library') {
942 + $data = $this->get($source_id, $source_type);
943 + if ($data) {
944 + $size = 'full';
945 + if (
946 + isset($data['original_source_path']) && !empty($data['original_source_path']) &&
947 + isset($data['original_key']) && !empty($data['original_key'])
948 + ) {
949 + $size = 'original';
950 + }
951 + return $this->moveToServer($source_id, $size, $source_type);
952 + }
953 + return false;
954 + }
955 +
956 +
957 + /**
958 + * Delete media item
959 + */
960 + public function delete_attachments_by_item($item, $delete_backup = true) {
961 + // Lets an integration veto the delete when another row still relies on the same key.
962 + if (!apply_filters('wpmcs_should_delete_cloud_files', true, $item)) {
963 + return;
964 + }
965 +
966 + $upload_dir = wp_get_upload_dir();
967 +
968 + if (isset($item['extra']) && !empty($item['extra'])) {
969 + $extras = Utils::maybe_unserialize($item['extra']);
970 + if (
971 + isset($extras) && !empty($extras) &&
972 + isset($extras['sizes']) && !empty($extras['sizes'])
973 + ) {
974 + foreach ($extras['sizes'] as $sub_image) {
975 + if (isset($sub_image['key']) && !empty($sub_image['key'])) {
976 + Service::instance()->deleteSingle($sub_image['key']);
393 977 }
394 978 }
395 - $extras = $this->get_extras($source_id) ? $this->get_extras($source_id) : [];
396 - if (isset($extras['sizes']) && !empty($extras['sizes'])) {
397 - $sizes = $extras['sizes'];
398 - if(isset($sizes[$size]) && !empty($sizes[$size])) {
399 - $sub_file_path = trailingslashit($upload_dir['basedir']) . $sizes[$size]['source_path'];
400 - if(file_exists($sub_file_path) || $wpmcsService->object_to_server($sizes[$size]['key'], $sub_file_path)) {
401 - $server_files[$size] = $sub_file_path;
979 + }
980 +
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->delete_attachments_by_item($backup, false);
989 + }
990 + }
991 + }
992 +
993 + if (
994 + isset($item['original_key']) && !empty($item['original_key'])
995 + ) {
996 + Service::instance()->deleteSingle($item['original_key']);
997 + }
998 +
999 + if (isset($item['key']) && !empty($item['key'])) {
1000 + Service::instance()->deleteSingle($item['key']);
1001 + }
1002 + }
1003 +
1004 +
1005 + /**
1006 + * Delete Cloud Files by Keys
1007 + * @since 1.3.6
1008 + */
1009 + public function delete_cloud_files_by_keys( $keys = [] ) {
1010 + if (Utils::is_empty($keys) || !is_array($keys)) {
1011 + return false;
1012 + }
1013 +
1014 + foreach ($keys as $key) {
1015 + Service::instance()->deleteSingle( $key );
1016 + }
1017 + return true;
1018 + }
1019 +
1020 +
1021 +
1022 + /**
1023 + * Pre-update item actions
1024 + * @since 1.2.13
1025 + * @param int $source_id
1026 + * @param array $data
1027 + * @param string $source_type
1028 + */
1029 + public function pre_update_item($source_id, $new_item, $old_item = [], $source_type = 'media_library') {
1030 + // Hook for pre-update actions
1031 + do_action('wpmcs_pre_update_item', $source_id, $new_item, $old_item, $source_type);
1032 +
1033 + // Additional filter to modify files to be removed from server if needed
1034 + $files_to_remove = apply_filters('wpmcs_pre_update_item_additional_files_to_remove_from_server', [], $source_id, $new_item, $old_item, $source_type);
1035 +
1036 + // Delete files if any
1037 + if (!Utils::is_empty($files_to_remove)) {
1038 + $this->may_be_delete_server_files_by_source_paths($files_to_remove);
1039 + }
1040 + }
1041 +
1042 +
1043 + /**
1044 + * Post-update item actions
1045 + * @since 1.2.13
1046 + * @param int $source_id
1047 + * @param array $data
1048 + * @param string $source_type
1049 + * This function is called after an item has been updated in the database.
1050 + * It triggers a WordPress action hook 'wpmcs_post_update_item' to allow other functions to hook into this event.
1051 + * After that, it calls may_be_delete_server_files_by_id to potentially delete server files associated with the item.
1052 + *
1053 + * @example
1054 + * $item = Item::instance();
1055 + * $item->post_update_item(123, $data, 'media_library');
1056 + *
1057 + * This example will trigger the post-update actions for the item with ID 123.
1058 + * It will execute any functions hooked to 'wpmcs_post_update_item' and may delete server files if the settings allow it.
1059 + */
1060 + public function post_update_item($source_id, $data, $source_type = 'media_library') {
1061 + // Hook for post-update actions
1062 + do_action('wpmcs_post_update_item', $source_id, $data, $source_type);
1063 +
1064 + // May be delete server files
1065 + $this->may_be_delete_server_files_by_id($source_id, $source_type, true, true);
1066 + }
1067 +
1068 + /**
1069 + * Track paths restored from cloud (by any integration) so they get removed
1070 + * again later, honoring "Remove from server" the way the normal sync pipeline
1071 + * would. Fed into the pre-update pipeline if a save happens this request (fast
1072 + * path — matches how the item's own pending removals already work), with a
1073 + * shutdown fallback (priority 1, ahead of most other plugins' shutdown hooks)
1074 + * for requests where nothing ever triggers a save.
1075 + *
1076 + * @param string[] $paths Absolute paths of the restored files.
1077 + * @return void
1078 + * @since 1.4.0
1079 + */
1080 + public function track_restored_for_cleanup(array $paths) {
1081 + foreach ($paths as $path) {
1082 + if (!in_array($path, $this->pending_restored_files, true)) {
1083 + $this->pending_restored_files[] = $path;
1084 + }
1085 + }
1086 +
1087 + if ($this->pending_cleanup_hooked) {
1088 + return;
1089 + }
1090 + $this->pending_cleanup_hooked = true;
1091 +
1092 + add_filter('wpmcs_pre_update_item_additional_files_to_remove_from_server', function ($files_to_remove) {
1093 + $files_to_remove = array_merge((array) $files_to_remove, $this->pending_restored_files);
1094 + $this->pending_restored_files = [];
1095 + return $files_to_remove;
1096 + });
1097 +
1098 + add_action('shutdown', array($this, 'flush_pending_restored_files'), 1);
1099 + }
1100 +
1101 + /**
1102 + * Shutdown fallback for track_restored_for_cleanup() — removes anything the
1103 + * pre-update pipeline didn't already pick up this request.
1104 + *
1105 + * @return void
1106 + * @since 1.4.0
1107 + */
1108 + public function flush_pending_restored_files() {
1109 + if (empty($this->pending_restored_files)) {
1110 + return;
1111 + }
1112 + $this->may_be_delete_server_files_by_source_paths($this->pending_restored_files);
1113 + $this->pending_restored_files = [];
1114 + }
1115 +
1116 +
1117 + public function may_be_delete_server_files_by_source_paths($source_paths) {
1118 + if (Utils::is_empty($source_paths) || !is_array($source_paths)) {
1119 + return false;
1120 + }
1121 +
1122 + if( !( isset($this->settings['remove_from_server']) && $this->settings['remove_from_server'] ) ) {
1123 + return false;
1124 + }
1125 +
1126 + foreach ($source_paths as $path) {
1127 + if(file_exists($path)) {
1128 + wp_delete_file($path, true);
1129 + }
1130 + }
1131 + return true;
1132 + }
1133 +
1134 + /**
1135 + * Function to remove media from server by id
1136 + * @param int $attachment_id
1137 + * @param string $source_type
1138 + * @param bool $delete_main_file
1139 + * @param bool $delete_backup
1140 + *
1141 + * This function checks if the item exists and if the setting to remove from server is enabled.
1142 + * If so, it deletes the main file and any backup files associated with the item.
1143 + * It also checks if the item has any extra data, and if so, it attempts to delete the backup files if specified.
1144 + * Finally, it deletes the main file associated with the item.
1145 + *
1146 + * @since 1.2.13
1147 + * @return bool Returns true if the deletion process was initiated, false otherwise.
1148 + *
1149 + * @throws \Exception If the item does not exist or if the removal from server setting is not enabled.
1150 + *
1151 + * @example
1152 + * $item = Item::instance();
1153 + * $item->may_be_delete_server_files_by_id(123, 'media_library', true, true);
1154 + *
1155 + * This example will attempt to delete the server files for the attachment with ID 123,
1156 + * including the main file and any backup files, if the settings allow it.
1157 + *
1158 + * @see Item::may_be_delete_server_files_by_item() for the function that actually performs the deletion.
1159 + */
1160 + public function may_be_delete_server_files_by_id($attachment_id, $source_type = 'media_library', $delete_main_file=false, $delete_backup = false) {
1161 + $item = $this->get($attachment_id, $source_type);
1162 + if(Utils::is_empty($item)) {
1163 + return false;
1164 + }
1165 +
1166 + if( !( isset($this->settings['remove_from_server']) && $this->settings['remove_from_server'] ) ) {
1167 + return false;
1168 + }
1169 +
1170 + if (isset($item['extra']) && !empty($item['extra'])) {
1171 + $extras = Utils::maybe_unserialize($item['extra']);
1172 + if (
1173 + isset($extras) && !empty($extras) &&
1174 + isset($extras['backup']) && !empty($extras['backup']) &&
1175 + $delete_backup
1176 + ) {
1177 + $backup = Utils::maybe_unserialize($extras['backup']);
1178 + if (isset($backup) && !empty($backup)) {
1179 + $this->may_be_delete_server_files_by_item($backup, $delete_main_file);
1180 + }
1181 + }
1182 + }
1183 +
1184 + $this->may_be_delete_server_files_by_item($item, $delete_main_file);
1185 +
1186 + return true;
1187 + }
1188 +
1189 + /**
1190 + * Function to remove media from server by item
1191 + */
1192 + public function may_be_delete_server_files_by_item( $item, $delete_main_file=false ) {
1193 + if(Utils::is_empty($item)) {
1194 + return false;
1195 + }
1196 +
1197 + if( !( isset($this->settings['remove_from_server']) && $this->settings['remove_from_server'] ) ) {
1198 + return false;
1199 + }
1200 +
1201 + return $this->delete_server_files_by_item( $item, $delete_main_file );
1202 + }
1203 +
1204 +
1205 + /**
1206 + * Function to remove media from server by item
1207 + */
1208 + public function delete_server_files_by_item( $item, $delete_main_file=false ) {
1209 + $upload_dir = wp_get_upload_dir();
1210 + $has_original = false;
1211 + $files_to_remove = array();
1212 +
1213 + $file_path = trailingslashit($upload_dir['basedir']) . $item['source_path'];
1214 +
1215 + if (isset($item['extra']) && !empty($item['extra'])) {
1216 + $extras = Utils::maybe_unserialize($item['extra']);
1217 + if (
1218 + isset($extras) && !empty($extras) &&
1219 + isset($extras['sizes']) && !empty($extras['sizes'])
1220 + ) {
1221 + foreach ($extras['sizes'] as $sub_image) {
1222 + if (isset($sub_image['source_path']) && !empty($sub_image['source_path'])) {
1223 + $file = trailingslashit($upload_dir['basedir']) . $sub_image['source_path'];
1224 + if(file_exists($file)) {
1225 + $files_to_remove[] = $file;
402 1226 }
403 1227 }
404 1228 }
405 - return !empty($server_files) ? $server_files : false;
406 - } else {
407 - if($size === 'full') {
408 - if (
409 - isset($data['source_path']) && !empty($data['source_path']) &&
410 - isset($data['key']) && !empty($data['key'])
411 - ) {
412 - $file_path = trailingslashit($upload_dir['basedir']) . $data['source_path'];
413 - if(file_exists($file_path) || $wpmcsService->object_to_server($data['key'], $file_path)) {
414 - $server_file = $file_path;
415 - }
416 - }
417 - } else {
418 - $extras = $this->get_extras($source_id) ? $this->get_extras($source_id) : [];
419 - if (isset($extras['sizes']) && !empty($extras['sizes'])) {
420 - $sizes = $extras['sizes'];
421 - if(isset($sizes[$size]) && !empty($sizes[$size])) {
422 - $sub_file_path = trailingslashit($upload_dir['basedir']) . $sizes[$size]['source_path'];
423 - if(file_exists($sub_file_path) || $wpmcsService->object_to_server($sizes[$size]['key'], $sub_file_path)) {
424 - $server_file = $sub_file_path;
425 - }
426 - }
427 - }
428 - }
429 - return $server_file;
430 1229 }
431 1230 }
432 - return false;
1231 + if (
1232 + isset($item['original_source_path']) && !empty($item['original_source_path'])
1233 + ) {
1234 + $has_original = true;
1235 + $file = trailingslashit($upload_dir['basedir']).$item['original_source_path'];
1236 + if(file_exists($file) && $delete_main_file) {
1237 + $files_to_remove[] = $file;
1238 + }
1239 + }
1240 + if(file_exists($file_path)) {
1241 + if ($has_original || (!$has_original && $delete_main_file)) {
1242 + $files_to_remove[] = $file_path;
1243 + }
1244 + }
1245 +
1246 +
1247 + $files_to_remove = apply_filters('wpmcs_files_to_remove_from_server', array_unique($files_to_remove), $item['source_id'], $item);
1248 +
1249 + if (!Utils::is_empty($files_to_remove)) {
1250 + foreach ($files_to_remove as $file) {
1251 + wp_delete_file($file, true);
1252 + }
1253 + }
1254 +
1255 + return true;
433 1256 }
434 -
435 1257
436 1258 /**
437 1259 * Ensures only one instance of Class is loaded or can be loaded.
438 1260 *