Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/seo-by-rank-math/includes/opengraph/class-image.php

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
1 -
1 + <?php
2 + /**
3 + * This code adds the OpenGraph Image parser.
4 + *
5 + * @since 0.9.0
6 + * @package RankMath
7 + * @subpackage RankMath\OpenGraph
8 + * @author Rank Math <support@rankmath.com>
9 + *
10 + * @copyright Copyright (C) 2008-2019, Yoast BV
11 + * The following code is a derivative work of the code from the Yoast(https://github.com/Yoast/wordpress-seo/), which is licensed under GPL v3.
12 + */
13 +
14 + namespace RankMath\OpenGraph;
15 +
16 + use RankMath\Helper;
17 + use RankMath\Post;
18 + use RankMath\Traits\Hooker;
19 + use RankMath\Helpers\Str;
20 + use RankMath\Helpers\Url;
21 + use RankMath\Helpers\Attachment;
22 +
23 + defined( 'ABSPATH' ) || exit;
24 +
25 + /**
26 + * Image class.
27 + */
28 + class Image {
29 +
30 + use Hooker;
31 +
32 + /**
33 + * Holds network slug.
34 + *
35 + * @var array
36 + */
37 + private $network;
38 +
39 + /**
40 + * Holds the images that have been put out as OG image.
41 + *
42 + * @var array
43 + */
44 + private $images = [];
45 +
46 + /**
47 + * Holds the OpenGraph instance.
48 + *
49 + * @var OpenGraph
50 + */
51 + private $opengraph;
52 +
53 + /**
54 + * The parameters we have for Facebook images.
55 + *
56 + * @var array
57 + */
58 + private $usable_dimensions = [
59 + 'min_width' => 200,
60 + 'max_width' => 2000,
61 + 'min_height' => 200,
62 + 'max_height' => 2000,
63 + ];
64 +
65 + /**
66 + * The Constructor.
67 + *
68 + * @param string $image (Optional) The image URL.
69 + * @param OpenGraph $opengraph (Optional) The OpenGraph object..
70 + */
71 + public function __construct( $image = false, $opengraph = null ) {
72 + $this->opengraph = $opengraph;
73 + $this->network = $opengraph->network;
74 +
75 + // If an image was not supplied or could not be added.
76 + if ( Str::is_non_empty( $image ) ) {
77 + $this->add_image_by_url( $image );
78 + }
79 +
80 + if ( ! post_password_required() ) {
81 + $this->set_images();
82 + }
83 + }
84 +
85 + /**
86 + * Outputs the images.
87 + */
88 + public function show() {
89 + foreach ( $this->get_images() as $image => $image_meta ) {
90 + $this->image_tag( $image_meta );
91 + $this->image_meta( $image_meta );
92 + }
93 + }
94 +
95 + /**
96 + * Return the images array.
97 + *
98 + * @return array
99 + */
100 + public function get_images() {
101 + return $this->images;
102 + }
103 +
104 + /**
105 + * Check whether we have images or not.
106 + *
107 + * @return bool
108 + */
109 + public function has_images() {
110 + return ! empty( $this->images );
111 + }
112 +
113 + /**
114 + * Generate secret key for safer image URLs.
115 + *
116 + * @param int $id The attachment ID.
117 + * @param string $type Overlay type.
118 + */
119 + public function generate_secret( $id, $type ) {
120 + return md5( $id . $type . wp_salt( 'nonce' ) );
121 + }
122 +
123 + /**
124 + * Outputs an image tag based on whether it's https or not.
125 + *
126 + * @param array $image_meta Image metadata.
127 + */
128 + private function image_tag( $image_meta ) {
129 + $overlay = $this->opengraph->get_overlay_image();
130 + $og_image = $image_meta['url'];
131 + if ( $overlay && ! empty( $image_meta['id'] ) ) {
132 + $secret = $this->generate_secret( $image_meta['id'], $overlay );
133 + $og_image = admin_url( "admin-ajax.php?action=rank_math_overlay_thumb&id={$image_meta['id']}&type={$overlay}&hash={$secret}" );
134 + }
135 + $this->opengraph->tag( 'og:image', esc_url_raw( $og_image ) );
136 +
137 + // Add secure URL if detected. Not all services implement this, so the regular one also needs to be rendered.
138 + if ( Str::starts_with( 'https://', $og_image ) ) {
139 + $this->opengraph->tag( 'og:image:secure_url', esc_url_raw( $og_image ) );
140 + }
141 + }
142 +
143 + /**
144 + * Output the image metadata.
145 + *
146 + * @param array $image_meta Image meta data to output.
147 + */
148 + private function image_meta( $image_meta ) {
149 + $image_tags = [ 'width', 'height', 'alt', 'type' ];
150 + foreach ( $image_tags as $key ) {
151 + if ( ! empty( $image_meta[ $key ] ) ) {
152 + $this->opengraph->tag( 'og:image:' . $key, $image_meta[ $key ] );
153 + }
154 + }
155 + }
156 +
157 + /**
158 + * Adds an image based on a given URL, and attempts to be smart about it.
159 + *
160 + * @param string $url The given URL.
161 + */
162 + public function add_image_by_url( $url ) {
163 + if ( empty( $url ) ) {
164 + return;
165 + }
166 +
167 + $attachment_id = Attachment::get_by_url( $url );
168 +
169 + if ( $attachment_id > 0 ) {
170 + $this->add_image_by_id( $attachment_id );
171 + return;
172 + }
173 +
174 + $this->add_image( [ 'url' => $url ] );
175 + }
176 +
177 + /**
178 + * Adds an image to the list by attachment ID.
179 + *
180 + * @param int $attachment_id The attachment ID to add.
181 + */
182 + public function add_image_by_id( $attachment_id ) {
183 + $variations = $this->get_image_variations( $attachment_id );
184 +
185 + // If we are left without variations, there is no valid variation for this attachment.
186 + if ( empty( $variations ) ) {
187 + return;
188 + }
189 +
190 + // The variations are ordered so the first variations is by definition the best one.
191 + $attachment = $variations[0];
192 +
193 + if ( $attachment ) {
194 + // In the past `add_image` accepted an image url, so leave this for backwards compatibility.
195 + if ( Str::is_non_empty( $attachment ) ) {
196 + $attachment = [ 'url' => $attachment ];
197 + }
198 + $attachment['alt'] = Attachment::get_alt_tag( $attachment_id );
199 +
200 + $this->add_image( $attachment );
201 + }
202 + }
203 +
204 + /**
205 + * Display an OpenGraph image tag.
206 + *
207 + * @param string $attachment Source URL to the image.
208 + */
209 + public function add_image( $attachment = '' ) {
210 + // In the past `add_image` accepted an image url, so leave this for backwards compatibility.
211 + if ( Str::is_non_empty( $attachment ) ) {
212 + $attachment = [ 'url' => $attachment ];
213 + }
214 +
215 + $validate_image = true;
216 + /**
217 + * Allow changing the OpenGraph image.
218 + * The dynamic part of the hook name, $this->network, is the network slug (facebook, twitter).
219 + *
220 + * @param string $img The image we are about to add.
221 + */
222 + $filter_image_url = trim( $this->do_filter( "opengraph/{$this->network}/image", isset( $attachment['url'] ) ? $attachment['url'] : '' ) );
223 + if ( ! empty( $filter_image_url ) && ( empty( $attachment['url'] ) || $filter_image_url !== $attachment['url'] ) ) {
224 + $attachment = [ 'url' => $filter_image_url ];
225 + $validate_image = false;
226 + }
227 +
228 + /**
229 + * Secondary filter to allow changing the whole array.
230 + * The dynamic part of the hook name, $this->network, is the network slug (facebook, twitter).
231 + * This makes it possible to change the image ID too, to allow for image overlays.
232 + *
233 + * @param array $attachment The image we are about to add.
234 + */
235 + $attachment = $this->do_filter( "opengraph/{$this->network}/image_array", $attachment );
236 +
237 + if ( ! is_array( $attachment ) || empty( $attachment['url'] ) ) {
238 + return;
239 + }
240 +
241 + // Validate image only when it is not added using the opengraph filter.
242 + if ( $validate_image ) {
243 + $attachment_url = explode( '?', $attachment['url'] );
244 + if ( ! empty( $attachment_url ) ) {
245 + $attachment['url'] = $attachment_url[0];
246 + }
247 +
248 + // If the URL ends in `.svg`, we need to return.
249 + if ( ! $this->is_valid_image_url( $attachment['url'] ) ) {
250 + return;
251 + }
252 + }
253 +
254 + $image_url = $attachment['url'];
255 + if ( empty( $image_url ) ) {
256 + return;
257 + }
258 +
259 + if ( Url::is_relative( $image_url ) ) {
260 + $image_url = Attachment::get_relative_path( $image_url );
261 + }
262 +
263 + if ( array_key_exists( $image_url, $this->images ) ) {
264 + return;
265 + }
266 +
267 + $attachment['url'] = $image_url;
268 +
269 + if ( empty( $attachment['alt'] ) && is_singular() ) {
270 + $attachment['alt'] = $this->get_attachment_alt();
271 + }
272 +
273 + $this->images[ $image_url ] = $attachment;
274 + }
275 +
276 + /**
277 + * Get attachment alt with fallback
278 + *
279 + * @return string
280 + */
281 + private function get_attachment_alt() {
282 + global $post;
283 +
284 + $focus_keywords = Helper::get_post_meta( 'focus_keyword', $post->ID );
285 + if ( ! empty( $focus_keywords ) ) {
286 + $focus_keywords = explode( ',', $focus_keywords );
287 + return $focus_keywords[0];
288 + }
289 +
290 + return get_the_title();
291 + }
292 +
293 + /**
294 + * Check if page is front page or singular and call the corresponding functions.
295 + */
296 + private function set_images() {
297 + /**
298 + * Allow developers to add images to the OpenGraph tags.
299 + *
300 + * The dynamic part of the hook name. $this->network, is the network slug.
301 + *
302 + * @param Image The current object.
303 + */
304 + $this->do_action( "opengraph/{$this->network}/add_images", $this );
305 +
306 + switch ( true ) {
307 + case is_front_page():
308 + $this->set_front_page_image();
309 + break;
310 + case is_home():
311 + $this->set_posts_page_image();
312 + break;
313 + case is_attachment():
314 + $this->set_attachment_page_image();
315 + break;
316 + case is_singular() || Post::is_shop_page():
317 + $this->set_singular_image();
318 + break;
319 + case is_post_type_archive():
320 + $this->set_archive_image();
321 + break;
322 + case is_category():
323 + case is_tag():
324 + case is_tax():
325 + $this->set_taxonomy_image();
326 + break;
327 + case is_author():
328 + $this->set_author_image();
329 + break;
330 + }
331 +
332 + /**
333 + * Allow developers to add images to the OpenGraph tags.
334 + *
335 + * The dynamic part of the hook name. $this->network, is the network slug.
336 + *
337 + * @param Image The current object.
338 + */
339 + $this->do_action( "opengraph/{$this->network}/add_additional_images", $this );
340 +
341 + /**
342 + * Passing a truthy value to the filter will effectively short-circuit the
343 + * set default image process.
344 + *
345 + * @param bool $return Short-circuit return value. Either false or true.
346 + */
347 + if ( false !== $this->do_filter( 'opengraph/pre_set_default_image', false ) ) {
348 + return;
349 + }
350 +
351 + // If not, get default image.
352 + $image_id = Helper::get_settings( 'titles.open_graph_image_id' );
353 + if ( ! $this->has_images() ) {
354 + if ( $image_id > 0 ) {
355 + $this->add_image_by_id( $image_id );
356 + return;
357 + }
358 +
359 + $this->add_image(); // This allows "opengraph/{$this->network}/image" filter to be used even if no image is set.
360 + }
361 + }
362 +
363 + /**
364 + * If the frontpage image exists, call `add_image`.
365 + *
366 + * @return void
367 + */
368 + private function set_front_page_image() {
369 + $this->set_user_defined_image();
370 +
371 + if ( $this->has_images() ) {
372 + return;
373 + }
374 +
375 + // If no frontpage image is found, don't add anything.
376 + if ( $image_id = Helper::get_settings( 'titles.homepage_facebook_image_id' ) ) { // phpcs:ignore
377 + $this->add_image_by_id( $image_id );
378 + }
379 + }
380 +
381 + /**
382 + * Gets the user-defined image of the post.
383 + *
384 + * @param null|int $post_id The post ID to get the images for.
385 + */
386 + private function set_user_defined_image( $post_id = null ) {
387 + if ( null === $post_id ) {
388 + $post_id = get_queried_object_id();
389 + }
390 +
391 + $this->set_image_post_meta( $post_id );
392 +
393 + if ( $this->has_images() ) {
394 + return;
395 + }
396 +
397 + $this->set_featured_image( $post_id );
398 + }
399 +
400 + /**
401 + * If opengraph-image is set, call `add_image` and return true.
402 + *
403 + * @param int $post_id Optional post ID to use.
404 + */
405 + private function set_image_post_meta( $post_id = 0 ) {
406 + if ( empty( $post_id ) ) {
407 + return;
408 + }
409 + $image_id = Helper::get_post_meta( "{$this->opengraph->prefix}_image_id", $post_id );
410 + $this->add_image_by_id( $image_id );
411 + }
412 +
413 + /**
414 + * Retrieve the featured image.
415 + *
416 + * @param int $post_id The post ID.
417 + */
418 + private function set_featured_image( $post_id = null ) {
419 + /**
420 + * Passing a truthy value to the filter will effectively short-circuit the
421 + * set featured image process.
422 + *
423 + * @param bool $return Short-circuit return value. Either false or true.
424 + * @param int $post_id Post ID for the current post.
425 + */
426 + if ( false !== $this->do_filter( 'opengraph/pre_set_featured_image', false, $post_id ) ) {
427 + return;
428 + }
429 +
430 + if ( $post_id && has_post_thumbnail( $post_id ) ) {
431 + $attachment_id = get_post_thumbnail_id( $post_id );
432 + $this->add_image_by_id( $attachment_id );
433 + }
434 + }
435 +
436 + /**
437 + * Get the images of the posts page.
438 + */
439 + private function set_posts_page_image() {
440 + $post_id = get_option( 'page_for_posts' );
441 +
442 + $this->set_image_post_meta( $post_id );
443 +
444 + if ( $this->has_images() ) {
445 + return;
446 + }
447 +
448 + $this->set_featured_image( $post_id );
449 + }
450 +
451 + /**
452 + * If this is an attachment page, call `add_image` with the attachment.
453 + */
454 + private function set_attachment_page_image() {
455 + $post_id = get_queried_object_id();
456 + if ( wp_attachment_is_image( $post_id ) ) {
457 + $this->add_image_by_id( $post_id );
458 + }
459 + }
460 +
461 + /**
462 + * Get the images of the singular post.
463 + *
464 + * @param null|int $post_id The post ID to get the images for.
465 + */
466 + private function set_singular_image( $post_id = null ) {
467 + $is_shop_page = Post::is_shop_page();
468 + if ( $is_shop_page ) {
469 + $post_id = Post::get_shop_page_id();
470 + }
471 +
472 + $post_id = is_null( $post_id ) ? get_queried_object_id() : $post_id;
473 + $this->set_user_defined_image( $post_id );
474 +
475 + if ( $this->has_images() ) {
476 + return;
477 + }
478 +
479 + /**
480 + * Passing a truthy value to the filter will effectively short-circuit the
481 + * set content image process.
482 + *
483 + * @param bool $return Short-circuit return value. Either false or true.
484 + * @param int $post_id Post ID for the current post.
485 + */
486 + if ( false !== $this->do_filter( 'opengraph/pre_set_content_image', false, $post_id ) ) {
487 + if ( $is_shop_page ) {
488 + $this->set_archive_image();
489 + }
490 +
491 + return;
492 + }
493 + $this->set_content_image( get_post( $post_id ) );
494 +
495 + if ( ! $this->has_images() && $is_shop_page ) {
496 + $this->set_archive_image();
497 + }
498 + }
499 +
500 + /**
501 + * Adds the first usable attachment image from the post content.
502 + *
503 + * @param object $post The post object.
504 + */
505 + private function set_content_image( $post ) {
506 + if ( empty( $post ) || ! $post instanceof \WP_Post ) {
507 + return;
508 + }
509 +
510 + $content = sanitize_post_field( 'post_content', $post->post_content, $post->ID );
511 +
512 + // Early bail!
513 + if ( '' === $content || false === Str::contains( '<img', $content ) ) {
514 + return;
515 + }
516 +
517 + $do_og_content_image_cache = $this->do_filter( 'opengraph/content_image_cache', true );
518 + if ( $do_og_content_image_cache ) {
519 + $cache_key = 'rank_math_og_content_image';
520 + $cache = get_post_meta( $post->ID, $cache_key, true );
521 + $check = md5( $post->post_content );
522 + if ( ! empty( $cache ) && isset( $cache['check'] ) && $check === $cache['check'] ) {
523 + foreach ( $cache['images'] as $image ) {
524 + if ( is_int( $image ) ) {
525 + $this->add_image_by_id( $image );
526 + } else {
527 + $this->add_image( $image );
528 + }
529 + }
530 + return;
531 + }
532 +
533 + $cache = [
534 + 'check' => $check,
535 + 'images' => [],
536 + ];
537 + }
538 +
539 + $images = [];
540 + if ( preg_match_all( '`<img [^>]+>`', $content, $matches ) ) {
541 + foreach ( $matches[0] as $img ) {
542 + if ( preg_match( '`src=(["\'])(.*?)\1`', $img, $match ) ) {
543 + if ( isset( $match[2] ) ) {
544 + $images[] = $match[2];
545 + }
546 + }
547 + }
548 + }
549 +
550 + $images = array_unique( $images );
551 + if ( empty( $images ) ) {
552 + return;
553 + }
554 +
555 + foreach ( $images as $image ) {
556 +
557 + // If an image has been added, we're done.
558 + if ( $this->has_images() ) {
559 + break;
560 + }
561 +
562 + if ( Url::is_external( $image ) ) {
563 + $this->add_image( $image );
564 + } else {
565 + $attachment_id = Attachment::get_by_url( $image );
566 + if ( 0 === $attachment_id ) {
567 + $this->add_image( $image );
568 + if ( $do_og_content_image_cache ) {
569 + $cache['images'][] = $image;
570 + }
571 + } else {
572 + $this->add_image_by_id( $attachment_id );
573 + if ( $do_og_content_image_cache ) {
574 + $cache['images'][] = $attachment_id;
575 + }
576 + }
577 + }
578 + }
579 +
580 + if ( $do_og_content_image_cache ) {
581 + update_post_meta( $post->ID, $cache_key, $cache );
582 + }
583 + }
584 +
585 + /**
586 + * Check if Author has an image and add this image.
587 + */
588 + private function set_author_image() {
589 + $image_id = Helper::get_user_meta( "{$this->opengraph->prefix}_image_id" );
590 + $this->add_image_by_id( $image_id );
591 + }
592 +
593 + /**
594 + * Check if taxonomy has an image and add this image.
595 + */
596 + private function set_taxonomy_image() {
597 + $image_id = Helper::get_term_meta( "{$this->opengraph->prefix}_image_id" );
598 + $this->add_image_by_id( $image_id );
599 + }
600 +
601 + /**
602 + * Check if archive has an image and add this image.
603 + */
604 + private function set_archive_image() {
605 + $post_type = get_query_var( 'post_type' );
606 + $post_type = is_array( $post_type ) ? reset( $post_type ) : $post_type;
607 + $image_id = Helper::get_settings( "titles.pt_{$post_type}_facebook_image_id" );
608 + $this->add_image_by_id( $image_id );
609 + }
610 +
611 + /**
612 + * Determines whether the passed URL is considered valid.
613 + *
614 + * @param string $url The URL to check.
615 + *
616 + * @return bool Whether or not the URL is a valid image.
617 + */
618 + protected function is_valid_image_url( $url ) {
619 + if ( ! is_string( $url ) ) {
620 + return false;
621 + }
622 +
623 + $check = wp_check_filetype( $url );
624 + if ( empty( $check['ext'] ) ) {
625 + return false;
626 + }
627 +
628 + $extensions = [ 'jpeg', 'jpg', 'gif', 'png', 'webp', 'avif' ];
629 +
630 + return in_array( $check['ext'], $extensions, true );
631 + }
632 +
633 + /**
634 + * Returns the different image variations for consideration.
635 + *
636 + * @param int $attachment_id The attachment to return the variations for.
637 + *
638 + * @return array The different variations possible for this attachment ID.
639 + */
640 + public function get_variations( $attachment_id ) {
641 + $variations = [];
642 +
643 + /**
644 + * Determines which image sizes we'll loop through to get an appropriate image.
645 + *
646 + * @param unsigned array - The array of image sizes to loop through.
647 + */
648 + $sizes = $this->do_filter( 'opengraph/image_sizes', [ 'full', 'large', 'medium_large' ] );
649 +
650 + foreach ( $sizes as $size ) {
651 + if ( $variation = $this->get_attachment_image( $attachment_id, $size ) ) { // phpcs:ignore
652 + if ( $this->has_usable_dimensions( $variation ) ) {
653 + $variations[] = $variation;
654 + }
655 + }
656 + }
657 +
658 + return $variations;
659 + }
660 +
661 + /**
662 + * Validate Attachment image and return its variations.
663 + *
664 + * @param int $attachment_id The attachment to return the variations for.
665 + *
666 + * @return array The different variations possible for this attachment ID.
667 + */
668 + private function get_image_variations( $attachment_id ) {
669 + /**
670 + * Allow plugins to change the blog in a multisite environment. This hook can be used by plugins that uses a global media library from the main site.
671 + */
672 + $this->do_action( 'opengraph/pre_attachment_image_check', $attachment_id );
673 +
674 + /**
675 + * Filter to change the attachment ID.
676 + *
677 + * @param int $attachment_id Attachment ID.
678 + */
679 + $attachment_id = $this->do_filter( 'opengraph/attachment_id', $attachment_id );
680 + if ( ! wp_attachment_is_image( $attachment_id ) ) {
681 + return;
682 + }
683 +
684 + $variations = $this->get_variations( $attachment_id );
685 +
686 + /**
687 + * Allow plugins to reset the blog in a multisite environment. This hook can be used by plugins that utilize a global media library from the main site.
688 + */
689 + $this->do_action( 'opengraph/post_attachment_image_check', $attachment_id );
690 +
691 + return $variations;
692 + }
693 +
694 + /**
695 + * Retrieve an image to represent an attachment.
696 + *
697 + * @param int $attachment_id Image attachment ID.
698 + * @param string|array $size Optional. Image size. Accepts any valid image size, or an array of width
699 + * and height values in pixels (in that order). Default 'thumbnail'.
700 + * @return false|array
701 + */
702 + private function get_attachment_image( $attachment_id, $size = 'thumbnail' ) {
703 + $image = wp_get_attachment_image_src( $attachment_id, $size );
704 +
705 + // Early Bail!
706 + if ( ! $image ) {
707 + return false;
708 + }
709 +
710 + list( $src, $width, $height ) = $image;
711 +
712 + return [
713 + 'id' => $attachment_id,
714 + 'url' => $src,
715 + 'width' => $width,
716 + 'height' => $height,
717 + 'type' => get_post_mime_type( $attachment_id ),
718 + 'alt' => Attachment::get_alt_tag( $attachment_id ),
719 + ];
720 + }
721 +
722 + /**
723 + * Checks whether an img sizes up to the parameters.
724 + *
725 + * @param array $dimensions The image values.
726 + * @return bool True if the image has usable measurements, false if not.
727 + */
728 + private function has_usable_dimensions( $dimensions ) {
729 + foreach ( [ 'width', 'height' ] as $param ) {
730 + $minimum = $this->usable_dimensions[ 'min_' . $param ];
731 + $maximum = $this->usable_dimensions[ 'max_' . $param ];
732 +
733 + $current = $dimensions[ $param ];
734 + if ( ( $current < $minimum ) || ( $current > $maximum ) ) {
735 + return false;
736 + }
737 + }
738 +
739 + return true;
740 + }
741 + }
742 +