PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.1-beta.3
Jetpack – WP Security, Backup, Speed, & Growth v16.1-beta.3
16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-post-media / src / class-twitter-cards.php
jetpack / jetpack_vendor / automattic / jetpack-post-media / src Last commit date
class-images.php 2 months ago class-post-media.php 1 month ago class-shortcodes.php 6 months ago class-twitter-cards.php 5 months ago
class-twitter-cards.php
355 lines
1 <?php
2 /**
3 * Twitter Cards handling.
4 *
5 * @package automattic/jetpack-post-media
6 */
7
8 namespace Automattic\Jetpack\Post_Media;
9
10 use WP_Post;
11
12 /**
13 * Twitter Cards
14 *
15 * Hooks onto the Open Graph protocol and extends it by adding only the tags
16 * we need for twitter cards.
17 *
18 * @see https://dev.twitter.com/cards/overview
19 */
20 class Twitter_Cards {
21
22 /**
23 * Adds Twitter Card tags.
24 *
25 * @param array $og_tags Existing OG tags.
26 *
27 * @return array OG tags inclusive of Twitter Card output.
28 */
29 public static function twitter_cards_tags( $og_tags ) {
30 global $post;
31 $post_id = ( $post instanceof WP_Post ) ? $post->ID : null;
32
33 /**
34 * Maximum alt text length.
35 *
36 * @see https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/summary-card-with-large-image.html
37 */
38 $alt_length = 420;
39
40 if ( post_password_required() ) {
41 return $og_tags;
42 }
43
44 /** This action is documented in class.jetpack.php */
45 if ( apply_filters( 'jetpack_disable_twitter_cards', false ) ) {
46 return $og_tags;
47 }
48
49 /*
50 * These tags apply to any page (home, archives, etc).
51 */
52
53 // If we have information on the author/creator, then include that as well.
54 if ( ! empty( $post ) && ! empty( $post->post_author ) ) {
55 /** This action is documented in modules/sharedaddy/sharing-sources.php */
56 $handle = apply_filters( 'jetpack_sharing_twitter_via', '', $post_id );
57 if ( ! empty( $handle ) && ! self::is_default_site_tag( $handle ) ) {
58 $og_tags['twitter:creator'] = self::sanitize_twitter_user( $handle );
59 }
60 }
61
62 $site_tag = self::site_tag();
63 /** This action is documented in modules/sharedaddy/sharing-sources.php */
64 $site_tag = apply_filters( 'jetpack_sharing_twitter_via', $site_tag, ( is_singular() ? $post_id : null ) );
65 /** This action is documented in modules/sharedaddy/sharing-sources.php */
66 $site_tag = apply_filters( 'jetpack_twitter_cards_site_tag', $site_tag, $og_tags );
67 if ( ! empty( $site_tag ) ) {
68 $og_tags['twitter:site'] = self::sanitize_twitter_user( $site_tag );
69 }
70
71 if ( ! is_singular() || ! empty( $og_tags['twitter:card'] ) ) {
72 /**
73 * Filter the default Twitter card image, used when no image can be found in a post.
74 *
75 * @module sharedaddy
76 *
77 * @since jetpack-5.9.0
78 *
79 * @param string $str Default image URL.
80 */
81 $image = apply_filters( 'jetpack_twitter_cards_image_default', '' );
82 if ( ! empty( $image ) ) {
83 $og_tags['twitter:image'] = $image;
84 }
85
86 return $og_tags;
87 }
88
89 $the_title = get_the_title();
90 if ( ! $the_title ) {
91 $the_title = get_bloginfo( 'name' );
92 }
93 $og_tags['twitter:text:title'] = $the_title;
94
95 /*
96 * The following tags only apply to single pages.
97 */
98
99 $card_type = 'summary';
100
101 // Try to give priority to featured images.
102 if ( ! empty( $post_id ) ) {
103 $post_image = Images::get_image(
104 $post_id,
105 array(
106 'width' => 144,
107 'height' => 144,
108 )
109 );
110 if ( ! empty( $post_image ) && is_array( $post_image ) ) {
111 // 4096 is the maximum size for an image per https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/summary .
112 if (
113 isset( $post_image['src_width'] ) && isset( $post_image['src_height'] )
114 && (int) $post_image['src_width'] <= 4096
115 && (int) $post_image['src_height'] <= 4096
116 ) {
117 // 300x157 is the minimum size for a summary_large_image per https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/summary-card-with-large-image .
118 if ( (int) $post_image['src_width'] >= 300 && (int) $post_image['src_height'] >= 157 ) {
119 $card_type = 'summary_large_image';
120 $og_tags['twitter:image'] = esc_url( add_query_arg( 'w', 640, $post_image['src'] ) );
121 } else {
122 $og_tags['twitter:image'] = esc_url( add_query_arg( 'w', 144, $post_image['src'] ) );
123 }
124
125 // Add the alt tag if we have one.
126 if ( ! empty( $post_image['alt_text'] ) ) {
127 // Shorten it if it is too long.
128 if ( strlen( $post_image['alt_text'] ) > $alt_length ) {
129 $og_tags['twitter:image:alt'] = esc_attr( mb_substr( $post_image['alt_text'], 0, $alt_length ) . '' );
130 } else {
131 $og_tags['twitter:image:alt'] = esc_attr( $post_image['alt_text'] );
132 }
133 }
134 }
135 }
136 }
137
138 $extract = array();
139
140 // Only proceed with media analysis if a featured image has not superseded it already.
141 if ( empty( $og_tags['twitter:image'] ) && empty( $og_tags['twitter:image:src'] ) ) {
142 // @todo Jetpack_Media_Summary is defined in the Jetpack plugin. It should be moved to this package.
143 if ( ! class_exists( 'Jetpack_Media_Summary' ) && defined( 'JETPACK__PLUGIN_DIR' ) ) {
144 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.media-summary.php';
145 }
146
147 // Test again, class should already be auto-loaded in Jetpack.
148 // If not, skip extra media analysis and stick with a summary card.
149 if ( class_exists( 'Jetpack_Media_Summary' ) && ! empty( $post_id ) ) {
150 $extract = \Jetpack_Media_Summary::get( $post_id ); // @phan-suppress-current-line PhanUndeclaredClassMethod -- Guarded by class_exists().
151
152 if ( 'gallery' === $extract['type'] ) {
153 list( $og_tags, $card_type ) = self::twitter_cards_define_type_based_on_image_count( $og_tags, $extract );
154 } elseif ( 'video' === $extract['type'] ) {
155 // Leave as summary, but with large pict of poster frame (we know those comply to Twitter's size requirements).
156 $card_type = 'summary_large_image';
157 $og_tags['twitter:image'] = esc_url( add_query_arg( 'w', 640, $extract['image'] ) );
158 } else {
159 list( $og_tags, $card_type ) = self::twitter_cards_define_type_based_on_image_count( $og_tags, $extract );
160 }
161 }
162 }
163
164 $og_tags['twitter:card'] = $card_type;
165
166 // Make sure we have a description for Twitter, their validator isn't happy without some content (single space not valid).
167 if ( ! isset( $og_tags['og:description'] ) || '' === trim( $og_tags['og:description'] ) || __( 'Visit the post for more.', 'jetpack-post-media' ) === $og_tags['og:description'] ) { // empty( trim( $og_tags['og:description'] ) ) isn't valid php.
168 $has_creator = ! empty( $og_tags['twitter:creator'] ) && '@wordpressdotcom' !== $og_tags['twitter:creator'];
169 if ( ! empty( $extract ) && 'video' === $extract['type'] ) { // use $extract['type'] since $card_type is 'summary' for video posts.
170 /* translators: %s is the post author */
171 $og_tags['twitter:description'] = ( $has_creator ) ? sprintf( __( 'Video post by %s.', 'jetpack-post-media' ), $og_tags['twitter:creator'] ?? '' ) : __( 'Video post.', 'jetpack-post-media' );
172 } else {
173 /* translators: %s is the post author */
174 $og_tags['twitter:description'] = ( $has_creator ) ? sprintf( __( 'Post by %s.', 'jetpack-post-media' ), $og_tags['twitter:creator'] ?? '' ) : __( 'Visit the post for more.', 'jetpack-post-media' );
175 }
176 }
177
178 if ( empty( $og_tags['twitter:image'] ) && empty( $og_tags['twitter:image:src'] ) ) {
179 /** This action is documented in class-twitter-cards.php */
180 $image = apply_filters( 'jetpack_twitter_cards_image_default', '' );
181 if ( ! empty( $image ) ) {
182 $og_tags['twitter:image'] = $image;
183 }
184 }
185
186 return $og_tags;
187 }
188
189 /**
190 * Sanitize the Twitter user by normalizing the @.
191 *
192 * @param string $str Twitter user value.
193 *
194 * @return string Twitter user value.
195 */
196 public static function sanitize_twitter_user( $str ) {
197 return '@' . preg_replace( '/^@/', '', $str );
198 }
199
200 /**
201 * Determines if a site tag is one of the default WP.com/Jetpack ones.
202 *
203 * @param string $site_tag Site tag.
204 *
205 * @return bool True if the default site tag is being used.
206 */
207 public static function is_default_site_tag( $site_tag ) {
208 return in_array( $site_tag, array( '@wordpressdotcom', '@jetpack', 'wordpressdotcom', 'jetpack' ), true );
209 }
210
211 /**
212 * Give priority to the creator tag if using the default site tag.
213 *
214 * @param string $site_tag Site tag.
215 * @param array $og_tags OG tags.
216 *
217 * @return string Site tag.
218 */
219 public static function prioritize_creator_over_default_site( $site_tag, $og_tags = array() ) {
220 if ( ! empty( $og_tags['twitter:creator'] ) && self::is_default_site_tag( $site_tag ) ) {
221 return $og_tags['twitter:creator'];
222 }
223 return $site_tag;
224 }
225
226 /**
227 * Define the Twitter Card type based on image count.
228 *
229 * @param array $og_tags Existing OG tags.
230 * @param array $extract Result of the Image Extractor class.
231 *
232 * @return array
233 */
234 public static function twitter_cards_define_type_based_on_image_count( $og_tags, $extract ) {
235 $card_type = 'summary';
236 $img_count = $extract['count']['image'];
237
238 if ( empty( $img_count ) ) {
239
240 // No images, use Blavatar as a thumbnail for the summary type.
241 // @todo blavatar_domain, blavatar_exists, and blavatar_url are WordPress.com functions. They should be abstracted.
242 if ( function_exists( 'blavatar_domain' ) ) {
243 $blavatar_domain = blavatar_domain( site_url() ); // @phan-suppress-current-line PhanUndeclaredFunction -- Guarded by function_exists().
244 if ( blavatar_exists( $blavatar_domain ) ) { // @phan-suppress-current-line PhanUndeclaredFunction -- Guarded by function_exists().
245 $og_tags['twitter:image'] = blavatar_url( $blavatar_domain, 'img', 240 ); // @phan-suppress-current-line PhanUndeclaredFunction -- Guarded by function_exists().
246 }
247 }
248
249 // Second fall back, Site Logo.
250 // @todo jetpack_has_site_logo and jetpack_get_site_logo are defined in the Jetpack plugin. They should be abstracted.
251 if ( empty( $og_tags['twitter:image'] ) && ( function_exists( 'jetpack_has_site_logo' ) && jetpack_has_site_logo() ) ) { // @phan-suppress-current-line PhanUndeclaredFunction -- Guarded by function_exists().
252 $og_tags['twitter:image'] = jetpack_get_site_logo( 'url' ); // @phan-suppress-current-line PhanUndeclaredFunction -- Guarded by function_exists().
253 }
254
255 // Third fall back, Site Icon.
256 if ( empty( $og_tags['twitter:image'] ) && has_site_icon() ) {
257 $og_tags['twitter:image'] = get_site_icon_url( 240 );
258 }
259
260 // Not falling back on Gravatar, because there's no way to know if we end up with an auto-generated one.
261
262 } elseif ( 'image' === $extract['type'] || 'gallery' === $extract['type'] ) {
263 // Test for $extract['type'] to limit to image and gallery, so we don't send a potential fallback image like a Gravatar as a photo post.
264 $card_type = 'summary_large_image';
265 $og_tags['twitter:image'] = esc_url( add_query_arg( 'w', 1400, ( empty( $extract['images'] ) ) ? $extract['image'] : $extract['images'][0]['url'] ) );
266 }
267
268 return array( $og_tags, $card_type );
269 }
270
271 /**
272 * Updates the Twitter Card output.
273 *
274 * @param string $og_tag A single OG tag.
275 *
276 * @return string Result of the OG tag.
277 */
278 public static function twitter_cards_output( $og_tag ) {
279 return ( str_contains( $og_tag, 'twitter:' ) ) ? preg_replace( '/property="([^"]+)"/', 'name="\1"', $og_tag ) : $og_tag;
280 }
281
282 /**
283 * Adds settings section and field.
284 */
285 public static function settings_init() {
286 add_settings_section( 'jetpack-twitter-cards-settings', 'Twitter Cards', '__return_false', 'sharing' );
287 add_settings_field(
288 'jetpack-twitter-cards-site-tag',
289 __( 'Twitter Site Tag', 'jetpack-post-media' ),
290 array( __CLASS__, 'settings_field' ),
291 'sharing',
292 'jetpack-twitter-cards-settings',
293 array(
294 'label_for' => 'jetpack-twitter-cards-site-tag',
295 )
296 );
297 }
298
299 /**
300 * Add global sharing options.
301 */
302 public static function sharing_global_options() {
303 do_settings_fields( 'sharing', 'jetpack-twitter-cards-settings' );
304 }
305
306 /**
307 * Get the Twitter Via tag.
308 *
309 * @return string Twitter via tag.
310 */
311 public static function site_tag() {
312 $site_tag = ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ?
313 trim( get_option( 'twitter_via' ) ) :
314 ( class_exists( 'Jetpack_Options' ) ? \Jetpack_Options::get_option_and_ensure_autoload( 'jetpack-twitter-cards-site-tag', '' ) : get_option( 'jetpack-twitter-cards-site-tag', '' ) );
315 if ( empty( $site_tag ) ) {
316 /** This action is documented in modules/sharedaddy/sharing-sources.php */
317 return apply_filters( 'jetpack_sharing_twitter_via', '', null );
318 }
319 return $site_tag;
320 }
321
322 /**
323 * Output the settings field.
324 */
325 public static function settings_field() {
326 wp_nonce_field( 'jetpack-twitter-cards-settings', 'jetpack_twitter_cards_nonce', false );
327 ?>
328 <input type="text" id="jetpack-twitter-cards-site-tag" class="regular-text" name="jetpack-twitter-cards-site-tag" value="<?php echo esc_attr( get_option( 'jetpack-twitter-cards-site-tag' ) ); ?>" />
329 <p class="description" style="width: auto;"><?php esc_html_e( 'The Twitter username of the owner of this site\'s domain.', 'jetpack-post-media' ); ?></p>
330 <?php
331 }
332
333 /**
334 * Validate the settings submission.
335 */
336 public static function settings_validate() {
337 if ( isset( $_POST['jetpack_twitter_cards_nonce'] ) && wp_verify_nonce( $_POST['jetpack_twitter_cards_nonce'], 'jetpack-twitter-cards-settings' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
338 update_option( 'jetpack-twitter-cards-site-tag', isset( $_POST['jetpack-twitter-cards-site-tag'] ) ? trim( ltrim( wp_strip_all_tags( filter_var( wp_unslash( $_POST['jetpack-twitter-cards-site-tag'] ) ) ), '@' ) ) : '' );
339 }
340 }
341
342 /**
343 * Initiates the class by registering all WordPress hooks.
344 */
345 public static function init() {
346 add_filter( 'jetpack_open_graph_tags', array( __CLASS__, 'twitter_cards_tags' ), 11 ); // $priority=11: this should hook into jetpack_open_graph_tags after 'class.jetpack-seo.php' has done so.
347 add_filter( 'jetpack_open_graph_output', array( __CLASS__, 'twitter_cards_output' ) );
348 add_filter( 'jetpack_twitter_cards_site_tag', array( __CLASS__, 'site_tag' ), -99 );
349 add_filter( 'jetpack_twitter_cards_site_tag', array( __CLASS__, 'prioritize_creator_over_default_site' ), 99, 2 );
350 add_action( 'admin_init', array( __CLASS__, 'settings_init' ) );
351 add_action( 'sharing_global_options', array( __CLASS__, 'sharing_global_options' ) );
352 add_action( 'sharing_admin_update', array( __CLASS__, 'settings_validate' ) );
353 }
354 }
355