PluginProbe
Media Cloud Sync / 1.3.11
Media Cloud Sync v1.3.11
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 / filters / content.php

content.php in Media Cloud Sync 1.3.11, at includes/filters/content.php

716 lines 18.7 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 FilterContent {
7 private static $instance = null;
8 private $assets_url;
9 private $version;
10 private $token;
11
12
13 protected $post_meta_key;
14 protected $query_cache = [];
15
16 /**
17 * Admin constructor.
18 * @since 1.0.0
19 */
20 public function __construct() {
21 $this->assets_url = WPMCS_ASSETS_URL;
22 $this->version = WPMCS_VERSION;
23 $this->token = WPMCS_TOKEN;
24
25 $this->post_meta_key = Schema::getConstant('CONTENT_META_KEY');
26
27 // Initialize setup
28 $this->registerActions();
29 }
30
31 /**
32 * Register integration access
33 */
34 public function registerActions() {
35 // Posts
36 add_action( 'the_post',[ $this, 'filter_post_data' ] );
37 add_filter( 'content_pagination',[ $this, 'filter_content_pagination' ] );
38 add_filter( 'the_content',[ $this, 'filter_post' ], 100 );
39 add_filter( 'the_excerpt',[ $this, 'filter_post' ], 100 );
40 add_filter( 'rss_enclosure',[ $this, 'filter_post' ], 100 );
41 add_filter( 'content_edit_pre',[ $this, 'filter_post' ] );
42 add_filter( 'excerpt_edit_pre',[ $this, 'filter_post' ] );
43 add_filter( 'wpmcs_filter_post',[ $this, 'filter_post' ] );
44
45 // Post edit screen use Backward compatibility
46 add_filter( 'content_save_pre', [ $this, 'filter_post_backward' ] );
47 add_filter( 'excerpt_save_pre', [ $this, 'filter_post_backward' ] );
48 }
49
50
51 /**
52 * Filter post data.
53 *
54 * @param WP_Post $post
55 */
56 public function filter_post_data( $post ) {
57 global $pages;
58
59 $cache = Utils::get_meta($post->ID, 'items', [], $this->post_meta_key);
60
61 $to_cache = [];
62
63 if ( is_array( $pages ) && 1 === count( $pages ) && ! empty( $pages[0] ) ) {
64 // Post already filtered and available on global $page array, continue
65 $post->post_content = $pages[0];
66 } else {
67 $post->post_content = $this->process_content( $post->post_content, $cache, $to_cache );
68 }
69
70 $post->post_excerpt = $this->process_content( $post->post_excerpt, $cache, $to_cache );
71
72 $this->maybe_update_post_cache( $to_cache );
73 }
74
75 /**
76 * filter post for backward compatibility
77 */
78 public function filter_post_backward( $content ) {
79
80 // Enable backword compatibility
81 add_filter( 'wpmcs_enable_backward_url_replacement', [ $this, 'enable_backward_url_check' ] );
82
83 $content = $this->filter_post( $content );
84
85 // remove backword compatibility after process
86 remove_filter( 'wpmcs_enable_backward_url_replacement', [ $this, 'enable_backward_url_check' ] );
87
88 return $content;
89 }
90
91
92 /**
93 * Filter post.
94 *
95 * @param string $content
96 *
97 * @return string
98 */
99 public function filter_post( $content ) {
100 if ( empty( $content ) ) {
101 // Nothing to filter, continue
102 return $content;
103 }
104
105 $cache = $this->get_post_cache();
106 $to_cache = array();
107
108 $content = $this->process_content( $content, $cache, $to_cache );
109
110 $this->maybe_update_post_cache( $to_cache );
111
112 return $content;
113 }
114
115 /**
116 * Filter content pagination.
117 *
118 * @param array $pages
119 *
120 * @return array
121 */
122 public function filter_content_pagination( $pages ) {
123 $cache = $this->get_post_cache();
124
125 $to_cache = array();
126
127 foreach ( $pages as $key => $page ) {
128 $pages[ $key ] = $this->process_content( $page, $cache, $to_cache );
129 }
130
131 $this->maybe_update_post_cache( $to_cache );
132
133 return $pages;
134 }
135
136
137 /**
138 * may be update the post cache
139 */
140 public function maybe_update_post_cache( $to_cache ) {
141 $post_id = (int) get_post_field( 'ID', null );
142
143 if ( ! $post_id ) {
144 return array();
145 }
146
147 $cache = $this->get_post_cache();
148
149 // Merge the arrays and keep unique key-value pairs
150 $to_cache = $to_cache + $cache;
151
152 if($to_cache !== $cache) {
153 Utils::update_meta($post_id, 'items', $to_cache, $this->post_meta_key);
154 }
155 }
156
157
158 /**
159 * Get post cache
160 *
161 * @param null|int|WP_Post $post Optional. Post ID or post object. Defaults to current post.
162 * @return array|int
163 */
164 public function get_post_cache( $post = null, $transform_ints = true ) {
165 $post_id = (int) get_post_field( 'ID', $post );
166
167 if ( ! $post_id ) {
168 return array();
169 }
170
171 $cache = Utils::get_meta($post_id, 'items', [], $this->post_meta_key);
172
173 if ( Utils::is_empty( $cache ) ) {
174 $cache = array();
175 }
176
177 return $cache;
178 }
179
180
181 /**
182 * Process content.
183 *
184 * @param string $content
185 * @param array $cache
186 * @param array $to_cache
187 *
188 * @return mixed
189 */
190 protected function process_content( $content, $cache, &$to_cache ) {
191 if ( empty( $content ) || !Utils::is_ok_to_serve(false, false) ) {
192 return $content;
193 }
194
195 $content = $this->pre_filter_content( $content );
196
197 // Find URLs from img src
198 $url_pairs = $this->get_urls_from_img_src( $content, $to_cache );
199 $content = $this->replace_urls( $content, $url_pairs );
200
201 // Find leftover URLs
202 $content = $this->find_urls_and_replace( $content, $cache, $to_cache );
203
204 // Perform post processing if required
205 $content = apply_filters('wpmcs_post_process_content', $content);
206
207 return $content;
208 }
209
210 /**
211 * Pre replace content.
212 * @since 1.0.0
213 * @param string $content
214 * @return string
215 */
216 protected function pre_filter_content( $content ) {
217 $uploads = wp_upload_dir();
218 $base_url = Utils::remove_scheme( $uploads['baseurl'] );
219
220 return Utils::remove_query_strings( $content, $base_url );
221 }
222
223 /**
224 * Get URLs from img src.
225 *
226 * @param string $content
227 * @param array $to_cache
228 *
229 * @return array
230 */
231 protected function get_urls_from_img_src( $content, &$to_cache ) {
232 $url_pairs = array();
233
234 if ( ! is_string( $content ) ) {
235 return $url_pairs;
236 }
237
238 if ( ! preg_match_all( '/<img [^>]+>/', $content, $matches ) || ! isset( $matches[0] ) ) { // No img tags found
239 return $url_pairs;
240 }
241
242 $matches = array_unique( $matches[0] );
243 $item_sources = array();
244
245 foreach ( $matches as $image ) {
246 if ( ! preg_match( '/wp-image-([0-9]+)/i', $image, $class_id ) || ! isset( $class_id[1] ) ) {
247 // Can't determine ID from class, skip
248 continue;
249 }
250
251 if ( ! preg_match( '/src=\\\?["\']+([^"\'\\\]+)/', $image, $src ) || ! isset( $src[1] ) ) {
252 // Can't determine URL, skip
253 continue;
254 }
255
256 $url = $src[1];
257
258 if ( ! Utils::is_file_url( $url ) ) {
259 continue;
260 }
261
262 if ( ! self::url_needs_replacing( $url ) ) {
263 // URL already correct, skip
264 continue;
265 }
266
267 $bare_url = Utils::reduce_url( $url );
268
269 $item_sources[ $bare_url ] = [
270 'id' => absint( $class_id[1] ),
271 'source_type' => 'media_library'
272 ];
273 }
274
275 if ( count( $item_sources ) > 1 ) {
276 /*
277 * Warm object cache for use with 'get_post_meta()'.
278 *
279 * To avoid making a database call for each image, a single query
280 * warms the object cache with the meta information for all images.
281 */
282 update_meta_cache( 'post', array_unique(array_column($item_sources, 'id')));
283 }
284
285 foreach ( $item_sources as $url => $item_source ) {
286 if ( ! $this->item_matches_src( $item_source, $url ) ) {
287 // Path doesn't match attachment, skip
288 continue;
289 }
290
291 $this->push_to_url_pairs( $url_pairs, $item_source, $url, $to_cache );
292 }
293
294 return $url_pairs;
295 }
296
297 /**
298 * Does URL need replacing?
299 *
300 * @param string $url
301 *
302 * @return bool
303 */
304 public static function url_needs_replacing( $url ) {
305 $reverse_compatibility = apply_filters('wpmcs_enable_backward_url_replacement', false);
306
307 if( $reverse_compatibility ) {
308 if ( str_replace( Filter::get_bare_upload_base_urls(), '', $url ) !== $url ) {
309 // Local URL, no replacement needed.
310 return false;
311 }
312
313 if ( str_replace( Filter::get_remote_domains(), '', $url ) === $url ) {
314 // Not a known remote URL, no replacement needed.
315 return false;
316 }
317 } else {
318 if ( str_replace( Filter::get_bare_upload_base_urls(), '', $url ) === $url ) {
319 // Remote URL, no replacement needed
320 return false;
321 }
322 }
323
324 // Server URL, perform replacement
325 return true;
326 }
327
328 /**
329 * Does attachment ID match src?
330 *
331 * @param array $item_source
332 * @param string $url
333 *
334 * @return bool
335 */
336 public function item_matches_src( $item_source, $url ): bool {
337 $upload_dir = wp_get_upload_dir();
338 $wpmcsItem = Item::instance();
339
340 $backward_replacement = apply_filters('wpmcs_enable_backward_url_replacement', false);
341
342 if (
343 $this->is_empty_item_source($item_source) ||
344 'media_library' !== $item_source['source_type'] ||
345 get_post_type( $item_source['id'] ) !== 'attachment'
346 ) {
347 return false;
348 }
349
350 $item = $wpmcsItem->get( $item_source['id'], $item_source['source_type'] );
351
352 if(!$item) return false;
353
354 $bare_url = [];
355
356 $bare_url[] = $backward_replacement
357 ? Utils::reduce_url($wpmcsItem->get_url( $item_source['id'], 'full', $item_source['source_type'] ))
358 : Utils::reduce_url(trailingslashit($upload_dir['baseurl']).$item['source_path']);
359
360
361
362 if(isset($item['original_source_path']) && !Utils::is_empty($item['original_source_path'])){
363 $bare_url[] = $backward_replacement
364 ? Utils::reduce_url($wpmcsItem->get_url( $item_source['id'], 'original', $item_source['source_type'] ))
365 : Utils::reduce_url(trailingslashit($upload_dir['baseurl']).$item['original_source_path']);
366 }
367
368 $extras = $wpmcsItem->get_extras( $item_source['id'], false, $item_source['source_type'] );
369
370 if(isset($extras['sizes']) && !Utils::is_empty($extras['sizes'])) {
371 foreach($extras['sizes'] as $size => $sub_image) {
372 if(!Utils::is_empty($sub_image)){
373 $bare_url[] = $backward_replacement
374 ? Utils::reduce_url($wpmcsItem->get_url( $item_source['id'], $size, $item_source['source_type'] ))
375 : Utils::reduce_url(trailingslashit($upload_dir['baseurl']).$sub_image['source_path']);
376 }
377 }
378 }
379
380 if ( in_array( $url, $bare_url ) ) {
381 // Match found, return true
382 return true;
383 }
384
385 return false;
386 }
387
388
389 /**
390 * Push to URL pairs.
391 *
392 * @param array $url_pairs
393 * @param array $item_source
394 * @param string $find
395 * @param array $to_cache
396 */
397 protected function push_to_url_pairs( &$url_pairs, $item_source, $find, &$to_cache ) {
398 $upload_dir = wp_get_upload_dir();
399 $wpmcsItem = Item::instance();
400 $item = $wpmcsItem->get( $item_source['id'], $item_source['source_type'] );
401
402 if (
403 Utils::is_empty($item) ||
404 get_post_type( $item_source['id'] ) !== 'attachment'
405 ) {
406 return false;
407 }
408
409 $base = trailingslashit($upload_dir['baseurl']);
410 $backward_replacement = apply_filters('wpmcs_enable_backward_url_replacement', false);
411
412 $source_server_file = Utils::remove_scheme($base.$item['source_path']);
413 $source_cloud_file = Utils::remove_scheme($wpmcsItem->get_url($item_source['id'], 'full', $item_source['source_type']));
414
415 $url_pairs[$source_server_file] = $source_cloud_file;
416 if($backward_replacement) {
417 $url_pairs[$source_cloud_file] = $source_server_file; // Backward compatibility
418 }
419
420
421 if(
422 isset($item['original_source_path']) &&
423 !Utils::is_empty($item['original_source_path'])
424 ) {
425 $original_server_file = Utils::remove_scheme($base.$item['original_source_path']);
426 $original_cloud_file = Utils::remove_scheme($wpmcsItem->get_url($item_source['id'], 'original', $item_source['source_type']));
427 $url_pairs[$original_server_file] = $original_cloud_file;
428 if($backward_replacement) {
429 $url_pairs[$original_cloud_file] = $original_server_file; // Backward compatibility
430 }
431 }
432
433 $extras = $wpmcsItem->get_extras($item_source['id'], false, $item_source['source_type']);
434
435 if(isset($extras['sizes'])){
436 foreach ($extras['sizes'] as $size => $sub_image) {
437 if(Utils::is_empty($sub_image['source_path'])) continue;
438
439 $sub_server_file = Utils::remove_scheme($base.$sub_image['source_path']);
440 $sub_cloud_file = Utils::remove_scheme($wpmcsItem->get_url( $item_source['id'], $size, $item_source['source_type'] ));
441 $url_pairs[$sub_server_file] = $sub_cloud_file;
442 if($backward_replacement) {
443 $url_pairs[$sub_cloud_file ] = $sub_server_file; // Backward compatibility
444 }
445 }
446 }
447
448 $to_cache[ $find ] = $item_source;
449 }
450
451
452 /**
453 * Replace URLs.
454 *
455 * @param string $content
456 * @param array $url_pairs
457 *
458 * @return string
459 */
460 protected function replace_urls( $content, $url_pairs ) {
461 if ( empty( $url_pairs ) ) {
462 // No URLs to replace return
463 return $content;
464 }
465
466 foreach ( $url_pairs as $find => $replace ) {
467 $content = apply_filters('wpmcs_before_url_replace', $content, $find, $replace );
468 $content = str_replace( $find, $replace, $content );
469 $content = apply_filters('wpmcs_after_url_replace', $content, $find, $replace );
470 }
471
472 return $content;
473 }
474
475 /**
476 * Find URLs and replace.
477 *
478 * @param string $value
479 * @param array $cache
480 * @param array $to_cache
481 *
482 * @return string
483 */
484 protected function find_urls_and_replace( $value, $cache, &$to_cache ) {
485 if ( !Utils::is_ok_to_serve(false, false) ) {
486 return $value;
487 }
488
489 $url_pairs = $this->get_urls_from_content( $value, $cache, $to_cache );
490 $value = $this->replace_urls( $value, $url_pairs );
491
492 return $value;
493 }
494
495 /**
496 * Get URLs from content.
497 *
498 * @param string $content
499 * @param array $cache
500 * @param array $to_cache
501 *
502 * @return array
503 */
504 protected function get_urls_from_content( $content, $cache, &$to_cache ) {
505 $url_pairs = array();
506
507 if ( ! is_string( $content ) ) {
508 return $url_pairs;
509 }
510
511 if ( ! preg_match_all( '/(http|https)?:?\/\/[^"\'\s<>()\\\]*/', $content, $matches ) || ! isset( $matches[0] ) ) {
512 // No URLs found, return
513 return $url_pairs;
514 }
515
516 $matches = array_unique( $matches[0] );
517 $urls = array();
518
519 foreach ( $matches as $url ) {
520 $url = preg_replace( '/[^a-zA-Z0-9]$/', '', $url );
521
522 if ( ! Utils::is_file_url( $url ) ) {
523 continue;
524 }
525
526 if ( ! self::url_needs_replacing( $url ) ) {
527 // URL already correct, skip
528 continue;
529 }
530
531 $item_source = null;
532 $bare_url = Utils::reduce_url( $url );
533
534 // If attachment ID recently or previously cached, skip full search.
535 if ( isset( $to_cache[ $bare_url ] ) ) {
536 $item_source = $to_cache[ $bare_url ];
537 } elseif ( isset( $cache[ $bare_url ] ) && is_array($cache[ $bare_url ]) ) {
538 $item_source = $cache[ $bare_url ];
539 }
540
541 if ( is_null( $item_source ) ) {
542 // Attachment ID not cached, need to search by URL.
543 $urls[] = $bare_url;
544 } else {
545 $this->push_to_url_pairs( $url_pairs, $item_source, $bare_url, $to_cache );
546 }
547 }
548
549 if ( ! empty( $urls ) ) {
550 $item_sources = $this->get_item_sources_from_urls( $urls );
551
552 foreach ( $item_sources as $url => $item_source ) {
553 if ( ! $item_source ) {
554 continue;
555 }
556
557 $this->push_to_url_pairs( $url_pairs, $item_source, $url, $to_cache );
558 }
559 }
560
561 return $url_pairs;
562 }
563
564 /**
565 * Get item source descriptors from URLs.
566 *
567 * @param array $urls
568 *
569 * @return array url => item source descriptor array (or false)
570 */
571 protected function get_item_sources_from_urls( $urls ) {
572 $results = array();
573
574 if ( empty( $urls ) ) {
575 return $results;
576 }
577 // Check if backward compatibility is enabled
578 $reverse_compatibility = apply_filters('wpmcs_enable_backward_url_replacement', false);
579
580 if ( ! is_array( $urls ) ) {
581 $urls = array( $urls );
582 }
583
584 $wpmcsItem = Item::instance();
585
586 $query_set = array();
587 $paths = array();
588 $full_urls = array();
589
590 // Quickly parse given URLs to add versions without size as we should lookup with size info first as that could be the "full" size.
591 foreach ( $urls as $url ) {
592 $query_set[] = $url;
593 $size_removed = Utils::remove_size_from_filename( $url );
594
595 if ( $url !== $size_removed ) {
596 $query_set[] = $size_removed;
597 }
598 }
599
600 foreach ( $query_set as $url ) {
601 // Path to search for in query set should be based on bare URL.
602 $bare_url = Utils::remove_scheme( $url );
603 // There can be multiple URLs in the query set that belong to the same full URL for the Media Library item.
604 $full_url = Utils::remove_size_from_filename( $bare_url );
605
606 if ( isset( $this->query_cache[ $full_url ] ) ) {
607 // ID already cached, use it.
608 $results[ $url ] = $this->query_cache[ $full_url ];
609
610 continue;
611 }
612
613
614 $path = Utils::get_attachment_source_path( $full_url, $reverse_compatibility ? 'key' : 'source' );
615
616 $paths[ $path ] = $full_url;
617 $full_urls[ $full_url ][] = $url;
618 }
619
620 if ( ! empty( $paths ) ) {
621 if($reverse_compatibility) {
622 // For backward compatibility, search inside 'key' and 'original_key' columns.
623 $wpmcsItems = $wpmcsItem->get_items_by_paths( array_keys( $paths ), true, false, 'key' );
624 } else {
625 $wpmcsItems = $wpmcsItem->get_items_by_paths( array_keys( $paths ) );
626 }
627
628 if ( ! empty( $wpmcsItems ) ) {
629 foreach ( $wpmcsItems as $item ) {
630 // Each returned item may have matched on either the source_path or original_source_path.
631 // Because the base image file name of a thumbnail might match the primary rather scaled or rotated full image
632 // it's possible that both source paths are used by separate URLs.
633 $source_id = (int)$item['source_id'];
634 $source_type = $item['source_type'];
635 $source_paths = [ $item['source_path'] ];
636
637 // If original_source_path is set, add it to source_paths.
638 if(isset($item['original_source_path']) && !Utils::is_empty($item['original_source_path'])) {
639 $source_paths[] = $item['original_source_path'];
640 }
641
642 foreach ( $source_paths as $source_path ) {
643 if ( ! empty( $paths[ $source_path ] ) ) {
644 $matched_full_url = $paths[ $source_path ];
645
646 if ( ! empty( $full_urls[ $matched_full_url ] ) ) {
647 $item_source = [
648 'id' => $source_id,
649 'source_type' => $source_type
650 ];
651
652 $this->query_cache[ $matched_full_url ] = $item_source;
653
654 foreach ( $full_urls[ $matched_full_url ] as $url ) {
655 $results[ $url ] = $item_source;
656 }
657 unset( $full_urls[ $matched_full_url ] );
658 }
659 }
660 }
661 }
662 }
663
664 // No more item IDs found, set remaining results as false.
665 if ( count( $query_set ) !== count( $results ) ) {
666 foreach ( $full_urls as $full_url => $schema_urls ) {
667 foreach ( $schema_urls as $url ) {
668 if ( ! array_key_exists( $url, $results ) ) {
669 $this->query_cache[ $full_url ] = false;
670 $results[ $url ] = false;
671 }
672 }
673 }
674 }
675 }
676
677 return $results;
678 }
679
680 /**
681 * Is the supplied item_source considered to be empty?
682 *
683 * @param array $item_source
684 *
685 * @return bool
686 */
687 public function is_empty_item_source( $item_source ) {
688 if (
689 empty( $item_source['source_type'] ) ||
690 ! isset( $item_source['id'] ) ||
691 ! is_numeric( $item_source['id'] ) ||
692 $item_source['id'] < 0
693 ) {
694 return true;
695 }
696
697 return false;
698 }
699
700
701
702 /**
703 * Define the method for the backword compatibility filter
704 * */
705 public function enable_backward_url_check( $enable ) {
706 return true;
707 }
708
709
710 public static function instance(){
711 if (is_null(self::$instance)) {
712 self::$instance = new self();
713 }
714 return self::$instance;
715 }
716 }