PluginProbe
Media Cloud Sync / 1.2.2
Media Cloud Sync v1.2.2
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.2.2, at includes/filters/content.php

670 lines 17.1 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_repalcement', [ $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_repalcement', [ $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 Service::instance()->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 ( ! $this->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 ] = absint( $class_id[1] );
270 }
271
272 if ( count( $item_sources ) > 1 ) {
273 /*
274 * Warm object cache for use with 'get_post_meta()'.
275 *
276 * To avoid making a database call for each image, a single query
277 * warms the object cache with the meta information for all images.
278 */
279 update_meta_cache( 'post', array_unique( $item_sources ) );
280 }
281
282 foreach ( $item_sources as $url => $item_source ) {
283 if ( ! $this->item_matches_src( $item_source, $url ) ) {
284 // Path doesn't match attachment, skip
285 continue;
286 }
287
288 $this->push_to_url_pairs( $url_pairs, $item_source, $url, $to_cache );
289 }
290
291 return $url_pairs;
292 }
293
294 /**
295 * Does URL need replacing?
296 *
297 * @param string $url
298 *
299 * @return bool
300 */
301 public function url_needs_replacing( $url ) {
302 $reverse_compatibility = apply_filters('wpmcs_enable_backward_url_repalcement', false);
303
304 if( $reverse_compatibility ) {
305 if ( str_replace( Filter::get_bare_upload_base_urls(), '', $url ) !== $url ) {
306 return false;
307 }
308
309 if ( str_replace( Filter::get_remote_domains(), '', $url ) === $url ) {
310 return false;
311 }
312 } else {
313 if ( str_replace( Filter::get_bare_upload_base_urls(), '', $url ) === $url ) {
314 // Remote URL, no replacement needed
315 if ( str_replace( Filter::get_bare_upload_base_urls(), '', $url ) === $url ) {
316 // Remote URL, no replacement needed
317 return false;
318 }
319 }
320 }
321
322 // Server URL, perform replacement
323 return true;
324 }
325
326 /**
327 * Does attachment ID match src?
328 *
329 * @param array $item_source
330 * @param string $url
331 *
332 * @return bool
333 */
334 public function item_matches_src( $item_source, $url ): bool {
335 $upload_dir = wp_get_upload_dir();
336 $wpmcsItem = Item::instance();
337 $item = $wpmcsItem->get( $item_source );
338
339
340 $backward_replacement = apply_filters('wpmcs_enable_backward_url_repalcement', false);
341
342 if (
343 Utils::is_empty($item) ||
344 get_post_type( $item_source ) !== 'attachment'
345 ) {
346 return false;
347 }
348
349 $bare_url = [];
350
351 $bare_url[] = $backward_replacement
352 ? Utils::reduce_url($wpmcsItem->get_url( $item_source ))
353 : Utils::reduce_url(trailingslashit($upload_dir['baseurl']).$item['source_path']);
354
355
356 $extras = $wpmcsItem->get_extras( $item_source );
357
358 if(isset($extras['original']) && !Utils::is_empty($extras['original'])) {
359 $bare_url[] = $backward_replacement
360 ? Utils::reduce_url($wpmcsItem->get_url( $item_source, 'original' ))
361 : Utils::reduce_url(trailingslashit($upload_dir['baseurl']).$extras['original']['source_path']);
362 }
363
364 if(isset($extras['sizes']) && !Utils::is_empty($extras['sizes'])) {
365 foreach($extras['sizes'] as $size => $sub_image) {
366 if(!Utils::is_empty($sub_image)){
367 $bare_url[] = $backward_replacement
368 ? Utils::reduce_url($wpmcsItem->get_url( $item_source, $size ))
369 : Utils::reduce_url(trailingslashit($upload_dir['baseurl']).$sub_image['source_path']);
370 }
371 }
372 }
373
374 if ( in_array( $url, $bare_url ) ) {
375 // Match found, return true
376 return true;
377 }
378
379 return false;
380 }
381
382
383 /**
384 * Push to URL pairs.
385 *
386 * @param array $url_pairs
387 * @param array $item_source
388 * @param string $find
389 * @param array $to_cache
390 */
391 protected function push_to_url_pairs( &$url_pairs, $item_source, $find, &$to_cache ) {
392 $upload_dir = wp_get_upload_dir();
393 $wpmcsItem = Item::instance();
394 $item = $wpmcsItem->get( $item_source );
395
396 if (
397 Utils::is_empty($item) ||
398 get_post_type( $item_source ) !== 'attachment'
399 ) {
400 return false;
401 }
402
403 $base = trailingslashit($upload_dir['baseurl']);
404 $backward_replacement = apply_filters('wpmcs_enable_backward_url_repalcement', false);
405
406 $source_server_file = Utils::remove_scheme($base.$item['source_path']);
407 $source_cloud_file = Utils::remove_scheme($wpmcsItem->get_url($item_source));
408
409 $url_pairs[$source_server_file] = $source_cloud_file;
410 if($backward_replacement) {
411 $url_pairs[$source_cloud_file] = $source_server_file; // Backward compatibility
412 }
413
414 $extras = $wpmcsItem->get_extras($item_source);
415
416 if(isset($extras['original']) && !Utils::is_empty($extras['original'])) {
417 $original_server_file = Utils::remove_scheme($base.$extras['original']['source_path']);
418 $original_cloud_file = Utils::remove_scheme($wpmcsItem->get_url($item_source, 'original'));
419 $url_pairs[$original_server_file] = $original_cloud_file;
420 if($backward_replacement) {
421 $url_pairs[$original_cloud_file] = $original_server_file; // Backward compatibility
422 }
423 }
424 if(isset($extras['sizes'])){
425 foreach ($extras['sizes'] as $size => $sub_image) {
426 $sub_server_file = Utils::remove_scheme($base.$sub_image['source_path']);
427 $sub_cloud_file = Utils::remove_scheme($wpmcsItem->get_url( $item_source, $size ));
428 $url_pairs[$sub_server_file] = $sub_cloud_file;
429 if($backward_replacement) {
430 $url_pairs[$sub_cloud_file ] = $sub_server_file; // Backward compatibility
431 }
432 }
433 }
434
435 $to_cache[ $find ] = $item_source;
436 }
437
438
439 /**
440 * Replace URLs.
441 *
442 * @param string $content
443 * @param array $url_pairs
444 *
445 * @return string
446 */
447 protected function replace_urls( $content, $url_pairs ) {
448 if ( empty( $url_pairs ) ) {
449 // No URLs to replace return
450 return $content;
451 }
452
453 foreach ( $url_pairs as $find => $replace ) {
454 $content = apply_filters('wpmcs_before_url_replace', $content, $find, $replace );
455 $content = str_replace( $find, $replace, $content );
456 $content = apply_filters('wpmcs_after_url_replace', $content, $find, $replace );
457 }
458
459 return $content;
460 }
461
462 /**
463 * Find URLs and replace.
464 *
465 * @param string $value
466 * @param array $cache
467 * @param array $to_cache
468 *
469 * @return string
470 */
471 protected function find_urls_and_replace( $value, $cache, &$to_cache ) {
472 if ( !Utils::is_ok_to_serve(false, false) ) {
473 return $value;
474 }
475
476 $url_pairs = $this->get_urls_from_content( $value, $cache, $to_cache );
477 $value = $this->replace_urls( $value, $url_pairs );
478
479 return $value;
480 }
481
482 /**
483 * Get URLs from content.
484 *
485 * @param string $content
486 * @param array $cache
487 * @param array $to_cache
488 *
489 * @return array
490 */
491 protected function get_urls_from_content( $content, $cache, &$to_cache ) {
492 $url_pairs = array();
493
494 if ( ! is_string( $content ) ) {
495 return $url_pairs;
496 }
497
498 if ( ! preg_match_all( '/(http|https)?:?\/\/[^"\'\s<>()\\\]*/', $content, $matches ) || ! isset( $matches[0] ) ) {
499 // No URLs found, return
500 return $url_pairs;
501 }
502
503 $matches = array_unique( $matches[0] );
504 $urls = array();
505
506 foreach ( $matches as $url ) {
507 $url = preg_replace( '/[^a-zA-Z0-9]$/', '', $url );
508
509 if ( ! Utils::is_file_url( $url ) ) {
510 continue;
511 }
512
513 if ( ! $this->url_needs_replacing( $url ) ) {
514 // URL already correct, skip
515 continue;
516 }
517
518 $item_source = null;
519 $bare_url = Utils::reduce_url( $url );
520
521 // If attachment ID recently or previously cached, skip full search.
522 if ( isset( $to_cache[ $bare_url ] ) ) {
523 $item_source = $to_cache[ $bare_url ];
524 } elseif ( isset( $cache[ $bare_url ] ) ) {
525 $item_source = $cache[ $bare_url ];
526 }
527
528 if ( is_null( $item_source ) ) {
529 // Attachment ID not cached, need to search by URL.
530 $urls[] = $bare_url;
531 } else {
532 $this->push_to_url_pairs( $url_pairs, $item_source, $bare_url, $to_cache );
533 }
534 }
535
536 if ( ! empty( $urls ) ) {
537 $item_sources = $this->get_item_sources_from_urls( $urls );
538
539 foreach ( $item_sources as $url => $item_source ) {
540 if ( ! $item_source ) {
541 continue;
542 }
543
544 $this->push_to_url_pairs( $url_pairs, $item_source, $url, $to_cache );
545 }
546 }
547
548 return $url_pairs;
549 }
550
551 /**
552 * Get item source descriptors from URLs.
553 *
554 * @param array $urls
555 *
556 * @return array url => item source descriptor array (or false)
557 */
558 protected function get_item_sources_from_urls( $urls ) {
559 $results = array();
560
561 if ( empty( $urls ) ) {
562 return $results;
563 }
564
565 if ( ! is_array( $urls ) ) {
566 $urls = array( $urls );
567 }
568
569 $wpmcsItem = Item::instance();
570
571 $query_set = array();
572 $paths = array();
573 $full_urls = array();
574
575 // Quickly parse given URLs to add versions without size as we should lookup with size info first as that could be the "full" size.
576 foreach ( $urls as $url ) {
577 $query_set[] = $url;
578 $size_removed = Utils::remove_size_from_filename( $url );
579
580 if ( $url !== $size_removed ) {
581 $query_set[] = $size_removed;
582 }
583 }
584
585 foreach ( $query_set as $url ) {
586 // Path to search for in query set should be based on bare URL.
587 $bare_url = Utils::remove_scheme( $url );
588 // There can be multiple URLs in the query set that belong to the same full URL for the Media Library item.
589 $full_url = Utils::remove_size_from_filename( $bare_url );
590
591 if ( isset( $this->query_cache[ $full_url ] ) ) {
592 // ID already cached, use it.
593 $results[ $url ] = $this->query_cache[ $full_url ];
594
595 continue;
596 }
597
598
599 $path = Utils::get_attachment_source_path( $full_url );
600
601 $paths[ $path ] = $full_url;
602 $full_urls[ $full_url ][] = $url;
603 }
604
605 if ( ! empty( $paths ) ) {
606 $wpmcsItems = $wpmcsItem->get_items_by_source_paths( array_keys( $paths ) );
607
608 if ( ! empty( $wpmcsItems ) ) {
609 foreach ( $wpmcsItems as $item ) {
610 // Each returned item may have matched on either the source_path or original_source_path.
611 // Because the base image file name of a thumbnail might match the primary rather scaled or rotated full image
612 // it's possible that both source paths are used by separate URLs.
613 $source_id = (int)$item['source_id'];
614 $source_paths = [ $item['source_path'] ];
615 $original = $wpmcsItem->get_extras($source_id, 'original');
616 if(isset($original['source_path'])) {
617 $source_paths[] = $original['source_path'];
618 }
619
620 foreach ( $source_paths as $source_path ) {
621 if ( ! empty( $paths[ $source_path ] ) ) {
622 $matched_full_url = $paths[ $source_path ];
623
624 if ( ! empty( $full_urls[ $matched_full_url ] ) ) {
625 $item_source = $source_id;
626
627 $this->query_cache[ $matched_full_url ] = $item_source;
628
629 foreach ( $full_urls[ $matched_full_url ] as $url ) {
630 $results[ $url ] = $item_source;
631 }
632 unset( $full_urls[ $matched_full_url ] );
633 }
634 }
635 }
636 }
637 }
638
639 // No more item IDs found, set remaining results as false.
640 if ( count( $query_set ) !== count( $results ) ) {
641 foreach ( $full_urls as $full_url => $schema_urls ) {
642 foreach ( $schema_urls as $url ) {
643 if ( ! array_key_exists( $url, $results ) ) {
644 $this->query_cache[ $full_url ] = false;
645 $results[ $url ] = false;
646 }
647 }
648 }
649 }
650 }
651
652 return $results;
653 }
654
655
656 /**
657 * Define the method for the backword compatibility filter
658 * */
659 public function enable_backward_url_check( $enable ) {
660 return true;
661 }
662
663
664 public static function instance(){
665 if (is_null(self::$instance)) {
666 self::$instance = new self();
667 }
668 return self::$instance;
669 }
670 }