Completed
Push — try/jetpack-stories-block-mobi... ( 2fea66 )
by
unknown
126:35 queued 116:47
created

pinterest.php ➔ fetch_pin_info()   B

Complexity

Conditions 10
Paths 10

Size

Total Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
nc 10
nop 1
dl 0
loc 43
rs 7.6666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Pinterest Block.
4
 *
5
 * @since 8.0.0
6
 *
7
 * @package Jetpack
8
 */
9
10
namespace Automattic\Jetpack\Extensions\Pinterest;
11
12
use Automattic\Jetpack\Blocks;
13
use WP_Error;
14
15
const FEATURE_NAME = 'pinterest';
16
const BLOCK_NAME   = 'jetpack/' . FEATURE_NAME;
17
const URL_PATTERN  = '#^https?://(?:www\.)?(?:[a-z]{2}\.)?pinterest\.[a-z.]+/pin/(?P<pin_id>[^/]+)/?#i'; // Taken from AMP plugin, originally from Jetpack.
18
// This is the validate Pinterest URLs, converted from URL_REGEX in extensions/blocks/pinterest/index.js.
19
const PINTEREST_URL_REGEX = '/^https?:\/\/(?:www\.)?(?:[a-z]{2\.)?(?:pinterest\.[a-z.]+|pin\.it)\/([^\/]+)(\/[^\/]+)?/i';
20
// This looks for matches in /foo/ of https://www.pinterest.ca/foo/.
21
const REMAINING_URL_PATH_REGEX = '/^\/([^\/]+)\/?$/';
22
// This looks for matches with /foo/bar/ of https://www.pinterest.ca/foo/bar/.
23
const REMAINING_URL_PATH_WITH_SUBPATH_REGEX = '/^\/([^\/]+)\/([^\/]+)\/?$/';
24
25
/**
26
 * Determines the Pinterest embed type from the URL.
27
 *
28
 * @param string $url the URL to check.
29
 * @returns {string} The pin type. Empty string if it isn't a valid Pinterest URL.
30
 */
31
function pin_type( $url ) {
32
	if ( ! preg_match( PINTEREST_URL_REGEX, $url ) ) {
33
		return '';
34
	}
35
36
	$path = wp_parse_url( $url, PHP_URL_PATH );
37
38
	if ( ! $path ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $path of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
39
		return '';
40
	}
41
42
	if ( substr( $path, 0, 5 ) === '/pin/' ) {
43
		return 'embedPin';
44
	}
45
46
	if ( preg_match( REMAINING_URL_PATH_REGEX, $path ) ) {
47
		return 'embedUser';
48
	}
49
50
	if ( preg_match( REMAINING_URL_PATH_WITH_SUBPATH_REGEX, $path ) ) {
51
		return 'embedBoard';
52
	}
53
54
	return '';
55
}
56
57
/**
58
 * Registers the block for use in Gutenberg
59
 * This is done via an action so that we can disable
60
 * registration if we need to.
61
 */
62
function register_block() {
63
	Blocks::jetpack_register_block(
64
		BLOCK_NAME,
65
		array( 'render_callback' => __NAMESPACE__ . '\load_assets' )
66
	);
67
}
68
add_action( 'init', __NAMESPACE__ . '\register_block' );
69
70
/**
71
 * Fetch info for a Pin.
72
 *
73
 * This is using the same pin info API as AMP is using client-side in the amp-pinterest component.
74
 * Successful API responses are cached in a transient for 1 month. Unsuccessful responses are cached for 1 hour.
75
 *
76
 * @link https://github.com/ampproject/amphtml/blob/b5dea36e0b8bd012585d50839766a084f99a3685/extensions/amp-pinterest/0.1/pin-widget.js#L83-L97
77
 * @param string $pin_id Pin ID.
78
 * @return array|WP_Error Pin info or error on failure.
79
 */
80
function fetch_pin_info( $pin_id ) {
81
	$transient_id = substr( "jetpack_pin_info_{$pin_id}", 0, 172 );
82
83
	$info = get_transient( $transient_id );
84
	if ( is_array( $info ) || is_wp_error( $info ) ) {
85
		return $info;
86
	}
87
88
	$pin_info_api_url = add_query_arg(
89
		array(
90
			'pin_ids'     => rawurlencode( $pin_id ),
91
			'sub'         => 'wwww',
92
			'base_scheme' => 'https',
93
		),
94
		'https://widgets.pinterest.com/v3/pidgets/pins/info/'
95
	);
96
97
	$response = wp_remote_get( esc_url_raw( $pin_info_api_url ) );
98
	if ( is_wp_error( $response ) ) {
99
		set_transient( $transient_id, $response, HOUR_IN_SECONDS );
100
		return $response;
101
	}
102
103
	$error = null;
104
	$body  = json_decode( wp_remote_retrieve_body( $response ), true );
105
	if ( ! is_array( $body ) || ! isset( $body['status'] ) ) {
106
		$error = new WP_Error( 'bad_json_response', '', compact( 'pin_id' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'bad_json_response'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
107
	} elseif ( 'success' !== $body['status'] || ! isset( $body['data'][0] ) ) {
108
		$error = new WP_Error( 'unsuccessful_request', '', compact( 'pin_id' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unsuccessful_request'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
109
	} elseif ( ! isset( $body['data'][0]['images']['237x'] ) ) {
110
		// See <https://github.com/ampproject/amphtml/blob/b5dea36e0b8bd012585d50839766a084f99a3685/extensions/amp-pinterest/0.1/pin-widget.js#L106>.
111
		$error = new WP_Error( 'missing_required_image', '', compact( 'pin_id' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'missing_required_image'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
112
	}
113
114
	if ( $error ) {
115
		set_transient( $transient_id, $error, HOUR_IN_SECONDS );
116
		return $error;
117
	} else {
118
		$data = $body['data'][0];
119
		set_transient( $transient_id, $data, MONTH_IN_SECONDS );
120
		return $data;
121
	}
122
}
123
124
/**
125
 * Render a Pin using the amp-pinterest component.
126
 *
127
 * This does not render boards or user profiles.
128
 *
129
 * Since AMP components need to be statically sized to be valid (so as to avoid layout shifting), there are quite a few
130
 * hard-coded numbers as taken from the CSS for the AMP component.
131
 *
132
 * @param array $attr Block attributes.
133
 * @return string Markup for <amp-pinterest>.
134
 */
135
function render_amp_pin( $attr ) {
136
	$info = null;
137
	if ( preg_match( URL_PATTERN, $attr['url'], $matches ) ) {
138
		$info = fetch_pin_info( $matches['pin_id'] );
139
	}
140
141
	if ( is_array( $info ) ) {
142
		$image       = $info['images']['237x'];
143
		$title       = isset( $info['rich_metadata']['title'] ) ? $info['rich_metadata']['title'] : null;
144
		$description = isset( $info['rich_metadata']['description'] ) ? $info['rich_metadata']['description'] : null;
145
146
		// This placeholder will appear while waiting for the amp-pinterest component to initialize (or if it fails to initialize due to JS being disabled).
147
		$placeholder = sprintf(
148
			// The AMP_Img_Sanitizer will convert his to <amp-img> while also supplying `noscript > img` as fallback when JS is disabled.
149
			'<a href="%s" placeholder><img src="%s" alt="%s" layout="fill" object-fit="contain" object-position="top left"></a>',
150
			esc_url( $attr['url'] ),
151
			esc_url( $image['url'] ),
152
			esc_attr( $title )
153
		);
154
155
		$amp_padding     = 5;   // See <https://github.com/ampproject/amphtml/blob/b5dea36e0b8bd012585d50839766a084f99a3685/extensions/amp-pinterest/0.1/amp-pinterest.css#L269>.
156
		$amp_fixed_width = 237; // See <https://github.com/ampproject/amphtml/blob/b5dea36e0b8bd012585d50839766a084f99a3685/extensions/amp-pinterest/0.1/amp-pinterest.css#L270>.
157
		$pin_info_height = 60;  // Minimum Obtained by measuring the height of the .-amp-pinterest-embed-pin-text element.
158
159
		// Add height based on how much description there is. There are roughly 30 characters on a line of description text.
160
		$has_description = false;
161
		if ( ! empty( $info['description'] ) ) {
162
			$desc_padding_top = 5;  // See <https://github.com/ampproject/amphtml/blob/b5dea36e0b8bd012585d50839766a084f99a3685/extensions/amp-pinterest/0.1/amp-pinterest.css#L342>.
163
			$pin_info_height += $desc_padding_top;
164
165
			// Trim whitespace on description if there is any left, use to calculate the likely rows of text.
166
			$description = trim( $info['description'] );
167
			if ( strlen( $description ) > 0 ) {
168
				$has_description  = true;
169
				$desc_line_height = 17; // See <https://github.com/ampproject/amphtml/blob/b5dea36e0b8bd012585d50839766a084f99a3685/extensions/amp-pinterest/0.1/amp-pinterest.css#L341>.
170
				$pin_info_height += ceil( strlen( $description ) / 30 ) * $desc_line_height;
171
			}
172
		}
173
174
		if ( ! empty( $info['repin_count'] ) ) {
175
			$pin_stats_height = 16;  // See <https://github.com/ampproject/amphtml/blob/b5dea36e0b8bd012585d50839766a084f99a3685/extensions/amp-pinterest/0.1/amp-pinterest.css#L322>.
176
			$pin_info_height += $pin_stats_height;
177
		}
178
179
		// When Pin description is empty, make sure title and description from rich metadata are supplied for accessibility and discoverability.
180
		$title = $has_description ? '' : implode( "\n", array_filter( array( $title, $description ) ) );
181
182
		$amp_pinterest = sprintf(
183
			'<amp-pinterest style="%1$s" data-do="embedPin" data-url="%2$s" width="%3$d" height="%4$d" title="%5$s">%6$s</amp-pinterest>',
184
			esc_attr( 'line-height:1.5; font-size:21px' ), // Override styles from theme due to precise height calculations above.
185
			esc_url( $attr['url'] ),
186
			$amp_fixed_width + ( $amp_padding * 2 ),
187
			$image['height'] + $pin_info_height + ( $amp_padding * 2 ),
188
			esc_attr( $title ),
189
			$placeholder
190
		);
191
	} else {
192
		// Fallback embed when info is not available.
193
		$amp_pinterest = sprintf(
194
			'<amp-pinterest data-do="embedPin" data-url="%1$s" width="%2$d" height="%3$d">%4$s</amp-pinterest>',
195
			esc_url( $attr['url'] ),
196
			450, // Fallback width.
197
			750, // Fallback height.
198
			sprintf(
199
				'<a placeholder href="%s">%s</a>',
200
				esc_url( $attr['url'] ),
201
				esc_html( $attr['url'] )
202
			)
203
		);
204
	}
205
206
	return sprintf(
207
		'<div class="wp-block-jetpack-pinterest">%s</div>',
208
		$amp_pinterest
209
	);
210
}
211
212
/**
213
 * Pinterest block registration/dependency declaration.
214
 *
215
 * @param array  $attr    Array containing the Pinterest block attributes.
216
 * @param string $content String containing the Pinterest block content.
217
 *
218
 * @return string
219
 */
220
function load_assets( $attr, $content ) {
221
	if ( ! jetpack_is_frontend() ) {
222
		return $content;
223
	}
224
	if ( Blocks::is_amp_request() ) {
225
		return render_amp_pin( $attr );
226
	} else {
227
		$url  = $attr['url'];
228
		$type = pin_type( $url );
229
230
		if ( ! $type ) {
231
			return '';
232
		}
233
234
		wp_enqueue_script( 'pinterest-pinit', 'https://assets.pinterest.com/js/pinit.js', array(), JETPACK__VERSION, true );
235
		return sprintf(
236
			'
237
			<div class="%1$s">
238
				<a data-pin-do="%2$s" href="%3$s"></a>
239
			</div>
240
		',
241
			esc_attr( Blocks::classes( FEATURE_NAME, $attr ) ),
242
			esc_attr( $type ),
243
			esc_url( $url )
244
		);
245
	}
246
}
247