class-wp-widget-media-video.php 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. <?php
  2. /**
  3. * Widget API: WP_Widget_Media_Video class
  4. *
  5. * @package WordPress
  6. * @subpackage Widgets
  7. * @since 4.8.0
  8. */
  9. /**
  10. * Core class that implements a video widget.
  11. *
  12. * @since 4.8.0
  13. *
  14. * @see WP_Widget_Media
  15. * @see WP_Widget
  16. */
  17. class WP_Widget_Media_Video extends WP_Widget_Media {
  18. /**
  19. * Constructor.
  20. *
  21. * @since 4.8.0
  22. */
  23. public function __construct() {
  24. parent::__construct(
  25. 'media_video',
  26. __( 'Video' ),
  27. array(
  28. 'description' => __( 'Displays a video from the media library or from YouTube, Vimeo, or another provider.' ),
  29. 'mime_type' => 'video',
  30. )
  31. );
  32. $this->l10n = array_merge(
  33. $this->l10n,
  34. array(
  35. 'no_media_selected' => __( 'No video selected' ),
  36. 'add_media' => _x( 'Add Video', 'label for button in the video widget' ),
  37. 'replace_media' => _x( 'Replace Video', 'label for button in the video widget; should preferably not be longer than ~13 characters long' ),
  38. 'edit_media' => _x( 'Edit Video', 'label for button in the video widget; should preferably not be longer than ~13 characters long' ),
  39. 'missing_attachment' => sprintf(
  40. /* translators: %s: URL to media library. */
  41. __( 'We can&#8217;t find that video. Check your <a href="%s">media library</a> and make sure it wasn&#8217;t deleted.' ),
  42. esc_url( admin_url( 'upload.php' ) )
  43. ),
  44. /* translators: %d: Widget count. */
  45. 'media_library_state_multi' => _n_noop( 'Video Widget (%d)', 'Video Widget (%d)' ),
  46. 'media_library_state_single' => __( 'Video Widget' ),
  47. /* translators: %s: A list of valid video file extensions. */
  48. 'unsupported_file_type' => sprintf( __( 'Sorry, we can&#8217;t load the video at the supplied URL. Please check that the URL is for a supported video file (%s) or stream (e.g. YouTube and Vimeo).' ), '<code>.' . implode( '</code>, <code>.', wp_get_video_extensions() ) . '</code>' ),
  49. )
  50. );
  51. }
  52. /**
  53. * Get schema for properties of a widget instance (item).
  54. *
  55. * @since 4.8.0
  56. *
  57. * @see WP_REST_Controller::get_item_schema()
  58. * @see WP_REST_Controller::get_additional_fields()
  59. * @link https://core.trac.wordpress.org/ticket/35574
  60. * @return array Schema for properties.
  61. */
  62. public function get_instance_schema() {
  63. $schema = array(
  64. 'preload' => array(
  65. 'type' => 'string',
  66. 'enum' => array( 'none', 'auto', 'metadata' ),
  67. 'default' => 'metadata',
  68. 'description' => __( 'Preload' ),
  69. 'should_preview_update' => false,
  70. ),
  71. 'loop' => array(
  72. 'type' => 'boolean',
  73. 'default' => false,
  74. 'description' => __( 'Loop' ),
  75. 'should_preview_update' => false,
  76. ),
  77. 'content' => array(
  78. 'type' => 'string',
  79. 'default' => '',
  80. 'sanitize_callback' => 'wp_kses_post',
  81. 'description' => __( 'Tracks (subtitles, captions, descriptions, chapters, or metadata)' ),
  82. 'should_preview_update' => false,
  83. ),
  84. );
  85. foreach ( wp_get_video_extensions() as $video_extension ) {
  86. $schema[ $video_extension ] = array(
  87. 'type' => 'string',
  88. 'default' => '',
  89. 'format' => 'uri',
  90. /* translators: %s: Video extension. */
  91. 'description' => sprintf( __( 'URL to the %s video source file' ), $video_extension ),
  92. );
  93. }
  94. return array_merge( $schema, parent::get_instance_schema() );
  95. }
  96. /**
  97. * Render the media on the frontend.
  98. *
  99. * @since 4.8.0
  100. *
  101. * @param array $instance Widget instance props.
  102. *
  103. * @return void
  104. */
  105. public function render_media( $instance ) {
  106. $instance = array_merge( wp_list_pluck( $this->get_instance_schema(), 'default' ), $instance );
  107. $attachment = null;
  108. if ( $this->is_attachment_with_mime_type( $instance['attachment_id'], $this->widget_options['mime_type'] ) ) {
  109. $attachment = get_post( $instance['attachment_id'] );
  110. }
  111. $src = $instance['url'];
  112. if ( $attachment ) {
  113. $src = wp_get_attachment_url( $attachment->ID );
  114. }
  115. if ( empty( $src ) ) {
  116. return;
  117. }
  118. $youtube_pattern = '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#';
  119. $vimeo_pattern = '#^https?://(.+\.)?vimeo\.com/.*#';
  120. if ( $attachment || preg_match( $youtube_pattern, $src ) || preg_match( $vimeo_pattern, $src ) ) {
  121. add_filter( 'wp_video_shortcode', array( $this, 'inject_video_max_width_style' ) );
  122. echo wp_video_shortcode(
  123. array_merge(
  124. $instance,
  125. compact( 'src' )
  126. ),
  127. $instance['content']
  128. );
  129. remove_filter( 'wp_video_shortcode', array( $this, 'inject_video_max_width_style' ) );
  130. } else {
  131. echo $this->inject_video_max_width_style( wp_oembed_get( $src ) );
  132. }
  133. }
  134. /**
  135. * Inject max-width and remove height for videos too constrained to fit inside sidebars on frontend.
  136. *
  137. * @since 4.8.0
  138. *
  139. * @param string $html Video shortcode HTML output.
  140. * @return string HTML Output.
  141. */
  142. public function inject_video_max_width_style( $html ) {
  143. $html = preg_replace( '/\sheight="\d+"/', '', $html );
  144. $html = preg_replace( '/\swidth="\d+"/', '', $html );
  145. $html = preg_replace( '/(?<=width:)\s*\d+px(?=;?)/', '100%', $html );
  146. return $html;
  147. }
  148. /**
  149. * Enqueue preview scripts.
  150. *
  151. * These scripts normally are enqueued just-in-time when a video shortcode is used.
  152. * In the customizer, however, widgets can be dynamically added and rendered via
  153. * selective refresh, and so it is important to unconditionally enqueue them in
  154. * case a widget does get added.
  155. *
  156. * @since 4.8.0
  157. */
  158. public function enqueue_preview_scripts() {
  159. /** This filter is documented in wp-includes/media.php */
  160. if ( 'mediaelement' === apply_filters( 'wp_video_shortcode_library', 'mediaelement' ) ) {
  161. wp_enqueue_style( 'wp-mediaelement' );
  162. wp_enqueue_script( 'mediaelement-vimeo' );
  163. wp_enqueue_script( 'wp-mediaelement' );
  164. }
  165. }
  166. /**
  167. * Loads the required scripts and styles for the widget control.
  168. *
  169. * @since 4.8.0
  170. */
  171. public function enqueue_admin_scripts() {
  172. parent::enqueue_admin_scripts();
  173. $handle = 'media-video-widget';
  174. wp_enqueue_script( $handle );
  175. $exported_schema = array();
  176. foreach ( $this->get_instance_schema() as $field => $field_schema ) {
  177. $exported_schema[ $field ] = wp_array_slice_assoc( $field_schema, array( 'type', 'default', 'enum', 'minimum', 'format', 'media_prop', 'should_preview_update' ) );
  178. }
  179. wp_add_inline_script(
  180. $handle,
  181. sprintf(
  182. 'wp.mediaWidgets.modelConstructors[ %s ].prototype.schema = %s;',
  183. wp_json_encode( $this->id_base ),
  184. wp_json_encode( $exported_schema )
  185. )
  186. );
  187. wp_add_inline_script(
  188. $handle,
  189. sprintf(
  190. '
  191. wp.mediaWidgets.controlConstructors[ %1$s ].prototype.mime_type = %2$s;
  192. wp.mediaWidgets.controlConstructors[ %1$s ].prototype.l10n = _.extend( {}, wp.mediaWidgets.controlConstructors[ %1$s ].prototype.l10n, %3$s );
  193. ',
  194. wp_json_encode( $this->id_base ),
  195. wp_json_encode( $this->widget_options['mime_type'] ),
  196. wp_json_encode( $this->l10n )
  197. )
  198. );
  199. }
  200. /**
  201. * Render form template scripts.
  202. *
  203. * @since 4.8.0
  204. */
  205. public function render_control_template_scripts() {
  206. parent::render_control_template_scripts()
  207. ?>
  208. <script type="text/html" id="tmpl-wp-media-widget-video-preview">
  209. <# if ( data.error && 'missing_attachment' === data.error ) { #>
  210. <div class="notice notice-error notice-alt notice-missing-attachment">
  211. <p><?php echo $this->l10n['missing_attachment']; ?></p>
  212. </div>
  213. <# } else if ( data.error && 'unsupported_file_type' === data.error ) { #>
  214. <div class="notice notice-error notice-alt notice-missing-attachment">
  215. <p><?php echo $this->l10n['unsupported_file_type']; ?></p>
  216. </div>
  217. <# } else if ( data.error ) { #>
  218. <div class="notice notice-error notice-alt">
  219. <p><?php _e( 'Unable to preview media due to an unknown error.' ); ?></p>
  220. </div>
  221. <# } else if ( data.is_oembed && data.model.poster ) { #>
  222. <a href="{{ data.model.src }}" target="_blank" class="media-widget-video-link">
  223. <img src="{{ data.model.poster }}" />
  224. </a>
  225. <# } else if ( data.is_oembed ) { #>
  226. <a href="{{ data.model.src }}" target="_blank" class="media-widget-video-link no-poster">
  227. <span class="dashicons dashicons-format-video"></span>
  228. </a>
  229. <# } else if ( data.model.src ) { #>
  230. <?php wp_underscore_video_template(); ?>
  231. <# } #>
  232. </script>
  233. <?php
  234. }
  235. }