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