WordPress.org

Make WordPress Core

Ticket #38678: 38678.13.diff

File 38678.13.diff, 16.2 KB (added by joemcgill, 4 years ago)
  • src/wp-includes/class-wp-customize-manager.php

    diff --git src/wp-includes/class-wp-customize-manager.php src/wp-includes/class-wp-customize-manager.php
    index 3aa7ebc..f6d77a6 100644
    final class WP_Customize_Manager { 
    35903590                $this->add_setting( 'external_header_video', array(
    35913591                        'theme_supports'    => array( 'custom-header', 'video' ),
    35923592                        'transport'         => 'postMessage',
    3593                         'sanitize_callback' => 'esc_url',
     3593                        'sanitize_callback' => 'esc_url_raw',
    35943594                        'validate_callback' => array( $this, '_validate_external_header_video' ),
    35953595                ) );
    35963596
  • src/wp-includes/js/wp-custom-header.js

    diff --git src/wp-includes/js/wp-custom-header.js src/wp-includes/js/wp-custom-header.js
    index 08e2488..8d15bca 100644
     
    1 (function( window, settings ) {
     1/* global YT */
     2( function( window, settings ) {
    23
     4        var NativeHandler, YouTubeHandler;
     5
     6        window.wp = window.wp || {};
     7
     8        // Fail gracefully in unsupported browsers.
    39        if ( ! ( 'addEventListener' in window ) ) {
    4                 // Fail gracefully in unsupported browsers.
    510                return;
    611        }
    712
    8         function wpCustomHeader() {
    9                 var handlers = {
    10                         nativeVideo: {
    11                                 test: function( settings ) {
    12                                         var video = document.createElement( 'video' );
    13                                         return video.canPlayType( settings.mimeType );
    14                                 },
    15                                 callback: nativeHandler
    16                         },
    17                         youtube: {
    18                                 test: function( settings ) {
    19                                         return 'video/x-youtube' === settings.mimeType;
    20                                 },
    21                                 callback: youtubeHandler
    22                         }
    23                 };
     13        /**
     14         * Trigger an event.
     15         *
     16         * @param {Element} target HTML element to dispatch the event on.
     17         * @param {string} name Event name.
     18         */
     19        function trigger( target, name ) {
     20                var evt;
     21
     22                if ( 'function' === typeof window.Event ) {
     23                        evt = new Event( name );
     24                } else {
     25                        evt = document.createEvent( 'Event' );
     26                        evt.initEvent( name, true, true );
     27                }
    2428
    25                 function initialize() {
    26                         settings.container = document.getElementById( 'wp-custom-header' );
     29                target.dispatchEvent( evt );
     30        }
    2731
    28                         if ( supportsVideo() ) {
    29                                 for ( var id in handlers ) {
    30                                         var handler = handlers[ id ];
     32        /**
     33         * Create a custom header instance.
     34         *
     35         * @class CustomHeader
     36         */
     37        function CustomHeader() {
     38                this.handlers = {
     39                        nativeVideo: new NativeHandler(),
     40                        youtube: new YouTubeHandler()
     41                };
     42        }
    3143
    32                                         if ( handlers.hasOwnProperty( id ) && handler.test( settings ) ) {
    33                                                 handler.callback( settings );
     44        CustomHeader.prototype = {
     45                /**
     46                 * Initalize the custom header.
     47                 *
     48                 * If the environment supports video, loops through registered handlers
     49                 * until one is found that can handle the video.
     50                 */
     51                initialize: function() {
     52                        if ( this.supportsVideo() ) {
     53                                for ( var id in this.handlers ) {
     54                                        var handler = this.handlers[ id ];
    3455
    35                                                 // Set up and dispatch custom event when the video is loaded.
    36                                                 if ( 'dispatchEvent' in window ) {
    37                                                         var videoLoaded = new Event( 'wp-custom-header-video-loaded' );
    38                                                         document.dispatchEvent( videoLoaded );
    39                                                 }
     56                                        if ( 'test' in handler && handler.test( settings ) ) {
     57                                                this.activeHandler = handler.initialize.call( handler, settings );
    4058
     59                                                // Dispatch custom event when the video is loaded.
     60                                                trigger( document, 'wp-custom-header-video-loaded' );
    4161                                                break;
    4262                                        }
    4363                                }
    4464                        }
    45                 }
     65                },
    4666
    47                 function supportsVideo() {
     67                /**
     68                 * Determines if the current environment supports video.
     69                 *
     70                 * Themes and plugins can override this method to change the criteria.
     71                 *
     72                 * @return {boolean}
     73                 */
     74                supportsVideo: function() {
    4875                        // Don't load video on small screens. @todo: consider bandwidth and other factors.
    49                         if ( window.innerWidth < settings.minWidth  || window.innerHeight < settings.minHeight ) {
     76                        if ( window.innerWidth < settings.minWidth || window.innerHeight < settings.minHeight ) {
    5077                                return false;
    5178                        }
    5279
    5380                        return true;
     81                },
     82
     83                /**
     84                 * Base handler for custom handlers to extend.
     85                 *
     86                 * @type {BaseHandler}
     87                 */
     88                BaseVideoHandler: BaseHandler
     89        };
     90
     91        /**
     92         * Create a video handler instance.
     93         *
     94         * @class BaseHandler
     95         */
     96        function BaseHandler() {}
     97
     98        BaseHandler.prototype = {
     99                /**
     100                 * Initialize the video handler.
     101                 *
     102                 * @param {object} settings Video settings.
     103                 */
     104                initialize: function( settings ) {
     105                        var handler = this,
     106                                button = document.createElement( 'button' );
     107
     108                        this.settings = settings;
     109                        this.container =  document.getElementById( 'wp-custom-header' ),
     110                        this.button = button;
     111
     112                        button.setAttribute( 'type', 'button' );
     113                        button.setAttribute( 'id', 'wp-custom-header-video-button' );
     114                        button.setAttribute( 'class', 'wp-custom-header-video-button wp-custom-header-video-play' );
     115                        button.innerHTML = settings.l10n.play;
     116
     117                        // Toggle video playback when the button is clicked.
     118                        button.addEventListener( 'click', function() {
     119                                if ( handler.isPaused() ) {
     120                                        handler.play();
     121                                } else {
     122                                        handler.pause();
     123                                }
     124                        });
     125
     126                        // Update the button class and text when the video state changes.
     127                        this.container.addEventListener( 'play', function() {
     128                                button.className = 'wp-custom-header-video-button wp-custom-header-video-play';
     129                                button.innerHTML = settings.l10n.pause;
     130                                if ( 'a11y' in window.wp ) {
     131                                        window.wp.a11y.speak( settings.l10n.playSpeak);
     132                                }
     133                        });
     134
     135                        this.container.addEventListener( 'pause', function() {
     136                                button.className = 'wp-custom-header-video-button wp-custom-header-video-pause';
     137                                button.innerHTML = settings.l10n.play;
     138                                if ( 'a11y' in window.wp ) {
     139                                        window.wp.a11y.speak( settings.l10n.pauseSpeak);
     140                                }
     141                        });
     142
     143                        this.ready();
     144                },
     145
     146                /**
     147                 * Ready method called after a handler is initialized.
     148                 *
     149                 * @abstract
     150                 */
     151                ready: function() {},
     152
     153                /**
     154                 * Whether the video is paused.
     155                 *
     156                 * @abstract
     157                 * @return {boolean}
     158                 */
     159                isPaused: function() {},
     160
     161                /**
     162                 * Pause the video.
     163                 *
     164                 * @abstract
     165                 */
     166                pause: function() {},
     167
     168                /**
     169                 * Play the video.
     170                 *
     171                 * @abstract
     172                 */
     173                play: function() {},
     174
     175                /**
     176                 * Append a video node to the header container.
     177                 *
     178                 * @param {Element} node HTML element.
     179                 */
     180                setVideo: function( node ) {
     181                        var editShortcutNode,
     182                                editShortcut = this.container.getElementsByClassName( 'customize-partial-edit-shortcut' );
     183
     184                        if ( editShortcut.length ) {
     185                                editShortcutNode = this.container.removeChild( editShortcut[0] );
     186                        }
     187
     188                        this.container.innerHTML = '';
     189                        this.container.appendChild( node );
     190
     191                        if ( editShortcutNode ) {
     192                                this.container.appendChild( editShortcutNode );
     193                        }
     194                },
     195
     196                /**
     197                 * Show the video controls.
     198                 *
     199                 * Appends a play/pause button to header container.
     200                 */
     201                showControls: function() {
     202                        if ( ! this.container.contains( this.button ) ) {
     203                                this.container.appendChild( this.button );
     204                        }
     205                },
     206
     207                /**
     208                 * Whether the handler can process a video.
     209                 *
     210                 * @abstract
     211                 * @param {object} settings Video settings.
     212                 * @return {boolean}
     213                 */
     214                test: function() {
     215                        return false;
     216                },
     217
     218                /**
     219                 * Trigger an event on the header container.
     220                 *
     221                 * @param {string} name Event name.
     222                 */
     223                trigger: function( name ) {
     224                        trigger( this.container, name );
    54225                }
     226        };
    55227
    56                 return {
    57                         handlers: handlers,
    58                         initialize: initialize,
    59                         supportsVideo: supportsVideo
    60                 };
    61         }
     228        /**
     229         * Create a custom handler.
     230         *
     231         * @param  {object} protoProps Properties to apply to the prototype.
     232         * @return CustomHandler The subclass.
     233         */
     234        BaseHandler.extend = function( protoProps ) {
     235                var prop;
     236
     237                function CustomHandler() {
     238                        var result = BaseHandler.apply( this, arguments );
     239                        return result;
     240                }
     241
     242                CustomHandler.prototype = Object.create( BaseHandler.prototype );
     243                CustomHandler.prototype.constructor = CustomHandler;
     244
     245                for ( prop in protoProps ) {
     246                        CustomHandler.prototype[ prop ] = protoProps[ prop ];
     247                }
     248
     249                return CustomHandler;
     250        };
     251
     252        /**
     253         * Native video handler.
     254         *
     255         * @class NativeHandler
     256         */
     257        NativeHandler = BaseHandler.extend({
     258                /**
     259                 * Whether the native handler supports a video.
     260                 *
     261                 * @param {object} settings Video settings.
     262                 * @return {boolean}
     263                 */
     264                test: function( settings ) {
     265                        var video = document.createElement( 'video' );
     266                        return video.canPlayType( settings.mimeType );
     267                },
     268
     269                /**
     270                 * Set up a native video element.
     271                 */
     272                ready: function() {
     273                        var handler = this,
     274                                video = document.createElement( 'video' );
     275
     276                        video.id = 'wp-custom-header-video';
     277                        video.autoplay = 'autoplay';
     278                        video.loop = 'loop';
     279                        video.muted = 'muted';
     280                        video.width = this.settings.width;
     281                        video.height = this.settings.height;
     282
     283                        video.addEventListener( 'play', function() {
     284                                handler.trigger( 'play' );
     285                        });
     286
     287                        video.addEventListener( 'pause', function() {
     288                                handler.trigger( 'pause' );
     289                        });
     290
     291                        video.addEventListener( 'canplay', function() {
     292                                handler.showControls();
     293                        });
     294
     295                        this.video = video;
     296                        handler.setVideo( video );
     297                        video.src = this.settings.videoUrl;
     298                },
     299
     300                /**
     301                 * Whether the video is paused.
     302                 *
     303                 * @return {boolean}
     304                 */
     305                isPaused: function() {
     306                        return this.video.paused;
     307                },
     308
     309                /**
     310                 * Pause the video.
     311                 */
     312                pause: function() {
     313                        this.video.pause();
     314                },
     315
     316                /**
     317                 * Play the video.
     318                 */
     319                play: function() {
     320                        this.video.play();
     321                }
     322        });
    62323
    63         function nativeHandler( settings ) {
    64                 var video = document.createElement( 'video' );
     324        /**
     325         * YouTube video handler.
     326         *
     327         * @class YouTubeHandler
     328         */
     329        YouTubeHandler = BaseHandler.extend({
     330                /**
     331                 * Whether the handler supports a video.
     332                 *
     333                 * @param {object} settings Video settings.
     334                 * @return {boolean}
     335                 */
     336                test: function( settings ) {
     337                        return 'video/x-youtube' === settings.mimeType;
     338                },
    65339
    66                 video.id = 'wp-custom-header-video';
    67                 video.autoplay = 'autoplay';
    68                 video.loop = 'loop';
    69                 video.muted = 'muted';
    70                 video.width = settings.width;
    71                 video.height = settings.height;
     340                /**
     341                 * Set up a YouTube iframe.
     342                 *
     343                 * Loads the YouTube IFrame API if the 'YT' global doesn't exist.
     344                 */
     345                ready: function() {
     346                        var handler = this;
    72347
    73                 video.addEventListener( 'click', function() {
    74                         if ( video.paused ) {
    75                                 video.play();
     348                        if ( 'YT' in window ) {
     349                                YT.ready( handler.loadVideo.bind( handler ) );
    76350                        } else {
    77                                 video.pause();
     351                                var tag = document.createElement( 'script' );
     352                                tag.src = 'https://www.youtube.com/iframe_api';
     353                                tag.onload = function () {
     354                                        YT.ready( handler.loadVideo.bind( handler ) );
     355                                };
     356
     357                                document.getElementsByTagName( 'head' )[0].appendChild( tag );
    78358                        }
    79                 });
     359                },
    80360
    81                 settings.container.innerHTML = '';
    82                 settings.container.appendChild( video );
    83                 video.src = settings.videoUrl;
    84         }
     361                /**
     362                 * Load a YouTube video.
     363                 */
     364                loadVideo: function() {
     365                        var handler = this,
     366                                video = document.createElement( 'div' ),
     367                                // @link http://stackoverflow.com/a/27728417
     368                                VIDEO_ID_REGEX = /^.*(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/;
    85369
    86         function youtubeHandler( settings ) {
    87                 // @link http://stackoverflow.com/a/27728417
    88                 var VIDEO_ID_REGEX = /^.*(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/,
    89                         videoId = settings.videoUrl.match( VIDEO_ID_REGEX )[1];
    90 
    91                 function loadVideo() {
    92                         var YT = window.YT || {};
    93 
    94                         YT.ready(function() {
    95                                 var video = document.createElement( 'div' );
    96                                 video.id = 'wp-custom-header-video';
    97                                 settings.container.innerHTML = '';
    98                                 settings.container.appendChild( video );
    99 
    100                                 new YT.Player( video, {
    101                                         height: settings.height,
    102                                         width: settings.width,
    103                                         videoId: videoId,
    104                                         events: {
    105                                                 onReady: function( e ) {
    106                                                         e.target.mute();
    107                                                 },
    108                                                 onStateChange: function( e ) {
    109                                                         if ( YT.PlayerState.ENDED === e.data ) {
    110                                                                 e.target.playVideo();
    111                                                         }
    112                                                 }
     370                        video.id = 'wp-custom-header-video';
     371                        handler.setVideo( video );
     372
     373                        handler.player = new YT.Player( video, {
     374                                height: this.settings.height,
     375                                width: this.settings.width,
     376                                videoId: this.settings.videoUrl.match( VIDEO_ID_REGEX )[1],
     377                                events: {
     378                                        onReady: function( e ) {
     379                                                e.target.mute();
     380                                                handler.showControls();
    113381                                        },
    114                                         playerVars: {
    115                                                 autoplay: 1,
    116                                                 controls: 0,
    117                                                 disablekb: 1,
    118                                                 fs: 0,
    119                                                 iv_load_policy: 3,
    120                                                 loop: 1,
    121                                                 modestbranding: 1,
    122                                                 //origin: '',
    123                                                 playsinline: 1,
    124                                                 rel: 0,
    125                                                 showinfo: 0
     382                                        onStateChange: function( e ) {
     383                                                if ( YT.PlayerState.PLAYING === e.data ) {
     384                                                        handler.trigger( 'play' );
     385                                                } else if ( YT.PlayerState.PAUSED === e.data ) {
     386                                                        handler.trigger( 'pause' );
     387                                                } else if ( YT.PlayerState.ENDED === e.data ) {
     388                                                        e.target.playVideo();
     389                                                }
    126390                                        }
    127                                 });
     391                                },
     392                                playerVars: {
     393                                        autoplay: 1,
     394                                        controls: 0,
     395                                        disablekb: 1,
     396                                        fs: 0,
     397                                        iv_load_policy: 3,
     398                                        loop: 1,
     399                                        modestbranding: 1,
     400                                        playsinline: 1,
     401                                        rel: 0,
     402                                        showinfo: 0
     403                                }
    128404                        });
    129                 }
     405                },
    130406
    131                 if ( 'YT' in window ) {
    132                         loadVideo();
    133                 } else {
    134                         var tag = document.createElement( 'script' );
    135                         tag.src = 'https://www.youtube.com/player_api';
    136                         tag.onload = function () { loadVideo(); };
    137                         document.getElementsByTagName( 'head' )[0].appendChild( tag );
     407                /**
     408                 * Whether the video is paused.
     409                 *
     410                 * @return {boolean}
     411                 */
     412                isPaused: function() {
     413                        return YT.PlayerState.PAUSED === this.player.getPlayerState();
     414                },
     415
     416                /**
     417                 * Pause the video.
     418                 */
     419                pause: function() {
     420                        this.player.pauseVideo();
     421                },
     422
     423                /**
     424                 * Play the video.
     425                 */
     426                play: function() {
     427                        this.player.playVideo();
    138428                }
    139         }
     429        });
    140430
    141         window.wp = window.wp || {};
    142         window.wp.customHeader = new wpCustomHeader();
    143         document.addEventListener( 'DOMContentLoaded', window.wp.customHeader.initialize, false );
     431        // Initialize the custom header when the DOM is ready.
     432        window.wp.customHeader = new CustomHeader();
     433        document.addEventListener( 'DOMContentLoaded', window.wp.customHeader.initialize.bind( window.wp.customHeader ), false );
    144434
     435        // Selective refresh support in the Customizer.
    145436        if ( 'customize' in window.wp ) {
    146                 wp.customize.selectiveRefresh.bind( 'render-partials-response', function( response ) {
     437                window.wp.customize.selectiveRefresh.bind( 'render-partials-response', function( response ) {
    147438                        if ( 'custom_header_settings' in response ) {
    148439                                settings = response.custom_header_settings;
    149440                        }
    150441                });
    151442
    152                 wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
     443                window.wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
    153444                        if ( 'custom_header' === placement.partial.id ) {
    154445                                window.wp.customHeader.initialize();
    155446                        }
  • src/wp-includes/script-loader.php

    diff --git src/wp-includes/script-loader.php src/wp-includes/script-loader.php
    index fadfb28..e405b13 100644
    function wp_default_scripts( &$scripts ) { 
    481481        $scripts->add( 'customize-nav-menus', "/wp-admin/js/customize-nav-menus$suffix.js", array( 'jquery', 'wp-backbone', 'customize-controls', 'accordion', 'nav-menu' ), false, 1 );
    482482        $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 );
    483483
    484         $scripts->add( 'wp-custom-header', "/wp-includes/js/wp-custom-header$suffix.js", array(), false, 1 );
     484        $scripts->add( 'wp-custom-header', "/wp-includes/js/wp-custom-header$suffix.js", array( 'wp-a11y' ), false, 1 );
    485485
    486486        $scripts->add( 'accordion', "/wp-admin/js/accordion$suffix.js", array( 'jquery' ), false, 1 );
    487487
  • src/wp-includes/theme.php

    diff --git src/wp-includes/theme.php src/wp-includes/theme.php
    index d892ea7..a3af51c 100644
    function get_header_video_settings() { 
    13811381                'height'    => absint( $header->height ),
    13821382                'minWidth'  => 900,
    13831383                'minHeight' => 500,
     1384                'l10n'      => array(
     1385                        'pause'      => __( 'Pause' ),
     1386                        'play'       => __( 'Play' ),
     1387                        'pauseSpeak' => __( 'Video is paused.'),
     1388                        'playSpeak'  => __( 'Video is playing.'),
     1389                ),
    13841390        );
    13851391
    13861392        if ( preg_match( '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#', $video_url ) ) {