PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.7
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.7
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 13.7.2 13.8.3 All 506 releases
← All changes | extensions/blocks/story/story.php +10 -459 12.5.2 → 16.3-a.7 View file →
@@ -9,20 +9,13 @@
9 9
10 10 namespace Automattic\Jetpack\Extensions\Story;
11 11
12 12 use Automattic\Jetpack\Blocks;
13 -use Jetpack;
14 -use Jetpack_Gutenberg;
15 -use Jetpack_PostImages;
16 13
17 -const FEATURE_NAME = 'story';
18 -const BLOCK_NAME = 'jetpack/' . FEATURE_NAME;
14 +if ( ! defined( 'ABSPATH' ) ) {
15 + exit( 0 );
16 +}
19 17
20 -const EMBED_SIZE = array( 360, 640 ); // twice as many pixels for retina displays.
21 -const CROP_UP_TO = 0.2;
22 -const MAX_BULLETS = 7;
23 -const IMAGE_BREAKPOINTS = '(max-width: 460px) 576w, (max-width: 614px) 768w, 120vw'; // 120vw to match the 20% CROP_UP_TO ratio
24 -
25 18 /**
26 19 * Registers the block for use in Gutenberg
27 20 * This is done via an action so that we can disable
28 21 * registration if we need to.
@@ -28,9 +21,9 @@
28 21 * registration if we need to.
29 22 */
30 23 function register_block() {
31 24 Blocks::jetpack_register_block(
32 - BLOCK_NAME,
25 + __DIR__,
33 26 array( 'render_callback' => __NAMESPACE__ . '\render_block' )
34 27 );
35 28 }
36 29 add_action( 'init', __NAMESPACE__ . '\register_block' );
@@ -35,459 +28,17 @@
35 28 }
36 29 add_action( 'init', __NAMESPACE__ . '\register_block' );
37 30
38 31 /**
39 - * Compare 2 urls and return true if they likely correspond to the same resource.
40 - * Ignore scheme, ports, query params and hashes and only compare hostname and pathname.
32 + * Render story block
41 33 *
42 - * @param string $url1 - First url used in comparison.
43 - * @param string $url2 - Second url used in comparison.
34 + * The render implementation lives in render.php and is only loaded when the
35 + * block is actually rendered, keeping it out of the eager front-end path.
44 36 *
45 - * @returns boolean
46 - */
47 -function is_same_resource( $url1, $url2 ) {
48 - $url1_parsed = wp_parse_url( $url1 );
49 - $url2_parsed = wp_parse_url( $url2 );
50 - return isset( $url1_parsed['host'] ) &&
51 - isset( $url2_parsed['host'] ) &&
52 - isset( $url1_parsed['path'] ) &&
53 - isset( $url2_parsed['path'] ) &&
54 - $url1_parsed['host'] === $url2_parsed['host'] &&
55 - $url1_parsed['path'] === $url2_parsed['path'];
56 -}
57 -
58 -/**
59 - * Enrich media files retrieved from the story block attributes
60 - * with extra information we can retrieve from the media library.
61 - *
62 - * @param array $media_files - List of media, each as an array containing the media attributes.
63 - *
64 - * @returns array $media_files
65 - */
66 -function enrich_media_files( $media_files ) {
67 - return array_filter(
68 - array_map(
69 - function ( $media_file ) {
70 - if ( 'image' === $media_file['type'] ) {
71 - return enrich_image_meta( $media_file );
72 - }
73 - // VideoPress videos can sometimes have type 'file', and mime 'video/videopress' or 'video/mp4'.
74 - // Let's fix `type` for those.
75 - if ( 'file' === $media_file['type'] && 'video' === substr( $media_file['mime'], 0, 5 ) ) {
76 - $media_file['type'] = 'video';
77 - }
78 - if ( 'video' !== $media_file['type'] ) { // we only support images and videos at this point.
79 - return null;
80 - }
81 - return enrich_video_meta( $media_file );
82 - },
83 - $media_files
84 - )
85 - );
86 -}
87 -
88 -/**
89 - * Enrich image information with extra data we can retrieve from the media library.
90 - * Add missing `width`, `height`, `srcset`, `sizes`, `title`, `alt` and `caption` properties to the image.
91 - *
92 - * @param array $media_file - An array containing the media attributes for a specific image.
93 - *
94 - * @returns array $media_file_enriched
95 - */
96 -function enrich_image_meta( $media_file ) {
97 - $attachment_id = isset( $media_file['id'] ) ? $media_file['id'] : null;
98 - $image = wp_get_attachment_image_src( $attachment_id, 'full', false );
99 - if ( ! $image ) {
100 - return $media_file;
101 - }
102 - list( $src, $width, $height ) = $image;
103 - // Bail if url stored in block attributes is different than the media library one for that id.
104 - if ( isset( $media_file['url'] ) && ! is_same_resource( $media_file['url'], $src ) ) {
105 - return $media_file;
106 - }
107 - $image_meta = wp_get_attachment_metadata( $attachment_id );
108 - if ( ! is_array( $image_meta ) ) {
109 - return $media_file;
110 - }
111 - $size_array = array( absint( $width ), absint( $height ) );
112 - return array_merge(
113 - $media_file,
114 - array(
115 - 'width' => absint( $width ),
116 - 'height' => absint( $height ),
117 - 'srcset' => wp_calculate_image_srcset( $size_array, $src, $image_meta, $attachment_id ),
118 - 'sizes' => IMAGE_BREAKPOINTS,
119 - 'title' => get_the_title( $attachment_id ),
120 - 'alt' => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),
121 - 'caption' => wp_get_attachment_caption( $attachment_id ),
122 - )
123 - );
124 -}
125 -
126 -/**
127 - * Enrich video information with extra data we can retrieve from the media library.
128 - * Add missing `width`, `height`, `alt`, `url`, `title`, `caption` and `poster` properties to the image.
129 - *
130 - * @param array $media_file - An array containing the media attributes for a specific video.
131 - *
132 - * @returns array $media_file_enriched
133 - */
134 -function enrich_video_meta( $media_file ) {
135 - $attachment_id = isset( $media_file['id'] ) ? $media_file['id'] : null;
136 - $video_meta = wp_get_attachment_metadata( $attachment_id );
137 - if ( ! $video_meta ) {
138 - return $media_file;
139 - }
140 -
141 - $video_url = ! empty( $video_meta['original']['url'] ) ? $video_meta['original']['url'] : wp_get_attachment_url( $attachment_id );
142 -
143 - // Set the poster attribute for the video tag if a poster image is available.
144 - $poster_url = null;
145 - if ( ! empty( $video_meta['videopress']['poster'] ) ) {
146 - $poster_url = $video_meta['videopress']['poster'];
147 - } elseif ( ! empty( $video_meta['thumb'] ) ) {
148 - $poster_url = str_replace( wp_basename( $video_url ), $video_meta['thumb'], $video_url );
149 - }
150 -
151 - if ( $poster_url ) {
152 - // Use the global content width for thumbnail resize so we match the `w=` query parameter
153 - // that jetpack is going to add when "Enable site accelerator" is enabled for images.
154 - $content_width = (int) Jetpack::get_content_width();
155 - $new_width = $content_width > 0 ? $content_width : EMBED_SIZE[0];
156 - $poster_url = add_query_arg( 'w', $new_width, $poster_url );
157 - }
158 -
159 - return array_merge(
160 - $media_file,
161 - array(
162 - 'width' => absint( ! empty( $video_meta['width'] ) ? $video_meta['width'] : $media_file['width'] ),
163 - 'height' => absint( ! empty( $video_meta['height'] ) ? $video_meta['height'] : $media_file['height'] ),
164 - 'alt' => ! empty( $video_meta['videopress']['description'] ) ? $video_meta['videopress']['description'] : $media_file['alt'],
165 - 'url' => $video_url,
166 - 'title' => get_the_title( $attachment_id ),
167 - 'caption' => wp_get_attachment_caption( $attachment_id ),
168 - 'poster' => $poster_url,
169 - )
170 - );
171 -}
172 -
173 -/**
174 - * Render an image inside a slide
175 - *
176 - * @param array $media - Image information.
177 - *
178 - * @returns string
179 - */
180 -function render_image( $media ) {
181 - if ( empty( $media['id'] ) || empty( $media['url'] ) ) {
182 - return __( 'Error retrieving media', 'jetpack' );
183 - }
184 - $image = wp_get_attachment_image_src( $media['id'], 'full', false );
185 - if ( $image ) {
186 - list( $src, $width, $height ) = $image;
187 - }
188 -
189 - // if image does not match.
190 - if ( ! $image || isset( $media['url'] ) && ! is_same_resource( $media['url'], $src ) ) {
191 - $width = isset( $media['width'] ) ? $media['width'] : null;
192 - $height = isset( $media['height'] ) ? $media['height'] : null;
193 - $title = isset( $media['title'] ) ? $media['title'] : '';
194 - $alt = isset( $media['alt'] ) ? $media['alt'] : '';
195 - return sprintf(
196 - '<img
197 - title="%1$s"
198 - alt="%2$s"
199 - class="wp-block-jetpack-story_image wp-story-image %3$s"
200 - src="%4$s"
201 - />',
202 - esc_attr( $title ),
203 - esc_attr( $alt ),
204 - $width && $height ? get_image_crop_class( $width, $height ) : '',
205 - esc_attr( $media['url'] )
206 - );
207 - }
208 -
209 - $crop_class = get_image_crop_class( $width, $height );
210 - // need to specify the size of the embed so it picks an image that is large enough for the `src` attribute
211 - // `sizes` is optimized for 1080x1920 (9:16) images
212 - // Note that the Story block does not have thumbnail support, it will load the right
213 - // image based on the viewport size only.
214 - return wp_get_attachment_image(
215 - $media['id'],
216 - EMBED_SIZE,
217 - false,
218 - array(
219 - 'class' => sprintf( 'wp-story-image wp-image-%d %s', $media['id'], $crop_class ),
220 - 'sizes' => IMAGE_BREAKPOINTS,
221 - 'title' => get_the_title( $media['id'] ),
222 - )
223 - );
224 -}
225 -
226 -/**
227 - * Return the css crop class if image width and height requires it
228 - *
229 - * @param int $width - Image width.
230 - * @param int $height - Image height.
231 - *
232 - * @returns string The CSS class which will display a cropped image
233 - */
234 -function get_image_crop_class( $width, $height ) {
235 - $crop_class = '';
236 - $media_aspect_ratio = $width / $height;
237 - $target_aspect_ratio = EMBED_SIZE[0] / EMBED_SIZE[1];
238 - if ( $media_aspect_ratio >= $target_aspect_ratio ) {
239 - // image wider than canvas.
240 - $media_too_wide_to_crop = $media_aspect_ratio > $target_aspect_ratio / ( 1 - CROP_UP_TO );
241 - if ( ! $media_too_wide_to_crop ) {
242 - $crop_class = 'wp-story-crop-wide';
243 - }
244 - } else {
245 - // image narrower than canvas.
246 - $media_too_narrow_to_crop = $media_aspect_ratio < $target_aspect_ratio * ( 1 - CROP_UP_TO );
247 - if ( ! $media_too_narrow_to_crop ) {
248 - $crop_class = 'wp-story-crop-narrow';
249 - }
250 - }
251 - return $crop_class;
252 -}
253 -
254 -/**
255 - * Returns a URL for the site icon.
256 - *
257 - * @param int $size - Size for (square) sitei icon.
258 - * @param string $fallback - Fallback URL to use if no site icon is found.
259 - *
260 - * @returns string
261 - */
262 -function get_blavatar_or_site_icon_url( $size, $fallback ) {
263 - $image_array = Jetpack_PostImages::from_blavatar( get_the_ID(), $size );
264 - if ( ! empty( $image_array ) ) {
265 - return $image_array[0]['src'];
266 - } else {
267 - return $fallback;
268 - }
269 -}
270 -
271 -/**
272 - * Render a video inside a slide
273 - *
274 - * @param array $media - Video information.
275 - *
276 - * @returns string
277 - */
278 -function render_video( $media ) {
279 - if ( empty( $media['id'] ) || empty( $media['mime'] ) || empty( $media['url'] ) ) {
280 - return __( 'Error retrieving media', 'jetpack' );
281 - }
282 -
283 - if ( ! empty( $media['poster'] ) ) {
284 - return render_image(
285 - array_merge(
286 - $media,
287 - array(
288 - 'type' => 'image',
289 - 'url' => $media['poster'],
290 - )
291 - )
292 - );
293 - }
294 -
295 - return sprintf(
296 - '<video
297 - title="%1$s"
298 - type="%2$s"
299 - class="wp-story-video intrinsic-ignore wp-video-%3$s"
300 - data-id="%3$s"
301 - src="%4$s">
302 - </video>',
303 - esc_attr( get_the_title( $media['id'] ) ),
304 - esc_attr( $media['mime'] ),
305 - $media['id'],
306 - esc_attr( $media['url'] )
307 - );
308 -}
309 -
310 -/**
311 - * Pick a thumbnail to render a static/embedded story
312 - *
313 - * @param array $media_files - list of Media files.
314 - *
315 - * @returns string
316 - */
317 -function render_static_slide( $media_files ) {
318 - $media_template = '';
319 - if ( empty( $media_files ) ) {
320 - return '';
321 - }
322 -
323 - // find an image to showcase.
324 - foreach ( $media_files as $media ) {
325 - switch ( $media['type'] ) {
326 - case 'image':
327 - $media_template = render_image( $media );
328 - break 2;
329 - case 'video':
330 - // ignore videos without a poster image.
331 - if ( empty( $media['poster'] ) ) {
332 - continue 2;
333 - }
334 - $media_template = render_video( $media );
335 - break 2;
336 - }
337 - }
338 -
339 - // if no "static" media was found for the thumbnail try to render a video tag without poster.
340 - if ( empty( $media_template ) && ! empty( $media_files ) ) {
341 - $media_template = render_video( $media_files[0] );
342 - }
343 -
344 - return sprintf(
345 - '<div class="wp-story-slide" style="display: block;">
346 - <figure>%s</figure>
347 - </div>',
348 - $media_template
349 - );
350 -}
351 -
352 -/**
353 - * Render the top right icon on top of the story embed
354 - *
355 - * @param array $settings - The block settings.
356 - *
357 - * @returns string
358 - */
359 -function render_top_right_icon( $settings ) {
360 - $show_slide_count = isset( $settings['showSlideCount'] ) ? $settings['showSlideCount'] : false;
361 - $slide_count = isset( $settings['slides'] ) ? count( $settings['slides'] ) : 0;
362 - if ( $show_slide_count ) {
363 - // Render the story block icon along with the slide count.
364 - return sprintf(
365 - '<div class="wp-story-embed-icon">
366 - <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" role="img" aria-hidden="true" focusable="false">
367 - <path d="M0 0h24v24H0z" fill="none"></path>
368 - <path fill-rule="evenodd" clip-rule="evenodd" d="M6 3H14V17H6L6 3ZM4 3C4 1.89543 4.89543 1 6 1H14C15.1046 1 16 1.89543 16 3V17C16 18.1046 15.1046 19 14 19H6C4.89543 19 4 18.1046 4 17V3ZM18 5C19.1046 5 20 5.89543 20 7V21C20 22.1046 19.1046 23 18 23H10C8.89543 23 8 22.1046 8 21H18V5Z"></path>
369 - </svg>
370 - <span>%d</span>
371 - </div>',
372 - $slide_count
373 - );
374 - } else {
375 - // Render the Fullscreen Gridicon.
376 - return (
377 - '<div class="wp-story-embed-icon-expand">
378 - <svg class="gridicon gridicons-fullscreen" role="img" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
379 - <g>
380 - <path d="M21 3v6h-2V6.41l-3.29 3.3-1.42-1.42L17.59 5H15V3zM3 3v6h2V6.41l3.29 3.3 1.42-1.42L6.41 5H9V3zm18 18v-6h-2v2.59l-3.29-3.29-1.41 1.41L17.59 19H15v2zM9 21v-2H6.41l3.29-3.29-1.41-1.42L5 17.59V15H3v6z"></path>
381 - </g>
382 - </svg>
383 - </div>'
384 - );
385 - }
386 -}
387 -
388 -/**
389 - * Render a pagination bullet
390 - *
391 - * @param int $slide_index - The slide index it corresponds to.
392 - * @param string $class_name - Optional css class name(s) to customize the bullet element.
393 - *
394 - * @returns string
395 - */
396 -function render_pagination_bullet( $slide_index, $class_name = '' ) {
397 - return sprintf(
398 - '<div class="wp-story-pagination-bullet %s" aria-label="%s">
399 - <div class="wp-story-pagination-bullet-bar"></div>
400 - </div>',
401 - esc_attr( $class_name ),
402 - /* translators: %d is the slide number (1, 2, 3...) */
403 - sprintf( __( 'Go to slide %d', 'jetpack' ), $slide_index )
404 - );
405 -}
406 -
407 -/**
408 - * Render pagination on top of the story embed
409 - *
410 - * @param array $settings - The block settings.
411 - *
412 - * @returns string
413 - */
414 -function render_pagination( $settings ) {
415 - $show_slide_count = isset( $settings['showSlideCount'] ) ? $settings['showSlideCount'] : false;
416 - if ( $show_slide_count ) {
417 - return '';
418 - }
419 - $slide_count = isset( $settings['slides'] ) ? count( $settings['slides'] ) : 0;
420 - $bullet_count = min( $slide_count, MAX_BULLETS );
421 - $bullet_ellipsis = $slide_count > $bullet_count
422 - ? render_pagination_bullet( $bullet_count + 1, 'wp-story-pagination-ellipsis' )
423 - : '';
424 - return sprintf(
425 - '<div class="wp-story-pagination wp-story-pagination-bullets">
426 - %s
427 - </div>',
428 - implode( "\n", array_map( __NAMESPACE__ . '\render_pagination_bullet', range( 1, $bullet_count ) ) ) . $bullet_ellipsis
429 - );
430 -}
431 -
432 -/**
433 - * Render story block
434 - *
435 37 * @param array $attributes - Block attributes.
436 38 *
437 - * @returns string
39 + * @return string
438 40 */
439 41 function render_block( $attributes ) {
440 - // Let's use a counter to have a different id for each story rendered in the same context.
441 - static $story_block_counter = 0;
442 -
443 - Jetpack_Gutenberg::load_assets_as_required( FEATURE_NAME );
444 -
445 - $media_files = isset( $attributes['mediaFiles'] ) ? enrich_media_files( $attributes['mediaFiles'] ) : array();
446 - $settings_from_attributes = isset( $attributes['settings'] ) ? $attributes['settings'] : array();
447 -
448 - $settings = array_merge(
449 - $settings_from_attributes,
450 - array(
451 - 'slides' => $media_files,
452 - )
453 - );
454 -
455 - return sprintf(
456 - '<div class="%1$s" data-id="%2$s" data-settings="%3$s">
457 - <div class="wp-story-app">
458 - <div class="wp-story-display-contents" style="display: contents;">
459 - <a class="wp-story-container" href="%4$s" title="%5$s">
460 - <div class="wp-story-meta">
461 - <div class="wp-story-icon">
462 - <img alt="%6$s" src="%7$s" width="40" height="40">
463 - </div>
464 - <div>
465 - <div class="wp-story-title">
466 - %8$s
467 - </div>
468 - </div>
469 - </div>
470 - <div class="wp-story-wrapper">
471 - %9$s
472 - </div>
473 - <div class="wp-story-overlay">
474 - %10$s
475 - </div>
476 - %11$s
477 - </a>
478 - </div>
479 - </div>
480 - </div>',
481 - esc_attr( Blocks::classes( FEATURE_NAME, $attributes, array( 'wp-story', 'aligncenter' ) ) ),
482 - esc_attr( 'wp-story-' . get_the_ID() . '-' . strval( ++$story_block_counter ) ),
483 - filter_var( wp_json_encode( $settings ), FILTER_SANITIZE_SPECIAL_CHARS ),
484 - get_permalink() . '?wp-story-load-in-fullscreen=true&amp;wp-story-play-on-load=true',
485 - __( 'Play story in new tab', 'jetpack' ),
486 - __( 'Site icon', 'jetpack' ),
487 - esc_attr( get_blavatar_or_site_icon_url( 80, includes_url( 'images/w-logo-blue.png' ) ) ),
488 - esc_html( get_the_title() ),
489 - render_static_slide( $media_files ),
490 - render_top_right_icon( $settings ),
491 - render_pagination( $settings )
492 - );
42 + require_once __DIR__ . '/render.php';
43 + return render( $attributes );
493 44 }