PluginProbe
Open Graph / trunk
Open Graph vtrunk
trunk 1.1 1.10.0 1.11.0 1.11.1 1.11.2 1.11.3 1.12.0 1.12.1 1.12.2 1.2 1.3 1.4 1.5 1.5.1 1.6 1.7.0 1.8.0 1.8.1 1.8.2 1.8.3 1.9.0 2.0.0 2.0.1 2.0.2
opengraph / opengraph.php

opengraph.php in Open Graph trunk, at opengraph.php

986 lines 25.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: Open Graph
4 * Plugin URI: https://wordpress.org/plugins/opengraph
5 * Description: Adds Open Graph metadata to your pages
6 * Author: Will Norris & Matthias Pfefferle
7 * Author URI: https://github.com/pfefferle/wordpress-opengraph
8 * Version: 2.0.2
9 * License: Apache License, Version 2.0
10 * License URI: http://www.apache.org/licenses/LICENSE-2.0.html
11 * Text Domain: opengraph
12 *
13 * @package opengraph
14 */
15
16 // If you have the opengraph plugin running alongside jetpack, we assume you'd
17 // rather use our opengraph support, so disable jetpack's opengraph functionality.
18 add_filter( 'jetpack_enable_opengraph', '__return_false' );
19 add_filter( 'jetpack_enable_open_graph', '__return_false' );
20
21
22 // Disable strict mode by default.
23 defined( 'OPENGRAPH_STRICT_MODE' ) || define( 'OPENGRAPH_STRICT_MODE', false );
24 // Set the maximum number of images to include in Open Graph metadata.
25 defined( 'OPENGRAPH_MAX_IMAGES' ) || define( 'OPENGRAPH_MAX_IMAGES', 3 );
26
27 /**
28 * Add Open Graph XML prefix to <html> element.
29 *
30 * @uses apply_filters calls 'opengraph_prefixes' filter on RDFa prefix array
31 *
32 * @param string $output The current list of prefixes.
33 *
34 * @return string The updated list of prefixes.
35 */
36 function opengraph_add_prefix( $output ) {
37 $prefixes = array(
38 'og' => 'http://ogp.me/ns#',
39 );
40 $prefixes = apply_filters( 'opengraph_prefixes', $prefixes );
41
42 $prefix_str = '';
43 foreach ( $prefixes as $k => $v ) {
44 $prefix_str .= $k . ': ' . $v . ' ';
45 }
46 $prefix_str = trim( $prefix_str );
47
48 if ( preg_match( '/(prefix\s*=\s*[\"|\'])/i', $output ) ) {
49 $output = preg_replace( '/(prefix\s*=\s*[\"|\'])/i', '${1}' . $prefix_str, $output );
50 } else {
51 $output .= ' prefix="' . esc_attr( $prefix_str ) . '"';
52 }
53
54 return $output;
55 }
56 add_filter( 'language_attributes', 'opengraph_add_prefix' );
57
58
59 /**
60 * Add additional prefix namespaces that are supported by the opengraph plugin.
61 *
62 * @param array $prefixes The current list of prefixes.
63 *
64 * @return array The updated list of prefixes.
65 */
66 function opengraph_additional_prefixes( $prefixes ) {
67 if ( is_author() ) {
68 $prefixes['profile'] = 'http://ogp.me/ns/profile#';
69 }
70 if ( is_singular() ) {
71 $prefixes['article'] = 'http://ogp.me/ns/article#';
72 }
73
74 return $prefixes;
75 }
76
77
78 /**
79 * Get the Open Graph metadata for the current page.
80 *
81 * @uses apply_filters() Calls 'opengraph_{$name}' for each property name
82 * @uses apply_filters() Calls 'twitter_{$name}' for each property name
83 * @uses apply_filters() Calls 'opengraph_metadata' before returning metadata array
84 */
85 function opengraph_metadata() {
86 $metadata = array();
87
88 // Default properties defined at http://ogp.me/.
89 $properties = array(
90 // Required properties.
91 'title' => '',
92 'type' => '',
93 'image' => array(),
94 'url' => '',
95
96 // Optional properties.
97 'audio' => array(),
98 'description' => '',
99 'determiner' => '',
100 'locale' => '',
101 'site_name' => '',
102 'video' => array(),
103 );
104
105 foreach ( $properties as $property => $default ) {
106 $filter = 'opengraph_' . $property;
107
108 /**
109 * Filter the Open Graph metadata.
110 *
111 * @param array $default The default value.
112 */
113 $metadata[ "og:$property" ] = apply_filters( $filter, $default );
114 }
115
116 $twitter_properties = array(
117 'card' => '',
118 'creator' => '',
119 );
120
121 foreach ( $twitter_properties as $property => $default ) {
122 $filter = 'twitter_' . $property;
123
124 /**
125 * Filter the Twitter Card metadata.
126 *
127 * @param array $default The default value.
128 */
129 $metadata[ "twitter:$property" ] = apply_filters( $filter, $default );
130 }
131
132 $fediverse_properties = array(
133 'creator' => array(),
134 );
135
136 foreach ( $fediverse_properties as $property => $default ) {
137 $filter = 'fediverse_' . $property;
138
139 /**
140 * Filter the Fediverse metadata.
141 *
142 * @param array $default The default value.
143 */
144 $metadata[ "fediverse:$property" ] = apply_filters( $filter, $default );
145 }
146
147 /**
148 * Filter the Open Graph metadata.
149 *
150 * @param array $metadata The metadata array.
151 */
152 return apply_filters( 'opengraph_metadata', $metadata );
153 }
154
155
156 /**
157 * Register filters for default Open Graph metadata.
158 */
159 function opengraph_default_metadata() {
160 // Core metadata attributes.
161 add_filter( 'opengraph_title', 'opengraph_default_title', 5 );
162 add_filter( 'opengraph_type', 'opengraph_default_type', 5 );
163 add_filter( 'opengraph_url', 'opengraph_default_url', 5 );
164
165 // Image metadata attributes with fallbacks.
166 add_filter( 'opengraph_image', 'opengraph_default_image', 5 );
167 add_filter( 'opengraph_image', 'opengraph_block_image', 15 );
168 add_filter( 'opengraph_image', 'opengraph_parsed_image', 25 );
169 add_filter( 'opengraph_image', 'opengraph_attached_image', 25 );
170 add_filter( 'opengraph_image', 'opengraph_fallback_image', 35 );
171 add_filter( 'opengraph_image', 'opengraph_ensure_max_image', 999 );
172
173 add_filter( 'opengraph_description', 'opengraph_default_description', 5 );
174 add_filter( 'opengraph_locale', 'opengraph_default_locale', 5 );
175 add_filter( 'opengraph_site_name', 'opengraph_default_sitename', 5 );
176 add_filter( 'opengraph_audio', 'opengraph_default_audio', 5 );
177 add_filter( 'opengraph_video', 'opengraph_default_video', 5 );
178
179 // Additional prefixes.
180 add_filter( 'opengraph_prefixes', 'opengraph_additional_prefixes' );
181
182 // Additional profile metadata.
183 add_filter( 'opengraph_metadata', 'opengraph_profile_metadata' );
184
185 // Additional article metadata.
186 add_filter( 'opengraph_metadata', 'opengraph_article_metadata' );
187
188 // twitter card metadata.
189 add_filter( 'twitter_card', 'twitter_default_card', 5 );
190 add_filter( 'twitter_creator', 'twitter_default_creator', 5 );
191
192 // fediverse creator metadata.
193 add_filter( 'fediverse_creator', 'fediverse_default_creator', 5 );
194 }
195 add_action( 'wp', 'opengraph_default_metadata' );
196
197
198 /**
199 * Default title property, using the page title.
200 *
201 * @param string $title The current title.
202 *
203 * @return string The title.
204 */
205 function opengraph_default_title( $title ) {
206 if ( $title ) {
207 return $title;
208 }
209
210 // Set default title, because twitter is requiring one.
211 $title = __( 'Untitled', 'opengraph' );
212
213 if ( is_home() || is_front_page() ) {
214 $title = get_bloginfo( 'name' );
215 } elseif ( is_singular() ) {
216 $title = get_the_title( get_queried_object_id() );
217 // Fall back to description.
218 if ( empty( $title ) ) {
219 $title = opengraph_default_description( null, 5 );
220 }
221 } elseif ( is_author() ) {
222 $author = get_queried_object();
223 $title = $author->display_name;
224 } elseif ( is_category() && single_cat_title( '', false ) ) {
225 $title = single_cat_title( '', false );
226 } elseif ( is_tag() && single_tag_title( '', false ) ) {
227 $title = single_tag_title( '', false );
228 } elseif ( is_archive() && get_post_format() ) {
229 $title = get_post_format_string( get_post_format() );
230 } elseif (
231 is_archive() &&
232 function_exists( 'get_the_archive_title' ) &&
233 get_the_archive_title()
234 ) { // New in version 4.1 to get all other archive titles.
235 $title = get_the_archive_title();
236 }
237
238 return wp_strip_all_tags( $title );
239 }
240
241
242 /**
243 * Default type property.
244 *
245 * @param string $type The current type.
246 *
247 * @return string The type.
248 */
249 function opengraph_default_type( $type = '' ) {
250 if ( empty( $type ) ) {
251 if ( is_singular( array( 'post', 'page' ) ) ) {
252 $type = 'article';
253 } elseif ( is_author() ) {
254 $type = 'profile';
255 } else {
256 $type = 'website';
257 }
258 }
259
260 return $type;
261 }
262
263
264 /**
265 * Default image property, using the post-thumbnail and any attached images.
266 *
267 * @param array $image The current list of images.
268 *
269 * @return array The list of images.
270 */
271 function opengraph_default_image( $image = array() ) {
272 // Show avatar on profile pages.
273 if ( is_author() ) {
274 return array( get_avatar_url( get_the_author_meta( 'ID' ), array( 'size' => 512 ) ) );
275 }
276
277 if ( count( $image ) >= opengraph_max_images() ) {
278 return $image;
279 }
280
281 if ( is_attachment() && wp_attachment_is_image() ) {
282 $id = get_queried_object_id();
283 $image[] = current( wp_get_attachment_image_src( $id, 'large' ) ?: array() ); // phpcs:ignore
284 } elseif ( is_singular() && ! is_attachment() ) {
285 $id = get_queried_object_id();
286
287 // List post thumbnail first if this post has one.
288 if ( function_exists( 'has_post_thumbnail' ) && has_post_thumbnail( $id ) ) {
289 $thumbnail_id = get_post_thumbnail_id( $id );
290 $image[] = current( wp_get_attachment_image_src( $thumbnail_id, 'large' ) ?: array() ); // phpcs:ignore
291 }
292 }
293
294 return array_unique( $image );
295 }
296
297
298 /**
299 * Block image property, using the first image in the post content.
300 *
301 * @param array $image The current list of images.
302 *
303 * @return array The list of images.
304 */
305 function opengraph_block_image( $image = array() ) {
306 if (
307 ! opengraph_site_supports_blocks() ||
308 count( $image ) >= opengraph_max_images() ||
309 ! is_singular() ||
310 is_attachment()
311 ) {
312 return $image;
313 }
314
315 // Get the first image in the post content.
316 $blocks = parse_blocks( get_the_content( null, false ) );
317 foreach ( $blocks as $block ) {
318 if ( count( $image ) >= opengraph_max_images() ) {
319 break;
320 }
321
322 if (
323 'core/image' === $block['blockName'] ||
324 'core/cover' === $block['blockName']
325 ) {
326 if ( ! isset( $block['attrs']['id'] ) ) {
327 continue;
328 }
329
330 $id = $block['attrs']['id'];
331 $image[] = current( wp_get_attachment_image_src( $id, 'large' ) ?: array() ); // phpcs:ignore
332 }
333 }
334
335 return array_unique( $image );
336 }
337
338
339 /**
340 * Parse images in the HTML content.
341 *
342 * @param array $image The current list of images.
343 *
344 * @return array The list of images.
345 */
346 function opengraph_parsed_image( $image = array() ) {
347 // If someone calls that function directly, bail.
348 if (
349 ! \class_exists( 'WP_HTML_Tag_Processor' ) ||
350 ! opengraph_site_supports_blocks() ||
351 count( $image ) >= opengraph_max_images() ||
352 ! is_singular() ||
353 is_attachment()
354 ) {
355 return $image;
356 }
357
358 $post_id = get_queried_object_id();
359 $base = wp_upload_dir()['baseurl'];
360 $content = get_post_field( 'post_content', $post_id );
361 $tags = new WP_HTML_Tag_Processor( $content );
362
363 // This linter warning is a false positive - we have to re-count each time here as we modify $images.
364 // phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found
365 while ( $tags->next_tag( 'img' ) && ( count( $image ) <= opengraph_max_images() ) ) {
366 $src = $tags->get_attribute( 'src' );
367
368 /*
369 * If the img source is in our uploads dir, get the
370 * associated ID. Note: if there's a -500x500
371 * type suffix, we remove it, but we try the original
372 * first in case the original image is actually called
373 * that. Likewise, we try adding the -scaled suffix for
374 * the case that this is a small version of an image
375 * that was big enough to get scaled down on upload:
376 * https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/
377 */
378 if ( null === $src || ! str_starts_with( $src, $base ) ) {
379 continue;
380 }
381
382 $img_id = attachment_url_to_postid( $src );
383
384 if ( 0 === $img_id ) {
385 $count = 0;
386 $src = strtok( $src, '?' );
387 $img_id = attachment_url_to_postid( $src );
388 }
389
390 if ( 0 === $img_id ) {
391 $count = 0;
392 $src = preg_replace( '/-(?:\d+x\d+)(\.[a-zA-Z]+)$/', '$1', $src, 1, $count );
393 if ( $count > 0 ) {
394 $img_id = attachment_url_to_postid( $src );
395 }
396 }
397
398 if ( 0 === $img_id ) {
399 $src = preg_replace( '/(\.[a-zA-Z]+)$/', '-scaled$1', $src );
400 $img_id = attachment_url_to_postid( $src );
401 }
402
403 if ( 0 !== $img_id ) {
404 $image[] = current( wp_get_attachment_image_src( $img_id, 'large' ) ?: array() ); // phpcs:ignore
405 }
406 }
407
408 return array_unique( $image );
409 }
410
411
412 /**
413 * Attached images.
414 *
415 * @param array $image The current list of images.
416 *
417 * @return array The list of images.
418 */
419 function opengraph_attached_image( $image = array() ) {
420 $max_images = opengraph_max_images();
421
422 if (
423 count( $image ) >= $max_images ||
424 ! is_singular() || is_attachment()
425 ) {
426 return $image;
427 }
428
429 $id = get_queried_object_id();
430
431 $query = new WP_Query(
432 array(
433 'post_parent' => $id,
434 'post_status' => 'inherit',
435 'post_type' => 'attachment',
436 'post_mime_type' => 'image',
437 'order' => 'ASC',
438 'orderby' => 'menu_order ID',
439 'fields' => 'ids',
440 'posts_per_page' => $max_images,
441 )
442 );
443
444 $image_ids = $query->get_posts();
445
446 // Get URLs for each image.
447 foreach ( $image_ids as $id ) {
448 if ( count( $image ) >= opengraph_max_images() ) {
449 break;
450 }
451
452 $thumbnail = wp_get_attachment_image_src( $id, 'large' );
453 if ( $thumbnail ) {
454 $image[] = $thumbnail[0];
455 }
456 }
457
458 return array_unique( $image );
459 }
460
461 /**
462 * Fallback image property, using the site icon, custom logo, or header images.
463 *
464 * @param array $image The current list of images.
465 *
466 * @return array The list of images.
467 */
468 function opengraph_fallback_image( $image = array() ) {
469 if ( $image ) {
470 return $image;
471 }
472
473 $max_images = opengraph_max_images();
474
475 // Try site icon.
476 if ( function_exists( 'get_site_icon_url' ) && has_site_icon() ) {
477 $image[] = get_site_icon_url( 512 );
478 }
479
480 // Try custom logo second.
481 if ( empty( $image ) ) {
482 $custom_logo = get_theme_mod( 'custom_logo' );
483 $image[] = wp_get_attachment_image_src( $custom_logo, 'large' );
484 }
485
486 // Try header images.
487 if ( empty( $image ) && function_exists( 'get_uploaded_header_images' ) ) {
488 if ( is_random_header_image() ) {
489 foreach ( get_uploaded_header_images() as $header_image ) {
490 $image[] = $header_image['url'];
491 if ( count( $image ) >= $max_images ) {
492 break;
493 }
494 }
495 } elseif ( get_header_image() ) {
496 $image[] = get_header_image();
497 }
498 }
499
500 return array_unique( $image );
501 }
502
503 /**
504 * Ensure the image count does not exceed the maximum.
505 *
506 * @param array $image The current list of images.
507 *
508 * @return array The list of images.
509 */
510 function opengraph_ensure_max_image( $image = array() ) {
511 return array_slice( $image, 0, opengraph_max_images() );
512 }
513
514 /**
515 * Default audio property, using get_attached_media.
516 *
517 * @param array $audio The current list of audio files.
518 *
519 * @return array The list of audio files.
520 */
521 function opengraph_default_audio( $audio = array() ) {
522 $id = get_queried_object_id();
523 $attachments = get_attached_media( 'audio', $id );
524
525 if ( empty( $attachments ) ) {
526 return $audio;
527 }
528
529 foreach ( $attachments as $attachment ) {
530 $audio[] = wp_get_attachment_url( $attachment->ID );
531 }
532
533 return $audio;
534 }
535
536
537 /**
538 * Default video property, using get_attached_media.
539 *
540 * @param array $video The current list of video files.
541 *
542 * @return array The list of video files.
543 */
544 function opengraph_default_video( $video = array() ) {
545 $id = get_queried_object_id();
546 $attachments = get_attached_media( 'video', $id );
547
548 if ( empty( $attachments ) ) {
549 return $video;
550 }
551
552 foreach ( $attachments as $attachment ) {
553 $video[] = wp_get_attachment_url( $attachment->ID );
554 }
555
556 return $video;
557 }
558
559
560 /**
561 * Default url property, using the permalink for the page.
562 *
563 * @param string $url The current URL.
564 *
565 * @return string The URL.
566 */
567 function opengraph_default_url( $url = '' ) {
568 if ( empty( $url ) ) {
569 if ( is_singular() ) {
570 $url = get_permalink();
571 } elseif ( is_author() ) {
572 $url = get_author_posts_url( get_queried_object_id() );
573 }
574 }
575
576 return esc_url( $url );
577 }
578
579
580 /**
581 * Default site_name property, using the bloginfo name.
582 *
583 * @param string $name The current site name.
584 *
585 * @return string The site name.
586 */
587 function opengraph_default_sitename( $name = '' ) {
588 if ( empty( $name ) ) {
589 $name = get_bloginfo( 'name' );
590 }
591
592 return wp_strip_all_tags( $name );
593 }
594
595
596 /**
597 * Default description property, using the excerpt or content for posts, or the
598 * bloginfo description.
599 *
600 * @param string $description The current description.
601 * @param int $length The maximum length of the description.
602 *
603 * @return string The description.
604 */
605 function opengraph_default_description( $description = '', $length = 55 ) {
606 if ( $description ) {
607 return $description;
608 }
609
610 if ( is_singular() ) {
611 $post = get_queried_object();
612 if ( post_password_required( $post ) ) {
613 $description = __( 'This content is password protected.', 'opengraph' );
614 } elseif ( ! empty( $post->post_excerpt ) ) {
615 $description = $post->post_excerpt;
616 } else {
617 $description = $post->post_content;
618 }
619 } elseif ( is_author() ) {
620 $id = get_queried_object_id();
621 $description = get_user_meta( $id, 'description', true );
622 } elseif ( is_category() && category_description() ) {
623 $description = category_description();
624 } elseif ( is_tag() && tag_description() ) {
625 $description = tag_description();
626 } elseif (
627 is_archive() &&
628 function_exists( 'get_the_archive_description' ) &&
629 get_the_archive_description()
630 ) { // New in version 4.1 to get all other archive descriptions.
631 $description = get_the_archive_description();
632 } else {
633 $description = get_bloginfo( 'description' );
634 }
635
636 // strip description to first 55 words.
637 $description = wp_strip_all_tags( strip_shortcodes( $description ) );
638 $description = opengraph_trim_text( $description, $length );
639
640 return wp_strip_all_tags( $description );
641 }
642
643
644 /**
645 * Default locale property, using the WordPress locale.
646 *
647 * @param string $locale The current locale.
648 *
649 * @return string The locale.
650 */
651 function opengraph_default_locale( $locale = '' ) {
652 if ( empty( $locale ) ) {
653 $locale = get_locale();
654 }
655
656 return $locale;
657 }
658
659
660 /**
661 * Default twitter-card type.
662 *
663 * @param string $card The current card type.
664 *
665 * @return string The card type.
666 */
667 function twitter_default_card( $card = '' ) {
668 if ( $card ) {
669 return $card;
670 }
671
672 $card = 'summary';
673 $images = apply_filters( 'opengraph_image', array() );
674
675 // Show large image on...
676 if ( is_singular() ) {
677 if (
678 // Gallery and image posts.
679 in_array( get_post_format(), array( 'image', 'gallery' ), true ) ||
680 // Posts with more than one image.
681 ( is_array( $images ) && count( $images ) > 1 ) ||
682 // Posts with a post-thumbnail.
683 ( function_exists( 'has_post_thumbnail' ) && has_post_thumbnail() )
684 ) {
685 $card = 'summary_large_image';
686 }
687 }
688
689 return $card;
690 }
691
692
693 /**
694 * Default twitter-card creator.
695 *
696 * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/guides/getting-started
697 *
698 * @param string $creator The current creator.
699 *
700 * @return string The creator.
701 */
702 function twitter_default_creator( $creator = '' ) {
703 if ( $creator || ! is_singular() ) {
704 return $creator;
705 }
706
707 $post = get_queried_object();
708 $author = $post->post_author;
709 $twitter = get_the_author_meta( 'twitter', $author );
710
711 if ( ! $twitter ) {
712 return $creator;
713 }
714
715 // Check if twitter-account matches "http://twitter.com/username".
716 if ( preg_match( '/^http:\/\/twitter\.com\/(#!\/)?(\w+)/i', $twitter, $matches ) ) {
717 $creator = '@' . $matches[2];
718 } elseif ( preg_match( '/^@?(\w+)$/i', $twitter, $matches ) ) { // Check if twitter-account matches "(@)username".
719 $creator = '@' . $matches[1];
720 }
721
722 return $creator;
723 }
724
725
726 /**
727 * Default fediverse creator.
728 *
729 * @see https://github.com/mastodon/mastodon/pull/30398
730 *
731 * @param string $creator The current creator.
732 *
733 * @return string The creator.
734 */
735 function fediverse_default_creator( $creator = '' ) {
736 if ( ! is_singular() ) {
737 return $creator;
738 }
739
740 $post = get_queried_object();
741 $author = $post->post_author;
742 $webfinger = get_the_author_meta( 'fediverse', $author );
743
744 if ( ! $webfinger ) {
745 return $creator;
746 }
747
748 $webfinger = ltrim( $webfinger, '@' );
749 $webfinger = str_replace( 'acct:', '', $webfinger );
750
751 return $webfinger;
752 }
753
754
755 /**
756 * Output Open Graph <meta> tags in the page header.
757 */
758 function opengraph_meta_tags() {
759 $metadata = opengraph_metadata();
760 foreach ( $metadata as $key => $value ) {
761 if ( empty( $key ) || empty( $value ) ) {
762 continue;
763 }
764 $value = (array) $value;
765
766 foreach ( $value as $v ) {
767 // Skip empty values.
768 if ( empty( $v ) ) {
769 continue;
770 }
771
772 // Check if "strict mode" is enabled.
773 if ( OPENGRAPH_STRICT_MODE === true ) {
774 if ( // Use "name" attribute for Twitter Cards.
775 str_starts_with( $key, 'twitter:' ) ||
776 str_starts_with( $key, 'fediverse:' )
777 ) {
778 printf(
779 '<meta name="%1$s" content="%2$s" />' . PHP_EOL,
780 esc_attr( $key ),
781 esc_attr( $v )
782 );
783 } else { // Use "property" attribute for Open Graph.
784 printf(
785 '<meta property="%1$s" content="%2$s" />' . PHP_EOL,
786 esc_attr( $key ),
787 esc_attr( $v )
788 );
789 }
790 } else {
791 // Use the "property" and "name" attributes.
792 printf(
793 '<meta property="%1$s" name="%1$s" content="%2$s" />' . PHP_EOL,
794 esc_attr( $key ),
795 esc_attr( $v )
796 );
797 }
798 }
799 }
800 }
801 add_action( 'wp_head', 'opengraph_meta_tags' );
802
803
804 /**
805 * Include profile metadata for author pages.
806 *
807 * @link http://ogp.me/#type_profile
808 *
809 * @param array $metadata The current metadata.
810 *
811 * @return array The updated metadata.
812 */
813 function opengraph_profile_metadata( $metadata ) {
814 if ( is_author() ) {
815 $id = get_queried_object_id();
816
817 $metadata['profile:first_name'] = get_the_author_meta( 'first_name', $id );
818 $metadata['profile:last_name'] = get_the_author_meta( 'last_name', $id );
819 $metadata['profile:username'] = get_the_author_meta( 'nicename', $id );
820 }
821
822 return $metadata;
823 }
824
825
826 /**
827 * Include article metadata for posts and pages.
828 *
829 * @link http://ogp.me/#type_article
830 *
831 * @param array $metadata The current metadata.
832 *
833 * @return array The updated metadata.
834 */
835 function opengraph_article_metadata( $metadata ) {
836 if ( ! is_singular() ) {
837 return $metadata;
838 }
839
840 $post = get_queried_object();
841 $author = $post->post_author;
842
843 // Check if page/post has tags.
844 $tags = wp_get_object_terms( $post->ID, 'post_tag' );
845 if ( $tags && is_array( $tags ) ) {
846 foreach ( $tags as $tag ) {
847 $metadata['article:tag'][] = $tag->name;
848 }
849 }
850
851 // Check if page/post has categories.
852 $categories = wp_get_object_terms( $post->ID, 'category' );
853 if ( $categories && is_array( $categories ) ) {
854 $metadata['article:section'][] = current( $categories )->name;
855 }
856
857 $metadata['article:published_time'] = get_the_time( 'c', $post->ID );
858 $metadata['article:modified_time'] = get_the_modified_time( 'c', $post->ID );
859 $metadata['article:author'][] = get_author_posts_url( $author );
860
861 $facebook = get_the_author_meta( 'facebook', $author );
862
863 if ( ! empty( $facebook ) ) {
864 $metadata['article:author'][] = $facebook;
865 }
866
867 return $metadata;
868 }
869
870
871 /**
872 * Add "twitter" as a contact method
873 *
874 * @param array $user_contactmethods The current list of contact methods.
875 *
876 * @return array The updated list of contact methods.
877 */
878 function opengraph_user_contactmethods( $user_contactmethods = array() ) {
879 $user_contactmethods['twitter'] = __( 'Twitter', 'opengraph' );
880 $user_contactmethods['facebook'] = __( 'Facebook (Profile URL)', 'opengraph' );
881 $user_contactmethods['fediverse'] = __( 'Fediverse (username@host.tld)', 'opengraph' );
882
883 return $user_contactmethods;
884 }
885 add_filter( 'user_contactmethods', 'opengraph_user_contactmethods', 1 );
886
887
888 /**
889 * Add 512x512 icon size
890 *
891 * @param array $sizes Sizes available for the site icon.
892 *
893 * @return array updated list of icons.
894 */
895 function opengraph_site_icon_image_sizes( $sizes ) {
896 $sizes[] = 512;
897
898 return array_unique( $sizes );
899 }
900 add_filter( 'site_icon_image_sizes', 'opengraph_site_icon_image_sizes' );
901
902
903 /**
904 * Helper function to trim text using the same default values for length and
905 * 'more' text as wp_trim_excerpt.
906 *
907 * @param string $text The text to trim.
908 * @param int $length The maximum number of words to include.
909 *
910 * @return string The trimmed text.
911 */
912 function opengraph_trim_text( $text, $length = 55 ) {
913 $excerpt_length = apply_filters( 'excerpt_length', $length );
914 $excerpt_more = apply_filters( 'excerpt_more', ' [...]' );
915
916 return wp_trim_words( $text, $excerpt_length, $excerpt_more );
917 }
918
919 /**
920 * Get the maximum number of images to include in Open Graph metadata.
921 *
922 * @return int The maximum number of images to include.
923 */
924 function opengraph_max_images() {
925 /**
926 * Filter the maximum number of images to include in Open Graph metadata.
927 *
928 * As of July 2014, Facebook seems to only let you select from the first 3 images.
929 *
930 * @param int $max_images The maximum number of images to include.
931 */
932 $max_images = apply_filters( 'opengraph_max_images', OPENGRAPH_MAX_IMAGES );
933
934 // Max images can't be negative or zero.
935 if ( $max_images <= 0 ) {
936 $max_images = 1;
937 }
938
939 return $max_images;
940 }
941
942 /**
943 * Check if a site supports the block editor.
944 *
945 * @return boolean True if the site supports the block editor, false otherwise.
946 */
947 function opengraph_site_supports_blocks() {
948 $return = true;
949
950 if ( version_compare( get_bloginfo( 'version' ), '5.9', '<' ) ) {
951 $return = false;
952 } elseif ( function_exists( 'classicpress_version' ) ) {
953 $return = false;
954 } elseif (
955 ! function_exists( 'register_block_type_from_metadata' ) ||
956 ! function_exists( 'do_blocks' )
957 ) {
958 $return = false;
959 }
960
961 /**
962 * Allow plugins to disable block editor support,
963 * thus disabling blocks registered by the OpenGraph plugin.
964 *
965 * @param boolean $supports_blocks True if the site supports the block editor, false otherwise.
966 */
967 return apply_filters( 'opengraph_site_supports_blocks', $return );
968 }
969
970
971 if ( ! function_exists( 'str_starts_with' ) ) {
972 /**
973 * `str_starts_with` function for PHP < 8.0.
974 *
975 * @see https://www.php.net/manual/en/function.str-starts-with
976 *
977 * @param string $haystack The string to search in.
978 * @param string $needle The string to search for.
979 *
980 * @return bool True if the string starts with the needle, false otherwise.
981 */
982 function str_starts_with( $haystack, $needle ) {
983 return 0 === strncmp( $haystack, $needle, \strlen( $needle ) );
984 }
985 }
986