PluginProbe
Media Cloud Sync / 1.1.1
Media Cloud Sync v1.1.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 1.3.0 All 34 releases
media-cloud-sync / includes / base / media.php

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

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