PluginProbe
Media Cloud Sync / 1.2.5
Media Cloud Sync v1.2.5
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 / media.php

media.php in Media Cloud Sync 1.2.5, at includes/base/media.php

740 lines 25.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Dudlewebs\WPMCS;
3
4 defined('ABSPATH') || exit;
5
6 class Media {
7 private static $instance = null;
8 private $assets_url;
9 private $version;
10 private $token;
11
12 protected $settings;
13 private $deleting_attachment = false;
14
15 /**
16 * Admin constructor.
17 * @since 1.0.0
18 */
19 public function __construct() {
20 $this->assets_url = WPMCS_ASSETS_URL;
21 $this->version = WPMCS_VERSION;
22 $this->token = WPMCS_TOKEN;
23
24 // Initialize setup
25 $this->init();
26 }
27
28 /**
29 * Initialize
30 */
31 public function init() {
32 $this->settings = Utils::get_settings();
33
34 // Load action for modifying URL s and uploading media
35 $this->register_actions();
36 }
37
38
39 /**
40 * Register Actions for for media handling
41 * @access public
42 * @return void
43 * @since 1.0.0
44 */
45
46 public function register_actions(){
47 /** URL RE-WRITING HOOKS */
48 add_filter( 'wp_get_attachment_url', [ $this, 'wp_get_attachment_url' ], 99, 2 );
49 add_filter( 'wp_get_attachment_image_attributes', [ $this, 'wp_get_attachment_image_attributes' ], 99, 3 );
50 add_filter( 'wp_calculate_image_srcset', [ $this, 'wp_calculate_image_srcset' ], 99, 5 );
51 add_filter( 'get_attached_file', [ $this, 'get_attached_file' ], 10, 2 );
52 add_filter( 'wp_get_original_image_path', [ $this, 'get_attached_file' ], 10, 2 );
53 add_filter( 'wp_video_shortcode', [ $this, 'wp_media_shortcode' ], 100, 5 );
54 add_filter( 'wp_audio_shortcode', [ $this, 'wp_media_shortcode' ], 100, 5 );
55
56 // /** FILE MANAGEMENT HOOKS */
57 add_filter( 'wp_unique_filename', [ $this, 'wp_unique_filename' ], 10, 3 );
58 add_filter( 'wp_update_attachment_metadata', [ $this, 'update_attachment_metadata' ], 110, 2 );
59 add_filter( 'wp_generate_attachment_metadata', [ $this, 'wp_generate_attachment_metadata' ], 110, 3 );
60 add_filter( 'pre_delete_attachment', [ $this, 'pre_delete_attachment' ], 20 );
61 add_filter( 'delete_attachment', [ $this, 'delete_attachment' ], 20 );
62 add_action( 'delete_post', [$this, 'delete_post'] );
63 add_filter( 'update_attached_file', [ $this, 'update_attached_file' ], 100, 2 );
64 add_filter( 'load_image_to_edit_path', [ $this, 'load_image_to_edit_path' ], 10, 3 );
65 }
66
67
68 /**
69 * Function to execute on wp_generate_attachment_metadata
70 * To delete files from server only after updating all meta
71 * @since 1.0.0
72 *
73 */
74
75 public function wp_generate_attachment_metadata( $attachment_meta, $attachment_id, $action) {
76 if($action == 'create') {
77 $attachment_id = (int)$attachment_id;
78
79 if (!Utils::is_ok_to_upload($attachment_id)) return $attachment_meta;
80
81 $item = Item::instance()->get($attachment_id);
82 if(!Utils::is_empty($item)) {
83 $this->may_be_delete_server_files_by_id($attachment_id, true, true);
84 }
85 }
86
87 return $attachment_meta;
88 }
89
90
91 /**
92 * Function to execute on wp_update_attachment_metadata
93 * @since 1.0.0
94 *
95 */
96 public function update_attachment_metadata($attachment_meta, $attachment_id) {
97 $wpmcsService = Service::instance();
98 $wpmcsItem = Item::instance();
99
100 $attachment_id = (int)$attachment_id;
101 $type = get_post_mime_type($attachment_id);
102 $is_image = (0 === strpos($type, 'image/'));
103 $is_image_edit = false;
104
105 if (!Utils::is_ok_to_upload($attachment_id)) return $attachment_meta;
106
107 //Generate attachment meta if empty
108 if (empty($attachment_meta)) {
109 $attachment_meta = wp_get_attachment_metadata($attachment_id);
110 }
111
112 // Get File Name/Path from Image meta
113 $file = isset($attachment_meta) && !empty($attachment_meta) && isset($attachment_meta['file']) && !empty($attachment_meta['file'])
114 ? $attachment_meta['file']
115 : Utils::get_post_meta((int) $attachment_id, '_wp_attached_file', true);
116
117 if($file === basename($file)) {
118 $file = Utils::get_post_meta((int) $attachment_id, '_wp_attached_file', true);
119 }
120
121 // Generate relative path of the file
122 $source_path = Utils::get_attachment_source_path($file);
123
124 if($source_path){
125 $existing = $wpmcsItem->get($attachment_id);
126 $backup = $wpmcsItem->get_backup($attachment_id);
127
128 if(Utils::is_empty($existing)) { // First Upload attempt
129 $uploaded_data = $wpmcsService->uploadMedia($attachment_meta, $attachment_id, $source_path);
130 if(isset($uploaded_data['file'])) {
131 $wpmcsItem->add(
132 $attachment_id,
133 $uploaded_data['file']['url'],
134 $uploaded_data['file']['key'],
135 $uploaded_data['file']['source_path'],
136 $uploaded_data['extra']
137 );
138 }
139 } else {
140 if(Utils::is_empty($backup)) { // if no backup available
141 $uploaded_data = $wpmcsService->uploadMedia($attachment_meta, $attachment_id, $source_path);
142
143 if( $existing['source_path'] != $source_path ) { // if existing item is different from new
144 $uploaded_data['extra']['backup'] = serialize($existing);
145 $is_image_edit = true; // if existing item is different from new it is image edit
146 }
147
148 if(isset($uploaded_data['file'])) {
149 $wpmcsItem->update(
150 $attachment_id,
151 [
152 'source_path' => $uploaded_data['file']['source_path'],
153 'url' => $uploaded_data['file']['url'],
154 'key' => $uploaded_data['file']['key'],
155 'extra' => maybe_serialize($uploaded_data['extra']),
156 ]
157 );
158 }
159 } else { // if backup available
160 if($backup['source_path'] != $source_path) {
161 $uploaded_data = $wpmcsService->uploadMedia($attachment_meta, $attachment_id, $source_path);
162 $uploaded_data['extra']['backup'] = maybe_serialize($backup);
163
164 if( $existing['source_path'] != $source_path ) { // if existing item is different from new
165 /**
166 * Since the backup is present and it is not the first edit
167 * It is a re-edit, So we don't need intermediate edit state.
168 * So removing cloud and server files immediately
169 */
170 $wpmcsService->delete_attachments_by_item($existing, false);
171 $this->may_be_delete_server_files_by_item($existing, true);
172
173 $is_image_edit = true; // if existing item is different from new it is image edit
174 }
175
176 if(isset($uploaded_data['file'])) {
177 $wpmcsItem->update(
178 $attachment_id,
179 [
180 'source_path' => $uploaded_data['file']['source_path'],
181 'url' => $uploaded_data['file']['url'],
182 'key' => $uploaded_data['file']['key'],
183 'extra' => maybe_serialize($uploaded_data['extra']),
184 ]
185 );
186 }
187 } else { // Restore backup
188 $wpmcsService->delete_attachments_by_item($existing, false);
189 $wpmcsItem->update(
190 $attachment_id,
191 [
192 'source_path' => $backup['source_path'],
193 'url' => $backup['url'],
194 'key' => $backup['key'],
195 'extra' => $backup['extra'],
196 ]
197 );
198 }
199 }
200 }
201 }
202
203 // Remove files from server if enabled ---- Remove main file if it is not image or a new image source url (image edit)
204 $delete_main_file = !$is_image || $is_image_edit;
205 // Remove files from server for backup ---- Remove backup from server if it is image edit (To remove restored file)
206 $delete_backup = $is_image_edit;
207 $this->may_be_delete_server_files_by_id($attachment_id, $delete_main_file, $delete_backup);
208
209 return $attachment_meta;
210 }
211
212
213
214
215 /**
216 * Function to remove media from server by id
217 * @since 1.0.0
218 *
219 */
220 public function may_be_delete_server_files_by_id($attachment_id, $delete_main_file=false, $delete_backup = false) {
221 $item = Item::instance()->get($attachment_id);
222 if(Utils::is_empty($item)) {
223 return false;
224 }
225
226 if( !( isset($this->settings['remove_from_server']) && $this->settings['remove_from_server'] ) ) {
227 return false;
228 }
229
230 if (isset($item['extra']) && !empty($item['extra'])) {
231 $extras = unserialize($item['extra']);
232 if (
233 isset($extras) && !empty($extras) &&
234 isset($extras['backup']) && !empty($extras['backup']) &&
235 $delete_backup
236 ) {
237 $backup = unserialize($extras['backup']);
238 if (isset($backup) && !empty($backup)) {
239 $this->may_be_delete_server_files_by_item($backup, $delete_main_file);
240 }
241 }
242 }
243
244 $this->may_be_delete_server_files_by_item($item, $delete_main_file);
245
246 return true;
247 }
248
249 /**
250 * Function to remove media from server by item
251 */
252 public function may_be_delete_server_files_by_item( $item, $delete_main_file=false ) {
253 if(Utils::is_empty($item)) {
254 return false;
255 }
256
257 if( !( isset($this->settings['remove_from_server']) && $this->settings['remove_from_server'] ) ) {
258 return false;
259 }
260
261 $upload_dir = wp_get_upload_dir();
262 $has_original = false;
263
264 $file_path = trailingslashit($upload_dir['basedir']) . $item['source_path'];
265
266 if (isset($item['extra']) && !empty($item['extra'])) {
267 $extras = unserialize($item['extra']);
268 if (
269 isset($extras) && !empty($extras) &&
270 isset($extras['sizes']) && !empty($extras['sizes'])
271 ) {
272 foreach ($extras['sizes'] as $sub_image) {
273 if (isset($sub_image['source_path']) && !empty($sub_image['source_path'])) {
274 $file = trailingslashit($upload_dir['basedir']) . $sub_image['source_path'];
275 if(file_exists($file)) {
276 wp_delete_file($file);
277 }
278 }
279 }
280 }
281
282 if (
283 isset($extras) && !empty($extras) &&
284 isset($extras['original']) && !empty($extras['original'])
285 ) {
286 $has_original = true;
287 $file = trailingslashit($upload_dir['basedir']).$extras['original']['source_path'];
288 if(file_exists($file) && $delete_main_file) {
289 wp_delete_file($file);
290 }
291 }
292 }
293 if(file_exists($file_path)) {
294 if ($has_original || (!$has_original && $delete_main_file)) {
295 wp_delete_file($file_path);
296 }
297 }
298 return true;
299 }
300
301 /**
302 * Function to remove data from provider by attachment ID
303 * @since 1.0.0
304 *
305 */
306 public function delete_attachment($attachment_id){
307 if (!Utils::is_service_enabled()) {
308 return $attachment_id;
309 }
310
311
312 $wpmcsService = Service::instance();
313 $wpmcsItem = Item::instance();
314
315
316 $item = $wpmcsItem->get($attachment_id);
317
318 if (Utils::is_empty($item)) {
319 return $attachment_id;
320 }
321
322 $wpmcsService->delete_attachments_by_item($item);
323
324 $wpmcsItem->delete($attachment_id);
325
326 return $attachment_id;
327 }
328
329 /**
330 * Allow processes to update the file on provider via update_attached_file()
331 * @since 1.0.0
332 * @param string $file
333 * @param int $attachment_id
334 *
335 * @return string
336 */
337 public function update_attached_file($file, $attachment_id) {
338 if( !Utils::is_ok_to_upload($attachment_id) ) {
339 return $file;
340 }
341
342 $item = Item::instance()->get($attachment_id);
343
344 if (Utils::is_empty($item)) {
345 return $file;
346 }
347
348 $file = apply_filters('wpmcs_update_attached_file', $file, $attachment_id, $item);
349
350 return $file;
351 }
352
353 /**
354 * Function to execute on load_image_to_edit_path
355 * @since 1.0.0
356 * @return string
357 *
358 */
359 public function load_image_to_edit_path($path, $attachment_id, $size) {
360 $wpmcsItem = Item::instance();
361 if (!Utils::is_ok_to_serve($attachment_id) && !$wpmcsItem->is_available_from_provider($attachment) ) {
362 return $path;
363 }
364
365
366 $server_file = false;
367 //If operation is Image editor Image Save
368 if(
369 isset($_REQUEST['do']) &&
370 isset($_REQUEST['action']) &&
371 $_REQUEST['action'] === 'image-editor' &&
372 $_REQUEST['do']==='save'
373 ) {
374 $server_file = $wpmcsItem->moveToServer($attachment_id, $size);
375 }
376
377 return $server_file ? $server_file : $path;
378 }
379
380 /**
381 * Get attachment url
382 * @since 1.0.0
383 * @param string $url
384 * @param int $attachment_id
385 *
386 * @return bool|mixed|WP_Error
387 */
388 public function wp_get_attachment_url($url, $attachment_id) {
389 if (!Utils::is_ok_to_serve($attachment_id)) {
390 return $url;
391 }
392
393 $new_url = Item::instance()->get_url($attachment_id);
394
395 if (Utils::is_empty($new_url)) {
396 return $url;
397 }
398
399 $new_url = apply_filters('wpmcs_wp_get_attachment_url', $new_url, $url, $attachment_id);
400
401 return $new_url;
402 }
403
404 /**
405 * Filters the list of attachment image attributes.
406 *
407 * @since 1.0.0
408 * @param array $attr Attributes for the image markup.
409 * @param WP_Post $attachment Image attachment post.
410 * @param string|array $size Requested size. Image size or array of width and height values (in that order).
411 *
412 * @return array
413 */
414 public function wp_get_attachment_image_attributes($attr, $attachment, $size='thumbnail') {
415 if (
416 !$attachment ||
417 !Utils::is_ok_to_serve($attachment->ID)
418 ) {
419 return $attr;
420 }
421
422 $wpmcsItem = Item::instance();
423
424 $item = $wpmcsItem->get($attachment->ID);
425
426 if (Utils::is_empty($item)) {
427 return $attr;
428 }
429
430 $size = Utils::maybe_convert_size_to_string($attachment->ID, $size);
431 if ($size === false) {
432 return $attr;
433 }
434
435
436
437 if (
438 isset($size) && !empty($size) &&
439 isset($attr['src']) && !empty($attr['src'])
440 ) {
441 $source = $wpmcsItem->get_url($attachment->ID, $size);
442 if (isset($source) && !empty($source)) {
443 $attr['src'] = $source;
444 }
445 }
446
447 /**
448 * Filtered list of attachment image attributes.
449 *
450 * @param array $attr Attributes for the image markup.
451 * @param WP_Post $attachment Image attachment post.
452 * @param string $size Requested size.
453 */
454 return apply_filters('wpmcs_wp_get_attachment_image_attributes', $attr, $attachment, $size, $item);
455 }
456
457 /**
458 * Return the provider URL when the local file is missing
459 * unless we know who the calling process is and we are happy
460 * to copy the file back to the server to be used.
461 *
462 * @handles get_attached_file
463 * @handles wp_get_original_image_path
464 * @since 1.0.0
465 * @param string $file
466 * @param int $attachment_id
467 *
468 * @return string
469 */
470 public function get_attached_file($file, $attachment_id) {
471 $attachment_id = (int)$attachment_id;
472
473 // During the deletion of an attachment, stream wrapper URLs should not be returned.
474 if ( $this->deleting_attachment ) {
475 return $file;
476 }
477
478 if (!Utils::is_ok_to_serve($attachment_id)) {
479 return $file;
480 }
481
482 if( isset($_REQUEST['action']) && $_REQUEST['action'] === 'image-editor' ) {
483 // Avoid rewriting URL when image restore in image editor
484 if(isset($_REQUEST['do']) && $_REQUEST['do']==='restore') {
485 return $file;
486 }
487 // Avoid rewriting URL when image save in image editor
488 if(isset($_REQUEST['do']) && $_REQUEST['do'] === 'save') {
489 return $file;
490 }
491 }
492
493 $wpmcsItem = Item::instance();
494
495 $item = $wpmcsItem->get($attachment_id);
496
497 if ( file_exists( $file ) || Utils::is_empty($item)) {
498 if(!Utils::is_empty($item)) {
499 /**
500 * Added filter to allow plugins to copy the pending size to the server
501 * before the file is served.
502 *
503 * @param string $file
504 * @param string $file
505 * @param int $attachment_id
506 */
507 return apply_filters( 'wpmcs_get_attached_file_noop', $file, $file, $attachment_id );
508 } else {
509 return $file;
510 }
511 }
512
513 $url = $wpmcsItem->get_url($attachment_id);
514 if(Utils::is_empty($url)) {
515 return $file;
516 }
517 /**
518 * This filter gives filter implementors a chance to copy back missing item files
519 * from the provider before WordPress returns the file name/path for it. Defaults to
520 * returning the remote URL.
521 *
522 * @param string $url Item URL
523 * @param string $file Local file path
524 * @param int $attachment_id Attachment ID
525 * @param array $item Item data
526 */
527 return apply_filters('wpmcs_get_attached_file', $url, $file, $attachment_id, $item);
528 }
529
530 /**
531 * Change src attributes
532 * @since 1.0.0
533 */
534
535 public function wp_calculate_image_srcset($sources, $size_array, $image_src, $attachment_meta, $attachment_id = 0) {
536 $attachment_id = (int)$attachment_id;
537
538 // Must need $attachment_id other wise not possible to get data from the table
539 if (!Utils::is_ok_to_serve($attachment_id)) {
540 return $sources;
541 }
542
543 $wpmcsItem = Item::instance();
544
545 $item = $wpmcsItem->get($attachment_id);
546
547 if (Utils::is_empty($item)) {
548 return $sources;
549 }
550
551 $item_extra = $wpmcsItem->get_extras($attachment_id);
552
553 if (isset($item_extra['width']) && !empty($item_extra['width'])) {
554 $sources[$item_extra['width']]=[
555 'url' => $wpmcsItem->get_url($attachment_id),
556 'descriptor' => 'w',
557 'value' => $item_extra['width']
558 ];
559 }
560
561 if ($item_extra) {
562 if (isset($item_extra['sizes']) && !empty($item_extra['sizes'])) {
563 foreach ($item_extra['sizes'] as $size => $size_array) {
564 if (isset($size_array['width']) && !empty($size_array['width'])) {
565 $w = $size_array['width'];
566 if (isset($sources[$w]) && !empty($sources[$w])) {
567 $sources[$w]['url'] = $wpmcsItem->get_url($attachment_id, $size);
568 }
569 }
570 }
571 }
572 }
573
574 return $sources;
575 }
576
577
578
579 /**
580 * Create unique names for files effects mainly on delete files from server settings
581 * @since 1.0.0
582 * @return string
583 */
584 public function wp_unique_filename($filename, $ext, $dir) {
585 // Get Post ID if uploaded in post screen.
586 $post_id = filter_input(INPUT_POST, 'post_id', FILTER_VALIDATE_INT);
587
588 $filename = $this->filter_unique_filename($filename, $ext, $dir, $post_id);
589
590 return $filename;
591 }
592
593 /**
594 * filter unique file names
595 * @since 1.0.0
596 * @return string
597 */
598 private function filter_unique_filename($filename, $ext, $dir, $post_id = null) {
599 if (!Utils::is_service_enabled()) {
600 return $filename;
601 }
602
603 // sanitize the file name before we begin processing
604 $filename = sanitize_file_name($filename);
605 $ext = strtolower($ext);
606 $name = wp_basename($filename, $ext);
607
608 // Edge case: if file is named '.ext', treat as an empty name.
609 if ($name === $ext) {
610 $name = '';
611 }
612
613 // Rebuild filename with lowercase extension as provider will have converted extension on upload.
614 $filename = $name . $ext;
615
616 return $this->generate_unique_filename($name, $ext, $dir);
617 }
618
619 /**
620 * Generate unique filename
621 * @since 1.0.0
622 * @param string $name
623 * @param string $ext
624 * @param string $time
625 *
626 * @return string
627 */
628 private function generate_unique_filename($name, $ext, $dir) {
629 $upload_dir = wp_get_upload_dir();
630 $filename = $name . $ext;
631 $no_ext_path = $dir . '/' . $name;
632 $rel_no_ext_path = substr($no_ext_path, strlen(trailingslashit($upload_dir['basedir'])),strlen($no_ext_path));
633 $path = $dir . '/' . $name . $ext;
634 $source_path = substr($path, strlen(trailingslashit($upload_dir['basedir'])),strlen($path));
635
636
637 $uploaded_files = Item::instance()->get_similar_files_by_path($rel_no_ext_path);
638
639 if ($uploaded_files !== false) {
640 if (Utils::check_existing_file_names($source_path, $uploaded_files) || file_exists($path)) {
641 $count = 1;
642 $new_file_name = '';
643 $found = true;
644 while ($found) {
645 $tmp_path = $dir . '/' . $name . '-' . $count . $ext;
646 $rel_temp_path = substr($tmp_path, strlen(trailingslashit($upload_dir['basedir'])),strlen($tmp_path));
647
648 if (Utils::check_existing_file_names($rel_temp_path, $uploaded_files) || file_exists($tmp_path)) {
649 $count++;
650 } else {
651 $found = false;
652 $new_file_name = $name . '-' . $count . $ext;
653 }
654 }
655 return $new_file_name;
656 }
657 } else {
658 if (file_exists($path)) {
659 $count = 1;
660 $new_file_name = '';
661 $found = true;
662
663 while ($found) {
664 $tmp_path = $dir . '/' . $name . '-' . $count . $ext;
665 if (file_exists($tmp_path)) {
666 $count++;
667 } else {
668 $found = false;
669 $new_file_name = $name . '-' . $count . $ext;
670 }
671 }
672 return $new_file_name;
673 }
674 }
675
676 return $filename;
677 }
678
679 /**
680 * Filters the audio & video shortcodes output to remove "&_=NN" params from source.src as it breaks signed URLs.
681 *
682 * @param string $html Shortcode HTML output.
683 * @param array $atts Array of shortcode attributes.
684 * @param string $media Media file.
685 * @param int $post_id Post ID.
686 * @param string $library Media library used for the shortcode.
687 *
688 * @return string
689 *
690 * Note: Depends on 30377.4.diff from https://core.trac.wordpress.org/ticket/30377
691 */
692 public function wp_media_shortcode( $html, $atts, $media, $post_id, $library ) {
693 return preg_replace( '/&#038;_=[0-9]+/', '', $html );
694 }
695
696
697 /**
698 * Takes notice that an attachment is about to be deleted and prepares for it.
699 *
700 * @handles pre_delete_attachment
701 *
702 * @param bool|null $delete Whether to go forward with deletion.
703 *
704 * @return bool|null
705 */
706 public function pre_delete_attachment( $delete ) {
707 if ( is_null( $delete ) ) {
708 $this->deleting_attachment = true;
709 }
710
711 return $delete;
712 }
713
714 /**
715 * Takes notice that an attachment has been deleted and undoes previous preparations for the event.
716 *
717 * @handles delete_post
718 *
719 * Note: delete_post is used as there is a potential that deleted_post is not reached.
720 */
721 public function delete_post() {
722 $this->deleting_attachment = false;
723 }
724
725
726
727 /**
728 * Ensures only one instance of Class is loaded or can be loaded.
729 *
730 * @return Media Class instance
731 * @since 1.0.0
732 * @static
733 */
734 public static function instance() {
735 if (is_null(self::$instance)) {
736 self::$instance = new self();
737 }
738 return self::$instance;
739 }
740 }