__NAMESPACE__ . '\render_block' )
);
}
add_action( 'init', __NAMESPACE__ . '\register_block' );
/**
* Compare 2 urls and return true if they likely correspond to the same resource.
* Ignore scheme, ports, query params and hashes and only compare hostname and pathname.
*
* @param string $url1 - First url used in comparison.
* @param string $url2 - Second url used in comparison.
*
* @return boolean
*/
function is_same_resource( $url1, $url2 ) {
$url1_parsed = wp_parse_url( $url1 );
$url2_parsed = wp_parse_url( $url2 );
return isset( $url1_parsed['host'] ) &&
isset( $url2_parsed['host'] ) &&
isset( $url1_parsed['path'] ) &&
isset( $url2_parsed['path'] ) &&
$url1_parsed['host'] === $url2_parsed['host'] &&
$url1_parsed['path'] === $url2_parsed['path'];
}
/**
* Enrich media files retrieved from the story block attributes
* with extra information we can retrieve from the media library.
*
* @param array $media_files - List of media, each as an array containing the media attributes.
*
* @return array $media_files
*/
function enrich_media_files( $media_files ) {
return array_filter(
array_map(
function ( $media_file ) {
if ( 'image' === $media_file['type'] ) {
return enrich_image_meta( $media_file );
}
// VideoPress videos can sometimes have type 'file', and mime 'video/videopress' or 'video/mp4'.
// Let's fix `type` for those.
if ( 'file' === $media_file['type'] && str_starts_with( $media_file['mime'], 'video' ) ) {
$media_file['type'] = 'video';
}
if ( 'video' !== $media_file['type'] ) { // we only support images and videos at this point.
return null;
}
return enrich_video_meta( $media_file );
},
$media_files
)
);
}
/**
* Enrich image information with extra data we can retrieve from the media library.
* Add missing `width`, `height`, `srcset`, `sizes`, `title`, `alt` and `caption` properties to the image.
*
* @param array $media_file - An array containing the media attributes for a specific image.
*
* @return array $media_file_enriched
*/
function enrich_image_meta( $media_file ) {
$attachment_id = isset( $media_file['id'] ) ? $media_file['id'] : null;
$image = wp_get_attachment_image_src( $attachment_id, 'full', false );
if ( ! $image ) {
return $media_file;
}
list( $src, $width, $height ) = $image;
// Bail if url stored in block attributes is different than the media library one for that id.
if ( isset( $media_file['url'] ) && ! is_same_resource( $media_file['url'], $src ) ) {
return $media_file;
}
$image_meta = wp_get_attachment_metadata( $attachment_id );
if ( ! is_array( $image_meta ) ) {
return $media_file;
}
$size_array = array( absint( $width ), absint( $height ) );
return array_merge(
$media_file,
array(
'width' => absint( $width ),
'height' => absint( $height ),
'srcset' => wp_calculate_image_srcset( $size_array, $src, $image_meta, $attachment_id ),
'sizes' => IMAGE_BREAKPOINTS,
'title' => get_the_title( $attachment_id ),
'alt' => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),
'caption' => wp_get_attachment_caption( $attachment_id ),
)
);
}
/**
* Enrich video information with extra data we can retrieve from the media library.
* Add missing `width`, `height`, `alt`, `url`, `title`, `caption` and `poster` properties to the image.
*
* @param array $media_file - An array containing the media attributes for a specific video.
*
* @return array $media_file_enriched
*/
function enrich_video_meta( $media_file ) {
$attachment_id = isset( $media_file['id'] ) ? $media_file['id'] : null;
$video_meta = wp_get_attachment_metadata( $attachment_id );
if ( ! $video_meta ) {
return $media_file;
}
$video_url = ! empty( $video_meta['original']['url'] ) ? $video_meta['original']['url'] : wp_get_attachment_url( $attachment_id );
// Set the poster attribute for the video tag if a poster image is available.
$poster_url = null;
if ( ! empty( $video_meta['videopress']['poster'] ) ) {
$poster_url = $video_meta['videopress']['poster'];
} elseif ( ! empty( $video_meta['thumb'] ) ) {
$poster_url = str_replace( wp_basename( $video_url ), $video_meta['thumb'], $video_url );
}
if ( $poster_url ) {
// Use the global content width for thumbnail resize so we match the `w=` query parameter
// that jetpack is going to add when "Enable site accelerator" is enabled for images.
$content_width = (int) Jetpack::get_content_width();
$new_width = $content_width > 0 ? $content_width : EMBED_SIZE[0];
$poster_url = add_query_arg( 'w', $new_width, $poster_url );
}
return array_merge(
$media_file,
array(
'width' => absint( ! empty( $video_meta['width'] ) ? $video_meta['width'] : $media_file['width'] ),
'height' => absint( ! empty( $video_meta['height'] ) ? $video_meta['height'] : $media_file['height'] ),
'alt' => ! empty( $video_meta['videopress']['description'] ) ? $video_meta['videopress']['description'] : $media_file['alt'],
'url' => $video_url,
'title' => get_the_title( $attachment_id ),
'caption' => wp_get_attachment_caption( $attachment_id ),
'poster' => $poster_url,
)
);
}
/**
* Render an image inside a slide
*
* @param array $media - Image information.
*
* @return string
*/
function render_image( $media ) {
if ( empty( $media['id'] ) || empty( $media['url'] ) ) {
return __( 'Error retrieving media', 'jetpack' );
}
$image = wp_get_attachment_image_src( $media['id'], 'full', false );
if ( $image ) {
list( $src, $width, $height ) = $image;
}
// if image does not match.
if ( ! $image || isset( $media['url'] ) && ! is_same_resource( $media['url'], $src ) ) {
$width = isset( $media['width'] ) ? $media['width'] : null;
$height = isset( $media['height'] ) ? $media['height'] : null;
$title = isset( $media['title'] ) ? $media['title'] : '';
$alt = isset( $media['alt'] ) ? $media['alt'] : '';
return sprintf(
'',
esc_attr( $title ),
esc_attr( $alt ),
$width && $height ? get_image_crop_class( $width, $height ) : '',
esc_attr( $media['url'] )
);
}
$crop_class = get_image_crop_class( $width, $height );
// need to specify the size of the embed so it picks an image that is large enough for the `src` attribute
// `sizes` is optimized for 1080x1920 (9:16) images
// Note that the Story block does not have thumbnail support, it will load the right
// image based on the viewport size only.
return wp_get_attachment_image(
$media['id'],
EMBED_SIZE,
false,
array(
'class' => sprintf( 'wp-story-image wp-image-%d %s', $media['id'], $crop_class ),
'sizes' => IMAGE_BREAKPOINTS,
'title' => get_the_title( $media['id'] ),
)
);
}
/**
* Return the css crop class if image width and height requires it
*
* @param int $width - Image width.
* @param int $height - Image height.
*
* @return string The CSS class which will display a cropped image
*/
function get_image_crop_class( $width, $height ) {
$crop_class = '';
$width = (int) $width;
$height = (int) $height;
if ( ! $width || ! $height ) {
return $crop_class;
}
$media_aspect_ratio = $width / $height;
$target_aspect_ratio = EMBED_SIZE[0] / EMBED_SIZE[1];
if ( $media_aspect_ratio >= $target_aspect_ratio ) {
// image wider than canvas.
$media_too_wide_to_crop = $media_aspect_ratio > $target_aspect_ratio / ( 1 - CROP_UP_TO );
if ( ! $media_too_wide_to_crop ) {
$crop_class = 'wp-story-crop-wide';
}
} else {
// image narrower than canvas.
$media_too_narrow_to_crop = $media_aspect_ratio < $target_aspect_ratio * ( 1 - CROP_UP_TO );
if ( ! $media_too_narrow_to_crop ) {
$crop_class = 'wp-story-crop-narrow';
}
}
return $crop_class;
}
/**
* Returns a URL for the site icon.
*
* @param int $size - Size for (square) sitei icon.
* @param string $fallback - Fallback URL to use if no site icon is found.
*
* @return string
*/
function get_blavatar_or_site_icon_url( $size, $fallback ) {
$image_array = Jetpack_PostImages::from_blavatar( get_the_ID(), $size );
if ( ! empty( $image_array ) ) {
return $image_array[0]['src'];
} else {
return $fallback;
}
}
/**
* Render a video inside a slide
*
* @param array $media - Video information.
*
* @return string
*/
function render_video( $media ) {
if ( empty( $media['id'] ) || empty( $media['mime'] ) || empty( $media['url'] ) ) {
return __( 'Error retrieving media', 'jetpack' );
}
if ( ! empty( $media['poster'] ) ) {
return render_image(
array_merge(
$media,
array(
'type' => 'image',
'url' => $media['poster'],
)
)
);
}
return sprintf(
'',
esc_attr( get_the_title( $media['id'] ) ),
esc_attr( $media['mime'] ),
absint( $media['id'] ),
esc_attr( $media['url'] )
);
}
/**
* Pick a thumbnail to render a static/embedded story
*
* @param array $media_files - list of Media files.
*
* @return string
*/
function render_static_slide( $media_files ) {
$media_template = '';
if ( empty( $media_files ) ) {
return '';
}
// find an image to showcase.
foreach ( $media_files as $media ) {
switch ( $media['type'] ) {
case 'image':
$media_template = render_image( $media );
break 2;
case 'video':
// ignore videos without a poster image.
if ( empty( $media['poster'] ) ) {
continue 2;
}
$media_template = render_video( $media );
break 2;
}
}
// if no "static" media was found for the thumbnail try to render a video tag without poster.
if ( empty( $media_template ) && ! empty( $media_files ) ) {
$media_template = render_video( $media_files[0] );
}
return sprintf(
'