PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 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 All 508 releases
← All changes | extensions/blocks/pinterest/pinterest.php +254 -0 16.2-beta → 16.3 View file →
@@ -1,0 +1,254 @@
1 +<?php
2 +/**
3 + * Pinterest Block.
4 + *
5 + * Note: this block is no longer available to be added to new posts.
6 + * It is only kept available for existing posts with Pinterest blocks.
7 + * You can still embed Pinterest content using the embed method provided by WordPress itself.
8 + *
9 + * @since 8.0.0
10 + *
11 + * @package automattic/jetpack
12 + */
13 +
14 +namespace Automattic\Jetpack\Extensions\Pinterest;
15 +
16 +use Automattic\Jetpack\Blocks;
17 +use Automattic\Jetpack\Status\Request;
18 +use WP_Error;
19 +
20 +if ( ! defined( 'ABSPATH' ) ) {
21 + exit( 0 );
22 +}
23 +
24 +const URL_PATTERN = '#^https?://(?:www\.)?(?:[a-z]{2}\.)?pinterest\.[a-z.]+/pin/(?P<pin_id>[^/]+)/?#i'; // Taken from AMP plugin, originally from Jetpack.
25 +// This is the validate Pinterest URLs, converted from URL_REGEX in extensions/blocks/pinterest/index.js.
26 +const PINTEREST_URL_REGEX = '/^https?:\/\/(?:www\.)?(?:[a-z]{2}\.)?(?:pinterest\.[a-z.]+|pin\.it)\/([^\/]+)(\/[^\/]+)?/i';
27 +// This looks for matches in /foo/ of https://www.pinterest.ca/foo/.
28 +const REMAINING_URL_PATH_REGEX = '/^\/([^\/]+)\/?$/';
29 +// This looks for matches with /foo/bar/ of https://www.pinterest.ca/foo/bar/.
30 +const REMAINING_URL_PATH_WITH_SUBPATH_REGEX = '/^\/([^\/]+)\/([^\/]+)\/?$/';
31 +
32 +/**
33 + * Determines the Pinterest embed type from the URL.
34 + *
35 + * @param string $url the URL to check.
36 + * @return string The pin type. Empty string if it isn't a valid Pinterest URL.
37 + */
38 +function pin_type( $url ) {
39 + if ( null === $url || ! preg_match( PINTEREST_URL_REGEX, $url ) ) {
40 + return '';
41 + }
42 +
43 + $path = wp_parse_url( $url, PHP_URL_PATH );
44 +
45 + if ( ! $path ) {
46 + return '';
47 + }
48 +
49 + if ( str_starts_with( $path, '/pin/' ) ) {
50 + return 'embedPin';
51 + }
52 +
53 + if ( preg_match( REMAINING_URL_PATH_REGEX, $path ) ) {
54 + return 'embedUser';
55 + }
56 +
57 + if ( preg_match( REMAINING_URL_PATH_WITH_SUBPATH_REGEX, $path ) ) {
58 + return 'embedBoard';
59 + }
60 +
61 + return '';
62 +}
63 +
64 +/**
65 + * Registers the block for use in Gutenberg
66 + * This is done via an action so that we can disable
67 + * registration if we need to.
68 + */
69 +function register_block() {
70 + Blocks::jetpack_register_block(
71 + __DIR__,
72 + array( 'render_callback' => __NAMESPACE__ . '\load_assets' )
73 + );
74 +}
75 +add_action( 'init', __NAMESPACE__ . '\register_block' );
76 +
77 +/**
78 + * Fetch info for a Pin.
79 + *
80 + * This is using the same pin info API as AMP is using client-side in the amp-pinterest component.
81 + * Successful API responses are cached in a transient for 1 month. Unsuccessful responses are cached for 1 hour.
82 + *
83 + * @link https://github.com/ampproject/amphtml/blob/b5dea36e0b8bd012585d50839766a084f99a3685/extensions/amp-pinterest/0.1/pin-widget.js#L83-L97
84 + * @param string $pin_id Pin ID.
85 + * @return array|WP_Error Pin info or error on failure.
86 + */
87 +function fetch_pin_info( $pin_id ) {
88 + $transient_id = substr( "jetpack_pin_info_{$pin_id}", 0, 172 );
89 +
90 + $info = get_transient( $transient_id );
91 + if ( is_array( $info ) || is_wp_error( $info ) ) {
92 + return $info;
93 + }
94 +
95 + $pin_info_api_url = add_query_arg(
96 + array(
97 + 'pin_ids' => rawurlencode( $pin_id ),
98 + 'sub' => 'wwww',
99 + 'base_scheme' => 'https',
100 + ),
101 + 'https://widgets.pinterest.com/v3/pidgets/pins/info/'
102 + );
103 +
104 + $response = wp_remote_get( esc_url_raw( $pin_info_api_url ) );
105 + if ( is_wp_error( $response ) ) {
106 + set_transient( $transient_id, $response, HOUR_IN_SECONDS );
107 + return $response;
108 + }
109 +
110 + $error = null;
111 + $body = json_decode( wp_remote_retrieve_body( $response ), true );
112 + if ( ! is_array( $body ) || ! isset( $body['status'] ) ) {
113 + $error = new WP_Error( 'bad_json_response', '', compact( 'pin_id' ) );
114 + } elseif ( 'success' !== $body['status'] || ! isset( $body['data'][0] ) ) {
115 + $error = new WP_Error( 'unsuccessful_request', '', compact( 'pin_id' ) );
116 + } elseif ( ! isset( $body['data'][0]['images']['237x'] ) ) {
117 + // See <https://github.com/ampproject/amphtml/blob/b5dea36e0b8bd012585d50839766a084f99a3685/extensions/amp-pinterest/0.1/pin-widget.js#L106>.
118 + $error = new WP_Error( 'missing_required_image', '', compact( 'pin_id' ) );
119 + }
120 +
121 + if ( $error ) {
122 + set_transient( $transient_id, $error, HOUR_IN_SECONDS );
123 + return $error;
124 + } else {
125 + $data = $body['data'][0];
126 + set_transient( $transient_id, $data, MONTH_IN_SECONDS );
127 + return $data;
128 + }
129 +}
130 +
131 +/**
132 + * Render a Pin using the amp-pinterest component.
133 + *
134 + * This does not render boards or user profiles.
135 + *
136 + * Since AMP components need to be statically sized to be valid (so as to avoid layout shifting), there are quite a few
137 + * hard-coded numbers as taken from the CSS for the AMP component.
138 + *
139 + * @param array $attr Block attributes.
140 + * @return string Markup for <amp-pinterest>.
141 + */
142 +function render_amp_pin( $attr ) {
143 + $info = null;
144 + if ( preg_match( URL_PATTERN, $attr['url'], $matches ) ) {
145 + $info = fetch_pin_info( $matches['pin_id'] );
146 + }
147 +
148 + if ( is_array( $info ) ) {
149 + $image = $info['images']['237x'];
150 + $title = $info['rich_metadata']['title'] ?? null;
151 + $description = $info['rich_metadata']['description'] ?? null;
152 +
153 + // This placeholder will appear while waiting for the amp-pinterest component to initialize (or if it fails to initialize due to JS being disabled).
154 + $placeholder = sprintf(
155 + // The AMP_Img_Sanitizer will convert his to <amp-img> while also supplying `noscript > img` as fallback when JS is disabled.
156 + '<a href="%s" placeholder><img src="%s" alt="%s" layout="fill" object-fit="contain" object-position="top left"></a>',
157 + esc_url( $attr['url'] ),
158 + esc_url( $image['url'] ),
159 + esc_attr( $title )
160 + );
161 +
162 + $amp_padding = 5; // See <https://github.com/ampproject/amphtml/blob/b5dea36e0b8bd012585d50839766a084f99a3685/extensions/amp-pinterest/0.1/amp-pinterest.css#L269>.
163 + $amp_fixed_width = 237; // See <https://github.com/ampproject/amphtml/blob/b5dea36e0b8bd012585d50839766a084f99a3685/extensions/amp-pinterest/0.1/amp-pinterest.css#L270>.
164 + $pin_info_height = 60; // Minimum Obtained by measuring the height of the .-amp-pinterest-embed-pin-text element.
165 +
166 + // Add height based on how much description there is. There are roughly 30 characters on a line of description text.
167 + $has_description = false;
168 + if ( ! empty( $info['description'] ) ) {
169 + $desc_padding_top = 5; // See <https://github.com/ampproject/amphtml/blob/b5dea36e0b8bd012585d50839766a084f99a3685/extensions/amp-pinterest/0.1/amp-pinterest.css#L342>.
170 + $pin_info_height += $desc_padding_top;
171 +
172 + // Trim whitespace on description if there is any left, use to calculate the likely rows of text.
173 + $description = trim( $info['description'] );
174 + if ( strlen( $description ) > 0 ) {
175 + $has_description = true;
176 + $desc_line_height = 17; // See <https://github.com/ampproject/amphtml/blob/b5dea36e0b8bd012585d50839766a084f99a3685/extensions/amp-pinterest/0.1/amp-pinterest.css#L341>.
177 + $pin_info_height += ceil( strlen( $description ) / 30 ) * $desc_line_height;
178 + }
179 + }
180 +
181 + if ( ! empty( $info['repin_count'] ) ) {
182 + $pin_stats_height = 16; // See <https://github.com/ampproject/amphtml/blob/b5dea36e0b8bd012585d50839766a084f99a3685/extensions/amp-pinterest/0.1/amp-pinterest.css#L322>.
183 + $pin_info_height += $pin_stats_height;
184 + }
185 +
186 + // When Pin description is empty, make sure title and description from rich metadata are supplied for accessibility and discoverability.
187 + $title = $has_description ? '' : implode( "\n", array_filter( array( $title, $description ) ) );
188 +
189 + $amp_pinterest = sprintf(
190 + '<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>',
191 + esc_attr( 'line-height:1.5; font-size:21px' ), // Override styles from theme due to precise height calculations above.
192 + esc_url( $attr['url'] ),
193 + $amp_fixed_width + ( $amp_padding * 2 ),
194 + $image['height'] + $pin_info_height + ( $amp_padding * 2 ),
195 + esc_attr( $title ),
196 + $placeholder
197 + );
198 + } else {
199 + // Fallback embed when info is not available.
200 + $amp_pinterest = sprintf(
201 + '<amp-pinterest data-do="embedPin" data-url="%1$s" width="%2$d" height="%3$d">%4$s</amp-pinterest>',
202 + esc_url( $attr['url'] ),
203 + 450, // Fallback width.
204 + 750, // Fallback height.
205 + sprintf(
206 + '<a placeholder href="%s">%s</a>',
207 + esc_url( $attr['url'] ),
208 + esc_html( $attr['url'] )
209 + )
210 + );
211 + }
212 +
213 + return sprintf(
214 + '<div class="wp-block-jetpack-pinterest">%s</div>',
215 + $amp_pinterest
216 + );
217 +}
218 +
219 +/**
220 + * Pinterest block registration/dependency declaration.
221 + *
222 + * @param array $attr Array containing the Pinterest block attributes.
223 + * @param string $content String containing the Pinterest block content.
224 + *
225 + * @return string
226 + */
227 +function load_assets( $attr, $content ) {
228 + if ( ! Request::is_frontend() ) {
229 + return $content;
230 + }
231 + $attr['url'] ??= '';
232 + if ( Blocks::is_amp_request() ) {
233 + return render_amp_pin( $attr );
234 + } else {
235 + $url = $attr['url'];
236 + $type = pin_type( $url );
237 +
238 + if ( ! $type ) {
239 + return '';
240 + }
241 +
242 + wp_enqueue_script( 'pinterest-pinit', 'https://assets.pinterest.com/js/pinit.js', array(), JETPACK__VERSION, true );
243 + return sprintf(
244 + '
245 + <div class="%1$s">
246 + <a data-pin-do="%2$s" href="%3$s"></a>
247 + </div>
248 + ',
249 + esc_attr( Blocks::classes( Blocks::get_block_feature( __DIR__ ), $attr ) ),
250 + esc_attr( $type ),
251 + esc_url( $url )
252 + );
253 + }
254 +}