Ticket #23282: 23282.10.diff

File 23282.10.diff, 20.6 KB (added by wonderboymusic, 3 months ago)
Line 
1diff --git wp-admin/includes/ajax-actions.php wp-admin/includes/ajax-actions.php
2index 8581c9e..93a897f 100644
3--- wp-admin/includes/ajax-actions.php
4+++ wp-admin/includes/ajax-actions.php
5@@ -2024,6 +2024,8 @@ function wp_ajax_send_attachment_to_editor() {
6                $caption = isset( $attachment['post_excerpt'] ) ? $attachment['post_excerpt'] : '';
7                $title = ''; // We no longer insert title tags into <img> tags, as they are redundant.
8                $html = get_image_send_to_editor( $id, $caption, $title, $align, $url, (bool) $rel, $size, $alt );
9+       } elseif ( 'video' === substr( $post->post_mime_type, 0, 5 ) || 'audio' === substr( $post->post_mime_type, 0, 5 )  ) {
10+               $html = stripslashes_deep( $_POST['html'] );
11        }
12 
13        $html = apply_filters( 'media_send_to_editor', $html, $id, $attachment );
14diff --git wp-includes/functions.php wp-includes/functions.php
15index b29b941..78dedb6 100644
16--- wp-includes/functions.php
17+++ wp-includes/functions.php
18@@ -4020,3 +4020,23 @@ $("#wp-auth-check-form iframe").load(function(){
19 </script>
20 </div>' ) );
21 }
22+
23+/**
24+ * Return RegEx body to liberally match an opening HTML tag that:
25+ * 1. Is self-closing or
26+ * 2. Has no body but has a closing tag of the same name or
27+ * 3. Contains a body and a closing tag of the same name
28+ *
29+ * Note: this RegEx does not balance inner tags and does not attempt to produce valid HTML
30+ *
31+ * @since 3.6.0
32+ *
33+ * @param string $tag An HTML tag name. Example: 'video'
34+ * @return string
35+ */
36+function get_tag_regex( $tag ) {
37+       if ( empty( $tag ) )
38+               return;
39+
40+       return sprintf( '(<%1$s[^>]*(?:/?>$|>[\s\S]*?</%1$s>))', tag_escape( $tag ) );
41+}
42\ No newline at end of file
43diff --git wp-includes/js/media-editor.js wp-includes/js/media-editor.js
44index 15eff8d..7dbbb79 100644
45--- wp-includes/js/media-editor.js
46+++ wp-includes/js/media-editor.js
47@@ -66,7 +66,8 @@
48                                        src:       size.url,
49                                        captionId: 'attachment_' + attachment.id
50                                });
51-
52+                       } else if ( 'video' === attachment.type || 'audio' === attachment.type ) {
53+                               _.extend( props, _.pick( attachment, 'title', 'type', 'icon', 'mime' ) );
54                        // Format properties for non-images.
55                        } else {
56                                props.title = props.title || attachment.filename;
57@@ -95,6 +96,89 @@
58                        return wp.html.string( options );
59                },
60 
61+               audio: function( props, attachment ) {
62+                       var shortcode, html;
63+
64+                       props = wp.media.string.props( props, attachment );
65+
66+                       shortcode = {};
67+
68+                       if ( props.mime ) {
69+                               switch ( props.mime ) {
70+                               case 'audio/mpeg':
71+                                       if ( props.linkUrl.indexOf( 'mp3' ) )
72+                                               shortcode.mp3 = props.linkUrl;
73+                                       else if ( props.linkUrl.indexOf( 'm4a' ) )
74+                                               shortcode.m4a = props.linkUrl;
75+                                       break;
76+                               case 'audio/mp3':
77+                                       shortcode.mp3 = props.linkUrl;
78+                                       break;
79+                               case 'audio/m4a':
80+                                       shortcode.m4a = props.linkUrl;
81+                                       break;
82+                               case 'audio/wav':
83+                                       shortcode.wav = props.linkUrl;
84+                                       break;
85+                               case 'audio/ogg':
86+                                       shortcode.ogg = props.linkUrl;
87+                                       break;
88+                               case 'audio/x-ms-wma':
89+                               case 'audio/wma':
90+                                       shortcode.wma = props.linkUrl;
91+                                       break;
92+                               }
93+                       }
94+
95+                       html = wp.shortcode.string({
96+                               tag:     'audio',
97+                               attrs:   shortcode
98+                       });
99+
100+                       return html;
101+               },
102+
103+               video: function( props, attachment ) {
104+                       var shortcode, html;
105+
106+                       props = wp.media.string.props( props, attachment );
107+
108+                       shortcode = {};
109+
110+                       if ( props.mime ) {
111+                               switch ( props.mime ) {
112+                               case 'video/mp4':
113+                                       shortcode.mp4 = props.linkUrl;
114+                                       break;
115+                               case 'video/m4v':
116+                                       shortcode.m4v = props.linkUrl;
117+                                       break;
118+                               case 'video/webm':
119+                                       shortcode.webm = props.linkUrl;
120+                                       break;
121+                               case 'video/ogg':
122+                                       shortcode.ogv = props.linkUrl;
123+                                       break;
124+                               case 'video/x-ms-wmv':
125+                               case 'video/wmv':
126+                               case 'video/asf':
127+                                       shortcode.wmv = props.linkUrl;
128+                                       break;
129+                               case 'video/flv':
130+                               case 'video/x-flv':
131+                                       shortcode.flv = props.linkUrl;
132+                                       break;
133+                               }
134+                       }
135+
136+                       html = wp.shortcode.string({
137+                               tag:     'video',
138+                               attrs:   shortcode
139+                       });
140+
141+                       return html;
142+               },
143+
144                image: function( props, attachment ) {
145                        var img = {},
146                                options, classes, shortcode, html;
147@@ -575,7 +659,10 @@
148                                                if ( props[ prop ] )
149                                                        options[ option ] = props[ prop ];
150                                        });
151-
152+                               } else if ( 'video' === attachment.type ) {
153+                                       html = wp.media.string.video( props );
154+                               } else if ( 'audio' === attachment.type ) {
155+                                       html = wp.media.string.audio( props );
156                                } else {
157                                        html = wp.media.string.link( props );
158                                        options.post_title = props.title;
159diff --git wp-includes/media.php wp-includes/media.php
160index f1f3737..c4d5a56 100644
161--- wp-includes/media.php
162+++ wp-includes/media.php
163@@ -803,6 +803,204 @@ function gallery_shortcode($attr) {
164 }
165 
166 /**
167+ * Return a filtered list of WP-supported audio formats
168+ *
169+ * @since 3.6.0
170+ * @return array
171+ */
172+function wp_get_audio_extensions() {
173+       return apply_filters( 'wp_audio_extensions', array( 'mp3', 'ogg', 'wma', 'm4a', 'wav' ) );
174+}
175+
176+/**
177+ * The Audio shortcode.
178+ *
179+ * This implements the functionality of the Audio Shortcode for displaying
180+ * WordPress mp3s in a post.
181+ *
182+ * @since 3.6.0
183+ *
184+ * @param array $attr Attributes of the shortcode.
185+ * @return string HTML content to display audio.
186+ */
187+function wp_audio_shortcode( $attr ) {
188+       $post_id = get_post() ? get_the_ID() : 0;
189+
190+       static $instances = 0;
191+       $instances++;
192+
193+       $audio = null;
194+
195+       $default_types = wp_get_audio_extensions();
196+       $defaults_atts = array( 'src' => '' );
197+       foreach ( $default_types as $type  )
198+               $defaults_atts[$type] = '';
199+
200+       extract( shortcode_atts( $defaults_atts, $attr ) );
201+
202+       $primary = false;
203+       if ( ! empty( $src ) ) {
204+               $type = wp_check_filetype( $src );
205+               if ( ! in_array( $type['ext'], $default_types ) ) {
206+                       printf( '<a class="wp-post-format-link-audio" href="%1$s">%1$s</a>', $src );
207+                       return;
208+               }
209+               $primary = true;
210+               array_unshift( $default_types, 'src' );
211+       } else {
212+               foreach ( $default_types as $ext ) {
213+                       if ( ! empty( $$ext ) ) {
214+                               $type = wp_check_filetype( $$ext );
215+                               if ( $type['ext'] === $ext )
216+                                       $primary = true;
217+                       }
218+               }
219+       }
220+
221+       if ( ! $primary ) {
222+               $audios = get_post_audio( $post_id );
223+               if ( empty( $audios ) )
224+                       return;
225+
226+               $audio = reset( $audios );
227+               $src = wp_get_attachment_url( $audio->ID );
228+               if ( empty( $src ) )
229+                       return;
230+
231+               array_unshift( $default_types, 'src' );
232+       }
233+
234+       wp_enqueue_style( 'wp-mediaelement' );
235+       wp_enqueue_script( 'wp-mediaelement' );
236+
237+       $atts = array(
238+               sprintf( 'class="%s"', apply_filters( 'wp_audio_shortcode_class', 'wp-audio-shortcode' ) ),
239+               sprintf( 'id="audio-%d-%d"', $post_id, $instances ),
240+       );
241+
242+       $html = sprintf( '<audio %s controls="controls" preload="none">', join( ' ', $atts ) );
243+
244+       $source = '<source type="%s" src="%s" />';
245+       foreach ( $default_types as $fallback ) {
246+               if ( ! empty( $$fallback ) ) {
247+                       $type = wp_check_filetype( $$fallback );
248+                       $html .= sprintf( $source, $type['type'], $$fallback );
249+               }
250+       }
251+
252+       $html .= '</audio>';
253+
254+       return apply_filters( 'wp_audio_shortcode', $html, $src, $audio, $post_id );
255+}
256+add_shortcode( 'audio', 'wp_audio_shortcode' );
257+
258+/**
259+ * Return a filtered list of WP-supported video formats
260+ *
261+ * @since 3.6.0
262+ * @return array
263+ */
264+function wp_get_video_extensions() {
265+       return apply_filters( 'wp_video_extensions', array( 'mp4', 'm4v', 'webm', 'ogv', 'wmv', 'flv' ) );
266+}
267+
268+/**
269+ * The Video shortcode.
270+ *
271+ * This implements the functionality of the Video Shortcode for displaying
272+ * WordPress mp4s in a post.
273+ *
274+ * @since 3.6.0
275+ *
276+ * @param array $attr Attributes of the shortcode.
277+ * @return string HTML content to display video.
278+ */
279+function wp_video_shortcode( $attr ) {
280+       global $content_width;
281+       $post_id = get_post() ? get_the_ID() : 0;
282+
283+       static $instances = 0;
284+       $instances++;
285+
286+       $video = null;
287+
288+       $default_types = wp_get_video_extensions();
289+       $defaults_atts = array(
290+               'src' => '',
291+               'poster' => '',
292+               'height' => 360,
293+               'width' => empty( $content_width ) ? 640 : $content_width,
294+       );
295+       foreach ( $default_types as $type  )
296+               $defaults_atts[$type] = '';
297+
298+       extract( shortcode_atts( $defaults_atts, $attr ) );
299+
300+       $primary = false;
301+       if ( ! empty( $src ) ) {
302+               $type = wp_check_filetype( $src );
303+               if ( ! in_array( $type['ext'], $default_types ) ) {
304+                       printf( '<a class="wp-post-format-link-video" href="%1$s">%1$s</a>', $src );
305+                       return;
306+               }
307+               $primary = true;
308+               array_unshift( $default_types, 'src' );
309+       } else {
310+               foreach ( $default_types as $ext ) {
311+                       if ( ! empty( $$ext ) ) {
312+                               $type = wp_check_filetype( $$ext );
313+                               if ( $type['ext'] === $ext )
314+                                       $primary = true;
315+                       }
316+               }
317+       }
318+
319+       if ( ! $primary ) {
320+               $videos = get_post_video( $post_id );
321+               if ( empty( $videos ) )
322+                       return;
323+
324+               $video = reset( $videos );
325+               $src = wp_get_attachment_url( $video->ID );
326+               if ( empty( $src ) )
327+                       return;
328+
329+               array_unshift( $default_types, 'src' );
330+       }
331+
332+       wp_enqueue_style( 'wp-mediaelement' );
333+       wp_enqueue_script( 'wp-mediaelement' );
334+
335+       $atts = array(
336+               sprintf( 'class="%s"', apply_filters( 'video_shortcode_class', 'wp-video-shortcode' ) ),
337+               sprintf( 'id="video-%d-%d"', $post_id, $instances ),
338+               sprintf( 'width="%d"', $width ),
339+               sprintf( 'height="%d"', $height ),
340+       );
341+
342+       if ( ! empty( $poster ) )
343+               $atts[] = sprintf( 'poster="%s"', esc_url( $poster ) );
344+
345+       $html = sprintf( '<video %s controls="controls" preload="none">', join( ' ', $atts ) );
346+
347+       $source = '<source type="%s" src="%s" />';
348+       foreach ( $default_types as $fallback ) {
349+               if ( ! empty( $$fallback ) ) {
350+                       $type = wp_check_filetype( $$fallback );
351+                       // m4v sometimes shows up as video/mpeg which collides with mp4
352+                       if ( 'm4v' === $type['ext'] )
353+                               $type['type'] = 'video/m4v';
354+                       $html .= sprintf( $source, $type['type'], $$fallback );
355+               }
356+       }
357+
358+       $html .= '</video>';
359+
360+       return apply_filters( 'wp_video_shortcode', $html, $src, $video, $post_id );
361+}
362+add_shortcode( 'video', 'wp_video_shortcode' );
363+
364+/**
365  * Display previous image link that has the same post parent.
366  *
367  * @since 2.5.0
368@@ -1542,3 +1740,217 @@ function wp_enqueue_media( $args = array() ) {
369 
370        do_action( 'wp_enqueue_media' );
371 }
372+
373+/**
374+ * Retrieve audio attached to the passed post
375+ *
376+ * @since 3.6.0
377+ *
378+ * @param int $post_id  Post ID
379+ * @return array Found audio attachments
380+ */
381+function get_post_audio( $post_id = 0 ) {
382+       $post = empty( $post_id ) ? get_post() : get_post( $post_id );
383+       if ( empty( $post ) )
384+               return;
385+
386+       $children = get_children( array(
387+               'post_parent' => $post->ID,
388+               'post_type' => 'attachment',
389+               'post_mime_type' => 'audio',
390+               'posts_per_page' => -1
391+       ) );
392+
393+       if ( ! empty( $children ) )
394+               return $children;
395+}
396+
397+/**
398+ * Check the content blob for an <audio>, <object>, <embed>, or <iframe>, in that order
399+ * If no HTML tag is found, check the first line of the post for a URL
400+ *
401+ * @param string $content A string which might contain audio data.
402+ * @param boolean $remove Whether to remove the found URL from the passed content.
403+ * @return string The found data
404+ */
405+function get_content_audio( &$content, $remove = false ) {
406+       $html = $matches = '';
407+       foreach ( array( 'audio', 'object', 'embed', 'iframe' ) as $tag ) {
408+               if ( preg_match( '#' . get_tag_regex( $tag ) . '#i', $content, $matches ) ) {
409+                       $html = $matches[1];
410+                       $count = 1;
411+                       if ( $remove )
412+                               $content = str_replace( $matches[0], '', $content, $count );
413+
414+                       return $html;
415+               }
416+       }
417+
418+       $lines = explode( "\n", trim( $content ) );
419+       $line = trim( array_shift( $lines ) );
420+
421+       if ( 0 === stripos( $line, 'http' ) ) {
422+               if ( $remove )
423+                       $content = join( "\n", $lines );
424+
425+               return $line;
426+       }
427+}
428+
429+/**
430+ * Return the found audio data for the passed post
431+ *
432+ * @since 3.6.0
433+ *
434+ * @param int $id Optional. Post ID
435+ */
436+function get_the_audio( $id = 0 ) {
437+       $post = empty( $id ) ? get_post() : get_post( $id );
438+       if ( empty( $post ) )
439+               return array();
440+
441+       $data = get_content_audio( $post->post_content );
442+       if ( ! empty( $data ) )
443+               return $data;
444+
445+       $audios = get_post_audio( $post->ID );
446+       if ( empty( $audios ) )
447+               return array();
448+
449+       $audio = reset( $audios );
450+       return wp_get_attachment_url( $audio->ID );
451+}
452+
453+/**
454+ * Output the found audio data for the current post
455+ *
456+ * @since 3.6.0
457+ */
458+function the_audio() {
459+       echo apply_filters( 'the_audio', get_the_audio() );
460+}
461+
462+/**
463+ * Retrieve video attached to the passed post
464+ *
465+ * @since 3.6.0
466+ *
467+ * @param int $post_id  Post ID
468+ * @return array Found video attachments
469+ */
470+function get_post_video( $post_id = 0 ) {
471+       $post = empty( $post_id ) ? get_post() : get_post( $post_id );
472+       if ( empty( $post ) )
473+               return;
474+
475+       $children = get_children( array(
476+               'post_parent' => $post->ID,
477+               'post_type' => 'attachment',
478+               'post_mime_type' => 'video',
479+               'posts_per_page' => -1
480+       ) );
481+
482+       if ( ! empty( $children ) )
483+               return $children;
484+}
485+
486+/**
487+ * Check the content blob for a <video>, <object>, <embed>, or <iframe>, in that order
488+ * If no HTML tag is found, check the first line of the post for a URL
489+ *
490+ * @param string $content A string which might contain video data.
491+ * @param boolean $remove Whether to remove the found URL from the passed content.
492+ * @return string The found data
493+ */
494+function get_content_video( &$content, $remove = false ) {
495+       $html = $matches = '';
496+       foreach ( array( 'video', 'object', 'embed', 'iframe' ) as $tag ) {
497+               if ( preg_match( '#' . get_tag_regex( $tag ) . '#i', $content, $matches ) ) {
498+                       $html = $matches[1];
499+                       $count = 1;
500+                       if ( $remove )
501+                               $content = str_replace( $matches[0], '', $content, $count );
502+
503+                       return $html;
504+               }
505+       }
506+
507+       $lines = explode( "\n", trim( $content ) );
508+       $line = trim( array_shift( $lines  ) );
509+
510+       if ( 0 === stripos( $line, 'http' ) ) {
511+               if ( $remove )
512+                       $content = join( "\n", $lines );
513+
514+               return $line;
515+       }
516+}
517+
518+/**
519+ * Return the found video data for the passed post
520+ *
521+ * @since 3.6.0
522+ *
523+ * @param int $id Optional. Post ID
524+ * @return string A video URL or embed code, if present.
525+ */
526+function get_the_video( $id = 0 ) {
527+       $post = empty( $id ) ? get_post() : get_post( $id );
528+       if ( empty( $post ) )
529+               return '';
530+
531+       $data = get_content_video( $post->post_content );
532+       if ( ! empty( $data ) )
533+               return $data;
534+
535+       $videos = get_post_video( $post->ID );
536+       if ( empty( $videos ) )
537+               return '';
538+
539+       $video = reset( $videos );
540+       return wp_get_attachment_url( $video->ID );
541+}
542+
543+/**
544+ * Output the found video data for the current post
545+ *
546+ * @since 3.6.0
547+ */
548+function the_video() {
549+       echo apply_filters( 'the_video', get_the_video() );
550+}
551+
552+/**
553+ * Audio embed handler callback.
554+ *
555+ * @param array $matches The regex matches from the provided regex when calling {@link wp_embed_register_handler()}.
556+ * @param array $attr Embed attributes.
557+ * @param string $url The original URL that was matched by the regex.
558+ * @param array $rawattr The original unmodified attributes.
559+ * @return string The embed HTML.
560+ */
561+function wp_audio_embed( $matches, $attr, $url, $rawattr ) {
562+       $audio = do_shortcode( '[audio src="' . $url . '" /]' );
563+       return apply_filters( 'wp_audio_embed', $audio, $attr, $url, $rawattr );
564+}
565+wp_embed_register_handler( 'wp_audio_embed', '#https?://.+?\.(' . join( '|', wp_get_audio_extensions() ) . ')#i', 'wp_audio_embed', 9999 );
566+
567+/**
568+ * Video embed handler callback.
569+ *
570+ * @param array $matches The regex matches from the provided regex when calling {@link wp_embed_register_handler()}.
571+ * @param array $attr Embed attributes.
572+ * @param string $url The original URL that was matched by the regex.
573+ * @param array $rawattr The original unmodified attributes.
574+ * @return string The embed HTML.
575+ */
576+function wp_video_embed( $matches, $attr, $url, $rawattr ) {
577+       $dimensions = '';
578+       if ( ! empty( $rawattr['width'] ) && ! empty( $rawattr['height'] ) ) {
579+               $dimensions .= sprintf( 'width="%d" ', (int) $rawattr['width'] );
580+               $dimensions .= sprintf( 'height="%d" ', (int) $rawattr['height'] );
581+       }
582+       $video = do_shortcode( '[video ' . $dimensions . 'src="' . $url . '" /]' );
583+       return apply_filters( 'wp_video_embed', $video, $attr, $url, $rawattr );
584+}
585+wp_embed_register_handler( 'wp_video_embed', '#https?://.+?\.(' . join( '|', wp_get_video_extensions() ) . ')#i', 'wp_video_embed', 9999 );
586\ No newline at end of file
587diff --git wp-includes/post-formats.php wp-includes/post-formats.php
588index 828447b..7dfc171 100644
589--- wp-includes/post-formats.php
590+++ wp-includes/post-formats.php
591@@ -308,6 +308,8 @@ function post_formats_compat( $content, $id = 0 ) {
592        $show_content = true;
593        $format_output = '';
594        $meta = get_post_format_meta( $post->ID );
595+       // passed by ref in preg_match()
596+       $matches = array();
597 
598        switch ( $format ) {
599                case 'link':
600@@ -365,34 +367,21 @@ function post_formats_compat( $content, $id = 0 ) {
601                        break;
602 
603                case 'gallery':
604-                       preg_match_all( '/' . get_shortcode_regex() . '/s', $content, $matches );
605-                       if ( ! empty( $matches ) && isset( $matches[2] ) ) {
606-                               foreach ( (array) $matches[2] as $match ) {
607-                                       if ( 'gallery' === $match )
608-                                               break 2; // foreach + case
609-                               }
610-                       }
611-
612-                       if ( ! empty( $meta['gallery'] ) ) {
613+                       if ( ! has_shortcode( $post->post_content, $format ) && ! empty( $meta['gallery'] ) )
614                                $format_output .= $meta['gallery'];
615-                       }
616                        break;
617 
618                case 'video':
619                case 'audio':
620-                       $shortcode_regex = '/' . get_shortcode_regex() . '/s';
621-                       $matches = preg_match( $shortcode_regex, $content );
622-                       if ( ! $matches || $format !== $matches[2] ) {
623-                               if ( ! empty( $meta['media'] ) ) {
624-                                       // the metadata is a shortcode or an embed code
625-                                       if ( preg_match( $shortcode_regex, $meta['media'] ) || preg_match( '#<[^>]+>#', $meta['media'] ) ) {
626-                                               $format_output .= $meta['media'];
627-                                       } elseif ( ! stristr( $content, $meta['media'] ) ) {
628-                                               // attempt to embed the URL
629-                                               $format_output .= sprintf( '[embed]%s[/embed]', $meta['media'] );
630-                                       }
631+                       if ( ! has_shortcode( $post->post_content, $format ) && ! empty( $meta['media'] ) ) {
632+                               // the metadata is a shortcode or an embed code
633+                               if ( preg_match( '/' . get_shortcode_regex() . '/s', $meta['media'] ) || preg_match( '#<[^>]+>#', $meta['media'] ) ) {
634+                                       $format_output .= $meta['media'];
635+                               } elseif ( ! stristr( $content, $meta['media'] ) ) {
636+                                       // attempt to embed the URL
637+                                       $format_output .= sprintf( '[embed]%s[/embed]', $meta['media'] );
638                                }
639-                       }
640+                       }
641                        break;
642                default:
643                        return $content;
644diff --git wp-includes/script-loader.php wp-includes/script-loader.php
645index b3bc26f..1b07594 100644
646--- wp-includes/script-loader.php
647+++ wp-includes/script-loader.php
648@@ -277,6 +277,9 @@ function wp_default_scripts( &$scripts ) {
649 
650        $scripts->add( 'imgareaselect', "/wp-includes/js/imgareaselect/jquery.imgareaselect$suffix.js", array('jquery'), '0.9.8', 1 );
651 
652+       $scripts->add( 'mediaelement', "/wp-includes/js/mediaelement/mediaelement-and-player$suffix.js", array('jquery'), '2.10.1', 1 );
653+       $scripts->add( 'wp-mediaelement', "/wp-includes/js/mediaelement/wp-mediaelement.js", array('mediaelement'), false, 1 );
654+
655        $scripts->add( 'password-strength-meter', "/wp-admin/js/password-strength-meter$suffix.js", array('jquery'), false, 1 );
656        did_action( 'init' ) && $scripts->localize( 'password-strength-meter', 'pwsL10n', array(
657                'empty' => __('Strength indicator'),
658@@ -543,6 +546,9 @@ function wp_default_styles( &$styles ) {
659        $styles->add( 'media-views', "/wp-includes/css/media-views$suffix.css", array( 'buttons' ) );
660        $styles->add( 'buttons', "/wp-includes/css/buttons$suffix.css" );
661 
662+       $styles->add( 'mediaelement', "/wp-includes/js/mediaelement/mediaelementplayer$suffix.css" );
663+       $styles->add( 'wp-mediaelement', "/wp-includes/js/mediaelement/wp-mediaelement.css", array( 'mediaelement' ) );
664+
665        foreach ( $rtl_styles as $rtl_style ) {
666                $styles->add_data( $rtl_style, 'rtl', true );
667                if ( $suffix && ! in_array( $rtl_style, $no_suffix ) )
668diff --git wp-includes/shortcodes.php wp-includes/shortcodes.php
669index 2dfc277..5389ed2 100644
670--- wp-includes/shortcodes.php
671+++ wp-includes/shortcodes.php
672@@ -128,6 +128,44 @@ function remove_all_shortcodes() {
673 }
674 
675 /**
676+ * Whether a registered shortcode exists named $tag
677+ *
678+ * @since 3.6.0
679+ *
680+ * @global array $shortcode_tags
681+ * @param string $tag
682+ * @return boolean
683+ */
684+function shortcode_exists( $tag ) {
685+       global $shortcode_tags;
686+       return array_key_exists( $tag, $shortcode_tags );
687+}
688+
689+/**
690+ * Whether the passed content contains the specified shortcode
691+ *
692+ * @since 3.6.0
693+ *
694+ * @global array $shortcode_tags
695+ * @param string $tag
696+ * @return boolean
697+ */
698+function has_shortcode( $content, $tag ) {
699+       if ( shortcode_exists( $tag ) ) {
700+               $matches = array();
701+               preg_match_all( '/' . get_shortcode_regex() . '/s', $content, $matches, PREG_SET_ORDER );
702+               if ( empty( $matches ) )
703+                       return false;
704+
705+               foreach ( $matches as $shortcode ) {
706+                       if ( $tag === $shortcode[2] )
707+                               return true;
708+               }
709+       }
710+       return false;
711+}
712+
713+/**
714  * Search content for shortcodes and filter shortcodes through their hooks.
715  *
716  * If there are no shortcode tags defined, then the content will be returned