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

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