+ $image_basename = wp_basename( $image_meta['file'] );
+
+ /*
+ * WordPress flattens animated GIFs into one frame when generating intermediate sizes.
+ * To avoid hiding animation in user content, if src is a full size GIF, a srcset attribute is not generated.
+ * If src is an intermediate size GIF, the full size is excluded from srcset to keep a flattened GIF from becoming animated.
+ */
+ if ( ! isset( $image_sizes['thumbnail']['mime-type'] ) || 'image/gif' !== $image_sizes['thumbnail']['mime-type'] ) {
+ $image_sizes[] = array(
+ 'width' => $image_meta['width'],
+ 'height' => $image_meta['height'],
+ 'file' => $image_basename,
+ );
+ } elseif ( strpos( $image_src, $image_meta['file'] ) ) {
+ return false;
+ }
+
+ // Retrieve the uploads sub-directory from the full size image.
+ $dirname = _wp_get_attachment_relative_path( $image_meta['file'] );
+
+ if ( $dirname ) {
+ $dirname = trailingslashit( $dirname );
+ }
+
+ $upload_dir = wp_get_upload_dir();
+ $image_baseurl = trailingslashit( $upload_dir['baseurl'] ) . $dirname;
+
+ /*
+ * If currently on HTTPS, prefer HTTPS URLs when we know they're supported by the domain
+ * (which is to say, when they share the domain name of the current request).
+ */
+ if ( is_ssl() && 'https' !== substr( $image_baseurl, 0, 5 ) && parse_url( $image_baseurl, PHP_URL_HOST ) === $_SERVER['HTTP_HOST'] ) {
+ $image_baseurl = set_url_scheme( $image_baseurl, 'https' );
+ }
+
+ /*
+ * Images that have been edited in WordPress after being uploaded will
+ * contain a unique hash. Look for that hash and use it later to filter
+ * out images that are leftovers from previous versions.
+ */
+ $image_edited = preg_match( '/-e[0-9]{13}/', wp_basename( $image_src ), $image_edit_hash );
+
+ /**
+ * Filters the maximum image width to be included in a 'srcset' attribute.
+ *
+ * @since 4.4.0
+ *
+ * @param int $max_width The maximum image width to be included in the 'srcset'. Default '1600'.
+ * @param array $size_array Array of width and height values in pixels (in that order).
+ */
+ $max_srcset_image_width = apply_filters( 'max_srcset_image_width', 1600, $size_array );
+
+ // Array to hold URL candidates.
+ $sources = array();
+
+ /**
+ * To make sure the ID matches our image src, we will check to see if any sizes in our attachment
+ * meta match our $image_src. If no matches are found we don't return a srcset to avoid serving
+ * an incorrect image. See #35045.
+ */
+ $src_matched = false;
+
+ /*
+ * Loop through available images. Only use images that are resized
+ * versions of the same edit.
+ */
+ foreach ( $image_sizes as $image ) {
+ $is_src = false;
+
+ // Check if image meta isn't corrupted.
+ if ( ! is_array( $image ) ) {
+ continue;
+ }
+
+ // If the file name is part of the `src`, we've confirmed a match.
+ if ( ! $src_matched && false !== strpos( $image_src, $dirname . $image['file'] ) ) {
+ $src_matched = $is_src = true;
+ }
+
+ // Filter out images that are from previous edits.
+ if ( $image_edited && ! strpos( $image['file'], $image_edit_hash[0] ) ) {
+ continue;
+ }
+
+ /*
+ * Filters out images that are wider than '$max_srcset_image_width' unless
+ * that file is in the 'src' attribute.
+ */
+ if ( $max_srcset_image_width && $image['width'] > $max_srcset_image_width && ! $is_src ) {
+ continue;
+ }
+
+ // If the image dimensions are within 1px of the expected size, use it.
+ if ( wp_image_matches_ratio( $image_width, $image_height, $image['width'], $image['height'] ) ) {
+ // Add the URL, descriptor, and value to the sources array to be returned.
+ $source = array(
+ 'url' => $image_baseurl . $image['file'],
+ 'descriptor' => 'w',
+ 'value' => $image['width'],
+ );
+
+ // The 'src' image has to be the first in the 'srcset', because of a bug in iOS8. See #35030.
+ if ( $is_src ) {
+ $sources = array( $image['width'] => $source ) + $sources;
+ } else {
+ $sources[ $image['width'] ] = $source;
+ }
+ }
+ }
+
+ /**
+ * Filters an image's 'srcset' sources.
+ *
+ * @since 4.4.0
+ *
+ * @param array $sources {
+ * One or more arrays of source data to include in the 'srcset'.
+ *
+ * @type array $width {
+ * @type string $url The URL of an image source.
+ * @type string $descriptor The descriptor type used in the image candidate string,
+ * either 'w' or 'x'.
+ * @type int $value The source width if paired with a 'w' descriptor, or a
+ * pixel density value if paired with an 'x' descriptor.
+ * }
+ * }
+ * @param array $size_array Array of width and height values in pixels (in that order).
+ * @param string $image_src The 'src' of the image.
+ * @param array $image_meta The image meta data as returned by 'wp_get_attachment_metadata()'.
+ * @param int $attachment_id Image attachment ID or 0.
+ */
+ $sources = apply_filters( 'wp_calculate_image_srcset', $sources, $size_array, $image_src, $image_meta, $attachment_id );
+
+ // Only return a 'srcset' value if there is more than one source.
+ if ( ! $src_matched || count( $sources ) < 2 ) {
+ return false;
+ }
+
+ $srcset = '';
+
+ foreach ( $sources as $source ) {
+ $srcset .= str_replace( ' ', '%20', $source['url'] ) . ' ' . $source['value'] . $source['descriptor'] . ', ';
+ }
+
+ return rtrim( $srcset, ', ' );
+}
+
+/**
+ * Retrieves the value for an image attachment's 'sizes' attribute.
+ *
+ * @since 4.4.0
+ *
+ * @see wp_calculate_image_sizes()
+ *
+ * @param int $attachment_id Image attachment ID.
+ * @param array|string $size Optional. Image size. Accepts any valid image size, or an array of width
+ * and height values in pixels (in that order). Default 'medium'.
+ * @param array $image_meta Optional. The image meta data as returned by 'wp_get_attachment_metadata()'.
+ * Default null.
+ * @return string|bool A valid source size value for use in a 'sizes' attribute or false.
+ */
+function wp_get_attachment_image_sizes( $attachment_id, $size = 'medium', $image_meta = null ) {
+ if ( ! $image = wp_get_attachment_image_src( $attachment_id, $size ) ) {
+ return false;
+ }
+
+ if ( ! is_array( $image_meta ) ) {
+ $image_meta = wp_get_attachment_metadata( $attachment_id );
+ }
+
+ $image_src = $image[0];
+ $size_array = array(
+ absint( $image[1] ),
+ absint( $image[2] )
+ );
+
+ return wp_calculate_image_sizes( $size_array, $image_src, $image_meta, $attachment_id );
+}
+
+/**
+ * Creates a 'sizes' attribute value for an image.
+ *
+ * @since 4.4.0
+ *
+ * @param array|string $size Image size to retrieve. Accepts any valid image size, or an array
+ * of width and height values in pixels (in that order). Default 'medium'.
+ * @param string $image_src Optional. The URL to the image file. Default null.
+ * @param array $image_meta Optional. The image meta data as returned by 'wp_get_attachment_metadata()'.
+ * Default null.
+ * @param int $attachment_id Optional. Image attachment ID. Either `$image_meta` or `$attachment_id`
+ * is needed when using the image size name as argument for `$size`. Default 0.
+ * @return string|bool A valid source size value for use in a 'sizes' attribute or false.
+ */
+function wp_calculate_image_sizes( $size, $image_src = null, $image_meta = null, $attachment_id = 0 ) {
+ $width = 0;
+
+ if ( is_array( $size ) ) {
+ $width = absint( $size[0] );
+ } elseif ( is_string( $size ) ) {
+ if ( ! $image_meta && $attachment_id ) {
+ $image_meta = wp_get_attachment_metadata( $attachment_id );
+ }
+
+ if ( is_array( $image_meta ) ) {
+ $size_array = _wp_get_image_size_from_meta( $size, $image_meta );
+ if ( $size_array ) {
+ $width = absint( $size_array[0] );
+ }
+ }
+ }
+
+ if ( ! $width ) {
+ return false;
+ }
+
+ // Setup the default 'sizes' attribute.
+ $sizes = sprintf( '(max-width: %1$dpx) 100vw, %1$dpx', $width );
+
+ /**
+ * Filters the output of 'wp_calculate_image_sizes()'.
+ *
+ * @since 4.4.0
+ *
+ * @param string $sizes A source size value for use in a 'sizes' attribute.
+ * @param array|string $size Requested size. Image size or array of width and height values
+ * in pixels (in that order).
+ * @param string|null $image_src The URL to the image file or null.
+ * @param array|null $image_meta The image meta data as returned by wp_get_attachment_metadata() or null.
+ * @param int $attachment_id Image attachment ID of the original image or 0.
+ */
+ return apply_filters( 'wp_calculate_image_sizes', $sizes, $size, $image_src, $image_meta, $attachment_id );
+}
+
+/**
+ * Filters 'img' elements in post content to add 'srcset' and 'sizes' attributes.
+ *
+ * @since 4.4.0
+ *
+ * @see wp_image_add_srcset_and_sizes()
+ *
+ * @param string $content The raw post content to be filtered.
+ * @return string Converted content with 'srcset' and 'sizes' attributes added to images.
+ */
+function wp_make_content_images_responsive( $content ) {
+ if ( ! preg_match_all( '/<img [^>]+>/', $content, $matches ) ) {
+ return $content;
+ }
+
+ $selected_images = $attachment_ids = array();
+
+ foreach( $matches[0] as $image ) {
+ if ( false === strpos( $image, ' srcset=' ) && preg_match( '/wp-image-([0-9]+)/i', $image, $class_id ) &&
+ ( $attachment_id = absint( $class_id[1] ) ) ) {
+
+ /*
+ * If exactly the same image tag is used more than once, overwrite it.
+ * All identical tags will be replaced later with 'str_replace()'.
+ */
+ $selected_images[ $image ] = $attachment_id;
+ // Overwrite the ID when the same image is included more than once.
+ $attachment_ids[ $attachment_id ] = true;
+ }
+ }
+
+ if ( count( $attachment_ids ) > 1 ) {
+ /*
+ * Warm object cache for use with 'get_post_meta()'.
+ *
+ * To avoid making a database call for each image, a single query
+ * warms the object cache with the meta information for all images.
+ */
+ update_meta_cache( 'post', array_keys( $attachment_ids ) );
+ }
+
+ foreach ( $selected_images as $image => $attachment_id ) {
+ $image_meta = wp_get_attachment_metadata( $attachment_id );
+ $content = str_replace( $image, wp_image_add_srcset_and_sizes( $image, $image_meta, $attachment_id ), $content );
+ }
+
+ return $content;
+}
+
+/**
+ * Adds 'srcset' and 'sizes' attributes to an existing 'img' element.
+ *
+ * @since 4.4.0
+ *
+ * @see wp_calculate_image_srcset()
+ * @see wp_calculate_image_sizes()
+ *
+ * @param string $image An HTML 'img' element to be filtered.
+ * @param array $image_meta The image meta data as returned by 'wp_get_attachment_metadata()'.
+ * @param int $attachment_id Image attachment ID.
+ * @return string Converted 'img' element with 'srcset' and 'sizes' attributes added.
+ */
+function wp_image_add_srcset_and_sizes( $image, $image_meta, $attachment_id ) {
+ // Ensure the image meta exists.
+ if ( empty( $image_meta['sizes'] ) ) {
+ return $image;
+ }
+
+ $image_src = preg_match( '/src="([^"]+)"/', $image, $match_src ) ? $match_src[1] : '';
+ list( $image_src ) = explode( '?', $image_src );
+
+ // Return early if we couldn't get the image source.
+ if ( ! $image_src ) {
+ return $image;
+ }
+
+ // Bail early if an image has been inserted and later edited.
+ if ( preg_match( '/-e[0-9]{13}/', $image_meta['file'], $img_edit_hash ) &&
+ strpos( wp_basename( $image_src ), $img_edit_hash[0] ) === false ) {
+
+ return $image;
+ }
+
+ $width = preg_match( '/ width="([0-9]+)"/', $image, $match_width ) ? (int) $match_width[1] : 0;
+ $height = preg_match( '/ height="([0-9]+)"/', $image, $match_height ) ? (int) $match_height[1] : 0;
+
+ if ( ! $width || ! $height ) {
+ /*
+ * If attempts to parse the size value failed, attempt to use the image meta data to match
+ * the image file name from 'src' against the available sizes for an attachment.
+ */
+ $image_filename = wp_basename( $image_src );
+
+ if ( $image_filename === wp_basename( $image_meta['file'] ) ) {
+ $width = (int) $image_meta['width'];
+ $height = (int) $image_meta['height'];
+ } else {
+ foreach( $image_meta['sizes'] as $image_size_data ) {
+ if ( $image_filename === $image_size_data['file'] ) {
+ $width = (int) $image_size_data['width'];
+ $height = (int) $image_size_data['height'];
+ break;
+ }
+ }
+ }
+ }
+
+ if ( ! $width || ! $height ) {
+ return $image;
+ }
+
+ $size_array = array( $width, $height );
+ $srcset = wp_calculate_image_srcset( $size_array, $image_src, $image_meta, $attachment_id );
+
+ if ( $srcset ) {
+ // Check if there is already a 'sizes' attribute.
+ $sizes = strpos( $image, ' sizes=' );
+
+ if ( ! $sizes ) {
+ $sizes = wp_calculate_image_sizes( $size_array, $image_src, $image_meta, $attachment_id );
+ }
+ }
+
+ if ( $srcset && $sizes ) {
+ // Format the 'srcset' and 'sizes' string and escape attributes.
+ $attr = sprintf( ' srcset="%s"', esc_attr( $srcset ) );
+
+ if ( is_string( $sizes ) ) {
+ $attr .= sprintf( ' sizes="%s"', esc_attr( $sizes ) );
+ }
+
+ // Add 'srcset' and 'sizes' attributes to the image markup.
+ $image = preg_replace( '/<img ([^>]+?)[\/ ]*>/', '<img $1' . $attr . ' />', $image );
+ }
+
+ return $image;
+}
+
+/**
+ * Adds a 'wp-post-image' class to post thumbnails. Internal use only.
+ *
+ * Uses the {@see 'begin_fetch_post_thumbnail_html'} and {@see 'end_fetch_post_thumbnail_html'}
+ * action hooks to dynamically add/remove itself so as to only filter post thumbnails.
+ *
+ * @ignore
+ * @since 2.9.0
+ *
+ * @param array $attr Thumbnail attributes including src, class, alt, title.
+ * @return array Modified array of attributes including the new 'wp-post-image' class.
+ */
+function _wp_post_thumbnail_class_filter( $attr ) {
+ $attr['class'] .= ' wp-post-image';
+ return $attr;
+}
+
+/**
+ * Adds '_wp_post_thumbnail_class_filter' callback to the 'wp_get_attachment_image_attributes'
+ * filter hook. Internal use only.
+ *
+ * @ignore
+ * @since 2.9.0
+ *
+ * @param array $attr Thumbnail attributes including src, class, alt, title.
+ */
+function _wp_post_thumbnail_class_filter_add( $attr ) {
+ add_filter( 'wp_get_attachment_image_attributes', '_wp_post_thumbnail_class_filter' );
+}
+
+/**
+ * Removes the '_wp_post_thumbnail_class_filter' callback from the 'wp_get_attachment_image_attributes'
+ * filter hook. Internal use only.
+ *
+ * @ignore
+ * @since 2.9.0
+ *
+ * @param array $attr Thumbnail attributes including src, class, alt, title.
+ */
+function _wp_post_thumbnail_class_filter_remove( $attr ) {
+ remove_filter( 'wp_get_attachment_image_attributes', '_wp_post_thumbnail_class_filter' );
+}
+
+add_shortcode('wp_caption', 'img_caption_shortcode');
+add_shortcode('caption', 'img_caption_shortcode');
+
+/**
+ * Builds the Caption shortcode output.
+ *
+ * Allows a plugin to replace the content that would otherwise be returned. The
+ * filter is {@see 'img_caption_shortcode'} and passes an empty string, the attr
+ * parameter and the content parameter values.
+ *
+ * The supported attributes for the shortcode are 'id', 'align', 'width', and
+ * 'caption'.
+ *
+ * @since 2.6.0
+ *
+ * @param array $attr {
+ * Attributes of the caption shortcode.
+ *
+ * @type string $id ID of the div element for the caption.
+ * @type string $align Class name that aligns the caption. Default 'alignnone'. Accepts 'alignleft',
+ * 'aligncenter', alignright', 'alignnone'.
+ * @type int $width The width of the caption, in pixels.
+ * @type string $caption The caption text.
+ * @type string $class Additional class name(s) added to the caption container.
+ * }
+ * @param string $content Shortcode content.
+ * @return string HTML content to display the caption.
+ */
+function img_caption_shortcode( $attr, $content = null ) {
+ // New-style shortcode with the caption inside the shortcode with the link and image tags.
+ if ( ! isset( $attr['caption'] ) ) {
+ if ( preg_match( '#((?:<a [^>]+>\s*)?<img [^>]+>(?:\s*</a>)?)(.*)#is', $content, $matches ) ) {
+ $content = $matches[1];
+ $attr['caption'] = trim( $matches[2] );
+ }
+ } elseif ( strpos( $attr['caption'], '<' ) !== false ) {
+ $attr['caption'] = wp_kses( $attr['caption'], 'post' );
+ }
+
+ /**
+ * Filters the default caption shortcode output.
+ *
+ * If the filtered output isn't empty, it will be used instead of generating
+ * the default caption template.
+ *
+ * @since 2.6.0
+ *
+ * @see img_caption_shortcode()
+ *
+ * @param string $output The caption output. Default empty.
+ * @param array $attr Attributes of the caption shortcode.
+ * @param string $content The image element, possibly wrapped in a hyperlink.
+ */
+ $output = apply_filters( 'img_caption_shortcode', '', $attr, $content );
+ if ( $output != '' )
+ return $output;
+
+ $atts = shortcode_atts( array(
+ 'id' => '',
+ 'align' => 'alignnone',
+ 'width' => '',
+ 'caption' => '',
+ 'class' => '',
+ ), $attr, 'caption' );
+
+ $atts['width'] = (int) $atts['width'];
+ if ( $atts['width'] < 1 || empty( $atts['caption'] ) )
+ return $content;
+
+ if ( ! empty( $atts['id'] ) )
+ $atts['id'] = 'id="' . esc_attr( sanitize_html_class( $atts['id'] ) ) . '" ';
+
+ $class = trim( 'wp-caption ' . $atts['align'] . ' ' . $atts['class'] );
+
+ $html5 = current_theme_supports( 'html5', 'caption' );
+ // HTML5 captions never added the extra 10px to the image width
+ $width = $html5 ? $atts['width'] : ( 10 + $atts['width'] );
+
+ /**
+ * Filters the width of an image's caption.
+ *
+ * By default, the caption is 10 pixels greater than the width of the image,
+ * to prevent post content from running up against a floated image.
+ *
+ * @since 3.7.0
+ *
+ * @see img_caption_shortcode()
+ *
+ * @param int $width Width of the caption in pixels. To remove this inline style,
+ * return zero.
+ * @param array $atts Attributes of the caption shortcode.
+ * @param string $content The image element, possibly wrapped in a hyperlink.
+ */
+ $caption_width = apply_filters( 'img_caption_shortcode_width', $width, $atts, $content );
+
+ $style = '';
+ if ( $caption_width ) {
+ $style = 'style="width: ' . (int) $caption_width . 'px" ';
+ }
+
+ if ( $html5 ) {
+ $html = '<figure ' . $atts['id'] . $style . 'class="' . esc_attr( $class ) . '">'
+ . do_shortcode( $content ) . '<figcaption class="wp-caption-text">' . $atts['caption'] . '</figcaption></figure>';
+ } else {
+ $html = '<div ' . $atts['id'] . $style . 'class="' . esc_attr( $class ) . '">'
+ . do_shortcode( $content ) . '<p class="wp-caption-text">' . $atts['caption'] . '</p></div>';
+ }
+
+ return $html;
+}
+
+add_shortcode('gallery', 'gallery_shortcode');
+
+/**
+ * Builds the Gallery shortcode output.
+ *
+ * This implements the functionality of the Gallery Shortcode for displaying
+ * WordPress images on a post.
+ *
+ * @since 2.5.0
+ *
+ * @staticvar int $instance
+ *
+ * @param array $attr {
+ * Attributes of the gallery shortcode.
+ *
+ * @type string $order Order of the images in the gallery. Default 'ASC'. Accepts 'ASC', 'DESC'.
+ * @type string $orderby The field to use when ordering the images. Default 'menu_order ID'.
+ * Accepts any valid SQL ORDERBY statement.
+ * @type int $id Post ID.
+ * @type string $itemtag HTML tag to use for each image in the gallery.
+ * Default 'dl', or 'figure' when the theme registers HTML5 gallery support.
+ * @type string $icontag HTML tag to use for each image's icon.
+ * Default 'dt', or 'div' when the theme registers HTML5 gallery support.
+ * @type string $captiontag HTML tag to use for each image's caption.
+ * Default 'dd', or 'figcaption' when the theme registers HTML5 gallery support.
+ * @type int $columns Number of columns of images to display. Default 3.
+ * @type string|array $size Size of the images to display. Accepts any valid image size, or an array of width
+ * and height values in pixels (in that order). Default 'thumbnail'.
+ * @type string $ids A comma-separated list of IDs of attachments to display. Default empty.
+ * @type string $include A comma-separated list of IDs of attachments to include. Default empty.
+ * @type string $exclude A comma-separated list of IDs of attachments to exclude. Default empty.
+ * @type string $link What to link each image to. Default empty (links to the attachment page).
+ * Accepts 'file', 'none'.
+ * }
+ * @return string HTML content to display gallery.
+ */
+function gallery_shortcode( $attr ) {
+ $post = get_post();
+
+ static $instance = 0;
+ $instance++;
+
+ if ( ! empty( $attr['ids'] ) ) {
+ // 'ids' is explicitly ordered, unless you specify otherwise.
+ if ( empty( $attr['orderby'] ) ) {
+ $attr['orderby'] = 'post__in';
+ }
+ $attr['include'] = $attr['ids'];
+ }
+
+ /**
+ * Filters the default gallery shortcode output.
+ *
+ * If the filtered output isn't empty, it will be used instead of generating
+ * the default gallery template.
+ *
+ * @since 2.5.0
+ * @since 4.2.0 The `$instance` parameter was added.
+ *
+ * @see gallery_shortcode()
+ *
+ * @param string $output The gallery output. Default empty.
+ * @param array $attr Attributes of the gallery shortcode.
+ * @param int $instance Unique numeric ID of this gallery shortcode instance.
+ */
+ $output = apply_filters( 'post_gallery', '', $attr, $instance );
+ if ( $output != '' ) {
+ return $output;
+ }
+
+ $html5 = current_theme_supports( 'html5', 'gallery' );
+ $atts = shortcode_atts( array(
+ 'order' => 'ASC',
+ 'orderby' => 'menu_order ID',
+ 'id' => $post ? $post->ID : 0,
+ 'itemtag' => $html5 ? 'figure' : 'dl',
+ 'icontag' => $html5 ? 'div' : 'dt',
+ 'captiontag' => $html5 ? 'figcaption' : 'dd',
+ 'columns' => 3,
+ 'size' => 'thumbnail',
+ 'include' => '',
+ 'exclude' => '',
+ 'link' => ''
+ ), $attr, 'gallery' );
+
+ $id = intval( $atts['id'] );
+
+ if ( ! empty( $atts['include'] ) ) {
+ $_attachments = get_posts( array( 'include' => $atts['include'], 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $atts['order'], 'orderby' => $atts['orderby'] ) );
+
+ $attachments = array();
+ foreach ( $_attachments as $key => $val ) {
+ $attachments[$val->ID] = $_attachments[$key];
+ }
+ } elseif ( ! empty( $atts['exclude'] ) ) {
+ $attachments = get_children( array( 'post_parent' => $id, 'exclude' => $atts['exclude'], 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $atts['order'], 'orderby' => $atts['orderby'] ) );
+ } else {
+ $attachments = get_children( array( 'post_parent' => $id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $atts['order'], 'orderby' => $atts['orderby'] ) );
+ }
+
+ if ( empty( $attachments ) ) {
+ return '';
+ }
+
+ if ( is_feed() ) {
+ $output = "\n";
+ foreach ( $attachments as $att_id => $attachment ) {
+ $output .= wp_get_attachment_link( $att_id, $atts['size'], true ) . "\n";
+ }
+ return $output;
+ }
+
+ $itemtag = tag_escape( $atts['itemtag'] );
+ $captiontag = tag_escape( $atts['captiontag'] );
+ $icontag = tag_escape( $atts['icontag'] );
+ $valid_tags = wp_kses_allowed_html( 'post' );
+ if ( ! isset( $valid_tags[ $itemtag ] ) ) {
+ $itemtag = 'dl';
+ }
+ if ( ! isset( $valid_tags[ $captiontag ] ) ) {
+ $captiontag = 'dd';
+ }
+ if ( ! isset( $valid_tags[ $icontag ] ) ) {
+ $icontag = 'dt';
+ }
+
+ $columns = intval( $atts['columns'] );
+ $itemwidth = $columns > 0 ? floor(100/$columns) : 100;
+ $float = is_rtl() ? 'right' : 'left';
+
+ $selector = "gallery-{$instance}";
+
+ $gallery_style = '';
+
+ /**
+ * Filters whether to print default gallery styles.
+ *
+ * @since 3.1.0
+ *
+ * @param bool $print Whether to print default gallery styles.
+ * Defaults to false if the theme supports HTML5 galleries.
+ * Otherwise, defaults to true.
+ */
+ if ( apply_filters( 'use_default_gallery_style', ! $html5 ) ) {
+ $gallery_style = "
+ <style type='text/css'>
+ #{$selector} {
+ margin: auto;
+ }
+ #{$selector} .gallery-item {
+ float: {$float};
+ margin-top: 10px;
+ text-align: center;
+ width: {$itemwidth}%;
+ }
+ #{$selector} img {
+ border: 2px solid #cfcfcf;
+ }
+ #{$selector} .gallery-caption {
+ margin-left: 0;
+ }
+ /* see gallery_shortcode() in wp-includes/media.php */
+ </style>\n\t\t";
+ }
+
+ $size_class = sanitize_html_class( $atts['size'] );
+ $gallery_div = "<div id='$selector' class='gallery galleryid-{$id} gallery-columns-{$columns} gallery-size-{$size_class}'>";
+
+ /**
+ * Filters the default gallery shortcode CSS styles.
+ *
+ * @since 2.5.0
+ *
+ * @param string $gallery_style Default CSS styles and opening HTML div container
+ * for the gallery shortcode output.
+ */
+ $output = apply_filters( 'gallery_style', $gallery_style . $gallery_div );
+
+ $i = 0;
+ foreach ( $attachments as $id => $attachment ) {
+
+ $attr = ( trim( $attachment->post_excerpt ) ) ? array( 'aria-describedby' => "$selector-$id" ) : '';
+ if ( ! empty( $atts['link'] ) && 'file' === $atts['link'] ) {
+ $image_output = wp_get_attachment_link( $id, $atts['size'], false, false, false, $attr );
+ } elseif ( ! empty( $atts['link'] ) && 'none' === $atts['link'] ) {
+ $image_output = wp_get_attachment_image( $id, $atts['size'], false, $attr );
+ } else {
+ $image_output = wp_get_attachment_link( $id, $atts['size'], true, false, false, $attr );
+ }
+ $image_meta = wp_get_attachment_metadata( $id );
+
+ $orientation = '';
+ if ( isset( $image_meta['height'], $image_meta['width'] ) ) {
+ $orientation = ( $image_meta['height'] > $image_meta['width'] ) ? 'portrait' : 'landscape';
+ }
+ $output .= "<{$itemtag} class='gallery-item'>";
+ $output .= "
+ <{$icontag} class='gallery-icon {$orientation}'>
+ $image_output
+ </{$icontag}>";
+ if ( $captiontag && trim($attachment->post_excerpt) ) {
+ $output .= "
+ <{$captiontag} class='wp-caption-text gallery-caption' id='$selector-$id'>
+ " . wptexturize($attachment->post_excerpt) . "
+ </{$captiontag}>";
+ }
+ $output .= "</{$itemtag}>";
+ if ( ! $html5 && $columns > 0 && ++$i % $columns == 0 ) {
+ $output .= '<br style="clear: both" />';
+ }
+ }
+
+ if ( ! $html5 && $columns > 0 && $i % $columns !== 0 ) {
+ $output .= "
+ <br style='clear: both' />";
+ }
+
+ $output .= "
+ </div>\n";
+
+ return $output;
+}
+
+/**
+ * Outputs the templates used by playlists.
+ *
+ * @since 3.9.0
+ */
+function wp_underscore_playlist_templates() {
+?>
+<script type="text/html" id="tmpl-wp-playlist-current-item">
+ <# if ( data.image ) { #>
+ <img src="{{ data.thumb.src }}" alt="" />
+ <# } #>
+ <div class="wp-playlist-caption">
+ <span class="wp-playlist-item-meta wp-playlist-item-title"><?php
+ /* translators: playlist item title */
+ printf( _x( '“%s”', 'playlist item title' ), '{{ data.title }}' );
+ ?></span>
+ <# if ( data.meta.album ) { #><span class="wp-playlist-item-meta wp-playlist-item-album">{{ data.meta.album }}</span><# } #>
+ <# if ( data.meta.artist ) { #><span class="wp-playlist-item-meta wp-playlist-item-artist">{{ data.meta.artist }}</span><# } #>
+ </div>
+</script>
+<script type="text/html" id="tmpl-wp-playlist-item">
+ <div class="wp-playlist-item">
+ <a class="wp-playlist-caption" href="{{ data.src }}">
+ {{ data.index ? ( data.index + '. ' ) : '' }}
+ <# if ( data.caption ) { #>
+ {{ data.caption }}
+ <# } else { #>
+ <span class="wp-playlist-item-title"><?php
+ /* translators: playlist item title */
+ printf( _x( '“%s”', 'playlist item title' ), '{{{ data.title }}}' );
+ ?></span>
+ <# if ( data.artists && data.meta.artist ) { #>
+ <span class="wp-playlist-item-artist"> — {{ data.meta.artist }}</span>
+ <# } #>
+ <# } #>
+ </a>
+ <# if ( data.meta.length_formatted ) { #>
+ <div class="wp-playlist-item-length">{{ data.meta.length_formatted }}</div>
+ <# } #>
+ </div>
+</script>
+<?php
+}
+
+/**
+ * Outputs and enqueue default scripts and styles for playlists.
+ *
+ * @since 3.9.0
+ *
+ * @param string $type Type of playlist. Accepts 'audio' or 'video'.
+ */
+function wp_playlist_scripts( $type ) {
+ wp_enqueue_style( 'wp-mediaelement' );
+ wp_enqueue_script( 'wp-playlist' );
+?>
+<!--[if lt IE 9]><script>document.createElement('<?php echo esc_js( $type ) ?>');</script><![endif]-->
+<?php
+ add_action( 'wp_footer', 'wp_underscore_playlist_templates', 0 );
+ add_action( 'admin_footer', 'wp_underscore_playlist_templates', 0 );
+}
+
+/**
+ * Builds the Playlist shortcode output.
+ *
+ * This implements the functionality of the playlist shortcode for displaying
+ * a collection of WordPress audio or video files in a post.
+ *
+ * @since 3.9.0
+ *
+ * @global int $content_width
+ * @staticvar int $instance
+ *
+ * @param array $attr {
+ * Array of default playlist attributes.
+ *
+ * @type string $type Type of playlist to display. Accepts 'audio' or 'video'. Default 'audio'.
+ * @type string $order Designates ascending or descending order of items in the playlist.
+ * Accepts 'ASC', 'DESC'. Default 'ASC'.
+ * @type string $orderby Any column, or columns, to sort the playlist. If $ids are
+ * passed, this defaults to the order of the $ids array ('post__in').
+ * Otherwise default is 'menu_order ID'.
+ * @type int $id If an explicit $ids array is not present, this parameter
+ * will determine which attachments are used for the playlist.
+ * Default is the current post ID.
+ * @type array $ids Create a playlist out of these explicit attachment IDs. If empty,
+ * a playlist will be created from all $type attachments of $id.
+ * Default empty.
+ * @type array $exclude List of specific attachment IDs to exclude from the playlist. Default empty.
+ * @type string $style Playlist style to use. Accepts 'light' or 'dark'. Default 'light'.
+ * @type bool $tracklist Whether to show or hide the playlist. Default true.
+ * @type bool $tracknumbers Whether to show or hide the numbers next to entries in the playlist. Default true.
+ * @type bool $images Show or hide the video or audio thumbnail (Featured Image/post
+ * thumbnail). Default true.
+ * @type bool $artists Whether to show or hide artist name in the playlist. Default true.
+ * }
+ *
+ * @return string Playlist output. Empty string if the passed type is unsupported.
+ */
+function wp_playlist_shortcode( $attr ) {
+ global $content_width;
+ $post = get_post();
+
+ static $instance = 0;
+ $instance++;
+
+ if ( ! empty( $attr['ids'] ) ) {
+ // 'ids' is explicitly ordered, unless you specify otherwise.
+ if ( empty( $attr['orderby'] ) ) {
+ $attr['orderby'] = 'post__in';
+ }
+ $attr['include'] = $attr['ids'];
+ }
+
+ /**
+ * Filters the playlist output.
+ *
+ * Passing a non-empty value to the filter will short-circuit generation
+ * of the default playlist output, returning the passed value instead.
+ *
+ * @since 3.9.0
+ * @since 4.2.0 The `$instance` parameter was added.
+ *
+ * @param string $output Playlist output. Default empty.
+ * @param array $attr An array of shortcode attributes.
+ * @param int $instance Unique numeric ID of this playlist shortcode instance.
+ */
+ $output = apply_filters( 'post_playlist', '', $attr, $instance );
+ if ( $output != '' ) {
+ return $output;
+ }
+
+ $atts = shortcode_atts( array(
+ 'type' => 'audio',
+ 'order' => 'ASC',
+ 'orderby' => 'menu_order ID',
+ 'id' => $post ? $post->ID : 0,
+ 'include' => '',
+ 'exclude' => '',
+ 'style' => 'light',
+ 'tracklist' => true,
+ 'tracknumbers' => true,
+ 'images' => true,
+ 'artists' => true
+ ), $attr, 'playlist' );
+
+ $id = intval( $atts['id'] );
+
+ if ( $atts['type'] !== 'audio' ) {
+ $atts['type'] = 'video';
+ }
+
+ $args = array(
+ 'post_status' => 'inherit',
+ 'post_type' => 'attachment',
+ 'post_mime_type' => $atts['type'],
+ 'order' => $atts['order'],
+ 'orderby' => $atts['orderby']
+ );
+
+ if ( ! empty( $atts['include'] ) ) {
+ $args['include'] = $atts['include'];
+ $_attachments = get_posts( $args );
+
+ $attachments = array();
+ foreach ( $_attachments as $key => $val ) {
+ $attachments[$val->ID] = $_attachments[$key];
+ }
+ } elseif ( ! empty( $atts['exclude'] ) ) {
+ $args['post_parent'] = $id;
+ $args['exclude'] = $atts['exclude'];
+ $attachments = get_children( $args );
+ } else {
+ $args['post_parent'] = $id;
+ $attachments = get_children( $args );
+ }
+
+ if ( empty( $attachments ) ) {
+ return '';
+ }
+
+ if ( is_feed() ) {
+ $output = "\n";
+ foreach ( $attachments as $att_id => $attachment ) {
+ $output .= wp_get_attachment_link( $att_id ) . "\n";
+ }
+ return $output;
+ }
+
+ $outer = 22; // default padding and border of wrapper
+
+ $default_width = 640;
+ $default_height = 360;
+
+ $theme_width = empty( $content_width ) ? $default_width : ( $content_width - $outer );
+ $theme_height = empty( $content_width ) ? $default_height : round( ( $default_height * $theme_width ) / $default_width );
+
+ $data = array(
+ 'type' => $atts['type'],
+ // don't pass strings to JSON, will be truthy in JS
+ 'tracklist' => wp_validate_boolean( $atts['tracklist'] ),
+ 'tracknumbers' => wp_validate_boolean( $atts['tracknumbers'] ),
+ 'images' => wp_validate_boolean( $atts['images'] ),
+ 'artists' => wp_validate_boolean( $atts['artists'] ),
+ );
+
+ $tracks = array();
+ foreach ( $attachments as $attachment ) {
+ $url = wp_get_attachment_url( $attachment->ID );
+ $ftype = wp_check_filetype( $url, wp_get_mime_types() );
+ $track = array(
+ 'src' => $url,
+ 'type' => $ftype['type'],
+ 'title' => $attachment->post_title,
+ 'caption' => $attachment->post_excerpt,
+ 'description' => $attachment->post_content
+ );
+
+ $track['meta'] = array();
+ $meta = wp_get_attachment_metadata( $attachment->ID );
+ if ( ! empty( $meta ) ) {
+
+ foreach ( wp_get_attachment_id3_keys( $attachment ) as $key => $label ) {
+ if ( ! empty( $meta[ $key ] ) ) {
+ $track['meta'][ $key ] = $meta[ $key ];
+ }
+ }
+
+ if ( 'video' === $atts['type'] ) {
+ if ( ! empty( $meta['width'] ) && ! empty( $meta['height'] ) ) {
+ $width = $meta['width'];
+ $height = $meta['height'];
+ $theme_height = round( ( $height * $theme_width ) / $width );
+ } else {
+ $width = $default_width;
+ $height = $default_height;
+ }
+
+ $track['dimensions'] = array(
+ 'original' => compact( 'width', 'height' ),
+ 'resized' => array(
+ 'width' => $theme_width,
+ 'height' => $theme_height
+ )
+ );
+ }
+ }
+
+ if ( $atts['images'] ) {
+ $thumb_id = get_post_thumbnail_id( $attachment->ID );
+ if ( ! empty( $thumb_id ) ) {
+ list( $src, $width, $height ) = wp_get_attachment_image_src( $thumb_id, 'full' );
+ $track['image'] = compact( 'src', 'width', 'height' );
+ list( $src, $width, $height ) = wp_get_attachment_image_src( $thumb_id, 'thumbnail' );
+ $track['thumb'] = compact( 'src', 'width', 'height' );
+ } else {
+ $src = wp_mime_type_icon( $attachment->ID );
+ $width = 48;
+ $height = 64;
+ $track['image'] = compact( 'src', 'width', 'height' );
+ $track['thumb'] = compact( 'src', 'width', 'height' );
+ }
+ }
+
+ $tracks[] = $track;
+ }
+ $data['tracks'] = $tracks;
+
+ $safe_type = esc_attr( $atts['type'] );
+ $safe_style = esc_attr( $atts['style'] );
+
+ ob_start();
+
+ if ( 1 === $instance ) {
+ /**
+ * Prints and enqueues playlist scripts, styles, and JavaScript templates.
+ *
+ * @since 3.9.0
+ *
+ * @param string $type Type of playlist. Possible values are 'audio' or 'video'.
+ * @param string $style The 'theme' for the playlist. Core provides 'light' and 'dark'.
+ */
+ do_action( 'wp_playlist_scripts', $atts['type'], $atts['style'] );
+ } ?>
+<div class="wp-playlist wp-<?php echo $safe_type ?>-playlist wp-playlist-<?php echo $safe_style ?>">
+ <?php if ( 'audio' === $atts['type'] ): ?>
+ <div class="wp-playlist-current-item"></div>
+ <?php endif ?>
+ <<?php echo $safe_type ?> controls="controls" preload="none" width="<?php
+ echo (int) $theme_width;
+ ?>"<?php if ( 'video' === $safe_type ):
+ echo ' height="', (int) $theme_height, '"';
+ endif; ?>></<?php echo $safe_type ?>>
+ <div class="wp-playlist-next"></div>
+ <div class="wp-playlist-prev"></div>
+ <noscript>
+ <ol><?php
+ foreach ( $attachments as $att_id => $attachment ) {
+ printf( '<li>%s</li>', wp_get_attachment_link( $att_id ) );
+ }
+ ?></ol>
+ </noscript>
+ <script type="application/json" class="wp-playlist-script"><?php echo wp_json_encode( $data ) ?></script>
+</div>
+ <?php
+ return ob_get_clean();
+}
+add_shortcode( 'playlist', 'wp_playlist_shortcode' );
+
+/**
+ * Provides a No-JS Flash fallback as a last resort for audio / video.
+ *
+ * @since 3.6.0
+ *
+ * @param string $url The media element URL.
+ * @return string Fallback HTML.
+ */
+function wp_mediaelement_fallback( $url ) {
+ /**
+ * Filters the Mediaelement fallback output for no-JS.
+ *
+ * @since 3.6.0
+ *
+ * @param string $output Fallback output for no-JS.
+ * @param string $url Media file URL.
+ */
+ return apply_filters( 'wp_mediaelement_fallback', sprintf( '<a href="%1$s">%1$s</a>', esc_url( $url ) ), $url );
+}
+
+/**
+ * Returns a filtered list of WP-supported audio formats.
+ *
+ * @since 3.6.0
+ *
+ * @return array Supported audio formats.
+ */
+function wp_get_audio_extensions() {
+ /**
+ * Filters the list of supported audio formats.
+ *
+ * @since 3.6.0
+ *
+ * @param array $extensions An array of support audio formats. Defaults are
+ * 'mp3', 'ogg', 'wma', 'm4a', 'wav'.
+ */
+ return apply_filters( 'wp_audio_extensions', array( 'mp3', 'ogg', 'wma', 'm4a', 'wav' ) );
+}
+
+/**
+ * Returns useful keys to use to lookup data from an attachment's stored metadata.
+ *
+ * @since 3.9.0
+ *
+ * @param WP_Post $attachment The current attachment, provided for context.
+ * @param string $context Optional. The context. Accepts 'edit', 'display'. Default 'display'.
+ * @return array Key/value pairs of field keys to labels.
+ */
+function wp_get_attachment_id3_keys( $attachment, $context = 'display' ) {
+ $fields = array(
+ 'artist' => __( 'Artist' ),
+ 'album' => __( 'Album' ),
+ );
+
+ if ( 'display' === $context ) {
+ $fields['genre'] = __( 'Genre' );
+ $fields['year'] = __( 'Year' );
+ $fields['length_formatted'] = _x( 'Length', 'video or audio' );
+ } elseif ( 'js' === $context ) {
+ $fields['bitrate'] = __( 'Bitrate' );
+ $fields['bitrate_mode'] = __( 'Bitrate Mode' );
+ }
+
+ /**
+ * Filters the editable list of keys to look up data from an attachment's metadata.
+ *
+ * @since 3.9.0
+ *
+ * @param array $fields Key/value pairs of field keys to labels.
+ * @param WP_Post $attachment Attachment object.
+ * @param string $context The context. Accepts 'edit', 'display'. Default 'display'.
+ */
+ return apply_filters( 'wp_get_attachment_id3_keys', $fields, $attachment, $context );
+}
+/**
+ * Builds the Audio shortcode output.
+ *
+ * This implements the functionality of the Audio Shortcode for displaying
+ * WordPress mp3s in a post.
+ *
+ * @since 3.6.0
+ *
+ * @staticvar int $instance
+ *
+ * @param array $attr {
+ * Attributes of the audio shortcode.
+ *
+ * @type string $src URL to the source of the audio file. Default empty.
+ * @type string $loop The 'loop' attribute for the `<audio>` element. Default empty.
+ * @type string $autoplay The 'autoplay' attribute for the `<audio>` element. Default empty.
+ * @type string $preload The 'preload' attribute for the `<audio>` element. Default 'none'.
+ * @type string $class The 'class' attribute for the `<audio>` element. Default 'wp-audio-shortcode'.
+ * @type string $style The 'style' attribute for the `<audio>` element. Default 'width: 100%;'.
+ * }
+ * @param string $content Shortcode content.
+ * @return string|void HTML content to display audio.
+ */
+function wp_audio_shortcode( $attr, $content = '' ) {
+ $post_id = get_post() ? get_the_ID() : 0;
+
+ static $instance = 0;
+ $instance++;
+
+ /**
+ * Filters the default audio shortcode output.
+ *
+ * If the filtered output isn't empty, it will be used instead of generating the default audio template.
+ *
+ * @since 3.6.0
+ *
+ * @param string $html Empty variable to be replaced with shortcode markup.
+ * @param array $attr Attributes of the shortcode. @see wp_audio_shortcode()
+ * @param string $content Shortcode content.
+ * @param int $instance Unique numeric ID of this audio shortcode instance.
+ */
+ $override = apply_filters( 'wp_audio_shortcode_override', '', $attr, $content, $instance );
+ if ( '' !== $override ) {
+ return $override;
+ }
+
+ $audio = null;
+
+ $default_types = wp_get_audio_extensions();
+ $defaults_atts = array(
+ 'src' => '',
+ 'loop' => '',
+ 'autoplay' => '',
+ 'preload' => 'none',
+ 'class' => 'wp-audio-shortcode',
+ 'style' => 'width: 100%;'
+ );
+ foreach ( $default_types as $type ) {
+ $defaults_atts[$type] = '';
+ }
+
+ $atts = shortcode_atts( $defaults_atts, $attr, 'audio' );
+
+ $primary = false;
+ if ( ! empty( $atts['src'] ) ) {
+ $type = wp_check_filetype( $atts['src'], wp_get_mime_types() );
+ if ( ! in_array( strtolower( $type['ext'] ), $default_types ) ) {
+ return sprintf( '<a class="wp-embedded-audio" href="%s">%s</a>', esc_url( $atts['src'] ), esc_html( $atts['src'] ) );
+ }
+ $primary = true;
+ array_unshift( $default_types, 'src' );
+ } else {
+ foreach ( $default_types as $ext ) {
+ if ( ! empty( $atts[ $ext ] ) ) {
+ $type = wp_check_filetype( $atts[ $ext ], wp_get_mime_types() );
+ if ( strtolower( $type['ext'] ) === $ext ) {
+ $primary = true;
+ }
+ }
+ }
+ }
+
+ if ( ! $primary ) {
+ $audios = get_attached_media( 'audio', $post_id );
+ if ( empty( $audios ) ) {
+ return;
+ }
+
+ $audio = reset( $audios );
+ $atts['src'] = wp_get_attachment_url( $audio->ID );
+ if ( empty( $atts['src'] ) ) {
+ return;
+ }
+
+ array_unshift( $default_types, 'src' );
+ }
+
+ /**
+ * Filters the media library used for the audio shortcode.
+ *
+ * @since 3.6.0
+ *
+ * @param string $library Media library used for the audio shortcode.
+ */
+ $library = apply_filters( 'wp_audio_shortcode_library', 'mediaelement' );
+ if ( 'mediaelement' === $library && did_action( 'init' ) ) {
+ wp_enqueue_style( 'wp-mediaelement' );
+ wp_enqueue_script( 'wp-mediaelement' );
+ }
+
+ /**
+ * Filters the class attribute for the audio shortcode output container.
+ *
+ * @since 3.6.0
+ *
+ * @param string $class CSS class or list of space-separated classes.
+ */
+ $atts['class'] = apply_filters( 'wp_audio_shortcode_class', $atts['class'] );
+
+ $html_atts = array(
+ 'class' => $atts['class'],
+ 'id' => sprintf( 'audio-%d-%d', $post_id, $instance ),
+ 'loop' => wp_validate_boolean( $atts['loop'] ),
+ 'autoplay' => wp_validate_boolean( $atts['autoplay'] ),
+ 'preload' => $atts['preload'],
+ 'style' => $atts['style'],
+ );
+
+ // These ones should just be omitted altogether if they are blank
+ foreach ( array( 'loop', 'autoplay', 'preload' ) as $a ) {
+ if ( empty( $html_atts[$a] ) ) {
+ unset( $html_atts[$a] );
+ }
+ }
+
+ $attr_strings = array();
+ foreach ( $html_atts as $k => $v ) {
+ $attr_strings[] = $k . '="' . esc_attr( $v ) . '"';
+ }
+
+ $html = '';
+ if ( 'mediaelement' === $library && 1 === $instance ) {
+ $html .= "<!--[if lt IE 9]><script>document.createElement('audio');</script><![endif]-->\n";
+ }
+ $html .= sprintf( '<audio %s controls="controls">', join( ' ', $attr_strings ) );
+
+ $fileurl = '';
+ $source = '<source type="%s" src="%s" />';
+ foreach ( $default_types as $fallback ) {
+ if ( ! empty( $atts[ $fallback ] ) ) {
+ if ( empty( $fileurl ) ) {
+ $fileurl = $atts[ $fallback ];
+ }
+ $type = wp_check_filetype( $atts[ $fallback ], wp_get_mime_types() );
+ $url = add_query_arg( '_', $instance, $atts[ $fallback ] );
+ $html .= sprintf( $source, $type['type'], esc_url( $url ) );
+ }
+ }
+
+ if ( 'mediaelement' === $library ) {
+ $html .= wp_mediaelement_fallback( $fileurl );
+ }
+ $html .= '</audio>';
+
+ /**
+ * Filters the audio shortcode output.
+ *
+ * @since 3.6.0
+ *
+ * @param string $html Audio shortcode HTML output.
+ * @param array $atts Array of audio shortcode attributes.
+ * @param string $audio Audio file.
+ * @param int $post_id Post ID.
+ * @param string $library Media library used for the audio shortcode.
+ */
+ return apply_filters( 'wp_audio_shortcode', $html, $atts, $audio, $post_id, $library );
+}
+add_shortcode( 'audio', 'wp_audio_shortcode' );
+
+/**
+ * Returns a filtered list of WP-supported video formats.
+ *
+ * @since 3.6.0
+ *
+ * @return array List of supported video formats.
+ */
+function wp_get_video_extensions() {
+ /**
+ * Filters the list of supported video formats.
+ *
+ * @since 3.6.0
+ *
+ * @param array $extensions An array of support video formats. Defaults are
+ * 'mp4', 'm4v', 'webm', 'ogv', 'wmv', 'flv'.
+ */
+ return apply_filters( 'wp_video_extensions', array( 'mp4', 'm4v', 'webm', 'ogv', 'wmv', 'flv' ) );
+}
+
+/**
+ * Builds the Video shortcode output.
+ *
+ * This implements the functionality of the Video Shortcode for displaying
+ * WordPress mp4s in a post.
+ *
+ * @since 3.6.0
+ *
+ * @global int $content_width
+ * @staticvar int $instance
+ *
+ * @param array $attr {
+ * Attributes of the shortcode.
+ *
+ * @type string $src URL to the source of the video file. Default empty.
+ * @type int $height Height of the video embed in pixels. Default 360.
+ * @type int $width Width of the video embed in pixels. Default $content_width or 640.
+ * @type string $poster The 'poster' attribute for the `<video>` element. Default empty.
+ * @type string $loop The 'loop' attribute for the `<video>` element. Default empty.
+ * @type string $autoplay The 'autoplay' attribute for the `<video>` element. Default empty.
+ * @type string $preload The 'preload' attribute for the `<video>` element.
+ * Default 'metadata'.
+ * @type string $class The 'class' attribute for the `<video>` element.
+ * Default 'wp-video-shortcode'.
+ * }
+ * @param string $content Shortcode content.
+ * @return string|void HTML content to display video.
+ */
+function wp_video_shortcode( $attr, $content = '' ) {
+ global $content_width;
+ $post_id = get_post() ? get_the_ID() : 0;
+
+ static $instance = 0;
+ $instance++;
+
+ /**
+ * Filters the default video shortcode output.
+ *
+ * If the filtered output isn't empty, it will be used instead of generating
+ * the default video template.
+ *
+ * @since 3.6.0
+ *
+ * @see wp_video_shortcode()
+ *
+ * @param string $html Empty variable to be replaced with shortcode markup.
+ * @param array $attr Attributes of the video shortcode.
+ * @param string $content Video shortcode content.
+ * @param int $instance Unique numeric ID of this video shortcode instance.
+ */
+ $override = apply_filters( 'wp_video_shortcode_override', '', $attr, $content, $instance );
+ if ( '' !== $override ) {
+ return $override;
+ }
+
+ $video = null;
+
+ $default_types = wp_get_video_extensions();
+ $defaults_atts = array(
+ 'src' => '',
+ 'poster' => '',
+ 'loop' => '',
+ 'autoplay' => '',
+ 'preload' => 'metadata',
+ 'width' => 640,
+ 'height' => 360,
+ 'class' => 'wp-video-shortcode',
+ );
+
+ foreach ( $default_types as $type ) {
+ $defaults_atts[$type] = '';
+ }
+
+ $atts = shortcode_atts( $defaults_atts, $attr, 'video' );
+
+ if ( is_admin() ) {
+ // shrink the video so it isn't huge in the admin
+ if ( $atts['width'] > $defaults_atts['width'] ) {
+ $atts['height'] = round( ( $atts['height'] * $defaults_atts['width'] ) / $atts['width'] );
+ $atts['width'] = $defaults_atts['width'];
+ }
+ } else {
+ // if the video is bigger than the theme
+ if ( ! empty( $content_width ) && $atts['width'] > $content_width ) {
+ $atts['height'] = round( ( $atts['height'] * $content_width ) / $atts['width'] );
+ $atts['width'] = $content_width;
+ }
+ }
+
+ $is_vimeo = $is_youtube = false;
+ $yt_pattern = '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#';
+ $vimeo_pattern = '#^https?://(.+\.)?vimeo\.com/.*#';
+
+ $primary = false;
+ if ( ! empty( $atts['src'] ) ) {
+ $is_vimeo = ( preg_match( $vimeo_pattern, $atts['src'] ) );
+ $is_youtube = ( preg_match( $yt_pattern, $atts['src'] ) );
+ if ( ! $is_youtube && ! $is_vimeo ) {
+ $type = wp_check_filetype( $atts['src'], wp_get_mime_types() );
+ if ( ! in_array( strtolower( $type['ext'] ), $default_types ) ) {
+ return sprintf( '<a class="wp-embedded-video" href="%s">%s</a>', esc_url( $atts['src'] ), esc_html( $atts['src'] ) );
+ }
+ }
+
+ if ( $is_vimeo ) {
+ wp_enqueue_script( 'froogaloop' );
+ }
+
+ $primary = true;
+ array_unshift( $default_types, 'src' );
+ } else {
+ foreach ( $default_types as $ext ) {
+ if ( ! empty( $atts[ $ext ] ) ) {
+ $type = wp_check_filetype( $atts[ $ext ], wp_get_mime_types() );
+ if ( strtolower( $type['ext'] ) === $ext ) {
+ $primary = true;
+ }
+ }
+ }
+ }
+
+ if ( ! $primary ) {
+ $videos = get_attached_media( 'video', $post_id );
+ if ( empty( $videos ) ) {
+ return;
+ }
+
+ $video = reset( $videos );
+ $atts['src'] = wp_get_attachment_url( $video->ID );
+ if ( empty( $atts['src'] ) ) {
+ return;
+ }
+
+ array_unshift( $default_types, 'src' );
+ }
+
+ /**
+ * Filters the media library used for the video shortcode.
+ *
+ * @since 3.6.0
+ *
+ * @param string $library Media library used for the video shortcode.
+ */
+ $library = apply_filters( 'wp_video_shortcode_library', 'mediaelement' );
+ if ( 'mediaelement' === $library && did_action( 'init' ) ) {
+ wp_enqueue_style( 'wp-mediaelement' );
+ wp_enqueue_script( 'wp-mediaelement' );
+ }
+
+ /**
+ * Filters the class attribute for the video shortcode output container.
+ *
+ * @since 3.6.0
+ *
+ * @param string $class CSS class or list of space-separated classes.
+ */
+ $atts['class'] = apply_filters( 'wp_video_shortcode_class', $atts['class'] );
+
+ $html_atts = array(
+ 'class' => $atts['class'],
+ 'id' => sprintf( 'video-%d-%d', $post_id, $instance ),
+ 'width' => absint( $atts['width'] ),
+ 'height' => absint( $atts['height'] ),
+ 'poster' => esc_url( $atts['poster'] ),
+ 'loop' => wp_validate_boolean( $atts['loop'] ),
+ 'autoplay' => wp_validate_boolean( $atts['autoplay'] ),
+ 'preload' => $atts['preload'],
+ );
+
+ // These ones should just be omitted altogether if they are blank
+ foreach ( array( 'poster', 'loop', 'autoplay', 'preload' ) as $a ) {
+ if ( empty( $html_atts[$a] ) ) {
+ unset( $html_atts[$a] );
+ }
+ }
+
+ $attr_strings = array();
+ foreach ( $html_atts as $k => $v ) {
+ $attr_strings[] = $k . '="' . esc_attr( $v ) . '"';
+ }
+
+ $html = '';
+ if ( 'mediaelement' === $library && 1 === $instance ) {
+ $html .= "<!--[if lt IE 9]><script>document.createElement('video');</script><![endif]-->\n";
+ }
+ $html .= sprintf( '<video %s controls="controls">', join( ' ', $attr_strings ) );
+
+ $fileurl = '';
+ $source = '<source type="%s" src="%s" />';
+ foreach ( $default_types as $fallback ) {
+ if ( ! empty( $atts[ $fallback ] ) ) {
+ if ( empty( $fileurl ) ) {
+ $fileurl = $atts[ $fallback ];
+ }
+ if ( 'src' === $fallback && $is_youtube ) {
+ $type = array( 'type' => 'video/youtube' );
+ } elseif ( 'src' === $fallback && $is_vimeo ) {
+ $type = array( 'type' => 'video/vimeo' );
+ } else {
+ $type = wp_check_filetype( $atts[ $fallback ], wp_get_mime_types() );
+ }
+ $url = add_query_arg( '_', $instance, $atts[ $fallback ] );
+ $html .= sprintf( $source, $type['type'], esc_url( $url ) );
+ }
+ }
+
+ if ( ! empty( $content ) ) {
+ if ( false !== strpos( $content, "\n" ) ) {
+ $content = str_replace( array( "\r\n", "\n", "\t" ), '', $content );
+ }
+ $html .= trim( $content );
+ }
+
+ if ( 'mediaelement' === $library ) {
+ $html .= wp_mediaelement_fallback( $fileurl );
+ }
+ $html .= '</video>';
+
+ $width_rule = '';
+ if ( ! empty( $atts['width'] ) ) {
+ $width_rule = sprintf( 'width: %dpx;', $atts['width'] );
+ }
+ $output = sprintf( '<div style="%s" class="wp-video">%s</div>', $width_rule, $html );
+
+ /**
+ * Filters the output of the video shortcode.
+ *
+ * @since 3.6.0
+ *
+ * @param string $output Video shortcode HTML output.
+ * @param array $atts Array of video shortcode attributes.
+ * @param string $video Video file.
+ * @param int $post_id Post ID.
+ * @param string $library Media library used for the video shortcode.
+ */
+ return apply_filters( 'wp_video_shortcode', $output, $atts, $video, $post_id, $library );
+}
+add_shortcode( 'video', 'wp_video_shortcode' );
+
+/**
+ * Displays previous image link that has the same post parent.
+ *
+ * @since 2.5.0
+ *
+ * @see adjacent_image_link()
+ *
+ * @param string|array $size Optional. Image size. Accepts any valid image size, an array of width and
+ * height values in pixels (in that order), 0, or 'none'. 0 or 'none' will
+ * default to 'post_title' or `$text`. Default 'thumbnail'.
+ * @param string $text Optional. Link text. Default false.
+ */
+function previous_image_link( $size = 'thumbnail', $text = false ) {
+ adjacent_image_link(true, $size, $text);
+}
+
+/**
+ * Displays next image link that has the same post parent.
+ *
+ * @since 2.5.0
+ *
+ * @see adjacent_image_link()
+ *
+ * @param string|array $size Optional. Image size. Accepts any valid image size, an array of width and
+ * height values in pixels (in that order), 0, or 'none'. 0 or 'none' will
+ * default to 'post_title' or `$text`. Default 'thumbnail'.
+ * @param string $text Optional. Link text. Default false.
+ */
+function next_image_link( $size = 'thumbnail', $text = false ) {
+ adjacent_image_link(false, $size, $text);
+}
+
+/**
+ * Displays next or previous image link that has the same post parent.
+ *
+ * Retrieves the current attachment object from the $post global.
+ *
+ * @since 2.5.0
+ *
+ * @param bool $prev Optional. Whether to display the next (false) or previous (true) link. Default true.
+ * @param string|array $size Optional. Image size. Accepts any valid image size, or an array of width and height
+ * values in pixels (in that order). Default 'thumbnail'.
+ * @param bool $text Optional. Link text. Default false.
+ */
+function adjacent_image_link( $prev = true, $size = 'thumbnail', $text = false ) {
+ $post = get_post();
+ $attachments = array_values( get_children( array( 'post_parent' => $post->post_parent, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => 'ASC', 'orderby' => 'menu_order ID' ) ) );
+
+ foreach ( $attachments as $k => $attachment ) {
+ if ( $attachment->ID == $post->ID ) {
+ break;
+ }
+ }
+
+ $output = '';
+ $attachment_id = 0;
+
+ if ( $attachments ) {
+ $k = $prev ? $k - 1 : $k + 1;
+
+ if ( isset( $attachments[ $k ] ) ) {
+ $attachment_id = $attachments[ $k ]->ID;
+ $output = wp_get_attachment_link( $attachment_id, $size, true, false, $text );
+ }
+ }
+
+ $adjacent = $prev ? 'previous' : 'next';
+
+ /**
+ * Filters the adjacent image link.
+ *
+ * The dynamic portion of the hook name, `$adjacent`, refers to the type of adjacency,
+ * either 'next', or 'previous'.
+ *
+ * @since 3.5.0
+ *
+ * @param string $output Adjacent image HTML markup.
+ * @param int $attachment_id Attachment ID
+ * @param string $size Image size.
+ * @param string $text Link text.
+ */
+ echo apply_filters( "{$adjacent}_image_link", $output, $attachment_id, $size, $text );
+}
+
+/**
+ * Retrieves taxonomies attached to given the attachment.
+ *
+ * @since 2.5.0
+ * @since 4.7.0 Introduced the `$output` parameter.
+ *
+ * @param int|array|object $attachment Attachment ID, data array, or data object.
+ * @param string $output Output type. 'names' to return an array of taxonomy names,
+ * or 'objects' to return an array of taxonomy objects.
+ * Default is 'names'.
+ * @return array Empty array on failure. List of taxonomies on success.
+ */
+function get_attachment_taxonomies( $attachment, $output = 'names' ) {
+ if ( is_int( $attachment ) ) {
+ $attachment = get_post( $attachment );
+ } elseif ( is_array( $attachment ) ) {
+ $attachment = (object) $attachment;
+ }
+ if ( ! is_object($attachment) )
+ return array();
+
+ $file = get_attached_file( $attachment->ID );
+ $filename = basename( $file );
+
+ $objects = array('attachment');
+
+ if ( false !== strpos($filename, '.') )
+ $objects[] = 'attachment:' . substr($filename, strrpos($filename, '.') + 1);
+ if ( !empty($attachment->post_mime_type) ) {
+ $objects[] = 'attachment:' . $attachment->post_mime_type;
+ if ( false !== strpos($attachment->post_mime_type, '/') )
+ foreach ( explode('/', $attachment->post_mime_type) as $token )