PluginProbe
Media Cloud Sync / 1.4.2
Media Cloud Sync v1.4.2
1.4.2 1.4.1 1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 All 36 releases
media-cloud-sync / includes / filters / content.php

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

725 lines 19.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 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 if ( ! apply_filters( 'wpmcs_push_to_url_pairs', true, $item_source['id'], $item_source['source_type'] ) ) {
410 return false;
411 }
412
413 $base = trailingslashit($upload_dir['baseurl']);
414 $backward_replacement = apply_filters('wpmcs_enable_backward_url_replacement', false);
415
416 $source_server_file = Utils::remove_scheme($base.$item['source_path']);
417 $source_cloud_file = Utils::remove_scheme($wpmcsItem->get_url($item_source['id'], 'full', $item_source['source_type']));
418
419 $url_pairs[$source_server_file] = $source_cloud_file;
420 if($backward_replacement) {
421 $url_pairs[$source_cloud_file] = $source_server_file; // Backward compatibility
422 }
423
424
425 if(
426 isset($item['original_source_path']) &&
427 !Utils::is_empty($item['original_source_path'])
428 ) {
429 $original_server_file = Utils::remove_scheme($base.$item['original_source_path']);
430 $original_cloud_file = Utils::remove_scheme($wpmcsItem->get_url($item_source['id'], 'original', $item_source['source_type']));
431 $url_pairs[$original_server_file] = $original_cloud_file;
432 if($backward_replacement) {
433 $url_pairs[$original_cloud_file] = $original_server_file; // Backward compatibility
434 }
435 }
436
437 $extras = $wpmcsItem->get_extras($item_source['id'], false, $item_source['source_type']);
438
439 if(isset($extras['sizes'])){
440 foreach ($extras['sizes'] as $size => $sub_image) {
441 if(Utils::is_empty($sub_image['source_path'])) continue;
442
443 $sub_server_file = Utils::remove_scheme($base.$sub_image['source_path']);
444 $sub_cloud_file = Utils::remove_scheme($wpmcsItem->get_url( $item_source['id'], $size, $item_source['source_type'] ));
445 $url_pairs[$sub_server_file] = $sub_cloud_file;
446 if($backward_replacement) {
447 $url_pairs[$sub_cloud_file ] = $sub_server_file; // Backward compatibility
448 }
449 }
450 }
451
452 $to_cache[ $find ] = $item_source;
453 }
454
455
456 /**
457 * Replace URLs.
458 *
459 * @param string $content
460 * @param array $url_pairs
461 *
462 * @return string
463 */
464 protected function replace_urls( $content, $url_pairs ) {
465 if ( empty( $url_pairs ) ) {
466 // No URLs to replace return
467 return $content;
468 }
469
470 foreach ( $url_pairs as $find => $replace ) {
471 $content = apply_filters('wpmcs_before_url_replace', $content, $find, $replace );
472 $content = str_replace( $find, $replace, $content );
473 $content = apply_filters('wpmcs_after_url_replace', $content, $find, $replace );
474 }
475
476 return $content;
477 }
478
479 /**
480 * Find URLs and replace.
481 *
482 * @param string $value
483 * @param array $cache
484 * @param array $to_cache
485 *
486 * @return string
487 */
488 protected function find_urls_and_replace( $value, $cache, &$to_cache ) {
489 if ( !Utils::is_ok_to_serve(false, false) ) {
490 return $value;
491 }
492
493 $url_pairs = $this->get_urls_from_content( $value, $cache, $to_cache );
494 $value = $this->replace_urls( $value, $url_pairs );
495
496 return $value;
497 }
498
499 /**
500 * Get URLs from content.
501 *
502 * @param string $content
503 * @param array $cache
504 * @param array $to_cache
505 *
506 * @return array
507 */
508 protected function get_urls_from_content( $content, $cache, &$to_cache ) {
509 $url_pairs = array();
510
511 if ( ! is_string( $content ) ) {
512 return $url_pairs;
513 }
514
515 if ( ! preg_match_all( '/(http|https)?:?\/\/[^"\'\s<>()\\\]*/', $content, $matches ) || ! isset( $matches[0] ) ) {
516 // No URLs found, return
517 return $url_pairs;
518 }
519
520 $matches = array_unique( $matches[0] );
521 $urls = array();
522
523 foreach ( $matches as $url ) {
524 $url = preg_replace( '/[^a-zA-Z0-9]$/', '', $url );
525
526 if ( ! Utils::is_file_url( $url ) ) {
527 continue;
528 }
529
530 if ( ! self::url_needs_replacing( $url ) ) {
531 // URL already correct, skip
532 continue;
533 }
534
535 $item_source = null;
536 $bare_url = Utils::reduce_url( $url );
537
538 // If attachment ID recently or previously cached, skip full search.
539 if ( isset( $to_cache[ $bare_url ] ) ) {
540 $item_source = $to_cache[ $bare_url ];
541 } elseif ( isset( $cache[ $bare_url ] ) && is_array($cache[ $bare_url ]) ) {
542 $item_source = $cache[ $bare_url ];
543 }
544
545 if ( is_null( $item_source ) ) {
546 // Attachment ID not cached, need to search by URL.
547 $urls[] = $bare_url;
548 } else {
549 $this->push_to_url_pairs( $url_pairs, $item_source, $bare_url, $to_cache );
550 }
551 }
552
553 if ( ! empty( $urls ) ) {
554 $item_sources = $this->get_item_sources_from_urls( $urls );
555
556 foreach ( $item_sources as $url => $item_source ) {
557 if ( ! $item_source ) {
558 continue;
559 }
560
561 $this->push_to_url_pairs( $url_pairs, $item_source, $url, $to_cache );
562 }
563 }
564
565 return $url_pairs;
566 }
567
568 /**
569 * Get item source descriptors from URLs.
570 *
571 * @param array $urls
572 *
573 * @return array url => item source descriptor array (or false)
574 */
575 protected function get_item_sources_from_urls( $urls ) {
576 $results = array();
577
578 if ( empty( $urls ) ) {
579 return $results;
580 }
581 // Check if backward compatibility is enabled
582 $reverse_compatibility = apply_filters('wpmcs_enable_backward_url_replacement', false);
583
584 if ( ! is_array( $urls ) ) {
585 $urls = array( $urls );
586 }
587
588 $wpmcsItem = Item::instance();
589
590 $query_set = array();
591 $paths = array();
592 $full_urls = array();
593
594 // Quickly parse given URLs to add versions without size as we should lookup with size info first as that could be the "full" size.
595 foreach ( $urls as $url ) {
596 $query_set[] = $url;
597 $size_removed = Utils::remove_size_from_filename( $url );
598
599 if ( $url !== $size_removed ) {
600 $query_set[] = $size_removed;
601 }
602 }
603
604 foreach ( $query_set as $url ) {
605 // Path to search for in query set should be based on bare URL.
606 $bare_url = Utils::remove_scheme( $url );
607 // There can be multiple URLs in the query set that belong to the same full URL for the Media Library item.
608 $full_url = Utils::remove_size_from_filename( $bare_url );
609
610 if ( isset( $this->query_cache[ $full_url ] ) ) {
611 // ID already cached, use it.
612 $results[ $url ] = $this->query_cache[ $full_url ];
613
614 continue;
615 }
616
617
618 $path = Utils::get_attachment_source_path( $full_url, $reverse_compatibility ? 'key' : 'source' );
619
620 $paths[ $path ] = $full_url;
621 $full_urls[ $full_url ][] = $url;
622 }
623
624 if ( ! empty( $paths ) ) {
625 if($reverse_compatibility) {
626 // For backward compatibility, search inside 'key' and 'original_key' columns.
627 $wpmcsItems = $wpmcsItem->get_items_by_paths( array_keys( $paths ), true, false, 'key' );
628 } else {
629 $wpmcsItems = $wpmcsItem->get_items_by_paths( array_keys( $paths ) );
630 }
631
632 if ( ! empty( $wpmcsItems ) ) {
633 foreach ( $wpmcsItems as $item ) {
634 // Each returned item may have matched on either the source_path or original_source_path
635 // (or, in backward-compatibility mode, key or original_key — $paths above is keyed by
636 // whichever field type get_attachment_source_path() was asked for, so the lookup below
637 // must compare against that same field, not always source_path).
638 // Because the base image file name of a thumbnail might match the primary rather scaled or rotated full image
639 // it's possible that both source paths are used by separate URLs.
640 $source_id = (int)$item['source_id'];
641 $source_type = $item['source_type'];
642 $path_field = $reverse_compatibility ? 'key' : 'source_path';
643 $original_field = $reverse_compatibility ? 'original_key' : 'original_source_path';
644 $source_paths = [ $item[ $path_field ] ];
645
646 // If the original path/key is set, add it to source_paths.
647 if(isset($item[ $original_field ]) && !Utils::is_empty($item[ $original_field ])) {
648 $source_paths[] = $item[ $original_field ];
649 }
650
651 foreach ( $source_paths as $source_path ) {
652 if ( ! empty( $paths[ $source_path ] ) ) {
653 $matched_full_url = $paths[ $source_path ];
654
655 if ( ! empty( $full_urls[ $matched_full_url ] ) ) {
656 $item_source = [
657 'id' => $source_id,
658 'source_type' => $source_type
659 ];
660
661 $this->query_cache[ $matched_full_url ] = $item_source;
662
663 foreach ( $full_urls[ $matched_full_url ] as $url ) {
664 $results[ $url ] = $item_source;
665 }
666 unset( $full_urls[ $matched_full_url ] );
667 }
668 }
669 }
670 }
671 }
672
673 // No more item IDs found, set remaining results as false.
674 if ( count( $query_set ) !== count( $results ) ) {
675 foreach ( $full_urls as $full_url => $schema_urls ) {
676 foreach ( $schema_urls as $url ) {
677 if ( ! array_key_exists( $url, $results ) ) {
678 $this->query_cache[ $full_url ] = false;
679 $results[ $url ] = false;
680 }
681 }
682 }
683 }
684 }
685
686 return $results;
687 }
688
689 /**
690 * Is the supplied item_source considered to be empty?
691 *
692 * @param array $item_source
693 *
694 * @return bool
695 */
696 public function is_empty_item_source( $item_source ) {
697 if (
698 empty( $item_source['source_type'] ) ||
699 ! isset( $item_source['id'] ) ||
700 ! is_numeric( $item_source['id'] ) ||
701 $item_source['id'] < 0
702 ) {
703 return true;
704 }
705
706 return false;
707 }
708
709
710
711 /**
712 * Define the method for the backword compatibility filter
713 * */
714 public function enable_backward_url_check( $enable ) {
715 return true;
716 }
717
718
719 public static function instance(){
720 if (is_null(self::$instance)) {
721 self::$instance = new self();
722 }
723 return self::$instance;
724 }
725 }