Completed
Push — branch-9.3-built ( cf2e3a...4150c4 )
by Jeremy
241:41 queued 236:52
created

Jetpack_Twitter_Cards   F

Complexity

Total Complexity 77

Size/Duplication

Total Lines 334
Duplicated Lines 0.9 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
wmc 77
lcom 1
cbo 2
dl 3
loc 334
rs 2.24
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
F twitter_cards_tags() 3 160 47
A sanitize_twitter_user() 0 3 1
A is_default_site_tag() 0 3 1
A prioritize_creator_over_default_site() 0 6 3
C twitter_cards_define_type_based_on_image_count() 0 34 13
A twitter_cards_output() 0 3 2
A settings_init() 0 13 1
A sharing_global_options() 0 3 1
A site_tag() 0 10 4
A settings_field() 0 7 1
A settings_validate() 0 5 2
A init() 0 9 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Jetpack_Twitter_Cards often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Jetpack_Twitter_Cards, and based on these observations, apply Extract Interface, too.

1
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2
/**
3
 * Jetpack Twitter Card handling.
4
 *
5
 * @package Jetpack.
6
 */
7
8
/**
9
 * Twitter Cards
10
 *
11
 * Hooks onto the Open Graph protocol and extends it by adding only the tags
12
 * we need for twitter cards.
13
 *
14
 * @see /wp-content/blog-plugins/open-graph.php
15
 * @see https://dev.twitter.com/cards/overview
16
 */
17
class Jetpack_Twitter_Cards {
18
19
	/**
20
	 * Adds Twitter Card tags.
21
	 *
22
	 * @param array $og_tags Existing OG tags.
23
	 *
24
	 * @return array OG tags inclusive of Twitter Card output.
25
	 */
26
	public static function twitter_cards_tags( $og_tags ) {
27
		global $post;
28
		$post_id = ( $post instanceof WP_Post ) ? $post->ID : null;
0 ignored issues
show
Bug introduced by
The class WP_Post does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
29
30
		/**
31
		 * Maximum alt text length.
32
		 *
33
		 * @see https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/summary-card-with-large-image.html
34
		 */
35
		$alt_length = 420;
36
37
		if ( post_password_required() ) {
38
			return $og_tags;
39
		}
40
41
		/** This action is documented in class.jetpack.php */
42
		if ( apply_filters( 'jetpack_disable_twitter_cards', false ) ) {
43
			return $og_tags;
44
		}
45
46
		/*
47
		 * These tags apply to any page (home, archives, etc).
48
		 */
49
50
		// If we have information on the author/creator, then include that as well.
51
		if ( ! empty( $post ) && ! empty( $post->post_author ) ) {
52
			/** This action is documented in modules/sharedaddy/sharing-sources.php */
53
			$handle = apply_filters( 'jetpack_sharing_twitter_via', '', $post_id );
54
			if ( ! empty( $handle ) && ! self::is_default_site_tag( $handle ) ) {
55
				$og_tags['twitter:creator'] = self::sanitize_twitter_user( $handle );
56
			}
57
		}
58
59
		$site_tag = self::site_tag();
60
		/** This action is documented in modules/sharedaddy/sharing-sources.php */
61
		$site_tag = apply_filters( 'jetpack_sharing_twitter_via', $site_tag, ( is_singular() ? $post_id : null ) );
62
		/** This action is documented in modules/sharedaddy/sharing-sources.php */
63
		$site_tag = apply_filters( 'jetpack_twitter_cards_site_tag', $site_tag, $og_tags );
64
		if ( ! empty( $site_tag ) ) {
65
			$og_tags['twitter:site'] = self::sanitize_twitter_user( $site_tag );
66
		}
67
68
		if ( ! is_singular() || ! empty( $og_tags['twitter:card'] ) ) {
69
			/**
70
			 * Filter the default Twitter card image, used when no image can be found in a post.
71
			 *
72
			 * @module sharedaddy, publicize
73
			 *
74
			 * @since 5.9.0
75
			 *
76
			 * @param string $str Default image URL.
77
			 */
78
			$image = apply_filters( 'jetpack_twitter_cards_image_default', '' );
79
			if ( ! empty( $image ) ) {
80
				$og_tags['twitter:image'] = $image;
81
			}
82
83
			return $og_tags;
84
		}
85
86
		$the_title = get_the_title();
87
		if ( ! $the_title ) {
88
			$the_title = get_bloginfo( 'name' );
89
		}
90
		$og_tags['twitter:text:title'] = $the_title;
91
92
		/*
93
		 * The following tags only apply to single pages.
94
		 */
95
96
		$card_type = 'summary';
97
98
		// Try to give priority to featured images.
99
		if ( class_exists( 'Jetpack_PostImages' ) && ! empty( $post_id ) ) {
100
			$post_image = Jetpack_PostImages::get_image(
101
				$post_id,
102
				array(
103
					'width'  => 144,
104
					'height' => 144,
105
				)
106
			);
107
			if ( ! empty( $post_image ) && is_array( $post_image ) ) {
108
				// 4096 is the maximum size for an image per https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/summary .
109
				if (
110
					isset( $post_image['src_width'], $post_image['src_height'] )
111
					&& (int) $post_image['src_width'] <= 4096
112
					&& (int) $post_image['src_height'] <= 4096
113
				) {
114
					// 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 .
115
					if ( (int) $post_image['src_width'] >= 300 && (int) $post_image['src_height'] >= 157 ) {
116
						$card_type                = 'summary_large_image';
117
						$og_tags['twitter:image'] = esc_url( add_query_arg( 'w', 640, $post_image['src'] ) );
118
					} else {
119
						$og_tags['twitter:image'] = esc_url( add_query_arg( 'w', 144, $post_image['src'] ) );
120
					}
121
122
					// Add the alt tag if we have one.
123
					if ( ! empty( $post_image['alt_text'] ) ) {
124
						// Shorten it if it is too long.
125
						if ( strlen( $post_image['alt_text'] ) > $alt_length ) {
126
							$og_tags['twitter:image:alt'] = esc_attr( mb_substr( $post_image['alt_text'], 0, $alt_length ) . '…' );
127
						} else {
128
							$og_tags['twitter:image:alt'] = esc_attr( $post_image['alt_text'] );
129
						}
130
					}
131
				}
132
			}
133
		}
134
135
		// Only proceed with media analysis if a featured image has not superseded it already.
136
		if ( empty( $og_tags['twitter:image'] ) && empty( $og_tags['twitter:image:src'] ) ) {
137 View Code Duplication
			if ( ! class_exists( 'Jetpack_Media_Summary' ) && defined( 'IS_WPCOM' ) && IS_WPCOM ) {
138
				include WP_CONTENT_DIR . '/lib/class.wpcom-media-summary.php';
139
			}
140
141
			if ( ! class_exists( 'Jetpack_Media_Summary' ) ) {
142
				jetpack_require_lib( 'class.media-summary' );
143
			}
144
145
			// Test again, class should already be auto-loaded in Jetpack.
146
			// If not, skip extra media analysis and stick with a summary card.
147
			if ( class_exists( 'Jetpack_Media_Summary' ) && ! empty( $post_id ) ) {
148
				$extract = Jetpack_Media_Summary::get( $post_id );
149
150
				if ( 'gallery' === $extract['type'] ) {
151
					list( $og_tags, $card_type ) = self::twitter_cards_define_type_based_on_image_count( $og_tags, $extract );
152
				} elseif ( 'video' === $extract['type'] ) {
153
					// Leave as summary, but with large pict of poster frame (we know those comply to Twitter's size requirements).
154
					$card_type                = 'summary_large_image';
155
					$og_tags['twitter:image'] = esc_url( add_query_arg( 'w', 640, $extract['image'] ) );
156
				} else {
157
					list( $og_tags, $card_type ) = self::twitter_cards_define_type_based_on_image_count( $og_tags, $extract );
158
				}
159
			}
160
		}
161
162
		$og_tags['twitter:card'] = $card_type;
163
164
		// Make sure we have a description for Twitter, their validator isn't happy without some content (single space not valid).
165
		if ( ! isset( $og_tags['og:description'] ) || '' === trim( $og_tags['og:description'] ) || __( 'Visit the post for more.', 'jetpack' ) === $og_tags['og:description'] ) { // empty( trim( $og_tags['og:description'] ) ) isn't valid php.
166
			$has_creator = ( ! empty( $og_tags['twitter:creator'] ) && '@wordpressdotcom' !== $og_tags['twitter:creator'] ) ? true : false;
167
			if ( ! empty( $extract ) && 'video' === $extract['type'] ) { // use $extract['type'] since $card_type is 'summary' for video posts.
168
				/* translators: %s is the post author */
169
				$og_tags['twitter:description'] = ( $has_creator ) ? sprintf( __( 'Video post by %s.', 'jetpack' ), $og_tags['twitter:creator'] ) : __( 'Video post.', 'jetpack' );
170
			} else {
171
				/* translators: %s is the post author */
172
				$og_tags['twitter:description'] = ( $has_creator ) ? sprintf( __( 'Post by %s.', 'jetpack' ), $og_tags['twitter:creator'] ) : __( 'Visit the post for more.', 'jetpack' );
173
			}
174
		}
175
176
		if ( empty( $og_tags['twitter:image'] ) && empty( $og_tags['twitter:image:src'] ) ) {
177
			/** This action is documented in class.jetpack-twitter-cards.php */
178
			$image = apply_filters( 'jetpack_twitter_cards_image_default', '' );
179
			if ( ! empty( $image ) ) {
180
				$og_tags['twitter:image'] = $image;
181
			}
182
		}
183
184
		return $og_tags;
185
	}
186
187
	/**
188
	 * Sanitize the Twitter user by normalizing the @.
189
	 *
190
	 * @param string $str Twitter user value.
191
	 *
192
	 * @return string Twitter user value.
193
	 */
194
	public static function sanitize_twitter_user( $str ) {
195
		return '@' . preg_replace( '/^@/', '', $str );
196
	}
197
198
	/**
199
	 * Determines if a site tag is one of the default WP.com/Jetpack ones.
200
	 *
201
	 * @param string $site_tag Site tag.
202
	 *
203
	 * @return bool True if the default site tag is being used.
204
	 */
205
	public static function is_default_site_tag( $site_tag ) {
206
		return in_array( $site_tag, array( '@wordpressdotcom', '@jetpack', 'wordpressdotcom', 'jetpack' ), true );
207
	}
208
209
	/**
210
	 * Give priority to the creator tag if using the default site tag.
211
	 *
212
	 * @param string $site_tag Site tag.
213
	 * @param array  $og_tags OG tags.
214
	 *
215
	 * @return string Site tag.
216
	 */
217
	public static function prioritize_creator_over_default_site( $site_tag, $og_tags = array() ) {
218
		if ( ! empty( $og_tags['twitter:creator'] ) && self::is_default_site_tag( $site_tag ) ) {
219
			return $og_tags['twitter:creator'];
220
		}
221
		return $site_tag;
222
	}
223
224
	/**
225
	 * Define the Twitter Card type based on image count.
226
	 *
227
	 * @param array $og_tags Existing OG tags.
228
	 * @param array $extract Result of the Image Extractor class.
229
	 *
230
	 * @return array
231
	 */
232
	public static function twitter_cards_define_type_based_on_image_count( $og_tags, $extract ) {
233
		$card_type = 'summary';
234
		$img_count = $extract['count']['image'];
235
236
		if ( empty( $img_count ) ) {
237
238
			// No images, use Blavatar as a thumbnail for the summary type.
239
			if ( function_exists( 'blavatar_domain' ) ) {
240
				$blavatar_domain = blavatar_domain( site_url() );
241
				if ( blavatar_exists( $blavatar_domain ) ) {
242
					$og_tags['twitter:image'] = blavatar_url( $blavatar_domain, 'img', 240 );
243
				}
244
			}
245
246
			// Second fall back, Site Logo.
247
			if ( empty( $og_tags['twitter:image'] ) && ( function_exists( 'jetpack_has_site_logo' ) && jetpack_has_site_logo() ) ) {
248
				$og_tags['twitter:image'] = jetpack_get_site_logo( 'url' );
249
			}
250
251
			// Third fall back, Site Icon.
252
			if ( empty( $og_tags['twitter:image'] ) && has_site_icon() ) {
253
				$og_tags['twitter:image'] = get_site_icon_url( '240' );
254
			}
255
256
			// Not falling back on Gravatar, because there's no way to know if we end up with an auto-generated one.
257
258
		} elseif ( $img_count && ( 'image' === $extract['type'] || 'gallery' === $extract['type'] ) ) {
259
			// 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.
260
			$card_type                = 'summary_large_image';
261
			$og_tags['twitter:image'] = esc_url( add_query_arg( 'w', 1400, ( empty( $extract['images'] ) ) ? $extract['image'] : $extract['images'][0]['url'] ) );
262
		}
263
264
		return array( $og_tags, $card_type );
265
	}
266
267
	/**
268
	 * Updates the Twitter Card output.
269
	 *
270
	 * @param string $og_tag A single OG tag.
271
	 *
272
	 * @return string Result of the OG tag.
273
	 */
274
	public static function twitter_cards_output( $og_tag ) {
275
		return ( false !== strpos( $og_tag, 'twitter:' ) ) ? preg_replace( '/property="([^"]+)"/', 'name="\1"', $og_tag ) : $og_tag;
276
	}
277
278
	/**
279
	 * Adds settings section and field.
280
	 */
281
	public static function settings_init() {
282
		add_settings_section( 'jetpack-twitter-cards-settings', 'Twitter Cards', '__return_false', 'sharing' );
283
		add_settings_field(
284
			'jetpack-twitter-cards-site-tag',
285
			__( 'Twitter Site Tag', 'jetpack' ),
286
			array( __CLASS__, 'settings_field' ),
287
			'sharing',
288
			'jetpack-twitter-cards-settings',
289
			array(
290
				'label_for' => 'jetpack-twitter-cards-site-tag',
291
			)
292
		);
293
	}
294
295
	/**
296
	 * Add global sharing options.
297
	 */
298
	public static function sharing_global_options() {
299
		do_settings_fields( 'sharing', 'jetpack-twitter-cards-settings' );
300
	}
301
302
	/**
303
	 * Get the Twitter Via tag.
304
	 *
305
	 * @return string Twitter via tag.
306
	 */
307
	public static function site_tag() {
308
		$site_tag = ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ?
309
			trim( get_option( 'twitter_via' ) ) :
310
			Jetpack_Options::get_option_and_ensure_autoload( 'jetpack-twitter-cards-site-tag', '' );
311
		if ( empty( $site_tag ) ) {
312
			/** This action is documented in modules/sharedaddy/sharing-sources.php */
313
			return apply_filters( 'jetpack_sharing_twitter_via', '', null );
314
		}
315
		return $site_tag;
316
	}
317
318
	/**
319
	 * Output the settings field.
320
	 */
321
	public static function settings_field() {
322
		wp_nonce_field( 'jetpack-twitter-cards-settings', 'jetpack_twitter_cards_nonce', false );
323
		?>
324
		<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' ) ); ?>" />
325
		<p class="description" style="width: auto;"><?php esc_html_e( 'The Twitter username of the owner of this site\'s domain.', 'jetpack' ); ?></p>
326
		<?php
327
	}
328
329
	/**
330
	 * Validate the settings submission.
331
	 */
332
	public static function settings_validate() {
333
		if ( wp_verify_nonce( $_POST['jetpack_twitter_cards_nonce'], 'jetpack-twitter-cards-settings' ) ) {
334
			update_option( 'jetpack-twitter-cards-site-tag', trim( ltrim( wp_strip_all_tags( $_POST['jetpack-twitter-cards-site-tag'] ), '@' ) ) );
335
		}
336
	}
337
338
	/**
339
	 * Initiates the class.
340
	 */
341
	public static function init() {
342
		add_filter( 'jetpack_open_graph_tags', array( __CLASS__, 'twitter_cards_tags' ) );
343
		add_filter( 'jetpack_open_graph_output', array( __CLASS__, 'twitter_cards_output' ) );
344
		add_filter( 'jetpack_twitter_cards_site_tag', array( __CLASS__, 'site_tag' ), -99 );
345
		add_filter( 'jetpack_twitter_cards_site_tag', array( __CLASS__, 'prioritize_creator_over_default_site' ), 99, 2 );
346
		add_action( 'admin_init', array( __CLASS__, 'settings_init' ) );
347
		add_action( 'sharing_global_options', array( __CLASS__, 'sharing_global_options' ) );
348
		add_action( 'sharing_admin_update', array( __CLASS__, 'settings_validate' ) );
349
	}
350
}
351
352
Jetpack_Twitter_Cards::init();
353