PluginProbe
Media Cloud Sync / 1.3.0
Media Cloud Sync v1.3.0
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 / integrations / media-library.php

media-library.php in Media Cloud Sync 1.3.0, at includes/integrations/media-library.php

1,105 lines 39.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Dudlewebs\WPMCS;
3
4 defined('ABSPATH') || exit;
5
6 use Exception;
7 use WP_Error;
8
9 class MediaLibrary {
10 private static $instance = null;
11 private $assets_url;
12 private $version;
13 private $token;
14
15 protected $settings;
16 private $deleting_attachment = false;
17
18
19 public static $source_type_prefix = "media";
20 public static $label = 'Media Library';
21
22 // Map which meta to be updated
23 public static $source_types = [
24 "media_library" => [
25 'table' => 'posts',
26 'class' => 'MediaLibrary'
27 ]
28 ];
29
30 /**
31 * Admin constructor.
32 * @since 1.0.0
33 */
34 public function __construct() {
35 $this->assets_url = WPMCS_ASSETS_URL;
36 $this->version = WPMCS_VERSION;
37 $this->token = WPMCS_TOKEN;
38 $this->settings = Utils::get_settings();
39
40 // Initialize setup
41 $this->registerActions();
42 }
43
44 /**
45 * Register integration access
46 */
47 public function registerActions() {
48 /** IMAGE EDITOR COMPATIBILITY */
49 add_action( 'attachment_submitbox_misc_actions', array( $this, 'attachment_submitbox_metadata'),99 );
50 //Media Modal Ajax
51 add_action( 'wp_ajax_wpmcs_get_attachment_details', array( $this, 'ajax_get_attachment_details' ) );
52
53 /** URL RE-WRITING HOOKS */
54 add_filter( 'wp_get_attachment_url', [ $this, 'wp_get_attachment_url' ], 99, 2 );
55 add_filter( 'wp_get_attachment_image_attributes', [ $this, 'wp_get_attachment_image_attributes' ], 99, 3 );
56 add_filter( 'wp_calculate_image_srcset', [ $this, 'wp_calculate_image_srcset' ], 99, 5 );
57 add_filter( 'get_attached_file', [ $this, 'get_attached_file' ], 10, 2 );
58 add_filter( 'wp_get_original_image_path', [ $this, 'get_attached_file' ], 10, 2 );
59 add_filter( 'wp_video_shortcode', [ $this, 'wp_media_shortcode' ], 100, 5 );
60 add_filter( 'wp_audio_shortcode', [ $this, 'wp_media_shortcode' ], 100, 5 );
61
62 /** FILE MANAGEMENT HOOKS */
63 add_filter( 'wp_unique_filename', [ $this, 'wp_unique_filename' ], 10, 3 );
64 add_filter( 'wp_update_attachment_metadata', [ $this, 'update_attachment_metadata' ], 110, 2 );
65 add_filter( 'pre_delete_attachment', [ $this, 'pre_delete_attachment' ], 20 );
66 add_filter( 'delete_attachment', [ $this, 'delete_attachment' ], 20 );
67 add_action( 'delete_post', [$this, 'delete_post'] );
68 add_filter( 'update_attached_file', [ $this, 'update_attached_file' ], 100, 2 );
69 }
70
71
72 /**
73 * Allow processes to update the file on provider via update_attached_file()
74 * @since 1.0.0
75 * @param string $file
76 * @param int $attachment_id
77 *
78 * @return string
79 */
80 public function update_attached_file($file, $attachment_id) {
81 if( !Utils::is_ok_to_upload($attachment_id) ) {
82 return $file;
83 }
84
85 $item = Item::instance()->get($attachment_id, 'media_library');
86
87 if (Utils::is_empty($item)) {
88 return $file;
89 }
90
91 $file = apply_filters('wpmcs_update_attached_file', $file, $attachment_id, $item);
92
93 return $file;
94 }
95
96
97 /**
98 * Function to remove data from provider by attachment ID
99 * @since 1.0.0
100 *
101 */
102 public function delete_attachment($attachment_id){
103 if (!Utils::is_service_enabled()) {
104 return $attachment_id;
105 }
106
107 $wpmcsItem = Item::instance();
108 $item = $wpmcsItem->get($attachment_id, 'media_library');
109
110 if (Utils::is_empty($item)) {
111 return $attachment_id;
112 }
113
114 $wpmcsItem->delete($attachment_id, 'media_library');
115
116 return $attachment_id;
117 }
118
119 /**
120 * Function to execute on wp_update_attachment_metadata
121 * @since 1.0.0
122 * @param array $attachment_meta
123 * @param int $attachment_id
124 * @return array|WP_Error
125 */
126 public function update_attachment_metadata($attachment_meta, $attachment_id) {
127 if (!Utils::is_ok_to_upload($attachment_id) || is_wp_error($attachment_meta)) {
128 return $attachment_meta;
129 }
130
131 $attachment_id = (int) $attachment_id;
132 $is_image = wp_attachment_is_image($attachment_id);
133
134 if ($is_image && $this->should_wait_for_subsizes($attachment_meta, $attachment_id)) {
135 return $attachment_meta;
136 }
137
138 $attachment_meta = $attachment_meta ?: wp_get_attachment_metadata($attachment_id);
139
140 $file = $this->get_attachment_file($attachment_meta, $attachment_id);
141 $source_path = Utils::get_attachment_source_path($file);
142
143 // Remove Logs
144 Logger::instance()->remove_log('sync_to_cloud', $attachment_id, 'media_library');
145
146 if (empty($source_path) || !is_string($source_path)) {
147 // Add Log
148 Logger::instance()->add_log('sync_to_cloud', $attachment_id, 'media_library', [
149 'message' => __('Unable to get attachment source path', 'media-cloud-sync'),
150 'file' => $source_path,
151 'code' => 400,
152 ]);
153 return $attachment_meta;
154 }
155
156 $existing = Item::instance()->get($attachment_id, 'media_library');
157 $backup = Item::instance()->get_backup($attachment_id, 'media_library');
158
159 if (Utils::is_empty($existing)) {
160 $this->handle_media_first_upload($attachment_meta, $attachment_id, $source_path);
161 } elseif (Utils::is_empty($backup)) {
162 $this->handle_media_with_no_backup($attachment_meta, $attachment_id, $source_path, $existing);
163 } else {
164 $this->handle_media_with_backup($attachment_meta, $attachment_id, $source_path, $existing, $backup);
165 }
166
167 return $attachment_meta;
168 }
169
170 /**
171 * Checks if we should wait for WordPress to generate subsizes.
172 */
173 private function should_wait_for_subsizes($attachment_meta, $attachment_id) {
174 if (empty($attachment_meta) || !function_exists('wp_get_registered_image_subsizes')) {
175 return false;
176 }
177
178 // Wait for generate_attachment_metadata if the filter is set to true.
179 if (apply_filters('wpmcs_wait_for_generate_attachment_metadata', false)) {
180 return true;
181 }
182
183 // Check if there are any missing image subsizes.
184 $image_sizes = apply_filters(
185 'intermediate_image_sizes_advanced',
186 wp_get_registered_image_subsizes(),
187 $attachment_meta,
188 $attachment_id
189 );
190
191 // If an image has been rotated, remove original image from metadata so that
192 // `wp_get_missing_image_subsizes()` doesn't use non-rotated image for
193 // generating missing thumbnail sizes.
194 // Also, some images, particularly SVGs, don't create thumbnails but do have
195 // metadata for them. At the time `wp_get_missing_image_subsizes()` checks
196 // the saved metadata, it isn't there, but we already have it.
197 $handle_post_meta = function ($value, $object_id, $meta_key, $single, $meta_type) use ($attachment_id, $attachment_meta) {
198 if (
199 !empty($value['image_meta']['orientation']) ||
200 !empty($attachment_meta['image_meta']['orientation'])
201 ) {
202 if (!empty($value['image_meta']['orientation'])) {
203 unset($value['image_meta']['orientation']);
204 }
205 if (!empty($attachment_meta['image_meta']['orientation'])) {
206 unset($attachment_meta['image_meta']['orientation']);
207 }
208 unset($value['original_image'], $attachment_meta['original_image']);
209 }
210 if (
211 is_null($value) &&
212 $object_id === $attachment_id &&
213 '_wp_attachment_metadata' === $meta_key &&
214 $single && $meta_type === 'post'
215 ) {
216 return [$attachment_meta];
217 }
218 return $value;
219 };
220
221 add_filter('get_post_metadata', $handle_post_meta, 10, 5);
222 $missing_sizes = wp_get_missing_image_subsizes($attachment_id);
223 remove_filter('get_post_metadata', $handle_post_meta);
224
225 return !empty(array_intersect_key($missing_sizes, $image_sizes));
226 }
227
228 /**
229 * Gets the attachment file path.
230 */
231 private function get_attachment_file($attachment_meta, $attachment_id) {
232 $file = $attachment_meta['file'] ?? Utils::get_post_meta($attachment_id, '_wp_attached_file', true);
233 if ($file === basename($file)) {
234 $file = Utils::get_post_meta($attachment_id, '_wp_attached_file', true);
235 }
236 return $file;
237 }
238
239 /**
240 * Handles first upload.
241 * @since 1.2.13
242 * @param array $attachment_meta
243 * @param int $attachment_id
244 * @param string $source_path
245 */
246 private function handle_media_first_upload($attachment_meta, $attachment_id, $source_path) {
247 $uploaded_data = $this->uploadMedia($attachment_meta, $attachment_id, $source_path);
248 if ( $uploaded_data && isset($uploaded_data['file']) && !empty($uploaded_data['file'])) {
249 Item::instance()->add(
250 $attachment_id,
251 $uploaded_data['file']['url'],
252 $uploaded_data['file']['key'],
253 $uploaded_data['file']['source_path'],
254 $uploaded_data['extra'],
255 'media_library'
256 );
257 }
258 }
259
260 /**
261 * Handles upload when no backup exists.
262 * @since 1.2.13
263 * @param array $attachment_meta
264 * @param int $attachment_id
265 * @param string $source_path
266 * @param array $existing
267 */
268 private function handle_media_with_no_backup($attachment_meta, $attachment_id, $source_path, $existing) {
269 $uploaded_data = $this->uploadMedia($attachment_meta, $attachment_id, $source_path);
270
271 if ($existing['source_path'] != $source_path) {
272 $uploaded_data['extra']['backup'] = serialize($existing);
273 }
274
275 $this->update_item_if_uploaded($attachment_id, $uploaded_data);
276 }
277
278 /**
279 * Handles upload when backup exists.
280 * @since 1.2.13
281 * @param array $attachment_meta
282 * @param int $attachment_id
283 * @param string $source_path
284 * @param array $existing
285 * @param array $backup
286 */
287 private function handle_media_with_backup($attachment_meta, $attachment_id, $source_path, $existing, $backup) {
288 $delete_existing = false;
289
290 if ($backup['source_path'] != $source_path) {
291 $uploaded_data = $this->uploadMedia($attachment_meta, $attachment_id, $source_path);
292 $uploaded_data['extra']['backup'] = maybe_serialize($backup);
293
294 if ($existing['source_path'] != $source_path) {
295 $delete_existing = true;
296 }
297 } else {
298 $delete_existing = true;
299
300 // construct upload data from backup
301 $uploaded_data = [
302 'file' => [
303 'source_path' => $backup['source_path'],
304 'url' => $backup['url'],
305 'key' => $backup['key'],
306 ],
307 'extra' => maybe_unserialize($backup['extra']),
308 ];
309 }
310
311 if ($delete_existing) {
312 Item::instance()->delete_attachments_by_item($existing, false);
313 Item::instance()->may_be_delete_server_files_by_item($existing, true);
314 }
315
316 $this->update_item_if_uploaded($attachment_id, $uploaded_data);
317 }
318
319 /**
320 * Updates item if upload was successful.
321 * @since 1.2.13
322 * @param int $attachment_id
323 * @param array $uploaded_data
324 * @return void
325 */
326 private function update_item_if_uploaded($attachment_id, $uploaded_data) {
327 if (!empty($uploaded_data['file'])) {
328 Item::instance()->update(
329 $attachment_id,
330 [
331 'source_path' => $uploaded_data['file']['source_path'],
332 'url' => $uploaded_data['file']['url'],
333 'key' => $uploaded_data['file']['key'],
334 'extra' => maybe_serialize($uploaded_data['extra']),
335 ],
336 'media_library'
337 );
338 }
339 }
340
341
342 /**
343 * Create unique names for files effects mainly on delete files from server settings
344 * @since 1.0.0
345 * @return string
346 */
347 public function wp_unique_filename($filename, $ext, $dir) {
348 // Get Post ID if uploaded in post screen.
349 $post_id = Utils::filter_input( 'post_id', INPUT_POST, FILTER_VALIDATE_INT );
350
351 $filename = $this->filter_unique_filename($filename, $ext, $dir, $post_id);
352
353 return $filename;
354 }
355
356 /**
357 * filter unique file names
358 * @since 1.0.0
359 * @return string
360 */
361 private function filter_unique_filename($filename, $ext, $dir, $post_id = null) {
362 if (!Utils::is_service_enabled()) {
363 return $filename;
364 }
365
366 // sanitize the file name before we begin processing
367 $filename = sanitize_file_name($filename);
368 $ext = strtolower($ext);
369 $name = wp_basename($filename, $ext);
370
371 // Edge case: if file is named '.ext', treat as an empty name.
372 if ($name === $ext) {
373 $name = '';
374 }
375
376 // Rebuild filename with lowercase extension as provider will have converted extension on upload.
377 $filename = $name . $ext;
378
379 return $this->generate_unique_filename($name, $ext, $dir);
380 }
381
382
383 /**
384 * Generate unique filename
385 * @since 1.0.0
386 * @param string $name
387 * @param string $ext
388 * @param string $time
389 *
390 * @return string
391 */
392 private function generate_unique_filename($name, $ext, $dir) {
393 $upload_dir = wp_get_upload_dir();
394 $filename = $name . $ext;
395 $no_ext_path = $dir . '/' . $name;
396 $rel_no_ext_path = substr($no_ext_path, strlen(trailingslashit($upload_dir['basedir'])),strlen($no_ext_path));
397 $path = $dir . '/' . $name . $ext;
398 $source_path = substr($path, strlen(trailingslashit($upload_dir['basedir'])),strlen($path));
399
400
401 $uploaded_files = Item::instance()->get_similar_files_by_path($rel_no_ext_path);
402
403 if ($uploaded_files !== false) {
404 if (Utils::check_existing_file_names($source_path, $uploaded_files) || file_exists($path)) {
405 $count = 1;
406 $new_file_name = '';
407 $found = true;
408 while ($found) {
409 $tmp_path = $dir . '/' . $name . '-' . $count . $ext;
410 $rel_temp_path = substr($tmp_path, strlen(trailingslashit($upload_dir['basedir'])),strlen($tmp_path));
411
412 if (Utils::check_existing_file_names($rel_temp_path, $uploaded_files) || file_exists($tmp_path)) {
413 $count++;
414 } else {
415 $found = false;
416 $new_file_name = $name . '-' . $count . $ext;
417 }
418 }
419 return $new_file_name;
420 }
421 } else {
422 if (file_exists($path)) {
423 $count = 1;
424 $new_file_name = '';
425 $found = true;
426
427 while ($found) {
428 $tmp_path = $dir . '/' . $name . '-' . $count . $ext;
429 if (file_exists($tmp_path)) {
430 $count++;
431 } else {
432 $found = false;
433 $new_file_name = $name . '-' . $count . $ext;
434 }
435 }
436 return $new_file_name;
437 }
438 }
439
440 return $filename;
441 }
442
443
444 /**
445 * Filters the audio & video shortcodes output to remove "&_=NN" params from source.src as it breaks signed URLs.
446 *
447 * @param string $html Shortcode HTML output.
448 * @param array $atts Array of shortcode attributes.
449 * @param string $media Media file.
450 * @param int $post_id Post ID.
451 * @param string $library Media library used for the shortcode.
452 *
453 * @return string
454 *
455 * Note: Depends on 30377.4.diff from https://core.trac.wordpress.org/ticket/30377
456 */
457 public function wp_media_shortcode( $html, $atts, $media, $post_id, $library ) {
458 return preg_replace( '/&#038;_=[0-9]+/', '', $html );
459 }
460
461 /**
462 * Return the provider URL when the server file is missing
463 * unless we know who the calling process is and we are happy
464 * to copy the file back to the server to be used.
465 *
466 * @handles get_attached_file
467 * @handles wp_get_original_image_path
468 * @since 1.0.0
469 * @param string $file
470 * @param int $attachment_id
471 *
472 * @return string
473 */
474 public function get_attached_file($file, $attachment_id) {
475 $attachment_id = (int)$attachment_id;
476
477 // During the deletion of an attachment, stream wrapper URLs should not be returned.
478 if ( $this->deleting_attachment ) {
479 return $file;
480 }
481
482 if (!Utils::is_ok_to_serve($attachment_id)) {
483 return $file;
484 }
485
486 $wpmcsItem = Item::instance();
487
488 $item = $wpmcsItem->get($attachment_id, 'media_library');
489
490 if ( file_exists( $file ) || Utils::is_empty($item)) {
491 if(!Utils::is_empty($item)) {
492 /**
493 * Added filter to allow plugins to copy the pending size to the server
494 * before the file is served.
495 *
496 * @param string $file
497 * @param string $file
498 * @param int $attachment_id
499 */
500 return apply_filters( 'wpmcs_get_attached_file_noop', $file, $file, $attachment_id, $item );
501 } else {
502 return $file;
503 }
504 }
505
506 $url = $wpmcsItem->get_url($attachment_id, 'full', 'media_library');
507 if(Utils::is_empty($url)) {
508 return $file;
509 }
510 /**
511 * This filter gives filter implementors a chance to copy back missing item files
512 * from the provider before WordPress returns the file name/path for it. Defaults to
513 * returning the remote URL.
514 *
515 * @param string $url Item URL
516 * @param string $file Server file path
517 * @param int $attachment_id Attachment ID
518 * @param array $item Item data
519 */
520 return apply_filters('wpmcs_get_attached_file', $url, $file, $attachment_id, $item);
521 }
522
523
524 /**
525 * Change src attributes
526 * @since 1.0.0
527 */
528
529 public function wp_calculate_image_srcset($sources, $size_array, $image_src, $attachment_meta, $attachment_id = 0) {
530 $attachment_id = (int)$attachment_id;
531
532 // Must need $attachment_id other wise not possible to get data from the table
533 if (!Utils::is_ok_to_serve($attachment_id)) {
534 return $sources;
535 }
536
537 $wpmcsItem = Item::instance();
538
539 $item = $wpmcsItem->get($attachment_id, 'media_library');
540
541 if (Utils::is_empty($item)) {
542 return $sources;
543 }
544
545 $item_extra = $wpmcsItem->get_extras($attachment_id, false, 'media_library');
546
547 if (isset($item_extra['width']) && !empty($item_extra['width'])) {
548 $sources[$item_extra['width']]=[
549 'url' => $wpmcsItem->get_url($attachment_id, 'full', 'media_library'),
550 'descriptor' => 'w',
551 'value' => $item_extra['width']
552 ];
553 }
554
555 if ($item_extra) {
556 if (isset($item_extra['sizes']) && !empty($item_extra['sizes'])) {
557 foreach ($item_extra['sizes'] as $size => $size_array) {
558 if (isset($size_array['width']) && !empty($size_array['width'])) {
559 $w = $size_array['width'];
560 if (isset($sources[$w]) && !empty($sources[$w])) {
561 $sources[$w]['url'] = $wpmcsItem->get_url($attachment_id, $size, 'media_library');
562 }
563 }
564 }
565 }
566 }
567
568 return $sources;
569 }
570
571 /**
572 * Filters the list of attachment image attributes.
573 *
574 * @since 1.0.0
575 * @param array $attr Attributes for the image markup.
576 * @param WP_Post $attachment Image attachment post.
577 * @param string|array $size Requested size. Image size or array of width and height values (in that order).
578 *
579 * @return array
580 */
581 public function wp_get_attachment_image_attributes($attr, $attachment, $size='thumbnail') {
582 if (
583 !$attachment ||
584 !Utils::is_ok_to_serve($attachment->ID)
585 ) {
586 return $attr;
587 }
588
589 $wpmcsItem = Item::instance();
590
591 $item = $wpmcsItem->get($attachment->ID, 'media_library');
592
593 if (Utils::is_empty($item)) {
594 return $attr;
595 }
596
597 $size = Utils::maybe_convert_size_to_string($attachment->ID, $size);
598 if ($size === false) {
599 return $attr;
600 }
601
602
603
604 if (
605 isset($size) && !empty($size) &&
606 isset($attr['src']) && !empty($attr['src'])
607 ) {
608 $source = $wpmcsItem->get_url($attachment->ID, $size, 'media_library');
609 if (isset($source) && !empty($source)) {
610 $attr['src'] = $source;
611 }
612 }
613
614 /**
615 * Filtered list of attachment image attributes.
616 *
617 * @param array $attr Attributes for the image markup.
618 * @param WP_Post $attachment Image attachment post.
619 * @param string $size Requested size.
620 */
621 return apply_filters('wpmcs_wp_get_attachment_image_attributes', $attr, $attachment, $size, $item);
622 }
623
624 /**
625 * Get attachment url
626 * @since 1.0.0
627 * @param string $url
628 * @param int $attachment_id
629 *
630 * @return bool|mixed|WP_Error
631 */
632 public function wp_get_attachment_url($url, $attachment_id) {
633 if (!Utils::is_ok_to_serve($attachment_id)) {
634 return $url;
635 }
636
637 $new_url = Item::instance()->get_url($attachment_id, 'full', 'media_library');
638
639 if (Utils::is_empty($new_url)) {
640 return $url;
641 }
642
643 $new_url = apply_filters('wpmcs_wp_get_attachment_url', $new_url, $url, $attachment_id);
644
645 return $new_url;
646 }
647
648 /**
649 * Upload media item
650 */
651 public function uploadMedia($attachment_meta, $attachment_id, $source_path) {
652 $wpmcsItem = Item::instance();
653 $upload_dir = wp_get_upload_dir();
654 $is_image = wp_attachment_is_image($attachment_id);
655 $existing = $wpmcsItem->get($attachment_id, 'media_library');
656 $existing_extras = $wpmcsItem->get_extras($attachment_id, false, 'media_library');
657 $has_existing = !Utils::is_empty($existing);
658 $sizes = [];
659 $uploaded = [];
660 $extras = [];
661 $prefix = '';
662 $file_path = '';
663 $file_dir = '';
664 $original_file_path = '';
665 $original_file_source_path = '';
666
667
668 // Add prefix if object versioning is ON
669 if (isset($this->settings['object_versioning']) && $this->settings['object_versioning']) {
670 $prefix = ($existing_extras && isset($existing_extras['prefix']) && !empty($existing_extras['prefix']))
671 ? $existing_extras['prefix']
672 : Utils::generate_object_versioning_prefix();
673 $extras['prefix'] = $prefix;
674 }
675
676 // Get width and height from image meta
677 if (isset($attachment_meta) && !empty($attachment_meta)) {
678 $extras['width'] = (isset($attachment_meta['width']) && !empty($attachment_meta['width'])) ? $attachment_meta['width'] : 0;
679 $extras['height'] = (isset($attachment_meta['height']) && !empty($attachment_meta['height'])) ? $attachment_meta['height'] : 0;
680 }
681
682
683 // Get Original File Name/Path from Image meta
684 $original_file = isset($attachment_meta) && !empty($attachment_meta) &&
685 isset($attachment_meta['original_image']) && !empty($attachment_meta['original_image'])
686 ? $attachment_meta['original_image'] : '';
687
688 $file_path = trailingslashit($upload_dir['basedir']) . $source_path;
689 $file_dir = isset(pathinfo($file_path)['dirname']) ? pathinfo($file_path)['dirname'] : '';
690
691 // Check whether the extension is enabled for uploading
692 if (!Utils::is_extension_available($file_path)) {
693 Logger::instance()->add_log('sync_to_cloud', $attachment_id, 'media_library', [
694 'message' => __('File extension is not supported', 'media-cloud-sync'),
695 'file' => $file_path,
696 'code' => 415
697 ]);
698 return $attachment_meta;
699 }
700
701 // Upload the Full Size file
702 if( $has_existing && ( $existing['source_path'] == $source_path ) ) {
703 $uploaded = [
704 'success' => true,
705 'file_url' => $existing['url'],
706 'key' => $existing['key']
707 ];
708 } else if(file_exists($file_path)){
709 $uploaded = Service::instance()->uploadSingle($file_path, $source_path, $prefix);
710 } else {
711 Logger::instance()->add_log('sync_to_cloud', $attachment_id, 'media_library', [
712 'message' => __('File not found', 'media-cloud-sync'),
713 'file' => $file_path,
714 'code' => 404
715 ]);
716 }
717
718 if(
719 isset($uploaded) && !empty($uploaded) &&
720 isset($uploaded['success']) && $uploaded['success']
721 ) {
722 if(isset($original_file) && !empty($original_file)){
723 // Find original image relative and absolute path
724 $original_file_path = trailingslashit($file_dir) . $original_file;
725 $original_file_source_path = Utils::get_attachment_source_path($original_file_path);
726 $uploaded_original = [];
727
728 // Upload Original File
729 if(
730 !Utils::is_empty($existing_extras) &&
731 isset($existing_extras['original']) && !Utils::is_empty($existing_extras['original']) &&
732 $existing_extras['original']['source_path'] == $original_file_source_path
733 ) {
734 $uploaded_original = [
735 'success' => true,
736 'file_url' => $existing_extras['original']['url'],
737 'key' => $existing_extras['original']['key']
738 ];
739 } else if(file_exists($original_file_path)) {
740 $uploaded_original = Service::instance()->uploadSingle($original_file_path, $original_file_source_path, $prefix);
741 } else {
742 Logger::instance()->add_log('sync_to_cloud', $attachment_id, 'media_library', [
743 'message' => __('Original File not found', 'media-cloud-sync'),
744 'file' => $original_file_path,
745 'code' => 404
746 ]);
747 }
748
749 if(
750 isset($uploaded_original) && !empty($uploaded_original) &&
751 isset($uploaded_original['success']) && $uploaded_original['success']
752 ) {
753 $extras['original'] = array(
754 'source_path' => $original_file_source_path,
755 'url' => $uploaded_original['file_url'],
756 'key' => $uploaded_original['key']
757 );
758 } else {
759 if(isset($uploaded_original['success']) && !$uploaded_original['success']) {
760 Logger::instance()->add_log('sync_to_cloud', $attachment_id, 'media_library', [
761 'message' => __('Original File upload failed', 'media-cloud-sync'),
762 'file' => $original_file_path,
763 'code' => 500
764 ]);
765 }
766 }
767 }
768
769 if (
770 $is_image &&
771 isset($attachment_meta['sizes']) && !empty($attachment_meta['sizes']) &&
772 isset($file_dir) && !empty($file_dir)
773 ) {
774 foreach ($attachment_meta['sizes'] as $size => $sub_image) {
775 $sub_size = [];
776 $sub_file = isset($sub_image['file']) ? $sub_image['file'] : false;
777 $sub_file_path = '';
778 $sub_file_source_path = '';
779 $uploaded_sub_image = [];
780
781 if ($sub_file) {
782 $sub_file_path = $file_dir . '/' . $sub_file;
783 $sub_file_source_path = Utils::get_attachment_source_path($sub_file_path);
784 }
785
786 // Upload Image size
787 if(
788 !Utils::is_empty($existing_extras) &&
789 isset($existing_extras['sizes']) && !Utils::is_empty($existing_extras['sizes']) &&
790 isset($existing_extras['sizes'][$size]) && !Utils::is_empty($existing_extras['sizes'][$size]) &&
791 $existing_extras['sizes'][$size]['source_path'] == $sub_file_source_path
792 ) {
793 $uploaded_sub_image = [
794 'success' => true,
795 'file_url' => $existing_extras['sizes'][$size]['url'],
796 'key' => $existing_extras['sizes'][$size]['key']
797 ];
798 } else if(file_exists($sub_file_path)) {
799 $uploaded_sub_image = Service::instance()->uploadSingle($sub_file_path, $sub_file_source_path, $prefix);
800 } else {
801 Logger::instance()->add_log('sync_to_cloud', $attachment_id, 'media_library', [
802 /* translators: %1$s: image size name */
803 'message' => sprintf(__('Image size %1$s not found', 'media-cloud-sync'), $size),
804 'file' => $sub_file_path,
805 'code' => 404
806 ]);
807 }
808
809 if (
810 isset($uploaded_sub_image) && !empty($uploaded_sub_image) &&
811 isset($uploaded_sub_image['success']) && $uploaded_sub_image['success']
812 ) {
813 $sub_size['source_path'] = $sub_file_source_path;
814 $sub_size['url'] = $uploaded_sub_image['file_url'];
815 $sub_size['key'] = $uploaded_sub_image['key'];
816 $sub_size['width'] = isset($sub_image['width']) ? $sub_image['width']: 0;
817 $sub_size['height'] = isset($sub_image['height']) ? $sub_image['height']: 0;
818 } else {
819 if(isset($uploaded_sub_image['success']) && !$uploaded_sub_image['success']) {
820 Logger::instance()->add_log('sync_to_cloud', $attachment_id, 'media_library', [
821 /* translators: %1$s: image size name */
822 'message' => sprintf(__('Image size %1$s upload failed', 'media-cloud-sync'), $size),
823 'file' => $sub_file_path,
824 'code' => 500
825 ]);
826 }
827 }
828
829 $sizes[$size] = $sub_size;
830 }
831 }
832
833
834 if (isset($sizes) && !empty($sizes)) {
835 $extras['sizes'] = $sizes;
836 }
837
838 return [
839 'file' => [
840 'source_path' => $source_path,
841 'url' => $uploaded['file_url'],
842 'key' => $uploaded['key'],
843 ],
844 'extra' => $extras
845 ];
846
847 } else {
848 if(isset($uploaded['success']) && !$uploaded['success']) {
849 Logger::instance()->add_log('sync_to_cloud', $attachment_id, 'media_library', [
850 'message' => __('File upload failed', 'media-cloud-sync'),
851 'file' => $file_path,
852 'code' => 500
853 ]);
854 }
855 }
856
857 return false;
858 }
859
860
861 /**
862 * Get Total Media Count (queried)
863 *
864 * Param added because need a generic function name,
865 * if the function calls by source_type
866 */
867 public static function get_total_media($source_type) {
868 global $wpdb;
869
870 // Fetch the count of attachments with the 'inherit' status
871 $count = $wpdb->get_var(
872 $wpdb->prepare(
873 "SELECT COUNT(1) FROM {$wpdb->posts} WHERE post_type = %s AND post_status = %s",
874 'attachment',
875 'inherit'
876 )
877 );
878
879 return (int)$count;
880 }
881
882
883 /**
884 * Upload pending media files.
885 *
886 * Processes media attachments that are pending and haven't been synced yet.
887 *
888 * @param string $source_type Source type for identifying which source..
889 * @param int $count Number of media to process. Default is 0.
890 * @param int $offset Offset for media query. Default is 0.
891 * @return array Status array with success and failed media IDs.
892 */
893 public function upload_pending_media($source_type, $count = 0, $offset = 0 ) {
894 global $wpdb;
895
896 $failed = 0;
897
898 $source_type_data = self::$source_types[$source_type] ?? false;
899 if (!$source_type_data) {
900 return [
901 'success' => true,
902 'failed' => $failed + $count,
903 ];
904 }
905
906 $source_table = $wpdb->prefix . $source_type_data['table'];
907
908 // Sanitize inputs
909 $count = intval($count);
910 $offset = intval($offset);
911
912 // Query pending media
913 $query = $wpdb->prepare(
914 "SELECT DISTINCT posts.ID
915 FROM {$source_table} AS posts
916 WHERE posts.post_type = 'attachment'
917 AND posts.ID NOT IN (
918 SELECT items.source_id
919 FROM " . Db::get_table_name() . " AS items
920 WHERE items.source_type = %s
921 AND items.source_id = posts.ID
922 )
923 ORDER BY posts.ID DESC LIMIT %d, %d",
924 $source_type,
925 $offset,
926 $count > 0 ? $count : PHP_INT_MAX // Handle no limit for -1
927 );
928
929 $medias = $wpdb->get_results($query);
930
931 // Process the queried media
932 if (!empty($medias)) {
933 foreach ($medias as $media) {
934 $media_id = $media->ID;
935 // Attempt to sync the attachment
936 try {
937 $this->update_attachment_metadata([], $media_id);
938 } catch (Exception $e) {
939 $failed++; // Add to failed if sync fails
940 continue;
941 }
942
943 // Verify if the media was successfully processed
944 if (!Item::instance()->get($media_id)) {
945 $failed++;
946 }
947 }
948 }
949
950 return [
951 'success' => true,
952 'failed' => $failed,
953 ];
954 }
955
956
957 /**
958 * Edit Image Meta In View Image
959 * @since 1.0.0
960 */
961 function attachment_submitbox_metadata( ) {
962 $post = get_post();
963 $attachment_id = $post->ID;
964
965 if (!Utils::is_ok_to_serve($attachment_id)) {
966 return;
967 }
968
969 $wpmcsItem = Item::instance();
970
971 $item = $wpmcsItem->get($attachment_id, 'media_library');
972
973 if (Utils::is_empty($item)) {
974 return;
975 }
976
977 $provider = $wpmcsItem->get_field($attachment_id, 'provider', 'media_library');
978 if($provider) {
979 $label = Schema::getServiceLabels($provider); ?>
980 <div class="misc-pub-section misc-pub-provider">
981 <?php esc_html_e( 'Provider :', 'media-cloud-sync' ); ?> <strong><?php echo esc_textarea(!empty($label) ? $label : $provider); ?></strong></a>
982 </div>
983 <?php
984 }
985
986 $region = $wpmcsItem->get_field($attachment_id, 'region', 'media_library');
987 if($region) { ?>
988 <div class="misc-pub-section misc-pub-provider">
989 <?php esc_html_e( 'Region :', 'media-cloud-sync' ); ?> <strong><?php echo esc_textarea($region); ?></strong></a>
990 </div>
991 <?php
992 }
993 $private = (int)$wpmcsItem->get_field($attachment_id, 'is_private', 'media_library');
994 ?>
995 <div class="misc-pub-section misc-pub-provider">
996 <?php esc_html_e( 'Access :', 'media-cloud-sync' ); ?> <strong><?php $private ? esc_html_e( 'Private', 'media-cloud-sync' ) : esc_html_e( 'Public', 'media-cloud-sync' ); ?></strong></a>
997 </div>
998 <?php
999 }
1000
1001
1002 /**
1003 * Function to get attachment details by ID
1004 */
1005 public function ajax_get_attachment_details() {
1006 $result = array(
1007 'status' => false,
1008 'data' => array(),
1009 'exclude' => false
1010 );
1011
1012 if ( ! isset( $_POST['id'] ) ) {
1013 wp_send_json_success( $result );
1014 }
1015
1016 check_ajax_referer( 'get_media_provider_details', '_nonce' );
1017
1018 $id= intval( sanitize_text_field( $_POST['id'] ) );
1019
1020 // Return if extension not allowed
1021 $path = get_attached_file( $id );
1022 if(!Utils::is_extension_available($path)) {
1023 $result['exclude'] = true;
1024 wp_send_json_success( $result );
1025 }
1026
1027 if (!Utils::is_ok_to_serve($id)) {
1028 wp_send_json_success( $result );
1029 }
1030
1031 $wpmcsItem = Item::instance();
1032
1033 $item = $wpmcsItem->get($id, 'media_library');
1034
1035 if (Utils::is_empty($item)) {
1036 wp_send_json_success( $result );
1037 }
1038
1039 $provider = $wpmcsItem->get_field($id, 'provider', 'media_library');
1040 if($provider){
1041 $label = Schema::getServiceLabels($provider);
1042 $item['provider'] = !empty($label) ? $label : $provider;
1043 }
1044 $region = $wpmcsItem->get_field($id, 'region', 'media_library');
1045 if($region){
1046 $item['region'] = $region;
1047 }
1048 $item['private'] = $wpmcsItem->get_field($id, 'private', 'media_library');
1049 if($item) {
1050 $result= array(
1051 'status' => true,
1052 'data' => $item
1053 );
1054 }
1055
1056 wp_send_json_success( $result );
1057 }
1058
1059 /**
1060 * Takes notice that an attachment is about to be deleted and prepares for it.
1061 *
1062 * @handles pre_delete_attachment
1063 *
1064 * @param bool|null $delete Whether to go forward with deletion.
1065 *
1066 * @return bool|null
1067 */
1068 public function pre_delete_attachment( $delete ) {
1069 if ( is_null( $delete ) ) {
1070 $this->deleting_attachment = true;
1071 }
1072
1073 return $delete;
1074 }
1075
1076 /**
1077 * Takes notice that an attachment has been deleted and undoes previous preparations for the event.
1078 *
1079 * @handles delete_post
1080 *
1081 * Note: delete_post is used as there is a potential that deleted_post is not reached.
1082 */
1083 public function delete_post() {
1084 $this->deleting_attachment = false;
1085 }
1086
1087 /**
1088 * Is installed?
1089 *
1090 * @return bool
1091 */
1092 public static function is_installed(): bool {
1093 // It is inbuilt in worpress
1094 return true;
1095 }
1096
1097
1098
1099 public static function instance(){
1100 if (is_null(self::$instance)) {
1101 self::$instance = new self();
1102 }
1103 return self::$instance;
1104 }
1105 }