diff --git a/wp-admin/includes/ajax-actions.php b/wp-admin/includes/ajax-actions.php index 4244a7b368..7d7d9ed6ec 100644 --- a/wp-admin/includes/ajax-actions.php +++ b/wp-admin/includes/ajax-actions.php @@ -2019,6 +2019,8 @@ function wp_ajax_send_attachment_to_editor() { $caption = isset( $attachment['post_excerpt'] ) ? $attachment['post_excerpt'] : ''; $title = ''; // We no longer insert title tags into tags, as they are redundant. $html = get_image_send_to_editor( $id, $caption, $title, $align, $url, (bool) $rel, $size, $alt ); + } elseif ( 'video' === substr( $post->post_mime_type, 0, 5 ) || 'audio' === substr( $post->post_mime_type, 0, 5 ) ) { + $html = stripslashes_deep( $_POST['html'] ); } $html = apply_filters( 'media_send_to_editor', $html, $id, $attachment ); diff --git a/wp-includes/functions.php b/wp-includes/functions.php index e0b8cb8fd9..ca414ea470 100644 --- a/wp-includes/functions.php +++ b/wp-includes/functions.php @@ -4020,3 +4020,23 @@ $("#wp-auth-check-form iframe").load(function(){ ' ) ); } + +/** + * Return RegEx body to liberally match an opening HTML tag that: + * 1. Is self-closing or + * 2. Has no body but has a closing tag of the same name or + * 3. Contains a body and a closing tag of the same name + * + * Note: this RegEx does not balance inner tags and does not attempt to produce valid HTML + * + * @since 3.6.0 + * + * @param string $tag An HTML tag name. Example: 'video' + * @return string + */ +function get_tag_regex( $tag ) { + if ( empty( $tag ) ) + return; + + return sprintf( '(<%1$s[^>]*(?:/?>$|>[\s\S]*?))', tag_escape( $tag ) ); +} \ No newline at end of file diff --git a/wp-includes/js/media-editor.js b/wp-includes/js/media-editor.js index 15eff8da5c..7dbbb79f7d 100644 --- a/wp-includes/js/media-editor.js +++ b/wp-includes/js/media-editor.js @@ -66,7 +66,8 @@ src: size.url, captionId: 'attachment_' + attachment.id }); - + } else if ( 'video' === attachment.type || 'audio' === attachment.type ) { + _.extend( props, _.pick( attachment, 'title', 'type', 'icon', 'mime' ) ); // Format properties for non-images. } else { props.title = props.title || attachment.filename; @@ -95,6 +96,89 @@ return wp.html.string( options ); }, + audio: function( props, attachment ) { + var shortcode, html; + + props = wp.media.string.props( props, attachment ); + + shortcode = {}; + + if ( props.mime ) { + switch ( props.mime ) { + case 'audio/mpeg': + if ( props.linkUrl.indexOf( 'mp3' ) ) + shortcode.mp3 = props.linkUrl; + else if ( props.linkUrl.indexOf( 'm4a' ) ) + shortcode.m4a = props.linkUrl; + break; + case 'audio/mp3': + shortcode.mp3 = props.linkUrl; + break; + case 'audio/m4a': + shortcode.m4a = props.linkUrl; + break; + case 'audio/wav': + shortcode.wav = props.linkUrl; + break; + case 'audio/ogg': + shortcode.ogg = props.linkUrl; + break; + case 'audio/x-ms-wma': + case 'audio/wma': + shortcode.wma = props.linkUrl; + break; + } + } + + html = wp.shortcode.string({ + tag: 'audio', + attrs: shortcode + }); + + return html; + }, + + video: function( props, attachment ) { + var shortcode, html; + + props = wp.media.string.props( props, attachment ); + + shortcode = {}; + + if ( props.mime ) { + switch ( props.mime ) { + case 'video/mp4': + shortcode.mp4 = props.linkUrl; + break; + case 'video/m4v': + shortcode.m4v = props.linkUrl; + break; + case 'video/webm': + shortcode.webm = props.linkUrl; + break; + case 'video/ogg': + shortcode.ogv = props.linkUrl; + break; + case 'video/x-ms-wmv': + case 'video/wmv': + case 'video/asf': + shortcode.wmv = props.linkUrl; + break; + case 'video/flv': + case 'video/x-flv': + shortcode.flv = props.linkUrl; + break; + } + } + + html = wp.shortcode.string({ + tag: 'video', + attrs: shortcode + }); + + return html; + }, + image: function( props, attachment ) { var img = {}, options, classes, shortcode, html; @@ -575,7 +659,10 @@ if ( props[ prop ] ) options[ option ] = props[ prop ]; }); - + } else if ( 'video' === attachment.type ) { + html = wp.media.string.video( props ); + } else if ( 'audio' === attachment.type ) { + html = wp.media.string.audio( props ); } else { html = wp.media.string.link( props ); options.post_title = props.title; diff --git a/wp-includes/media.php b/wp-includes/media.php index 319980fab7..cdcdd23fd3 100644 --- a/wp-includes/media.php +++ b/wp-includes/media.php @@ -805,6 +805,233 @@ function gallery_shortcode($attr) { return $output; } +/** + * Provide a No-JS Flash fallback as a last resort for audio / video + * + * @since 3.6.0 + * + * @param string $url + * @return string Fallback HTML + */ +function wp_mediaelement_fallback( $url ) { + return apply_filters( 'wp_mediaelement_fallback', sprintf( '%1$s', esc_url( $url ) ), $url ); +} + +/** + * Return a filtered list of WP-supported audio formats + * + * @since 3.6.0 + * @return array + */ +function wp_get_audio_extensions() { + return apply_filters( 'wp_audio_extensions', array( 'mp3', 'ogg', 'wma', 'm4a', 'wav' ) ); +} + +/** + * The Audio shortcode. + * + * This implements the functionality of the Audio Shortcode for displaying + * WordPress mp3s in a post. + * + * @since 3.6.0 + * + * @param array $attr Attributes of the shortcode. + * @return string HTML content to display audio. + */ +function wp_audio_shortcode( $attr ) { + $post_id = get_post() ? get_the_ID() : 0; + + static $instances = 0; + $instances++; + + $audio = null; + + $default_types = wp_get_audio_extensions(); + $defaults_atts = array( 'src' => '' ); + foreach ( $default_types as $type ) + $defaults_atts[$type] = ''; + + $atts = shortcode_atts( $defaults_atts, $attr ); + extract( $atts ); + + $primary = false; + if ( ! empty( $src ) ) { + $type = wp_check_filetype( $src ); + if ( ! in_array( $type['ext'], $default_types ) ) { + printf( '%1$s', $src ); + return; + } + $primary = true; + array_unshift( $default_types, 'src' ); + } else { + foreach ( $default_types as $ext ) { + if ( ! empty( $$ext ) ) { + $type = wp_check_filetype( $$ext ); + if ( $type['ext'] === $ext ) + $primary = true; + } + } + } + + if ( ! $primary ) { + $audios = get_post_audio( $post_id ); + if ( empty( $audios ) ) + return; + + $audio = reset( $audios ); + $src = wp_get_attachment_url( $audio->ID ); + if ( empty( $src ) ) + return; + + array_unshift( $default_types, 'src' ); + } + + $library = apply_filters( 'wp_audio_shortcode_library', 'mediaelement' ); + if ( 'mediaelement' === $library ) { + wp_enqueue_style( 'wp-mediaelement' ); + wp_enqueue_script( 'wp-mediaelement' ); + } + + $atts = array( + sprintf( 'class="%s"', apply_filters( 'wp_audio_shortcode_class', 'wp-audio-shortcode' ) ), + sprintf( 'id="audio-%d-%d"', $post_id, $instances ), + ); + + $html = sprintf( ''; + + return apply_filters( 'wp_audio_shortcode', $html, $atts, $audio, $post_id ); +} +add_shortcode( 'audio', apply_filters( 'wp_audio_shortcode_handler', 'wp_audio_shortcode' ) ); + +/** + * Return a filtered list of WP-supported video formats + * + * @since 3.6.0 + * @return array + */ +function wp_get_video_extensions() { + return apply_filters( 'wp_video_extensions', array( 'mp4', 'm4v', 'webm', 'ogv', 'wmv', 'flv' ) ); +} + +/** + * The Video shortcode. + * + * This implements the functionality of the Video Shortcode for displaying + * WordPress mp4s in a post. + * + * @since 3.6.0 + * + * @param array $attr Attributes of the shortcode. + * @return string HTML content to display video. + */ +function wp_video_shortcode( $attr ) { + global $content_width; + $post_id = get_post() ? get_the_ID() : 0; + + static $instances = 0; + $instances++; + + $video = null; + + $default_types = wp_get_video_extensions(); + $defaults_atts = array( + 'src' => '', + 'poster' => '', + 'height' => 360, + 'width' => empty( $content_width ) ? 640 : $content_width, + ); + foreach ( $default_types as $type ) + $defaults_atts[$type] = ''; + + $atts = shortcode_atts( $defaults_atts, $attr ); + extract( $atts ); + + $primary = false; + if ( ! empty( $src ) ) { + $type = wp_check_filetype( $src ); + if ( ! in_array( $type['ext'], $default_types ) ) { + printf( '%1$s', $src ); + return; + } + $primary = true; + array_unshift( $default_types, 'src' ); + } else { + foreach ( $default_types as $ext ) { + if ( ! empty( $$ext ) ) { + $type = wp_check_filetype( $$ext ); + if ( $type['ext'] === $ext ) + $primary = true; + } + } + } + + if ( ! $primary ) { + $videos = get_post_video( $post_id ); + if ( empty( $videos ) ) + return; + + $video = reset( $videos ); + $src = wp_get_attachment_url( $video->ID ); + if ( empty( $src ) ) + return; + + array_unshift( $default_types, 'src' ); + } + + $library = apply_filters( 'wp_video_shortcode_library', 'mediaelement' ); + if ( 'mediaelement' === $library ) { + wp_enqueue_style( 'wp-mediaelement' ); + wp_enqueue_script( 'wp-mediaelement' ); + } + + $atts = array( + sprintf( 'class="%s"', apply_filters( 'wp_video_shortcode_class', 'wp-video-shortcode' ) ), + sprintf( 'id="video-%d-%d"', $post_id, $instances ), + sprintf( 'width="%d"', $width ), + sprintf( 'height="%d"', $height ), + ); + + if ( ! empty( $poster ) ) + $atts[] = sprintf( 'poster="%s"', esc_url( $poster ) ); + + $html = sprintf( ''; + + return apply_filters( 'wp_video_shortcode', $html, $atts, $video, $post_id ); +} +add_shortcode( 'video', apply_filters( 'wp_video_shortcode_handler', 'wp_video_shortcode' ) ); + /** * Display previous image link that has the same post parent. * @@ -1545,3 +1772,91 @@ function wp_enqueue_media( $args = array() ) { do_action( 'wp_enqueue_media' ); } + +/** + * Retrieve audio attached to the passed post + * + * @since 3.6.0 + * + * @param int $post_id Post ID + * @return array Found audio attachments + */ +function get_post_audio( $post_id = 0 ) { + $post = empty( $post_id ) ? get_post() : get_post( $post_id ); + if ( empty( $post ) ) + return; + + $children = get_children( array( + 'post_parent' => $post->ID, + 'post_type' => 'attachment', + 'post_mime_type' => 'audio', + 'posts_per_page' => -1 + ) ); + + if ( ! empty( $children ) ) + return $children; +} + +/** + * Retrieve video attached to the passed post + * + * @since 3.6.0 + * + * @param int $post_id Post ID + * @return array Found video attachments + */ +function get_post_video( $post_id = 0 ) { + $post = empty( $post_id ) ? get_post() : get_post( $post_id ); + if ( empty( $post ) ) + return; + + $children = get_children( array( + 'post_parent' => $post->ID, + 'post_type' => 'attachment', + 'post_mime_type' => 'video', + 'posts_per_page' => -1 + ) ); + + if ( ! empty( $children ) ) + return $children; +} + +/** + * Audio embed handler callback. + * + * @param array $matches The regex matches from the provided regex when calling {@link wp_embed_register_handler()}. + * @param array $attr Embed attributes. + * @param string $url The original URL that was matched by the regex. + * @param array $rawattr The original unmodified attributes. + * @return string The embed HTML. + */ +function wp_audio_embed( $matches, $attr, $url, $rawattr ) { + $audio = $url; + if ( shortcode_exists( 'audio' ) ) + $audio = do_shortcode( '[audio src="' . $url . '" /]' ); + return apply_filters( 'wp_audio_embed', $audio, $attr, $url, $rawattr ); +} +wp_embed_register_handler( 'wp_audio_embed', '#https?://.+?\.(' . join( '|', wp_get_audio_extensions() ) . ')#i', apply_filters( 'wp_audio_embed_handler', 'wp_audio_embed' ), 9999 ); + +/** + * Video embed handler callback. + * + * @param array $matches The regex matches from the provided regex when calling {@link wp_embed_register_handler()}. + * @param array $attr Embed attributes. + * @param string $url The original URL that was matched by the regex. + * @param array $rawattr The original unmodified attributes. + * @return string The embed HTML. + */ +function wp_video_embed( $matches, $attr, $url, $rawattr ) { + $dimensions = ''; + $video = $url; + if ( shortcode_exists( 'video' ) ) { + if ( ! empty( $rawattr['width'] ) && ! empty( $rawattr['height'] ) ) { + $dimensions .= sprintf( 'width="%d" ', (int) $rawattr['width'] ); + $dimensions .= sprintf( 'height="%d" ', (int) $rawattr['height'] ); + } + $video = do_shortcode( '[video ' . $dimensions . 'src="' . $url . '" /]' ); + } + return apply_filters( 'wp_video_embed', $video, $attr, $url, $rawattr ); +} +wp_embed_register_handler( 'wp_video_embed', '#https?://.+?\.(' . join( '|', wp_get_video_extensions() ) . ')#i', apply_filters( 'wp_video_embed_handler', 'wp_video_embed' ), 9999 ); diff --git a/wp-includes/mediaelement/background.png b/wp-includes/mediaelement/background.png new file mode 100644 index 0000000000..fd428412ae Binary files /dev/null and b/wp-includes/mediaelement/background.png differ diff --git a/wp-includes/mediaelement/bigplay.png b/wp-includes/mediaelement/bigplay.png new file mode 100644 index 0000000000..694553e31c Binary files /dev/null and b/wp-includes/mediaelement/bigplay.png differ diff --git a/wp-includes/mediaelement/bigplay.svg b/wp-includes/mediaelement/bigplay.svg new file mode 100644 index 0000000000..c2f62bbc0d --- /dev/null +++ b/wp-includes/mediaelement/bigplay.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/wp-includes/mediaelement/controls.png b/wp-includes/mediaelement/controls.png new file mode 100644 index 0000000000..f6a857d800 Binary files /dev/null and b/wp-includes/mediaelement/controls.png differ diff --git a/wp-includes/mediaelement/controls.svg b/wp-includes/mediaelement/controls.svg new file mode 100644 index 0000000000..af3bd41606 --- /dev/null +++ b/wp-includes/mediaelement/controls.svg @@ -0,0 +1 @@ + cc \ No newline at end of file diff --git a/wp-includes/mediaelement/flashmediaelement.swf b/wp-includes/mediaelement/flashmediaelement.swf new file mode 100644 index 0000000000..3e347aa23b Binary files /dev/null and b/wp-includes/mediaelement/flashmediaelement.swf differ diff --git a/wp-includes/mediaelement/loading.gif b/wp-includes/mediaelement/loading.gif new file mode 100644 index 0000000000..612222be5e Binary files /dev/null and b/wp-includes/mediaelement/loading.gif differ diff --git a/wp-includes/mediaelement/mediaelement-and-player.js b/wp-includes/mediaelement/mediaelement-and-player.js new file mode 100644 index 0000000000..3601e4caae --- /dev/null +++ b/wp-includes/mediaelement/mediaelement-and-player.js @@ -0,0 +1,4649 @@ +/*! +* MediaElement.js +* HTML5