PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 12.6.4
Jetpack – WP Security, Backup, Speed, & Growth v12.6.4
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 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / extensions / blocks / story / story.php
jetpack / extensions / blocks / story Last commit date
story.php 2 years ago
story.php
494 lines
1 <?php
2 /**
3 * Story Block.
4 *
5 * @since 8.6.1
6 *
7 * @package automattic/jetpack
8 */
9
10 namespace Automattic\Jetpack\Extensions\Story;
11
12 use Automattic\Jetpack\Blocks;
13 use Jetpack;
14 use Jetpack_Gutenberg;
15 use Jetpack_PostImages;
16
17 const FEATURE_NAME = 'story';
18 const BLOCK_NAME = 'jetpack/' . FEATURE_NAME;
19
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 /**
26 * Registers the block for use in Gutenberg
27 * This is done via an action so that we can disable
28 * registration if we need to.
29 */
30 function register_block() {
31 Blocks::jetpack_register_block(
32 BLOCK_NAME,
33 array( 'render_callback' => __NAMESPACE__ . '\render_block' )
34 );
35 }
36 add_action( 'init', __NAMESPACE__ . '\register_block' );
37
38 /**
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.
41 *
42 * @param string $url1 - First url used in comparison.
43 * @param string $url2 - Second url used in comparison.
44 *
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$d"
301 src="%4$s">
302 </video>',
303 esc_attr( get_the_title( $media['id'] ) ),
304 esc_attr( $media['mime'] ),
305 absint( $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 * @param array $attributes - Block attributes.
436 *
437 * @returns string
438 */
439 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 );
493 }
494