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 Jetpack_AMP_Support; |
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
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Registers the block for use in Gutenberg |
21
|
|
|
* This is done via an action so that we can disable |
22
|
|
|
* registration if we need to. |
23
|
|
|
*/ |
24
|
|
|
function register_block() { |
25
|
|
|
jetpack_register_block( |
26
|
|
|
BLOCK_NAME, |
27
|
|
|
array( 'render_callback' => __NAMESPACE__ . '\load_assets' ) |
28
|
|
|
); |
29
|
|
|
} |
30
|
|
|
add_action( 'init', __NAMESPACE__ . '\register_block' ); |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Fetch info for a Pin. |
34
|
|
|
* |
35
|
|
|
* This is using the same pin info API as AMP is using client-side in the amp-pinterest component. |
36
|
|
|
* Successful API responses are cached in a transient for 1 month. Unsuccessful responses are cached for 1 hour. |
37
|
|
|
* |
38
|
|
|
* @link https://github.com/ampproject/amphtml/blob/b5dea36e0b8bd012585d50839766a084f99a3685/extensions/amp-pinterest/0.1/pin-widget.js#L83-L97 |
39
|
|
|
* @param string $pin_id Pin ID. |
40
|
|
|
* @return array|WP_Error Pin info or error on failure. |
41
|
|
|
*/ |
42
|
|
|
function fetch_pin_info( $pin_id ) { |
43
|
|
|
$transient_id = substr( "jetpack_pin_info_{$pin_id}", 0, 172 ); |
44
|
|
|
|
45
|
|
|
$info = get_transient( $transient_id ); |
46
|
|
|
if ( is_array( $info ) || is_wp_error( $info ) ) { |
47
|
|
|
return $info; |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
$pin_info_api_url = add_query_arg( |
51
|
|
|
array( |
52
|
|
|
'pin_ids' => rawurlencode( $pin_id ), |
53
|
|
|
'sub' => 'wwww', |
54
|
|
|
'base_scheme' => 'https', |
55
|
|
|
), |
56
|
|
|
'https://widgets.pinterest.com/v3/pidgets/pins/info/' |
57
|
|
|
); |
58
|
|
|
|
59
|
|
|
$response = wp_remote_get( esc_url_raw( $pin_info_api_url ) ); |
60
|
|
|
if ( is_wp_error( $response ) ) { |
61
|
|
|
set_transient( $transient_id, $response, HOUR_IN_SECONDS ); |
62
|
|
|
return $response; |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
$error = null; |
66
|
|
|
$body = json_decode( wp_remote_retrieve_body( $response ), true ); |
67
|
|
|
if ( ! is_array( $body ) || ! isset( $body['status'] ) ) { |
68
|
|
|
$error = new WP_Error( 'bad_json_response', '', compact( 'pin_id' ) ); |
|
|
|
|
69
|
|
|
} elseif ( 'success' !== $body['status'] || ! isset( $body['data'][0] ) ) { |
70
|
|
|
$error = new WP_Error( 'unsuccessful_request', '', compact( 'pin_id' ) ); |
|
|
|
|
71
|
|
|
} elseif ( ! isset( $body['data'][0]['images']['237x'] ) ) { |
72
|
|
|
// See <https://github.com/ampproject/amphtml/blob/b5dea36e0b8bd012585d50839766a084f99a3685/extensions/amp-pinterest/0.1/pin-widget.js#L106>. |
73
|
|
|
$error = new WP_Error( 'missing_required_image', '', compact( 'pin_id' ) ); |
|
|
|
|
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
if ( $error ) { |
77
|
|
|
set_transient( $transient_id, $error, HOUR_IN_SECONDS ); |
78
|
|
|
return $error; |
79
|
|
|
} else { |
80
|
|
|
$data = $body['data'][0]; |
81
|
|
|
set_transient( $transient_id, $data, MONTH_IN_SECONDS ); |
82
|
|
|
return $data; |
83
|
|
|
} |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* Render a Pin using the amp-pinterest component. |
88
|
|
|
* |
89
|
|
|
* This does not render boards or user profiles. |
90
|
|
|
* |
91
|
|
|
* Since AMP components need to be statically sized to be valid (so as to avoid layout shifting), there are quite a few |
92
|
|
|
* hard-coded numbers as taken from the CSS for the AMP component. |
93
|
|
|
* |
94
|
|
|
* @param array $attr Block attributes. |
95
|
|
|
* @return string Markup for <amp-pinterest>. |
96
|
|
|
*/ |
97
|
|
|
function render_amp_pin( $attr ) { |
98
|
|
|
$info = null; |
99
|
|
|
if ( preg_match( URL_PATTERN, $attr['url'], $matches ) ) { |
100
|
|
|
$info = fetch_pin_info( $matches['pin_id'] ); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
if ( is_array( $info ) ) { |
104
|
|
|
$image = $info['images']['237x']; |
105
|
|
|
$title = isset( $info['rich_metadata']['title'] ) ? $info['rich_metadata']['title'] : null; |
106
|
|
|
$description = isset( $info['rich_metadata']['description'] ) ? $info['rich_metadata']['description'] : null; |
107
|
|
|
|
108
|
|
|
// This placeholder will appear while waiting for the amp-pinterest component to initialize (or if it fails to initialize due to JS being disabled). |
109
|
|
|
$placeholder = sprintf( |
110
|
|
|
// The AMP_Img_Sanitizer will convert his to <amp-img> while also supplying `noscript > img` as fallback when JS is disabled. |
111
|
|
|
'<a href="%s" placeholder><img src="%s" alt="%s" layout="fill" object-fit="contain" object-position="top left"></a>', |
112
|
|
|
esc_url( $attr['url'] ), |
113
|
|
|
esc_url( $image['url'] ), |
114
|
|
|
esc_attr( $title ) |
115
|
|
|
); |
116
|
|
|
|
117
|
|
|
$amp_padding = 5; // See <https://github.com/ampproject/amphtml/blob/b5dea36e0b8bd012585d50839766a084f99a3685/extensions/amp-pinterest/0.1/amp-pinterest.css#L269>. |
118
|
|
|
$amp_fixed_width = 237; // See <https://github.com/ampproject/amphtml/blob/b5dea36e0b8bd012585d50839766a084f99a3685/extensions/amp-pinterest/0.1/amp-pinterest.css#L270>. |
119
|
|
|
$pin_info_height = 60; // Minimum Obtained by measuring the height of the .-amp-pinterest-embed-pin-text element. |
120
|
|
|
|
121
|
|
|
// Add height based on how much description there is. There are roughly 30 characters on a line of description text. |
122
|
|
|
$has_description = false; |
123
|
|
|
if ( ! empty( $info['description'] ) ) { |
124
|
|
|
$desc_padding_top = 5; // See <https://github.com/ampproject/amphtml/blob/b5dea36e0b8bd012585d50839766a084f99a3685/extensions/amp-pinterest/0.1/amp-pinterest.css#L342>. |
125
|
|
|
$pin_info_height += $desc_padding_top; |
126
|
|
|
|
127
|
|
|
// Trim whitespace on description if there is any left, use to calculate the likely rows of text. |
128
|
|
|
$description = trim( $info['description'] ); |
129
|
|
|
if ( strlen( $description ) > 0 ) { |
130
|
|
|
$has_description = true; |
131
|
|
|
$desc_line_height = 17; // See <https://github.com/ampproject/amphtml/blob/b5dea36e0b8bd012585d50839766a084f99a3685/extensions/amp-pinterest/0.1/amp-pinterest.css#L341>. |
132
|
|
|
$pin_info_height += ceil( strlen( $description ) / 30 ) * $desc_line_height; |
133
|
|
|
} |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
if ( ! empty( $info['repin_count'] ) ) { |
137
|
|
|
$pin_stats_height = 16; // See <https://github.com/ampproject/amphtml/blob/b5dea36e0b8bd012585d50839766a084f99a3685/extensions/amp-pinterest/0.1/amp-pinterest.css#L322>. |
138
|
|
|
$pin_info_height += $pin_stats_height; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
// When Pin description is empty, make sure title and description from rich metadata are supplied for accessibility and discoverability. |
142
|
|
|
$title = $has_description ? '' : implode( "\n", array_filter( array( $title, $description ) ) ); |
143
|
|
|
|
144
|
|
|
$amp_pinterest = sprintf( |
145
|
|
|
'<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>', |
146
|
|
|
esc_attr( 'line-height:1.5; font-size:21px' ), // Override styles from theme due to precise height calculations above. |
147
|
|
|
esc_url( $attr['url'] ), |
148
|
|
|
$amp_fixed_width + ( $amp_padding * 2 ), |
149
|
|
|
$image['height'] + $pin_info_height + ( $amp_padding * 2 ), |
150
|
|
|
esc_attr( $title ), |
151
|
|
|
$placeholder |
152
|
|
|
); |
153
|
|
|
} else { |
154
|
|
|
// Fallback embed when info is not available. |
155
|
|
|
$amp_pinterest = sprintf( |
156
|
|
|
'<amp-pinterest data-do="embedPin" data-url="%1$s" width="%2$d" height="%3$d">%4$s</amp-pinterest>', |
157
|
|
|
esc_url( $attr['url'] ), |
158
|
|
|
450, // Fallback width. |
159
|
|
|
750, // Fallback height. |
160
|
|
|
sprintf( |
161
|
|
|
'<a placeholder href="%s">%s</a>', |
162
|
|
|
esc_url( $attr['url'] ), |
163
|
|
|
esc_html( $attr['url'] ) |
164
|
|
|
) |
165
|
|
|
); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
return sprintf( |
169
|
|
|
'<div class="wp-block-jetpack-pinterest">%s</div>', |
170
|
|
|
$amp_pinterest |
171
|
|
|
); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* Pinterest block registration/dependency declaration. |
176
|
|
|
* |
177
|
|
|
* @param array $attr Array containing the Pinterest block attributes. |
178
|
|
|
* @param string $content String containing the Pinterest block content. |
179
|
|
|
* |
180
|
|
|
* @return string |
181
|
|
|
*/ |
182
|
|
|
function load_assets( $attr, $content ) { |
183
|
|
|
if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) { |
184
|
|
|
return render_amp_pin( $attr ); |
185
|
|
|
} else { |
186
|
|
|
wp_enqueue_script( 'pinterest-pinit', 'https://assets.pinterest.com/js/pinit.js', array(), JETPACK__VERSION, true ); |
187
|
|
|
return $content; |
188
|
|
|
} |
189
|
|
|
} |
190
|
|
|
|
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.