PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 9.6.1
Jetpack – WP Security, Backup, Speed, & Growth v9.6.1
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 All 500 releases
jetpack / functions.opengraph.php
functions.opengraph.php
513 lines 16.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Open Graph Tags
4 *
5 * Add Open Graph tags so that Facebook (and any other service that supports them)
6 * can crawl the site better and we provide a better sharing experience.
7 *
8 * @link https://ogp.me/
9 * @link https://developers.facebook.com/docs/opengraph/
10 *
11 * @package automattic/jetpack
12 */
13
14 add_action( 'wp_head', 'jetpack_og_tags' );
15 add_action( 'web_stories_story_head', 'jetpack_og_tags' );
16
17 /**
18 * Outputs Open Graph tags generated by Jetpack.
19 */
20 function jetpack_og_tags() {
21 global $post;
22 $data = $post; // so that we don't accidentally explode the global.
23 /**
24 * Allow Jetpack to output Open Graph Meta Tags.
25 *
26 * @module sharedaddy, publicize
27 *
28 * @since 2.0.0
29 * @deprecated 2.0.3 Duplicative filter. Use `jetpack_enable_open_graph`.
30 *
31 * @param bool true Should Jetpack's Open Graph Meta Tags be enabled. Default to true.
32 */
33 if ( false === apply_filters( 'jetpack_enable_opengraph', true ) ) {
34 _deprecated_function( 'jetpack_enable_opengraph', '2.0.3', 'jetpack_enable_open_graph' );
35 return;
36 }
37
38 $is_amp_response = ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() );
39
40 // Disable the widont filter on WP.com to avoid stray &nbsps.
41 $disable_widont = remove_filter( 'the_title', 'widont' );
42
43 $og_output = "\n";
44 if ( ! $is_amp_response ) { // Because AMP optimizes the order or the nodes in the head.
45 $og_output .= "<!-- Jetpack Open Graph Tags -->\n";
46 }
47 $tags = array();
48
49 /**
50 * Filter the minimum width of the images used in Jetpack Open Graph Meta Tags.
51 *
52 * @module sharedaddy, publicize
53 *
54 * @since 2.0.0
55 *
56 * @param int 200 Minimum image width used in Jetpack Open Graph Meta Tags.
57 */
58 $image_width = absint( apply_filters( 'jetpack_open_graph_image_width', 200 ) );
59 /**
60 * Filter the minimum height of the images used in Jetpack Open Graph Meta Tags.
61 *
62 * @module sharedaddy, publicize
63 *
64 * @since 2.0.0
65 *
66 * @param int 200 Minimum image height used in Jetpack Open Graph Meta Tags.
67 */
68 $image_height = absint( apply_filters( 'jetpack_open_graph_image_height', 200 ) );
69 $description_length = 197;
70
71 if ( is_home() || is_front_page() ) {
72 $site_type = Jetpack_Options::get_option_and_ensure_autoload( 'open_graph_protocol_site_type', '' );
73 $tags['og:type'] = ! empty( $site_type ) ? $site_type : 'website';
74 $tags['og:title'] = get_bloginfo( 'name' );
75 $tags['og:description'] = get_bloginfo( 'description' );
76
77 $front_page_id = get_option( 'page_for_posts' );
78 if ( 'page' === get_option( 'show_on_front' ) && $front_page_id && is_home() ) {
79 $tags['og:url'] = get_permalink( $front_page_id );
80 } else {
81 $tags['og:url'] = home_url( '/' );
82 }
83
84 // Associate a blog's root path with one or more Facebook accounts.
85 $facebook_admins = Jetpack_Options::get_option_and_ensure_autoload( 'facebook_admins', array() );
86 if ( ! empty( $facebook_admins ) ) {
87 $tags['fb:admins'] = $facebook_admins;
88 }
89 } elseif ( is_author() ) {
90 $tags['og:type'] = 'profile';
91
92 $author = get_queried_object();
93
94 if ( is_a( $author, 'WP_User' ) ) {
95 $tags['og:title'] = $author->display_name;
96 if ( ! empty( $author->user_url ) ) {
97 $tags['og:url'] = $author->user_url;
98 } else {
99 $tags['og:url'] = get_author_posts_url( $author->ID );
100 }
101 $tags['og:description'] = $author->description;
102 $tags['profile:first_name'] = get_the_author_meta( 'first_name', $author->ID );
103 $tags['profile:last_name'] = get_the_author_meta( 'last_name', $author->ID );
104 }
105 } elseif ( is_archive() ) {
106 $tags['og:type'] = 'website';
107 $tags['og:title'] = wp_get_document_title();
108
109 $archive = get_queried_object();
110 if ( ! empty( $archive ) ) {
111 if ( is_category() || is_tag() || is_tax() ) {
112 $tags['og:url'] = get_term_link( $archive->term_id, $archive->taxonomy );
113 $tags['og:description'] = $archive->description;
114 } elseif ( is_post_type_archive() ) {
115 $tags['og:url'] = get_post_type_archive_link( $archive->name );
116 $tags['og:description'] = $archive->description;
117 }
118 }
119 } elseif ( is_singular() && is_a( $data, 'WP_Post' ) ) {
120 $tags['og:type'] = 'article';
121 if ( empty( $data->post_title ) ) {
122 $tags['og:title'] = ' ';
123 } else {
124 /** This filter is documented in core/src/wp-includes/post-template.php */
125 $tags['og:title'] = wp_kses( apply_filters( 'the_title', $data->post_title, $data->ID ), array() );
126 }
127
128 $tags['og:url'] = get_permalink( $data->ID );
129 if ( ! post_password_required() ) {
130 /*
131 * If the post author set an excerpt, use that.
132 * Otherwise, pick the post content that comes before the More tag if there is one.
133 * Do not use the post content if it contains premium content.
134 */
135 if ( ! empty( $data->post_excerpt ) ) {
136 $tags['og:description'] = jetpack_og_get_description( $data->post_excerpt );
137 } elseif ( ! has_block( 'premium-content/container', $data->post_content ) ) {
138 $excerpt = explode( '<!--more-->', $data->post_content )[0];
139 $tags['og:description'] = jetpack_og_get_description( $excerpt );
140 }
141 }
142
143 $tags['article:published_time'] = gmdate( 'c', strtotime( $data->post_date_gmt ) );
144 $tags['article:modified_time'] = gmdate( 'c', strtotime( $data->post_modified_gmt ) );
145 if ( post_type_supports( get_post_type( $data ), 'author' ) && isset( $data->post_author ) ) {
146 $publicize_facebook_user = get_post_meta( $data->ID, '_publicize_facebook_user', true );
147 if ( ! empty( $publicize_facebook_user ) ) {
148 $tags['article:author'] = esc_url( $publicize_facebook_user );
149 }
150 }
151 } elseif ( is_search() ) {
152 if ( '' !== get_query_var( 's', '' ) ) {
153 $tags['og:title'] = wp_get_document_title();
154 }
155 }
156 /**
157 * Allow plugins to inject additional template-specific Open Graph tags.
158 *
159 * @module sharedaddy, publicize
160 *
161 * @since 3.0.0
162 *
163 * @param array $tags Array of Open Graph Meta tags.
164 * @param array $args Array of image size parameters.
165 */
166 $tags = apply_filters( 'jetpack_open_graph_base_tags', $tags, compact( 'image_width', 'image_height' ) );
167
168 // Re-enable widont if we had disabled it.
169 if ( $disable_widont ) {
170 add_filter( 'the_title', 'widont' );
171 }
172
173 /**
174 * Do not return any Open Graph Meta tags if we don't have any info about a post.
175 *
176 * @module sharedaddy, publicize
177 *
178 * @since 3.0.0
179 *
180 * @param bool true Do not return any Open Graph Meta tags if we don't have any info about a post.
181 */
182 if ( empty( $tags ) && apply_filters( 'jetpack_open_graph_return_if_empty', true ) ) {
183 return;
184 }
185
186 $tags['og:site_name'] = get_bloginfo( 'name' );
187
188 // Get image info and build tags.
189 if ( ! post_password_required() ) {
190 $image_info = jetpack_og_get_image( $image_width, $image_height );
191 $tags['og:image'] = $image_info['src'];
192
193 if ( ! empty( $image_info['width'] ) ) {
194 $tags['og:image:width'] = (int) $image_info['width'];
195 }
196 if ( ! empty( $image_info['height'] ) ) {
197 $tags['og:image:height'] = (int) $image_info['height'];
198 }
199 if ( ! empty( $image_info['alt_text'] ) ) {
200 $tags['og:image:alt'] = esc_attr( $image_info['alt_text'] );
201 }
202 }
203
204 // Facebook whines if you give it an empty title.
205 if ( empty( $tags['og:title'] ) ) {
206 $tags['og:title'] = __( '(no title)', 'jetpack' );
207 }
208
209 // Shorten the description if it's too long.
210 if ( isset( $tags['og:description'] ) ) {
211 $tags['og:description'] = strlen( $tags['og:description'] ) > $description_length ? mb_substr( $tags['og:description'], 0, $description_length ) . '' : $tags['og:description'];
212 }
213
214 // Try to add OG locale tag if the WP->FB data mapping exists.
215 if ( defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' ) && file_exists( JETPACK__GLOTPRESS_LOCALES_PATH ) ) {
216 require_once JETPACK__GLOTPRESS_LOCALES_PATH;
217 $_locale = get_locale();
218
219 // We have to account for w.org vs WP.com locale divergence.
220 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
221 $gp_locale = GP_Locales::by_field( 'slug', $_locale );
222 } else {
223 $gp_locale = GP_Locales::by_field( 'wp_locale', $_locale );
224 }
225 }
226
227 if ( isset( $gp_locale->facebook_locale ) && ! empty( $gp_locale->facebook_locale ) ) {
228 $tags['og:locale'] = $gp_locale->facebook_locale;
229 }
230
231 /**
232 * Allow the addition of additional Open Graph Meta tags, or modify the existing tags.
233 *
234 * @module sharedaddy, publicize
235 *
236 * @since 2.0.0
237 *
238 * @param array $tags Array of Open Graph Meta tags.
239 * @param array $args Array of image size parameters.
240 */
241 $tags = apply_filters( 'jetpack_open_graph_tags', $tags, compact( 'image_width', 'image_height' ) );
242
243 // secure_urls need to go right after each og:image to work properly so we will abstract them here.
244 $tags['og:image:secure_url'] = ( empty( $tags['og:image:secure_url'] ) ) ? '' : $tags['og:image:secure_url'];
245 $secure = $tags['og:image:secure_url'];
246 unset( $tags['og:image:secure_url'] );
247 $secure_image_num = 0;
248
249 foreach ( (array) $tags as $tag_property => $tag_content ) {
250 // to accommodate multiple images.
251 $tag_content = (array) $tag_content;
252 $tag_content = array_unique( $tag_content );
253
254 foreach ( $tag_content as $tag_content_single ) {
255 if ( empty( $tag_content_single ) ) {
256 continue; // Don't ever output empty tags.
257 }
258 $og_tag = sprintf( '<meta property="%s" content="%s" />', esc_attr( $tag_property ), esc_attr( $tag_content_single ) );
259 /**
260 * Filter the HTML Output of each Open Graph Meta tag.
261 *
262 * @module sharedaddy, publicize
263 *
264 * @since 2.0.0
265 *
266 * @param string $og_tag HTML HTML Output of each Open Graph Meta tag.
267 */
268 $og_output .= apply_filters( 'jetpack_open_graph_output', $og_tag );
269 $og_output .= "\n";
270
271 if ( 'og:image' === $tag_property ) {
272 if ( is_array( $secure ) && ! empty( $secure[ $secure_image_num ] ) ) {
273 $og_tag = sprintf( '<meta property="og:image:secure_url" content="%s" />', esc_url( $secure[ $secure_image_num ] ) );
274 /** This filter is documented in functions.opengraph.php */
275 $og_output .= apply_filters( 'jetpack_open_graph_output', $og_tag );
276 $og_output .= "\n";
277 } elseif ( ! is_array( $secure ) && ! empty( $secure ) ) {
278 $og_tag = sprintf( '<meta property="og:image:secure_url" content="%s" />', esc_url( $secure ) );
279 /** This filter is documented in functions.opengraph.php */
280 $og_output .= apply_filters( 'jetpack_open_graph_output', $og_tag );
281 $og_output .= "\n";
282 }
283 $secure_image_num++;
284 }
285 }
286 }
287
288 if ( ! $is_amp_response ) { // Because AMP optimizes the order or the nodes in the head.
289 $og_output .= "\n<!-- End Jetpack Open Graph Tags -->";
290 }
291 $og_output .= "\n";
292 // This is trusted output or added by a filter.
293 echo $og_output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
294 }
295
296 /**
297 * Returns an image used in social shares.
298 *
299 * @since 2.0.0
300 *
301 * @param int $width Minimum width for the image. Default is 200 based on Facebook's requirement.
302 * @param int $height Minimum height for the image. Default is 200 based on Facebook's requirement.
303 * @param null $deprecated Deprecated.
304 *
305 * @return array The source ('src'), 'width', and 'height' of the image.
306 */
307 function jetpack_og_get_image( $width = 200, $height = 200, $deprecated = null ) {
308 if ( ! empty( $deprecated ) ) {
309 _deprecated_argument( __FUNCTION__, '6.6.0' );
310 }
311 $image = array();
312
313 if ( is_singular() && ! is_home() ) {
314 // Grab obvious image if post is an attachment page for an image.
315 if ( is_attachment( get_the_ID() ) && 'image' === substr( get_post_mime_type(), 0, 5 ) ) {
316 $image['src'] = wp_get_attachment_url( get_the_ID() );
317 }
318
319 // Attempt to find something good for this post using our generalized PostImages code.
320 if ( empty( $image ) && class_exists( 'Jetpack_PostImages' ) ) {
321 $post_image = Jetpack_PostImages::get_image(
322 get_the_ID(),
323 array(
324 'width' => $width,
325 'height' => $height,
326 )
327 );
328 if ( ! empty( $post_image ) && is_array( $post_image ) ) {
329 $image['src'] = $post_image['src'];
330 if ( isset( $post_image['src_width'], $post_image['src_height'] ) ) {
331 $image['width'] = $post_image['src_width'];
332 $image['height'] = $post_image['src_height'];
333 }
334 if ( ! empty( $post_image['alt_text'] ) ) {
335 $image['alt_text'] = $post_image['alt_text'];
336 }
337 }
338 }
339 } elseif ( is_author() ) {
340 $author = get_queried_object();
341 if ( is_a( $author, 'WP_User' ) ) {
342 $image['src'] = get_avatar_url(
343 $author->user_email,
344 array(
345 'size' => $width,
346 )
347 );
348 }
349 }
350
351 // First fall back, blavatar.
352 if ( empty( $image ) && function_exists( 'blavatar_domain' ) ) {
353 $blavatar_domain = blavatar_domain( site_url() );
354 if ( blavatar_exists( $blavatar_domain ) ) {
355 $image['src'] = blavatar_url( $blavatar_domain, 'img', $width, false, true );
356 $image['width'] = $width;
357 $image['height'] = $height;
358 }
359 }
360
361 // Second fall back, Site Logo.
362 if ( empty( $image ) && ( function_exists( 'jetpack_has_site_logo' ) && jetpack_has_site_logo() ) ) {
363 $image_id = jetpack_get_site_logo( 'id' );
364 $logo = wp_get_attachment_image_src( $image_id, 'full' );
365 if (
366 isset( $logo[0], $logo[1], $logo[2] )
367 && ( _jetpack_og_get_image_validate_size( $logo[1], $logo[2], $width, $height ) )
368 ) {
369 $image['src'] = $logo[0];
370 $image['width'] = $logo[1];
371 $image['height'] = $logo[2];
372 }
373 }
374
375 // Third fall back, Core Site Icon, if valid in size.
376 if ( empty( $image ) && has_site_icon() ) {
377 $image_id = get_option( 'site_icon' );
378 $icon = wp_get_attachment_image_src( $image_id, 'full' );
379 if (
380 isset( $icon[0], $icon[1], $icon[2] )
381 && ( _jetpack_og_get_image_validate_size( $icon[1], $icon[2], $width, $height ) )
382 ) {
383 $image['src'] = $icon[0];
384 $image['width'] = $icon[1];
385 $image['height'] = $icon[2];
386 }
387 }
388
389 // Final fall back, blank image.
390 if ( empty( $image ) ) {
391 /**
392 * Filter the default Open Graph Image tag, used when no Image can be found in a post.
393 *
394 * @since 3.0.0
395 *
396 * @param string $str Default Image URL.
397 */
398 $image['src'] = apply_filters( 'jetpack_open_graph_image_default', 'https://s0.wp.com/i/blank.jpg' );
399 }
400
401 return $image;
402 }
403
404 /**
405 * Validate the width and height against required width and height
406 *
407 * @param int $width Width of the image.
408 * @param int $height Height of the image.
409 * @param int $req_width Required width to pass validation.
410 * @param int $req_height Required height to pass validation.
411 *
412 * @return bool - True if the image passed the required size validation
413 */
414 function _jetpack_og_get_image_validate_size( $width, $height, $req_width, $req_height ) {
415 if ( ! $width || ! $height ) {
416 return false;
417 }
418
419 $valid_width = ( $width >= $req_width );
420 $valid_height = ( $height >= $req_height );
421 $is_image_acceptable = $valid_width && $valid_height;
422
423 return $is_image_acceptable;
424 }
425
426 /**
427 * Gets a gravatar URL of the specified size.
428 *
429 * @param string $email E-mail address to get gravatar for.
430 * @param int $width Size of returned gravatar.
431 * @return array|bool|mixed|string
432 */
433 function jetpack_og_get_image_gravatar( $email, $width ) {
434 return get_avatar_url(
435 $email,
436 array(
437 'size' => $width,
438 )
439 );
440 }
441
442 /**
443 * Clean up text meant to be used as Description Open Graph tag.
444 *
445 * There should be:
446 * - no links
447 * - no shortcodes
448 * - no html tags or their contents
449 * - not too many words.
450 *
451 * @param string $description Text coming from WordPress (autogenerated or manually generated by author).
452 * @param WP_Post|null $data Information about our post.
453 *
454 * @return string $description Cleaned up description string.
455 */
456 function jetpack_og_get_description( $description = '', $data = null ) {
457 // Remove tags such as <style or <script.
458 $description = wp_strip_all_tags( $description );
459
460 /*
461 * Clean up any plain text entities left into formatted entities.
462 * Intentionally not using a filter to prevent pollution.
463 * @see https://github.com/Automattic/jetpack/pull/2899#issuecomment-151957382
464 */
465 $description = wp_kses(
466 trim(
467 convert_chars(
468 wptexturize( $description )
469 )
470 ),
471 array()
472 );
473
474 // Remove shortcodes.
475 $description = strip_shortcodes( $description );
476
477 // Remove links.
478 $description = preg_replace(
479 '@https?://[\S]+@',
480 '',
481 $description
482 );
483
484 /*
485 * Limit things to a small text blurb.
486 * There isn't a hard limit set by Facebook, so let's rely on WP's own limit.
487 * (55 words or the localized equivalent).
488 * This limit can be customized with the wp_trim_words filter.
489 */
490 $description = wp_trim_words( $description );
491
492 // Let's set a default if we have no text by now.
493 if ( empty( $description ) ) {
494 /**
495 * Filter the fallback `og:description` used when no excerpt information is provided.
496 *
497 * @module sharedaddy, publicize
498 *
499 * @since 3.9.0
500 *
501 * @param string $var Fallback og:description. Default is translated `Visit the post for more'.
502 * @param object $data Post object for the current post.
503 */
504 $description = apply_filters(
505 'jetpack_open_graph_fallback_description',
506 __( 'Visit the post for more.', 'jetpack' ),
507 $data
508 );
509 }
510
511 return $description;
512 }
513