Themes: Enable video in custom headers.

This adds the ability for themes to add support for videos in custom headers
by passing `'video' => true` as an argument when adding theme support for
custom headers.

Custom video headers are managed through the “Header Visuals” (i.e. “Header Image”)
panel in the Customizer where you can select a video from the media library or set a
URL to an external video (YouTube for now) for use in custom headers.

This introduces several new functions:

`has_header_video()` – Check whether a header video is set or not.
`get_header_video_url()` – Retrieve header video URL for custom header.
`the_header_video_url()` – Display header video URL.
`get_header_video_settings()` – Retrieve header video settings.
`has_custom_header()` – Check whether a custom header is set or not.
`get_custom_header_markup()` – Retrieve the markup for a custom header.
`the_custom_header_markup()` – Print the markup for a custom header.

And a new file, `wp-includes/js/wp-custom-header.js` that handles loading videos
in custom headers.

This also enables video headers in the Twenty Seventeen and Twenty Fourteen themes.

Props davidakennedy, celloexpressions, bradyvercher, laurelfulford, joemcgill.
Fixes #38172.
Built from https://develop.svn.wordpress.org/trunk@38985


git-svn-id: http://core.svn.wordpress.org/trunk@38928 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Joe McGill
2016-10-27 21:51:31 +00:00
parent 7b0abd1cea
commit 030bc377dd
21 changed files with 552 additions and 62 deletions

View File

@@ -363,6 +363,9 @@ final class WP_Customize_Manager {
add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_section_templates' ), 1 );
add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_control_templates' ), 1 );
// Export header video settings with the partial response.
add_filter( 'customize_render_partials_response', array( $this, 'export_header_video_settings' ), 10, 3 );
// Export the settings to JS via the _wpCustomizeSettings variable.
add_action( 'customize_controls_print_footer_scripts', array( $this, 'customize_pane_settings' ), 1000 );
@@ -3249,12 +3252,54 @@ final class WP_Customize_Manager {
/* Custom Header */
if ( current_theme_supports( 'custom-header', 'video' ) ) {
$title = __( 'Header Visuals' );
$description = __( 'If you add a video, the image will be used as a fallback while the video loads.' );
$width = absint( get_theme_support( 'custom-header', 'width' ) );
$height = absint( get_theme_support( 'custom-header', 'height' ) );
if ( $width && $height ) {
/* translators: %s: header size in pixels */
$control_description = sprintf( __( 'Upload your video in <code>.mp4</code> format and minimize its file size for best results. Your theme recommends dimensions of %s pixels.' ),
sprintf( '<strong>%s &times; %s</strong>', $width, $height )
);
} elseif ( $width ) {
/* translators: %s: header width in pixels */
$control_description = sprintf( __( 'Upload your video in <code>.mp4</code> format and minimize its file size for best results. Your theme recommends a width of %s pixels.' ),
sprintf( '<strong>%s</strong>', $width )
);
} else {
/* translators: %s: header height in pixels */
$control_description = sprintf( __( 'Upload your video in <code>.mp4</code> format and minimize its file size for best results. Your theme recommends a height of %s pixels.' ),
sprintf( '<strong>%s</strong>', $height )
);
}
} else {
$title = __( 'Header Image' );
$description = '';
$control_description = '';
}
$this->add_section( 'header_image', array(
'title' => __( 'Header Image' ),
'title' => $title,
'description' => $description,
'theme_supports' => 'custom-header',
'priority' => 60,
) );
$this->add_setting( 'header_video', array(
'theme_supports' => array( 'custom-header', 'video' ),
'transport' => 'postMessage',
'sanitize_callback' => 'absint',
'validate_callback' => array( $this, '_validate_header_video' ),
) );
$this->add_setting( 'external_header_video', array(
'theme_supports' => array( 'custom-header', 'video' ),
'transport' => 'postMessage',
'sanitize_callback' => 'esc_url',
'validate_callback' => array( $this, '_validate_external_header_video' ),
) );
$this->add_setting( new WP_Customize_Filter_Setting( $this, 'header_image', array(
'default' => get_theme_support( 'custom-header', 'default-image' ),
'theme_supports' => 'custom-header',
@@ -3265,8 +3310,30 @@ final class WP_Customize_Manager {
'theme_supports' => 'custom-header',
) ) );
$this->add_control( new WP_Customize_Media_Control( $this, 'header_video', array(
'theme_supports' => array( 'custom-header', 'video' ),
'label' => __( 'Header Video' ),
'description' => $control_description,
'section' => 'header_image',
'mime_type' => 'video',
) ) );
$this->add_control( 'external_header_video', array(
'theme_supports' => array( 'custom-header', 'video' ),
'type' => 'url',
'description' => __( 'Or, enter a YouTube or Vimeo URL:' ),
'section' => 'header_image',
) );
$this->add_control( new WP_Customize_Header_Image_Control( $this ) );
$this->selective_refresh->add_partial( 'custom_header', array(
'selector' => '#wp-custom-header',
'render_callback' => 'the_custom_header_markup',
'settings' => array( 'header_video', 'external_header_video', 'header_image' ), // The image is used as a video fallback here.
'container_inclusive' => true,
) );
/* Custom Background */
$this->add_section( 'background_image', array(
@@ -3704,6 +3771,71 @@ final class WP_Customize_Manager {
return $value;
}
/**
* Export header video settings to facilitate selective refresh.
*
* @since 4.7.0
*
* @param array $response Response.
* @param WP_Customize_Selective_Refresh $selective_refresh Selective refresh component.
* @param array $partials Array of partials.
* @return array
*/
public function export_header_video_settings( $response, $selective_refresh, $partials ) {
if ( isset( $partials['header_video'] ) || isset( $partials['external_header_video'] ) ) {
$response['custom_header_settings'] = get_header_video_settings();
}
return $response;
}
/**
* Callback for validating the header_video value.
*
* Ensures that the selected video is less than 8MB and provides an error message.
*
* @since 4.7.0
*
* @param WP_Error $validity
* @param mixed $value
* @return mixed
*/
public function _validate_header_video( $validity, $value ) {
$video = get_attached_file( absint( $value ) );
if ( $video ) {
$size = filesize( $video );
if ( 8 < $size / pow( 1024, 2 ) ) { // Check whether the size is larger than 8MB.
$validity->add( 'size_too_large', __( 'This video file is too large to use as a header video. Try a shorter video or optimize the compression settings and re-upload a file that is less than 8MB. Or, upload your video to YouTube and link it with the option below.' ) );
}
if ( '.mp4' !== substr( $video, -4 ) && '.mov' !== substr( $video, -4 ) ) { // Check for .mp4 or .mov format, which (assuming h.264 encoding) are the only cross-browser-supported formats.
$validity->add( 'invalid_file_type', __( 'Only <code>.mp4</code> or <code>.mov</code> files may be used for header video. Please convert your video file and try again, or, upload your video to YouTube and link it with the option below.' ) );
}
}
return $validity;
}
/**
* Callback for validating the external_header_video value.
*
* Ensures that the provided URL is for YouTube or Vimeo.
*
* @since 4.7.0
*
* @param WP_Error $validity
* @param mixed $value
* @return mixed
*/
public function _validate_external_header_video( $validity, $value ) {
$video = esc_url( $value );
if ( $video ) {
if ( ! preg_match( '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#', $video )
&& ! preg_match( '#^https?://(.+\.)?vimeo\.com/.*#', $video ) ) {
$validity->add( 'invalid_url', __( 'Please enter a valid YouTube or Vimeo video URL.' ) );
}
}
return $validity;
}
/**
* Callback for rendering the custom logo, used in the custom_logo partial.
*

View File

@@ -166,9 +166,14 @@ class WP_Customize_Header_Image_Control extends WP_Customize_Image_Control {
$height = absint( get_theme_support( 'custom-header', 'height' ) );
?>
<div class="customize-control-content">
<p class="customizer-section-intro">
<?php if ( current_theme_supports( 'custom-header', 'video' ) ) {
echo '<span class="customize-control-title">' . $this->label . '</span>';
} ?>
<p class="customizer-section-intro customize-control-description">
<?php
if ( $width && $height ) {
if ( current_theme_supports( 'custom-header', 'video' ) ) {
_e( 'While you can crop images to your liking after clicking <strong>Add new image</strong>, we recommend matching the size of your video.' );
} elseif ( $width && $height ) {
/* translators: %s: header size in pixels */
printf( __( 'While you can crop images to your liking after clicking <strong>Add new image</strong>, your theme recommends a header size of %s pixels.' ),
sprintf( '<strong>%s &times; %s</strong>', $width, $height )

View File

@@ -0,0 +1,149 @@
(function( window, $, settings ) {
function wpCustomHeader() {
var handlers = {
nativeVideo: {
test: function( settings ) {
var video = document.createElement( 'video' );
return video.canPlayType( settings.mimeType );
},
callback: nativeHandler
},
youtube: {
test: function( settings ) {
return 'video/x-youtube' === settings.mimeType;
},
callback: youtubeHandler
}
};
function initialize() {
settings.container = document.getElementById( 'wp-custom-header' );
if ( supportsVideo() ) {
for ( var id in handlers ) {
var handler = handlers[ id ];
if ( handlers.hasOwnProperty( id ) && handler.test( settings ) ) {
handler.callback( settings );
break;
}
}
$( 'body' ).trigger( 'wp-custom-header-video-loaded' );
}
}
function supportsVideo() {
// Don't load video on small screens. @todo: consider bandwidth and other factors.
if ( window.innerWidth < settings.minWidth || window.innerHeight < settings.minHeight ) {
return false;
}
return true;
}
return {
handlers: handlers,
initialize: initialize,
supportsVideo: supportsVideo
};
}
function nativeHandler( settings ) {
var video = document.createElement( 'video' );
video.id = 'wp-custom-header-video';
video.autoplay = 'autoplay';
video.loop = 'loop';
video.muted = 'muted';
video.width = settings.width;
video.height = settings.height;
video.addEventListener( 'click', function() {
if ( video.paused ) {
video.play();
} else {
video.pause();
}
});
settings.container.innerHTML = '';
settings.container.appendChild( video );
video.src = settings.videoUrl;
}
function youtubeHandler( settings ) {
// @link http://stackoverflow.com/a/27728417
var VIDEO_ID_REGEX = /^.*(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/,
videoId = settings.videoUrl.match( VIDEO_ID_REGEX )[1];
function loadVideo() {
var YT = window.YT || {};
YT.ready(function() {
var video = document.createElement( 'div' );
video.id = 'wp-custom-header-video';
settings.container.innerHTML = '';
settings.container.appendChild( video );
new YT.Player( video, {
height: settings.height,
width: settings.width,
videoId: videoId,
events: {
onReady: function( e ) {
e.target.mute();
},
onStateChange: function( e ) {
if ( YT.PlayerState.ENDED === e.data ) {
e.target.playVideo();
}
}
},
playerVars: {
autoplay: 1,
controls: 0,
disablekb: 1,
fs: 0,
iv_load_policy: 3,
loop: 1,
modestbranding: 1,
//origin: '',
playsinline: 1,
rel: 0,
showinfo: 0
}
});
});
}
if ( 'YT' in window ) {
loadVideo();
} else {
var tag = document.createElement( 'script' );
tag.src = 'https://www.youtube.com/player_api';
tag.onload = function () { loadVideo(); };
document.getElementsByTagName( 'head' )[0].appendChild( tag );
}
}
window.wp = window.wp || {};
window.wp.customHeader = new wpCustomHeader();
document.addEventListener( 'DOMContentLoaded', window.wp.customHeader.initialize, false );
if ( 'customize' in window.wp ) {
wp.customize.selectiveRefresh.bind( 'render-partials-response', function( response ) {
if ( 'custom_header_settings' in response ) {
settings = response.custom_header_settings;
}
});
wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
if ( 'custom_header' === placement.partial.id ) {
window.wp.customHeader.initialize();
}
});
}
})( window, jQuery, window._wpCustomHeaderSettings || {} );

View File

@@ -0,0 +1 @@
!function(a,b,c){function d(){function d(){if(c.container=document.getElementById("wp-custom-header"),g()){for(var a in h){var d=h[a];if(h.hasOwnProperty(a)&&d.test(c)){d.callback(c);break}}b("body").trigger("wp-custom-header-video-loaded")}}function g(){return!(a.innerWidth<c.minWidth||a.innerHeight<c.minHeight)}var h={nativeVideo:{test:function(a){var b=document.createElement("video");return b.canPlayType(a.mimeType)},callback:e},youtube:{test:function(a){return"video/x-youtube"===a.mimeType},callback:f}};return{handlers:h,initialize:d,supportsVideo:g}}function e(a){var b=document.createElement("video");b.id="wp-custom-header-video",b.autoplay="autoplay",b.loop="loop",b.muted="muted",b.width=a.width,b.height=a.height,b.addEventListener("click",function(){b.paused?b.play():b.pause()}),a.container.innerHTML="",a.container.appendChild(b),b.src=a.videoUrl}function f(b){function c(){var c=a.YT||{};c.ready(function(){var a=document.createElement("div");a.id="wp-custom-header-video",b.container.innerHTML="",b.container.appendChild(a),new c.Player(a,{height:b.height,width:b.width,videoId:e,events:{onReady:function(a){a.target.mute()},onStateChange:function(a){c.PlayerState.ENDED===a.data&&a.target.playVideo()}},playerVars:{autoplay:1,controls:0,disablekb:1,fs:0,iv_load_policy:3,loop:1,modestbranding:1,playsinline:1,rel:0,showinfo:0}})})}var d=/^.*(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/,e=b.videoUrl.match(d)[1];if("YT"in a)c();else{var f=document.createElement("script");f.src="https://www.youtube.com/player_api",f.onload=function(){c()},document.getElementsByTagName("head")[0].appendChild(f)}}a.wp=a.wp||{},a.wp.customHeader=new d,document.addEventListener("DOMContentLoaded",a.wp.customHeader.initialize,!1),"customize"in a.wp&&(wp.customize.selectiveRefresh.bind("render-partials-response",function(a){"custom_header_settings"in a&&(c=a.custom_header_settings)}),wp.customize.selectiveRefresh.bind("partial-content-rendered",function(b){"custom_header"===b.partial.id&&a.wp.customHeader.initialize()}))}(window,jQuery,window._wpCustomHeaderSettings||{});

View File

@@ -479,6 +479,8 @@ function wp_default_scripts( &$scripts ) {
$scripts->add( 'customize-nav-menus', "/wp-admin/js/customize-nav-menus$suffix.js", array( 'jquery', 'wp-backbone', 'customize-controls', 'accordion', 'nav-menu' ), false, 1 );
$scripts->add( 'customize-preview-nav-menus', "/wp-includes/js/customize-preview-nav-menus$suffix.js", array( 'jquery', 'wp-util', 'customize-preview', 'customize-selective-refresh' ), false, 1 );
$scripts->add( 'wp-custom-header', "/wp-includes/js/wp-custom-header$suffix.js", array(), false, 1 );
$scripts->add( 'accordion', "/wp-admin/js/accordion$suffix.js", array( 'jquery' ), false, 1 );
$scripts->add( 'shortcode', "/wp-includes/js/shortcode$suffix.js", array( 'underscore' ), false, 1 );

View File

@@ -1264,6 +1264,7 @@ function get_custom_header() {
'thumbnail_url' => '',
'width' => get_theme_support( 'custom-header', 'width' ),
'height' => get_theme_support( 'custom-header', 'height' ),
'video' => get_theme_support( 'custom-header', 'video' ),
);
return (object) wp_parse_args( $data, $default );
}
@@ -1310,6 +1311,138 @@ function unregister_default_headers( $header ) {
}
}
/**
* Check whether a header video is set or not.
*
* @since 4.7.0
*
* @see get_header_video_url()
*
* @return bool Whether a header video is set or not.
*/
function has_header_video() {
return (bool) get_header_video_url();
}
/* Retrieve header video URL for custom header.
*
* Uses a local video if present, or falls back to an external video. Returns false if there is no video.
*
* @since 4.7.0
*
* @return string|false
*/
function get_header_video_url() {
$id = absint( get_theme_mod( 'header_video' ) );
$url = esc_url( get_theme_mod( 'external_header_video' ) );
if ( ! $id && ! $url ) {
return false;
}
if ( $id ) {
// Get the file URL from the attachment ID.
$url = wp_get_attachment_url( $id );
}
return esc_url_raw( set_url_scheme( $url ) );
}
/**
* Display header video URL.
*
* @since 4.7.0
*/
function the_header_video_url() {
$video = get_header_video_url();
if ( $video ) {
echo esc_url( $video );
}
}
/**
* Retrieve header video settings.
*
* @since 4.7.0
*
* @return array
*/
function get_header_video_settings() {
$header = get_custom_header();
$video_url = get_header_video_url();
$video_type = wp_check_filetype( $video_url, wp_get_mime_types() );
$settings = array(
'mimeType' => '',
'posterUrl' => get_header_image(),
'videoUrl' => $video_url,
'width' => absint( $header->width ),
'height' => absint( $header->height ),
'minWidth' => 900,
'minHeight' => 500,
);
if ( preg_match( '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#', $video_url ) ) {
$settings['mimeType'] = 'video/x-youtube';
} elseif ( preg_match( '#^https?://(.+\.)?vimeo\.com/.*#', $video_url ) ) {
$settings['mimeType'] = 'video/x-vimeo';
} elseif ( ! empty( $video_type['type'] ) ) {
$settings['mimeType'] = $video_type['type'];
}
return apply_filters( 'header_video_settings', $settings );
}
/**
* Check whether a custom header is set or not.
*
* @since 4.7.0
*
* @return bool True if a custom header is set. False if not.
*/
function has_custom_header() {
if ( has_header_image() || ( is_front_page() && has_header_video() ) ) {
return true;
}
return false;
}
/**
* Retrieve the markup for a custom header.
*
* @since 4.7.0
*
* @return string|false The markup for a custom header on success. False if not.
*/
function get_custom_header_markup() {
if ( ! has_custom_header() ) {
return false;
}
return sprintf(
'<div id="wp-custom-header" class="wp-custom-header">%s</div>',
get_header_image_tag()
);
}
/**
* Print the markup for a custom header.
*
* @since 4.7.0
*/
function the_custom_header_markup() {
if ( ! $custom_header = get_custom_header_markup() ) {
return;
}
echo $custom_header;
if ( has_header_video() && is_front_page() ) {
wp_enqueue_script( 'wp-custom-header' );
wp_localize_script( 'wp-custom-header', '_wpCustomHeaderSettings', get_header_video_settings() );
}
}
/**
* Retrieve background image for custom background.
*
@@ -1732,6 +1865,7 @@ function add_theme_support( $feature ) {
'wp-head-callback' => '',
'admin-head-callback' => '',
'admin-preview-callback' => '',
'video' => false,
);
$jit = isset( $args[0]['__jit'] );

View File

@@ -4,7 +4,7 @@
*
* @global string $wp_version
*/
$wp_version = '4.7-alpha-38984';
$wp_version = '4.7-alpha-38985';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.