You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

334 lines
9.8 KiB

<?php
/**
* Class AMP_Playlist_Embed_Handler
*
* @package AMP
* @since 0.7
*/
/**
* Class AMP_Playlist_Embed_Handler
*
* Creates AMP-compatible markup for the WordPress 'playlist' shortcode.
*
* @package AMP
*/
class AMP_Playlist_Embed_Handler extends AMP_Base_Embed_Handler {
/**
* The tag of the shortcode.
*
* @var string
*/
const SHORTCODE = 'playlist';
/**
* The default height of the thumbnail image for 'audio' playlist tracks.
*
* @var int
*/
const DEFAULT_THUMB_HEIGHT = 64;
/**
* The default width of the thumbnail image for 'audio' playlist tracks.
*
* @var int
*/
const DEFAULT_THUMB_WIDTH = 48;
/**
* The max width of the audio thumbnail image.
*
* This corresponds to the max-width in wp-mediaelement.css:
* .wp-playlist .wp-playlist-current-item img
*
* @var int
*/
const THUMB_MAX_WIDTH = 60;
/**
* The height of the carousel.
*
* @var int
*/
const CAROUSEL_HEIGHT = 160;
/**
* The pattern to get the playlist data.
*
* @var string
*/
const PLAYLIST_REGEX = ':<script type="application/json" class="wp-playlist-script">(.+?)</script>:s';
/**
* The ID of individual playlist.
*
* @var int
*/
public static $playlist_id = 0;
/**
* The removed shortcode callback.
*
* @var callable
*/
public $removed_shortcode_callback;
/**
* Registers the playlist shortcode.
*
* @global array $shortcode_tags
* @return void
*/
public function register_embed() {
global $shortcode_tags;
if ( shortcode_exists( self::SHORTCODE ) ) {
$this->removed_shortcode_callback = $shortcode_tags[ self::SHORTCODE ];
}
add_shortcode( self::SHORTCODE, [ $this, 'shortcode' ] );
remove_action( 'wp_playlist_scripts', 'wp_playlist_scripts' );
}
/**
* Unregisters the playlist shortcode.
*
* @return void
*/
public function unregister_embed() {
if ( $this->removed_shortcode_callback ) {
add_shortcode( self::SHORTCODE, $this->removed_shortcode_callback );
$this->removed_shortcode_callback = null;
}
add_action( 'wp_playlist_scripts', 'wp_playlist_scripts' );
}
/**
* Enqueues the playlist styling.
*
* @return void
*/
public function enqueue_styles() {
wp_enqueue_style(
'amp-playlist-shortcode',
amp_get_asset_url( 'css/amp-playlist-shortcode.css' ),
[ 'wp-mediaelement' ],
AMP__VERSION
);
wp_styles()->add_data( 'amp-playlist-shortcode', 'rtl', 'replace' );
}
/**
* Gets AMP-compliant markup for the playlist shortcode.
*
* Uses the JSON that wp_playlist_shortcode() produces.
* Gets the markup, based on the type of playlist.
*
* @param array $attr The playlist attributes.
* @return string Playlist shortcode markup.
*/
public function shortcode( $attr ) {
$data = $this->get_data( $attr );
if ( isset( $data['type'] ) && ( 'audio' === $data['type'] ) ) {
return $this->audio_playlist( $data );
}
if ( isset( $data['type'] ) && ( 'video' === $data['type'] ) ) {
return $this->video_playlist( $data );
}
return '';
}
/**
* Gets an AMP-compliant audio playlist.
*
* @param array $data Data.
* @return string Playlist shortcode markup, or an empty string.
*/
public function audio_playlist( $data ) {
if ( ! isset( $data['tracks'] ) ) {
return '';
}
self::$playlist_id++;
$container_id = 'wpPlaylist' . self::$playlist_id . 'Carousel';
$state_id = 'wpPlaylist' . self::$playlist_id;
$amp_state = [
'selectedIndex' => 0,
];
$this->enqueue_styles();
ob_start();
?>
<div class="wp-playlist wp-audio-playlist wp-playlist-light">
<amp-state id="<?php echo esc_attr( $state_id ); ?>">
<script type="application/json"><?php echo wp_json_encode( $amp_state ); ?></script>
</amp-state>
<amp-carousel id="<?php echo esc_attr( $container_id ); ?>" [slide]="<?php echo esc_attr( $state_id . '.selectedIndex' ); ?>" height="<?php echo esc_attr( self::CAROUSEL_HEIGHT ); ?>" width="auto" type="slides">
<?php
foreach ( $data['tracks'] as $track ) :
$title = $this->get_title( $track );
$image_url = isset( $track['thumb']['src'] ) ? $track['thumb']['src'] : '';
$dimensions = $this->get_thumb_dimensions( $track );
?>
<div>
<div class="wp-playlist-current-item">
<?php if ( $image_url ) : ?>
<amp-img src="<?php echo esc_url( $image_url ); ?>" height="<?php echo esc_attr( $dimensions['height'] ); ?>" width="<?php echo esc_attr( $dimensions['width'] ); ?>"></amp-img>
<?php endif; ?>
<div class="wp-playlist-caption">
<span class="wp-playlist-item-meta wp-playlist-item-title"><?php echo esc_html( $title ); ?></span>
</div>
</div>
<amp-audio width="auto" height="50" src="<?php echo esc_url( $track['src'] ); ?>"></amp-audio>
</div>
<?php endforeach; ?>
</amp-carousel>
<?php $this->print_tracks( $state_id, $data['tracks'] ); ?>
</div>
<?php
return ob_get_clean();
}
/**
* Gets an AMP-compliant video playlist.
*
* This uses similar markup to the native playlist shortcode output.
* So the styles from wp-mediaelement.min.css will apply to it.
*
* @global int $content_width
* @param array $data Data.
* @return string $video_playlist Markup for the video playlist.
*/
public function video_playlist( $data ) {
global $content_width;
if ( ! isset( $data['tracks'][0]['src'] ) ) {
return '';
}
self::$playlist_id++;
$state_id = 'wpPlaylist' . self::$playlist_id;
$amp_state = [
'selectedIndex' => 0,
];
foreach ( $data['tracks'] as $index => $track ) {
$amp_state[ $index ] = [
'videoUrl' => $track['src'],
'thumb' => isset( $track['thumb']['src'] ) ? $track['thumb']['src'] : '',
];
}
$dimensions = isset( $data['tracks'][0]['dimensions']['resized'] ) ? $data['tracks'][0]['dimensions']['resized'] : null;
$width = isset( $dimensions['width'] ) ? $dimensions['width'] : $content_width;
$height = isset( $dimensions['height'] ) ? $dimensions['height'] : null;
$src_bound = sprintf( '%s[%s.selectedIndex].videoUrl', $state_id, $state_id );
$this->enqueue_styles();
ob_start();
?>
<div class="wp-playlist wp-video-playlist wp-playlist-light">
<amp-state id="<?php echo esc_attr( $state_id ); ?>">
<script type="application/json"><?php echo wp_json_encode( $amp_state ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></script>
</amp-state>
<amp-video id="amp-video" src="<?php echo esc_url( $data['tracks'][0]['src'] ); ?>" [src]="<?php echo esc_attr( $src_bound ); ?>" width="<?php echo esc_attr( $width ); ?>" height="<?php echo esc_attr( $height ); ?>" controls></amp-video>
<?php $this->print_tracks( $state_id, $data['tracks'] ); ?>
</div>
<?php
return ob_get_clean(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* Gets the thumbnail image dimensions, including height and width.
*
* If the width is higher than the maximum width,
* reduces it to the maximum width.
* And it proportionally reduces the height.
*
* @param array $track The data for the track.
* @return array {
* Dimensions.
*
* @type int $height Image height.
* @type int $width Image width.
* }
*/
public function get_thumb_dimensions( $track ) {
$original_height = isset( $track['thumb']['height'] ) ? (int) $track['thumb']['height'] : self::DEFAULT_THUMB_HEIGHT;
$original_width = isset( $track['thumb']['width'] ) ? (int) $track['thumb']['width'] : self::DEFAULT_THUMB_WIDTH;
if ( $original_width > self::THUMB_MAX_WIDTH ) {
$ratio = $original_width / self::THUMB_MAX_WIDTH;
$height = (int) ( $original_height / $ratio );
} else {
$height = $original_height;
}
$width = min( self::THUMB_MAX_WIDTH, $original_width );
return compact( 'height', 'width' );
}
/**
* Outputs the playlist tracks, based on the type of playlist.
*
* These typically appear below the player.
* Clicking a track triggers the player to appear with its src.
*
* @param string $state_id The ID of the container.
* @param array $tracks Tracks.
* @return void
*/
public function print_tracks( $state_id, $tracks ) {
?>
<div class="wp-playlist-tracks">
<?php foreach ( $tracks as $index => $track ) : ?>
<?php
$on = 'tap:AMP.setState(' . wp_json_encode( [ $state_id => [ 'selectedIndex' => $index ] ] ) . ')';
$initial_class = 0 === $index ? 'wp-playlist-item wp-playlist-playing' : 'wp-playlist-item';
$bound_class = sprintf( '%d == %s.selectedIndex ? "wp-playlist-item wp-playlist-playing" : "wp-playlist-item"', $index, $state_id );
?>
<div class="<?php echo esc_attr( $initial_class ); ?>" [class]="<?php echo esc_attr( $bound_class ); ?>" >
<a class="wp-playlist-caption" on="<?php echo esc_attr( $on ); ?>">
<?php echo esc_html( ( $index + 1 ) . '.' ); ?> <span class="wp-playlist-item-title"><?php echo esc_html( $this->get_title( $track ) ); ?></span>
</a>
<?php if ( isset( $track['meta']['length_formatted'] ) ) : ?>
<div class="wp-playlist-item-length"><?php echo esc_html( $track['meta']['length_formatted'] ); ?></div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php
}
/**
* Gets the data for the playlist.
*
* @see wp_playlist_shortcode()
* @param array $attr The shortcode attributes.
* @return array $data The data for the playlist.
*/
public function get_data( $attr ) {
$markup = wp_playlist_shortcode( $attr );
preg_match( self::PLAYLIST_REGEX, $markup, $matches );
if ( empty( $matches[1] ) ) {
return [];
}
return json_decode( $matches[1], true );
}
/**
* Gets the title for the track.
*
* @param array $track The track data.
* @return string $title The title of the track.
*/
public function get_title( $track ) {
if ( ! empty( $track['caption'] ) ) {
return $track['caption'];
}
if ( ! empty( $track['title'] ) ) {
return $track['title'];
}
return '';
}
}