Skip to content

Instantly share code, notes, and snippets.

@taggon
Forked from westonruter/class-wp-widget-media.php
Last active February 28, 2017 22:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save taggon/d381e421b743eb5f0e71d225ea18fffa to your computer and use it in GitHub Desktop.
Save taggon/d381e421b743eb5f0e71d225ea18fffa to your computer and use it in GitHub Desktop.
<?php
/**
* Widget API: WP_Media_Widget class
*
* @package WordPress
* @subpackage Widgets
* @since 4.8.0
*/
/**
* Core class that implements a media widget.
*
* @since 4.8.0
*
* @see WP_Widget
*/
class WP_Widget_Media extends WP_Widget {
/**
* Default instance.
*
* @var array
*/
private $default_instance = array(
'id' => '',
'title' => '',
'link' => '',
'align' => 'none',
);
/**
* Constructor.
*
* @since 4.8.0
* @access public
*
* @param string $id_base Optional Base ID for the widget, lowercase and unique. If left empty,
* a portion of the widget's class name will be used Has to be unique.
* @param string $name Optional. Name for the widget displayed on the configuration page.
* Default empty.
* @param array $widget_options Optional. Widget options. See wp_register_sidebar_widget() for
* information on accepted arguments. Default empty array.
* @param array $control_options Optional. Widget control options. See wp_register_widget_control()
* for information on accepted arguments. Default empty array.
*/
public function __construct( $id_base = '', $name = '', $widget_options = array(), $control_options = array() ) {
$widget_opts = wp_parse_args( $widget_options, array(
'classname' => 'widget_media',
'description' => __( 'An image, video, or audio file.' ),
'customize_selective_refresh' => true,
) );
$control_opts = wp_parse_args( $control_options, array() );
parent::__construct(
$id_base ? $id_base : 'wp-media-widget', // @todo This should just be 'media'.
$name ? $name : __( 'Media' ),
$widget_opts,
$control_opts
);
if ( is_customize_preview() ) {
$this->enqueue_mediaelement_script();
}
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
}
/**
* Displays the widget on the front-end.
*
* @since 4.8.0
* @access public
*
* @see WP_Widget::widget()
*
* @param array $args Display arguments including before_title, after_title, before_widget, and after_widget.
* @param array $instance Saved setting from the database.
*/
public function widget( $args, $instance ) {
$output = $args['before_widget'];
$instance = array_merge( $this->default_instance, $instance );
if ( $instance['title'] ) {
$title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base );
$output .= $args['before_title'] . $title . $args['after_title'];
}
// Render the media.
$attachment = $instance['id'] ? get_post( $instance['id'] ) : null;
if ( $attachment ) {
$output .= $this->render_media( $attachment, $args['widget_id'], $instance );
}
$output .= $args['after_widget'];
echo $output;
}
/**
* Sanitizes the widget form values as they are saved.
*
* @since 4.8.0
* @access public
*
* @see WP_Widget::update()
*
* @param array $new_instance Values just sent to be saved.
* @param array $old_instance Previously saved values from database.
* @return array Updated safe values to be saved.
*/
public function update( $new_instance, $old_instance ) {
$instance = $old_instance;
// ID and title.
$instance['id'] = (int) $new_instance['id'];
$instance['title'] = sanitize_text_field( $new_instance['title'] );
// Everything else.
$instance['align'] = sanitize_text_field( $new_instance['align'] );
$instance['size'] = sanitize_text_field( $new_instance['size'] );
$instance['link'] = sanitize_text_field( $new_instance['link'] );
return $instance;
}
/**
* Get type of a media attachment
*
* @since 4.8.0
* @access private
* @todo Why private? What about plugins that extend? Should they be able to easily call the parent method?
*
* @param WP_Post $attachment Attachment object.
* @return String type string such as image, audio and video. Returns empty string for unknown type
*/
private function get_typeof_media( $attachment ) {
if ( wp_attachment_is_image( $attachment ) ) {
return 'image';
}
if ( wp_attachment_is( 'audio', $attachment ) ) {
return 'audio';
}
if ( wp_attachment_is( 'video', $attachment ) ) {
return 'video';
}
// Unknown media type.
return '';
}
/**
* Renders a single media attachment
*
* @since 4.8.0
* @access public
*
* @param WP_Post $attachment Attachment object.
* @param string $widget_id Widget ID.
* @param array $instance Current widget instance arguments.
* @return string
*/
public function render_media( $attachment, $widget_id, $instance ) {
$output = '';
$renderer = 'render_' . $this->get_typeof_media( $attachment );
if ( method_exists( $this, $renderer ) ) {
$output .= call_user_func( array( $this, $renderer ), $attachment, $widget_id, $instance );
}
return $output;
}
/**
* Renders an image attachment preview.
*
* @since 4.8.0
* @access private
*
* @param WP_Post $attachment Attachment object.
* @param string $widget_id Widget ID.
* @param array $instance Current widget instance arguments.
* @return string
*/
private function render_image( $attachment, $widget_id, $instance ) {
$has_caption = ( ! empty( $attachment->post_excerpt ) );
$img_attrs = array(
'data-id' => $widget_id,
'title' => $attachment->post_title,
'class' => 'image wp-image-' . $attachment->ID,
'style' => 'width: 100%; height: auto;',
);
if ( ! $has_caption ) {
$img_attrs['class'] .= ' align' . $instance['align'];
}
$image = wp_get_attachment_image( $attachment->ID, $instance['size'], false, $img_attrs );
if ( ! $has_caption ) {
return $image;
}
$fig_attrs = array(
'id' => $widget_id . '-caption',
'width' => get_option( $instance['size'] . '_size_w' ),
'align' => $instance['align'],
'caption' => $attachment->post_excerpt,
);
$figure = img_caption_shortcode( $fig_attrs, $image );
return $figure;
}
/**
* Renders an audio attachment preview.
*
* @since 4.8.0
* @access private
*
* @param WP_Post $attachment Attachment object.
* @param string $widget_id Widget ID.
* @param array $instance Current widget instance arguments.
* @return string
*/
private function render_audio( $attachment, $widget_id, $instance ) {
unset( $widget_id );
if ( in_array( $instance['link'], array( 'file', 'post' ), true ) ) {
return $this->create_link_for( $attachment, $instance['link'] );
}
return wp_audio_shortcode( array(
'src' => wp_get_attachment_url( $attachment->ID ),
) );
}
/**
* Renders a video attachment preview.
*
* @since 4.8.0
* @access private
*
* @param WP_Post $attachment Attachment object.
* @param string $widget_id Widget ID.
* @param array $instance Current widget instance arguments.
* @return string
*/
private function render_video( $attachment, $widget_id, $instance ) {
unset( $widget_id );
if ( in_array( $instance['link'], array( 'file', 'post' ), true ) ) {
return $this->create_link_for( $attachment, $instance['link'] );
}
return wp_video_shortcode( array(
'src' => wp_get_attachment_url( $attachment->ID ),
) );
}
/**
* Creates and returns a link for an attachment.
*
* @param WP_Post $attachment Attachment object.
* @param string $type link type.
* @return string
*/
private function create_link_for( $attachment, $type = '' ) {
$url = '#';
if ( 'file' === $type ) {
$url = wp_get_attachment_url( $attachment->ID );
} elseif ( 'post' === $type ) {
$url = get_attachment_link( $attachment->ID );
}
return '<a href="' . esc_url( $url ) . '">' . $attachment->post_title . '</a>';
}
/**
* Renders a placeholder.
*
* @since 4.8.0
* @access private
* @return string
*/
private function render_placeholder() {
return '<p class="placeholder">' . esc_html__( 'No media selected' ) . '</p>';
}
/**
* Outputs the settings update form.
*
* @since 4.8.0
* @access public
*
* @param array $saved_instance Current settings.
* @return void
*/
public function form( $saved_instance ) {
$defaults = array(
'title' => '',
// Attachment props.
'id' => '',
'align' => '',
'size' => '',
'link' => '',
);
$instance = wp_parse_args( (array) $saved_instance, $defaults );
$attachment = empty( $instance['id'] ) ? null : get_post( $instance['id'] );
$widget_id = $this->id;
?>
<div class="<?php echo esc_attr( $widget_id ); ?> media-widget-preview">
<p>
<label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php esc_html_e( 'Title:' ); ?></label>
<input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $instance['title'] ); ?>" />
</p>
<div class="media-widget-admin-preview" id="<?php echo esc_attr( $widget_id ); ?>">
<?php
if ( $attachment ) {
echo $this->render_media( $attachment, $widget_id, $instance );
} else {
echo $this->render_placeholder();
}
?>
</div>
<p>
<button type="button" data-id="<?php echo esc_attr( $widget_id ); ?>" class="button select-media widefat">
<?php $attachment ? esc_html_e( 'Change Media' ) : esc_html_e( 'Select Media' ); ?>
</button>
</p>
<?php
// Use hidden form fields to capture the attachment details from the media manager.
unset( $instance['title'] );
?>
<?php foreach ( $instance as $name => $value ) : ?>
<input type="hidden" id="<?php echo esc_attr( $this->get_field_id( $name ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $name ) ); ?>" value="<?php echo esc_attr( $value ); ?>" />
<?php endforeach; ?>
</div>
<?php
}
/**
* Registers the stylesheet for handling the widget in the back-end.
*
* @since 4.8.0
* @access public
*/
public function enqueue_admin_styles() {
wp_enqueue_style( 'wp-media-widget' );
}
/**
* Registers the scripts for handling the widget in the back-end.
*
* @since 4.8.0
* @access public
*/
public function enqueue_admin_scripts() {
global $pagenow;
// Bail if we are not in the widgets or customize screens.
if ( 'widgets.php' !== $pagenow && ! is_customize_preview() ) {
return;
}
// Load the required media files for the media manager.
wp_enqueue_media();
wp_enqueue_script( 'wp-media-widget' );
add_action( 'admin_print_footer_scripts', array( $this, 'admin_print_footer_scripts' ) );
}
/**
* Prints footer scripts.
*
* @since 4.8.0
* @access public
*/
public function admin_print_footer_scripts() {
?>
<script type="text/html" id="tmpl-wp-media-widget-audio">
<?php wp_underscore_audio_template() ?>
</script>
<script type="text/html" id="tmpl-wp-media-widget-video">
<?php wp_underscore_video_template() ?>
</script>
<?php
}
/**
* Enqueue media element script and style if in need.
*
* This ensures the first instance of the media widget can properly handle media elements.
*
* @since 4.8.0
* @access private
*/
private function enqueue_mediaelement_script() {
/** This filter is documented in wp-includes/media.php */
$audio_library = apply_filters( 'wp_audio_shortcode_library', 'mediaelement' );
/** This filter is documented in wp-includes/media.php */
$video_library = apply_filters( 'wp_video_shortcode_library', 'mediaelement' );
if ( 'mediaelement' !== $audio_library && 'mediaelement' !== $video_library ) {
return;
}
wp_enqueue_style( 'wp-mediaelement' );
wp_enqueue_script( 'wp-mediaelement' );
}
}
.media-widget-preview .button {
text-align: center
}
.media-widget-preview .wp-caption {
max-width: 100%;
margin: 0;
}
.media-widget-preview .image {
height: auto;
max-width: 100%;
}
.media-widget-preview .aligncenter {
display: block;
margin: 0 auto;
text-align: center
}
/**
* @since 4.8.0
*
* @package WP_Media_Widget
*/
( function( $, l10n ) {
'use strict';
var frame = {
defaultProps: {
id: '',
align: '',
size: '',
link: ''
},
/**
* Init.
*
* @returns {void}
*/
init: function() {
frame.bindEvent();
wp.mediaelement.initialize();
},
/**
* Bind event.
*
* @param {jQuery} context Element.
* @returns {void}
*/
bindEvent: function( context ) {
$( '.button.select-media', context || '.media-widget-preview' )
.off( 'click.mediaWidget' )
.on( 'click.mediaWidget', frame.openMediaManager );
},
/**
* Get current selection of media.
*
* @param {String} widgetId Widget ID.
* @returns {wp.media.models.Selection|null} Selection or null if no current selection.
*/
getSelection: function( widgetId ) {
var ids, selection;
ids = $( '#widget-' + widgetId + '-id' ).val();
if ( ! ids ) {
return null;
}
selection = ids.split( ',' ).reduce( function( list, id ) {
var attachment = wp.media.attachment( id );
if ( id && attachment ) {
list.push( attachment );
}
return list;
}, [] );
return new wp.media.model.Selection( selection );
},
/**
* Open media manager.
*
* @param {jQuery.Event} event Event.
* @returns {void}
*/
openMediaManager: function( event ) {
var widgetFrame, widgetId, selection, prevAttachmentId;
widgetId = $( event.target ).data( 'id' );
selection = frame.getSelection( widgetId );
if ( selection && selection.length > 0 ) {
prevAttachmentId = selection.first().get('id');
}
// Create the media frame.
widgetFrame = wp.media( {
button: {
text: translate( 'addToWidget', 'Add to widget' ) // Text of the submit button.
},
states: new wp.media.controller.Library( {
library: wp.media.query( { type: [ 'image', 'audio', 'video' ] } ),
title: translate( 'selectMedia', 'Select Media' ), // Media frame title
selection: selection,
multiple: false,
priority: 20,
display: true, // Attachment display setting
filterable: 'all'
} )
} );
// Render the attachment details.
widgetFrame.on( 'select', function() {
var attachment, props;
attachment = frame.getFirstAttachment( widgetFrame );
props = frame.getDisplayProps( widgetFrame );
// Only try to render the attachment details if a selection was made.
if ( props && attachment && prevAttachmentId !== attachment.id ) {
frame.renderFormView( widgetId, props, attachment );
}
} );
/*
* Try to render the form only if the selection doesn't change.
* This ensures that changes of props will reflect in the form and the preview
* even when user doesn't click the Add button.
*/
widgetFrame.on( 'close', function() {
var attachment, props;
attachment = frame.getFirstAttachment( widgetFrame );
if ( attachment && prevAttachmentId && prevAttachmentId === attachment.id ) {
props = frame.getDisplayProps( widgetFrame );
frame.renderFormView( widgetId, props, attachment );
}
} );
widgetFrame.open( widgetId );
},
/**
* Get the first attachment of the selection in the widget frame.
*
* @param {wp.media.view.MediaFrame} widgetFrame Widget frame
* @return {object|null} JSON object of the attachment if it exists, otherwise null
*/
getFirstAttachment: function( widgetFrame ) {
var selection = widgetFrame.state().get( 'selection' );
if ( 0 === selection.length ) {
return null;
}
return selection.first().toJSON();
},
/**
* Get display props of the current selection from the widget frame.
*
* @param {wp.media.view.MediaFrame} widgetFrame Widget frame
* @return {object|null} JSON object of the props if possible, otherwise null
*/
getDisplayProps: function( widgetFrame ) {
if ( 0 === widgetFrame.state().get( 'selection' ).length ) {
return null;
}
return widgetFrame.content.get( '.attachments-browser' ).sidebar.get( 'display' ).model.toJSON();
},
/**
* Renders the attachment details from the media modal into the widget.
*
* @param {String} widgetId Widget ID.
* @param {Object} props Attachment Display Settings (align, link, size, etc).
* @param {Object} attachment Attachment Details (title, description, caption, url, sizes, etc).
* @returns {void}
*/
renderFormView: function( widgetId, props, attachment ) {
var formView, serializedAttachment;
// Start with container elements for the widgets page, customizer controls, and customizer preview.
formView = $( '.' + widgetId + ', #customize-control-widget_' + widgetId + ', #' + widgetId );
// Bail if there is no target form
if ( ! formView.length ) {
return;
}
_.extend( attachment, _.pick( props, 'link', 'size' ) );
// Show/hide the widget description
formView.find( '.attachment-description' )
.toggleClass( 'hidden', ! attachment.description )
.html( attachment.description );
// Set the preview content and apply responsive styles to the media.
formView.find( '.media-widget-admin-preview' )
.html( frame.renderMediaElement( widgetId, props, attachment ) )
.find( '.wp-video, .wp-caption' ).css( 'width', '100%' ).end()
.find( 'img.image' ).css( { width: '100%', height: 'auto' } );
if ( _.contains( [ 'audio', 'video' ], attachment.type ) ) {
wp.mediaelement.initialize();
}
frame.bindEvent( formView );
// Populate form fields with selection data from the media frame.
_.each( _.keys( frame.defaultProps ), function( key ) {
formView.find( '#widget-' + widgetId + '-' + key ).val( attachment[ key ] || props[ key ] ).trigger( 'change' );
} );
/*
* Force the widget's partial in the preview to refresh even when the instance was not changed.
* This ensures that changes to attachment's caption or description will be shown in the
* preview since these are not in the widget's instance state.
*/
serializedAttachment = JSON.stringify( _.pick( attachment, 'id', 'title', 'caption', 'link', 'size' ) );
if ( formView.data( 'attachment' ) !== serializedAttachment && wp.customize && wp.customize.previewer ) {
wp.customize.previewer.send( 'refresh-partial', 'widget[' + widgetId + ']' );
formView.data( 'attachment', serializedAttachment );
}
// Change button text
formView.find( '.select-media' ).text( translate( 'changeMedia', 'Change Media' ) );
},
/**
* Renders the media attachment in HTML.
*
* @param {String} widgetId Widget ID.
* @param {Object} props Attachment Display Settings (align, link, size, etc).
* @param {Object} attachment Attachment Details (title, description, caption, url, sizes, etc).
*
* @returns {String} Render media element.
*/
renderMediaElement: function( widgetId, props, attachment ) {
var type, renderer;
type = attachment.type || '';
renderer = 'render' + type.charAt( 0 ).toUpperCase() + type.slice( 1 );
if ( 'function' === typeof frame[ renderer ] ) {
return frame[renderer]( widgetId, props, attachment );
}
// In case no renderer found
return '';
},
/**
* Renders the image attachment
*
* @param {String} widgetId Widget ID.
* @param {Object} props Attachment Display Settings (align, link, size, etc).
* @param {Object} attachment Attachment Details (title, description, caption, url, sizes, etc).
*
* @returns {String} Rendered image.
*/
renderImage: function( widgetId, props, attachment ) {
var image = $( '<img />' )
.addClass( 'image wp-image' + attachment.id )
.attr( {
'data-id': widgetId,
src: attachment.sizes[ props.size ].url,
title: attachment.title,
alt: attachment.alt,
width: attachment.sizes[ props.size ].width,
height: attachment.sizes[ props.size ].height
} );
if ( attachment.caption ) {
image = $( '<figure />' )
.width( attachment.sizes[ props.size ].width )
.addClass( 'wp-caption' )
.attr( 'id', widgetId + '-caption' )
.append( image );
$( '<figcaption class="wp-caption-text" />' ).text( attachment.caption ).appendTo( image );
}
return image.wrap( '<div />' ).parent().html();
},
/**
* Renders the audio attachment.
*
* @param {String} widgetId Widget ID.
* @param {Object} props Attachment Display Settings (align, link, size, etc).
* @param {Object} attachment Attachment Details (title, description, caption, url, sizes, etc).
*
* @returns {String} Rendered audio.
*/
renderAudio: function( widgetId, props, attachment ) {
if ( 'embed' === props.link ) {
return wp.media.template( 'wp-media-widget-audio' )( {
model: {
src: attachment.url
}
} );
}
return wp.html.string( {
tag: 'a',
content: attachment.title,
attrs: {
href: '#'
}
} );
},
/**
* Renders the video attachment.
*
* @param {String} widgetId Widget ID.
* @param {Object} props Attachment Display Settings (align, link, size, etc).
* @param {Object} attachment Attachment Details (title, description, caption, url, sizes, etc).
*
* @returns {String} Rendered video.
*/
renderVideo: function( widgetId, props, attachment ) {
if ( 'embed' === props.link ) {
return wp.media.template( 'wp-media-widget-video' )( {
model: {
src: attachment.url,
width: attachment.width,
height: attachment.height
}
} );
}
return wp.html.string( {
tag: 'a',
content: attachment.title,
attrs: {
href: '#'
}
} );
}
};
/**
* Translate.
*
* @param {string} key Key.
* @param {string} defaultText Default text.
* @return {string} Translated string.
*/
function translate( key, defaultText ) {
return l10n[ key ] || defaultText;
}
$( document )
.ready( frame.init )
.on( 'widget-added widget-updated', frame.init );
window.wp = window.wp || {};
window.wp.MediaWidget = frame;
} )( jQuery, window._mediaWidgetL10n || {} );
<?php
/**
* Plugin Name: WP Media Widget
* Version: 0.1.0
* Description: Adding images to your widget areas is a common, yet currently incredibly tedious task -- you need to upload it in your media library, find the url, copy the url, and then manually add an image tag inside of a text widget. That's a lot to ask for a beginner user to do. We should include a default image widget in core to make this task easier.
* Author: WordPress.org
* Plugin URI: https://core.trac.wordpress.org/ticket/32417
* Domain Path: /languages
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2 or, at
* your discretion, any later version, as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* Register widget scripts.
*
* @param WP_Scripts $scripts
*/
function wp32417_default_scripts( WP_Scripts $scripts ) {
$scripts->add( 'wp-media-widget', plugin_dir_url( __FILE__ ) . 'wp-media-widget.js', array( 'jquery', 'media-models', 'media-views', 'wp-mediaelement' ) );
}
add_action( 'wp_default_scripts', 'wp32417_default_scripts' );
/**
* Register widget styles.
*
* @param WP_Styles $styles
*/
function wp32417_default_styles( WP_Styles $styles ) {
$styles->add( 'wp-media-widget', plugin_dir_url( __FILE__ ) . 'wp-media-widget.css', array( 'media-views' ) );
}
add_action( 'wp_default_styles', 'wp32417_default_styles' );
/**
* Register widget.
*/
function wp32417_widgets_init() {
require_once( __DIR__ . '/class-wp-widget-media.php' );
register_widget( 'WP_Widget_Media' );
}
add_action( 'widgets_init', 'wp32417_widgets_init' );
@westonruter
Copy link

I've cloned this over to its own dedicated GitHub project, so please push additional commits to https://github.com/xwp/wp-core-media-widgets

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment