Created
August 5, 2024 15:22
-
-
Save damiencarbery/623264d2b9fff7997031f6f3979da370 to your computer and use it in GitHub Desktop.
Add Download link to WordPress audio playlist player - Initially an intimidating prospect, a few shortcuts got a Download button for each track in the playlist. https://www.damiencarbery.com/2024/08/add-download-link-to-wordpress-audio-playlist-player/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
Plugin Name: Append Download button to audio playlist | |
Plugin URI: https://www.damiencarbery.com/2017/02/add-download-link-to-wordpress-audio-player/ | |
Description: Add a Download link to the tracks in an audio playlist. This is based on the [playlist] shortcode from wp-includes/media.php. | |
Author: Damien Carbery | |
Author URI: https://www.damiencarbery.com | |
Version: 0.1 | |
*/ | |
if ( ! defined( 'WPINC' ) ) { | |
die; | |
} | |
/** | |
* Builds the Playlist shortcode output. | |
* | |
* This implements the functionality of the playlist shortcode for displaying | |
* a collection of WordPress audio or video files in a post. | |
* | |
* @since 3.9.0 | |
* | |
* @global int $content_width | |
* | |
* @param array $attr { | |
* Array of default playlist attributes. | |
* | |
* @type string $type Type of playlist to display. Accepts 'audio' or 'video'. Default 'audio'. | |
* @type string $order Designates ascending or descending order of items in the playlist. | |
* Accepts 'ASC', 'DESC'. Default 'ASC'. | |
* @type string $orderby Any column, or columns, to sort the playlist. If $ids are | |
* passed, this defaults to the order of the $ids array ('post__in'). | |
* Otherwise default is 'menu_order ID'. | |
* @type int $id If an explicit $ids array is not present, this parameter | |
* will determine which attachments are used for the playlist. | |
* Default is the current post ID. | |
* @type array $ids Create a playlist out of these explicit attachment IDs. If empty, | |
* a playlist will be created from all $type attachments of $id. | |
* Default empty. | |
* @type array $exclude List of specific attachment IDs to exclude from the playlist. Default empty. | |
* @type string $style Playlist style to use. Accepts 'light' or 'dark'. Default 'light'. | |
* @type bool $tracklist Whether to show or hide the playlist. Default true. | |
* @type bool $tracknumbers Whether to show or hide the numbers next to entries in the playlist. Default true. | |
* @type bool $images Show or hide the video or audio thumbnail (Featured Image/post | |
* thumbnail). Default true. | |
* @type bool $artists Whether to show or hide artist name in the playlist. Default true. | |
* } | |
* | |
* @return string Playlist output. Empty string if the passed type is unsupported. | |
*/ | |
function dcwd_playlist_download_shortcode( $attr ) { | |
global $content_width; | |
$post = get_post(); | |
static $instance = 0; | |
++$instance; | |
if ( ! empty( $attr['ids'] ) ) { | |
// 'ids' is explicitly ordered, unless you specify otherwise. | |
if ( empty( $attr['orderby'] ) ) { | |
$attr['orderby'] = 'post__in'; | |
} | |
$attr['include'] = $attr['ids']; | |
} | |
/** | |
* Filters the playlist output. | |
* | |
* Returning a non-empty value from the filter will short-circuit generation | |
* of the default playlist output, returning the passed value instead. | |
* | |
* @since 3.9.0 | |
* @since 4.2.0 The `$instance` parameter was added. | |
* | |
* @param string $output Playlist output. Default empty. | |
* @param array $attr An array of shortcode attributes. | |
* @param int $instance Unique numeric ID of this playlist shortcode instance. | |
*/ | |
$output = apply_filters( 'post_playlist', '', $attr, $instance ); | |
if ( ! empty( $output ) ) { | |
return $output; | |
} | |
$atts = shortcode_atts( | |
array( | |
'type' => 'audio', | |
'order' => 'ASC', | |
'orderby' => 'menu_order ID', | |
'id' => $post ? $post->ID : 0, | |
'include' => '', | |
'exclude' => '', | |
'style' => 'light', | |
'tracklist' => true, | |
'tracknumbers' => true, | |
'images' => true, | |
'artists' => true, | |
), | |
$attr, | |
'playlist' | |
); | |
$id = (int) $atts['id']; | |
if ( 'audio' !== $atts['type'] ) { | |
$atts['type'] = 'video'; | |
} | |
$args = array( | |
'post_status' => 'inherit', | |
'post_type' => 'attachment', | |
'post_mime_type' => $atts['type'], | |
'order' => $atts['order'], | |
'orderby' => $atts['orderby'], | |
); | |
if ( ! empty( $atts['include'] ) ) { | |
$args['include'] = $atts['include']; | |
$_attachments = get_posts( $args ); | |
$attachments = array(); | |
foreach ( $_attachments as $key => $val ) { | |
$attachments[ $val->ID ] = $_attachments[ $key ]; | |
} | |
} elseif ( ! empty( $atts['exclude'] ) ) { | |
$args['post_parent'] = $id; | |
$args['exclude'] = $atts['exclude']; | |
$attachments = get_children( $args ); | |
} else { | |
$args['post_parent'] = $id; | |
$attachments = get_children( $args ); | |
} | |
if ( ! empty( $args['post_parent'] ) ) { | |
$post_parent = get_post( $id ); | |
// Terminate the shortcode execution if the user cannot read the post or it is password-protected. | |
if ( ! current_user_can( 'read_post', $post_parent->ID ) || post_password_required( $post_parent ) ) { | |
return ''; | |
} | |
} | |
if ( empty( $attachments ) ) { | |
return ''; | |
} | |
if ( is_feed() ) { | |
$output = "\n"; | |
foreach ( $attachments as $att_id => $attachment ) { | |
$output .= wp_get_attachment_link( $att_id ) . "\n"; | |
} | |
return $output; | |
} | |
$outer = 22; // Default padding and border of wrapper. | |
$default_width = 640; | |
$default_height = 360; | |
$theme_width = empty( $content_width ) ? $default_width : ( $content_width - $outer ); | |
$theme_height = empty( $content_width ) ? $default_height : round( ( $default_height * $theme_width ) / $default_width ); | |
$data = array( | |
'type' => $atts['type'], | |
// Don't pass strings to JSON, will be truthy in JS. | |
'tracklist' => wp_validate_boolean( $atts['tracklist'] ), | |
'tracknumbers' => wp_validate_boolean( $atts['tracknumbers'] ), | |
'images' => wp_validate_boolean( $atts['images'] ), | |
'artists' => wp_validate_boolean( $atts['artists'] ), | |
); | |
$tracks = array(); | |
foreach ( $attachments as $attachment ) { | |
$url = wp_get_attachment_url( $attachment->ID ); | |
$ftype = wp_check_filetype( $url, wp_get_mime_types() ); | |
$track = array( | |
'src' => $url, | |
'type' => $ftype['type'], | |
'title' => $attachment->post_title, | |
'caption' => $attachment->post_excerpt, | |
'description' => $attachment->post_content, | |
); | |
$track['meta'] = array(); | |
$meta = wp_get_attachment_metadata( $attachment->ID ); | |
if ( ! empty( $meta ) ) { | |
foreach ( wp_get_attachment_id3_keys( $attachment ) as $key => $label ) { | |
if ( ! empty( $meta[ $key ] ) ) { | |
$track['meta'][ $key ] = $meta[ $key ]; | |
} | |
} | |
if ( 'video' === $atts['type'] ) { | |
if ( ! empty( $meta['width'] ) && ! empty( $meta['height'] ) ) { | |
$width = $meta['width']; | |
$height = $meta['height']; | |
$theme_height = round( ( $height * $theme_width ) / $width ); | |
} else { | |
$width = $default_width; | |
$height = $default_height; | |
} | |
$track['dimensions'] = array( | |
'original' => compact( 'width', 'height' ), | |
'resized' => array( | |
'width' => $theme_width, | |
'height' => $theme_height, | |
), | |
); | |
} | |
} | |
if ( $atts['images'] ) { | |
$thumb_id = get_post_thumbnail_id( $attachment->ID ); | |
if ( ! empty( $thumb_id ) ) { | |
list( $src, $width, $height ) = wp_get_attachment_image_src( $thumb_id, 'full' ); | |
$track['image'] = compact( 'src', 'width', 'height' ); | |
list( $src, $width, $height ) = wp_get_attachment_image_src( $thumb_id, 'thumbnail' ); | |
$track['thumb'] = compact( 'src', 'width', 'height' ); | |
} else { | |
$src = wp_mime_type_icon( $attachment->ID, '.svg' ); | |
$width = 48; | |
$height = 64; | |
$track['image'] = compact( 'src', 'width', 'height' ); | |
$track['thumb'] = compact( 'src', 'width', 'height' ); | |
} | |
} | |
$tracks[] = $track; | |
} | |
$data['tracks'] = $tracks; | |
$safe_type = esc_attr( $atts['type'] ); | |
$safe_style = esc_attr( $atts['style'] ); | |
ob_start(); | |
if ( 1 === $instance ) { | |
/** | |
* Prints and enqueues playlist scripts, styles, and JavaScript templates. | |
* | |
* @since 3.9.0 | |
* | |
* @param string $type Type of playlist. Possible values are 'audio' or 'video'. | |
* @param string $style The 'theme' for the playlist. Core provides 'light' and 'dark'. | |
*/ | |
//do_action( 'wp_playlist_scripts', $atts['type'], $atts['style'] ); | |
// Call the function directly instead of via add_action() so it is not called for regular [playlist] shortcode. | |
dcwd_playlist_download_scripts( $atts['type'] ); | |
} | |
?> | |
<div class="wp-playlist wp-<?php echo $safe_type; ?>-playlist wp-playlist-<?php echo $safe_style; ?>"> | |
<?php if ( 'audio' === $atts['type'] ) : ?> | |
<div class="wp-playlist-current-item"></div> | |
<?php endif; ?> | |
<<?php echo $safe_type; ?> controls="controls" preload="none" width="<?php echo (int) $theme_width; ?>" | |
<?php | |
if ( 'video' === $safe_type ) { | |
echo ' height="', (int) $theme_height, '"'; | |
} | |
?> | |
></<?php echo $safe_type; ?>> | |
<div class="wp-playlist-next"></div> | |
<div class="wp-playlist-prev"></div> | |
<noscript> | |
<ol> | |
<?php | |
foreach ( $attachments as $att_id => $attachment ) { | |
printf( '<li>%s</li>', wp_get_attachment_link( $att_id ) ); | |
} | |
?> | |
</ol> | |
</noscript> | |
<script type="application/json" class="wp-playlist-script"><?php echo wp_json_encode( $data ); ?></script> | |
</div> | |
<style> | |
/* Use CSS grid to put the track name/time in one call (10fr) and the download button in the other (1fr). */ | |
.wp-playlist-item-and-download { display: grid; grid-template-columns: 10fr 1fr; margin-bottom: 5px; } | |
/* Remove the link underline. */ | |
.wp-playlist-item-download a:where(:not(.wp-element-button)) { text-decoration: none; } | |
</style> | |
<?php | |
return ob_get_clean(); | |
} | |
add_shortcode( 'playlist_download', 'dcwd_playlist_download_shortcode' ); | |
function dcwd_playlist_download_scripts( $type ) { | |
wp_enqueue_style( 'wp-mediaelement' ); | |
wp_enqueue_script( 'wp-playlist' ); | |
?> | |
<!--[if lt IE 9]><script>document.createElement('<?php echo esc_js( $type ); ?>');</script><![endif]--> | |
<?php | |
add_action( 'wp_footer', 'dcwd_underscore_playlist_download_templates', 0 ); | |
add_action( 'admin_footer', 'dcwd_underscore_playlist_download_templates', 0 ); | |
} | |
/** | |
* Outputs the templates used by playlists. | |
* | |
* @since 3.9.0 | |
*/ | |
function dcwd_underscore_playlist_download_templates() { | |
?> | |
<script type="text/html" id="tmpl-wp-playlist-current-item"> | |
<# if ( data.thumb && data.thumb.src ) { #> | |
<img src="{{ data.thumb.src }}" alt="" /> | |
<# } #> | |
<div class="wp-playlist-caption"> | |
<span class="wp-playlist-item-meta wp-playlist-item-title"> | |
<# if ( data.meta.album || data.meta.artist ) { #> | |
<?php | |
/* translators: %s: Playlist item title. */ | |
printf( _x( '“%s”', 'playlist item title' ), '{{ data.title }}' ); | |
?> | |
<# } else { #> | |
{{ data.title }} | |
<# } #> | |
</span> | |
<# if ( data.meta.album ) { #><span class="wp-playlist-item-meta wp-playlist-item-album">{{ data.meta.album }}</span><# } #> | |
<# if ( data.meta.artist ) { #><span class="wp-playlist-item-meta wp-playlist-item-artist">{{ data.meta.artist }}</span><# } #> | |
</div> | |
</script> | |
<script type="text/html" id="tmpl-wp-playlist-item"> | |
<div class="wp-playlist-item-and-download"> | |
<div class="wp-playlist-item"> | |
<a class="wp-playlist-caption" href="{{ data.src }}"> | |
{{ data.index ? ( data.index + '. ' ) : '' }} | |
<# if ( data.caption ) { #> | |
{{ data.caption }} | |
<# } else { #> | |
<# if ( data.artists && data.meta.artist ) { #> | |
<span class="wp-playlist-item-title"> | |
<?php | |
/* translators: %s: Playlist item title. */ | |
printf( _x( '“%s”', 'playlist item title' ), '{{{ data.title }}}' ); | |
?> | |
</span> | |
<span class="wp-playlist-item-artist"> — {{ data.meta.artist }}</span> | |
<# } else { #> | |
<span class="wp-playlist-item-title">{{{ data.title }}}</span> | |
<# } #> | |
<# } #> | |
</a> | |
<# if ( data.meta.length_formatted ) { #> | |
<div class="wp-playlist-item-length">{{ data.meta.length_formatted }}</div> | |
<# } #> | |
</div> | |
<div class="wp-playlist-item-download"><button type="button"><a href="{{ data.src }}" download>Download</a></button></p></div> | |
</div> | |
</script> | |
<?php | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment