"Fossies" - the Fresh Open Source Software Archive

Member "wordpress/wp-includes/media.php" (9 Feb 2021, 172258 Bytes) of package /linux/www/wordpress-5.7-RC1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "media.php" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 5.6.2_vs_5.7-RC1.

    1 <?php
    2 /**
    3  * WordPress API for media display.
    4  *
    5  * @package WordPress
    6  * @subpackage Media
    7  */
    8 
    9 /**
   10  * Retrieve additional image sizes.
   11  *
   12  * @since 4.7.0
   13  *
   14  * @global array $_wp_additional_image_sizes
   15  *
   16  * @return array Additional images size data.
   17  */
   18 function wp_get_additional_image_sizes() {
   19     global $_wp_additional_image_sizes;
   20 
   21     if ( ! $_wp_additional_image_sizes ) {
   22         $_wp_additional_image_sizes = array();
   23     }
   24 
   25     return $_wp_additional_image_sizes;
   26 }
   27 
   28 /**
   29  * Scale down the default size of an image.
   30  *
   31  * This is so that the image is a better fit for the editor and theme.
   32  *
   33  * The `$size` parameter accepts either an array or a string. The supported string
   34  * values are 'thumb' or 'thumbnail' for the given thumbnail size or defaults at
   35  * 128 width and 96 height in pixels. Also supported for the string value is
   36  * 'medium', 'medium_large' and 'full'. The 'full' isn't actually supported, but any value other
   37  * than the supported will result in the content_width size or 500 if that is
   38  * not set.
   39  *
   40  * Finally, there is a filter named {@see 'editor_max_image_size'}, that will be
   41  * called on the calculated array for width and height, respectively.
   42  *
   43  * @since 2.5.0
   44  *
   45  * @global int $content_width
   46  *
   47  * @param int          $width   Width of the image in pixels.
   48  * @param int          $height  Height of the image in pixels.
   49  * @param string|int[] $size    Optional. Image size. Accepts any registered image size name, or an array
   50  *                              of width and height values in pixels (in that order). Default 'medium'.
   51  * @param string       $context Optional. Could be 'display' (like in a theme) or 'edit'
   52  *                              (like inserting into an editor). Default null.
   53  * @return int[] {
   54  *     An array of width and height values.
   55  *
   56  *     @type int $0 The maximum width in pixels.
   57  *     @type int $1 The maximum height in pixels.
   58  * }
   59  */
   60 function image_constrain_size_for_editor( $width, $height, $size = 'medium', $context = null ) {
   61     global $content_width;
   62 
   63     $_wp_additional_image_sizes = wp_get_additional_image_sizes();
   64 
   65     if ( ! $context ) {
   66         $context = is_admin() ? 'edit' : 'display';
   67     }
   68 
   69     if ( is_array( $size ) ) {
   70         $max_width  = $size[0];
   71         $max_height = $size[1];
   72     } elseif ( 'thumb' === $size || 'thumbnail' === $size ) {
   73         $max_width  = (int) get_option( 'thumbnail_size_w' );
   74         $max_height = (int) get_option( 'thumbnail_size_h' );
   75         // Last chance thumbnail size defaults.
   76         if ( ! $max_width && ! $max_height ) {
   77             $max_width  = 128;
   78             $max_height = 96;
   79         }
   80     } elseif ( 'medium' === $size ) {
   81         $max_width  = (int) get_option( 'medium_size_w' );
   82         $max_height = (int) get_option( 'medium_size_h' );
   83 
   84     } elseif ( 'medium_large' === $size ) {
   85         $max_width  = (int) get_option( 'medium_large_size_w' );
   86         $max_height = (int) get_option( 'medium_large_size_h' );
   87 
   88         if ( (int) $content_width > 0 ) {
   89             $max_width = min( (int) $content_width, $max_width );
   90         }
   91     } elseif ( 'large' === $size ) {
   92         /*
   93          * We're inserting a large size image into the editor. If it's a really
   94          * big image we'll scale it down to fit reasonably within the editor
   95          * itself, and within the theme's content width if it's known. The user
   96          * can resize it in the editor if they wish.
   97          */
   98         $max_width  = (int) get_option( 'large_size_w' );
   99         $max_height = (int) get_option( 'large_size_h' );
  100 
  101         if ( (int) $content_width > 0 ) {
  102             $max_width = min( (int) $content_width, $max_width );
  103         }
  104     } elseif ( ! empty( $_wp_additional_image_sizes ) && in_array( $size, array_keys( $_wp_additional_image_sizes ), true ) ) {
  105         $max_width  = (int) $_wp_additional_image_sizes[ $size ]['width'];
  106         $max_height = (int) $_wp_additional_image_sizes[ $size ]['height'];
  107         // Only in admin. Assume that theme authors know what they're doing.
  108         if ( (int) $content_width > 0 && 'edit' === $context ) {
  109             $max_width = min( (int) $content_width, $max_width );
  110         }
  111     } else { // $size === 'full' has no constraint.
  112         $max_width  = $width;
  113         $max_height = $height;
  114     }
  115 
  116     /**
  117      * Filters the maximum image size dimensions for the editor.
  118      *
  119      * @since 2.5.0
  120      *
  121      * @param int[]        $max_image_size {
  122      *     An array of width and height values.
  123      *
  124      *     @type int $0 The maximum width in pixels.
  125      *     @type int $1 The maximum height in pixels.
  126      * }
  127      * @param string|int[] $size     Requested image size. Can be any registered image size name, or
  128      *                               an array of width and height values in pixels (in that order).
  129      * @param string       $context  The context the image is being resized for.
  130      *                               Possible values are 'display' (like in a theme)
  131      *                               or 'edit' (like inserting into an editor).
  132      */
  133     list( $max_width, $max_height ) = apply_filters( 'editor_max_image_size', array( $max_width, $max_height ), $size, $context );
  134 
  135     return wp_constrain_dimensions( $width, $height, $max_width, $max_height );
  136 }
  137 
  138 /**
  139  * Retrieve width and height attributes using given width and height values.
  140  *
  141  * Both attributes are required in the sense that both parameters must have a
  142  * value, but are optional in that if you set them to false or null, then they
  143  * will not be added to the returned string.
  144  *
  145  * You can set the value using a string, but it will only take numeric values.
  146  * If you wish to put 'px' after the numbers, then it will be stripped out of
  147  * the return.
  148  *
  149  * @since 2.5.0
  150  *
  151  * @param int|string $width  Image width in pixels.
  152  * @param int|string $height Image height in pixels.
  153  * @return string HTML attributes for width and, or height.
  154  */
  155 function image_hwstring( $width, $height ) {
  156     $out = '';
  157     if ( $width ) {
  158         $out .= 'width="' . (int) $width . '" ';
  159     }
  160     if ( $height ) {
  161         $out .= 'height="' . (int) $height . '" ';
  162     }
  163     return $out;
  164 }
  165 
  166 /**
  167  * Scale an image to fit a particular size (such as 'thumb' or 'medium').
  168  *
  169  * The URL might be the original image, or it might be a resized version. This
  170  * function won't create a new resized copy, it will just return an already
  171  * resized one if it exists.
  172  *
  173  * A plugin may use the {@see 'image_downsize'} filter to hook into and offer image
  174  * resizing services for images. The hook must return an array with the same
  175  * elements that are normally returned from the function.
  176  *
  177  * @since 2.5.0
  178  *
  179  * @param int          $id   Attachment ID for image.
  180  * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array
  181  *                           of width and height values in pixels (in that order). Default 'medium'.
  182  * @return array|false {
  183  *     Array of image data, or boolean false if no image is available.
  184  *
  185  *     @type string $0 Image source URL.
  186  *     @type int    $1 Image width in pixels.
  187  *     @type int    $2 Image height in pixels.
  188  *     @type bool   $3 Whether the image is a resized image.
  189  * }
  190  */
  191 function image_downsize( $id, $size = 'medium' ) {
  192     $is_image = wp_attachment_is_image( $id );
  193 
  194     /**
  195      * Filters whether to preempt the output of image_downsize().
  196      *
  197      * Returning a truthy value from the filter will effectively short-circuit
  198      * down-sizing the image, returning that value instead.
  199      *
  200      * @since 2.5.0
  201      *
  202      * @param bool|array   $downsize Whether to short-circuit the image downsize.
  203      * @param int          $id       Attachment ID for image.
  204      * @param string|int[] $size     Requested image size. Can be any registered image size name, or
  205      *                               an array of width and height values in pixels (in that order).
  206      */
  207     $out = apply_filters( 'image_downsize', false, $id, $size );
  208 
  209     if ( $out ) {
  210         return $out;
  211     }
  212 
  213     $img_url          = wp_get_attachment_url( $id );
  214     $meta             = wp_get_attachment_metadata( $id );
  215     $width            = 0;
  216     $height           = 0;
  217     $is_intermediate  = false;
  218     $img_url_basename = wp_basename( $img_url );
  219 
  220     // If the file isn't an image, attempt to replace its URL with a rendered image from its meta.
  221     // Otherwise, a non-image type could be returned.
  222     if ( ! $is_image ) {
  223         if ( ! empty( $meta['sizes']['full'] ) ) {
  224             $img_url          = str_replace( $img_url_basename, $meta['sizes']['full']['file'], $img_url );
  225             $img_url_basename = $meta['sizes']['full']['file'];
  226             $width            = $meta['sizes']['full']['width'];
  227             $height           = $meta['sizes']['full']['height'];
  228         } else {
  229             return false;
  230         }
  231     }
  232 
  233     // Try for a new style intermediate size.
  234     $intermediate = image_get_intermediate_size( $id, $size );
  235 
  236     if ( $intermediate ) {
  237         $img_url         = str_replace( $img_url_basename, $intermediate['file'], $img_url );
  238         $width           = $intermediate['width'];
  239         $height          = $intermediate['height'];
  240         $is_intermediate = true;
  241     } elseif ( 'thumbnail' === $size ) {
  242         // Fall back to the old thumbnail.
  243         $thumb_file = wp_get_attachment_thumb_file( $id );
  244         $info       = null;
  245 
  246         if ( $thumb_file ) {
  247             $info = wp_getimagesize( $thumb_file );
  248         }
  249 
  250         if ( $thumb_file && $info ) {
  251             $img_url         = str_replace( $img_url_basename, wp_basename( $thumb_file ), $img_url );
  252             $width           = $info[0];
  253             $height          = $info[1];
  254             $is_intermediate = true;
  255         }
  256     }
  257 
  258     if ( ! $width && ! $height && isset( $meta['width'], $meta['height'] ) ) {
  259         // Any other type: use the real image.
  260         $width  = $meta['width'];
  261         $height = $meta['height'];
  262     }
  263 
  264     if ( $img_url ) {
  265         // We have the actual image size, but might need to further constrain it if content_width is narrower.
  266         list( $width, $height ) = image_constrain_size_for_editor( $width, $height, $size );
  267 
  268         return array( $img_url, $width, $height, $is_intermediate );
  269     }
  270 
  271     return false;
  272 }
  273 
  274 /**
  275  * Register a new image size.
  276  *
  277  * @since 2.9.0
  278  *
  279  * @global array $_wp_additional_image_sizes Associative array of additional image sizes.
  280  *
  281  * @param string     $name   Image size identifier.
  282  * @param int        $width  Optional. Image width in pixels. Default 0.
  283  * @param int        $height Optional. Image height in pixels. Default 0.
  284  * @param bool|array $crop   Optional. Image cropping behavior. If false, the image will be scaled (default),
  285  *                           If true, image will be cropped to the specified dimensions using center positions.
  286  *                           If an array, the image will be cropped using the array to specify the crop location.
  287  *                           Array values must be in the format: array( x_crop_position, y_crop_position ) where:
  288  *                               - x_crop_position accepts: 'left', 'center', or 'right'.
  289  *                               - y_crop_position accepts: 'top', 'center', or 'bottom'.
  290  */
  291 function add_image_size( $name, $width = 0, $height = 0, $crop = false ) {
  292     global $_wp_additional_image_sizes;
  293 
  294     $_wp_additional_image_sizes[ $name ] = array(
  295         'width'  => absint( $width ),
  296         'height' => absint( $height ),
  297         'crop'   => $crop,
  298     );
  299 }
  300 
  301 /**
  302  * Check if an image size exists.
  303  *
  304  * @since 3.9.0
  305  *
  306  * @param string $name The image size to check.
  307  * @return bool True if the image size exists, false if not.
  308  */
  309 function has_image_size( $name ) {
  310     $sizes = wp_get_additional_image_sizes();
  311     return isset( $sizes[ $name ] );
  312 }
  313 
  314 /**
  315  * Remove a new image size.
  316  *
  317  * @since 3.9.0
  318  *
  319  * @global array $_wp_additional_image_sizes
  320  *
  321  * @param string $name The image size to remove.
  322  * @return bool True if the image size was successfully removed, false on failure.
  323  */
  324 function remove_image_size( $name ) {
  325     global $_wp_additional_image_sizes;
  326 
  327     if ( isset( $_wp_additional_image_sizes[ $name ] ) ) {
  328         unset( $_wp_additional_image_sizes[ $name ] );
  329         return true;
  330     }
  331 
  332     return false;
  333 }
  334 
  335 /**
  336  * Registers an image size for the post thumbnail.
  337  *
  338  * @since 2.9.0
  339  *
  340  * @see add_image_size() for details on cropping behavior.
  341  *
  342  * @param int        $width  Image width in pixels.
  343  * @param int        $height Image height in pixels.
  344  * @param bool|array $crop   Optional. Whether to crop images to specified width and height or resize.
  345  *                           An array can specify positioning of the crop area. Default false.
  346  */
  347 function set_post_thumbnail_size( $width = 0, $height = 0, $crop = false ) {
  348     add_image_size( 'post-thumbnail', $width, $height, $crop );
  349 }
  350 
  351 /**
  352  * Gets an img tag for an image attachment, scaling it down if requested.
  353  *
  354  * The {@see 'get_image_tag_class'} filter allows for changing the class name for the
  355  * image without having to use regular expressions on the HTML content. The
  356  * parameters are: what WordPress will use for the class, the Attachment ID,
  357  * image align value, and the size the image should be.
  358  *
  359  * The second filter, {@see 'get_image_tag'}, has the HTML content, which can then be
  360  * further manipulated by a plugin to change all attribute values and even HTML
  361  * content.
  362  *
  363  * @since 2.5.0
  364  *
  365  * @param int          $id    Attachment ID.
  366  * @param string       $alt   Image description for the alt attribute.
  367  * @param string       $title Image description for the title attribute.
  368  * @param string       $align Part of the class name for aligning the image.
  369  * @param string|int[] $size  Optional. Image size. Accepts any registered image size name, or an array of
  370  *                            width and height values in pixels (in that order). Default 'medium'.
  371  * @return string HTML IMG element for given image attachment
  372  */
  373 function get_image_tag( $id, $alt, $title, $align, $size = 'medium' ) {
  374 
  375     list( $img_src, $width, $height ) = image_downsize( $id, $size );
  376     $hwstring                         = image_hwstring( $width, $height );
  377 
  378     $title = $title ? 'title="' . esc_attr( $title ) . '" ' : '';
  379 
  380     $size_class = is_array( $size ) ? implode( 'x', $size ) : $size;
  381     $class      = 'align' . esc_attr( $align ) . ' size-' . esc_attr( $size_class ) . ' wp-image-' . $id;
  382 
  383     /**
  384      * Filters the value of the attachment's image tag class attribute.
  385      *
  386      * @since 2.6.0
  387      *
  388      * @param string       $class CSS class name or space-separated list of classes.
  389      * @param int          $id    Attachment ID.
  390      * @param string       $align Part of the class name for aligning the image.
  391      * @param string|int[] $size  Requested image size. Can be any registered image size name, or
  392      *                            an array of width and height values in pixels (in that order).
  393      */
  394     $class = apply_filters( 'get_image_tag_class', $class, $id, $align, $size );
  395 
  396     $html = '<img src="' . esc_attr( $img_src ) . '" alt="' . esc_attr( $alt ) . '" ' . $title . $hwstring . 'class="' . $class . '" />';
  397 
  398     /**
  399      * Filters the HTML content for the image tag.
  400      *
  401      * @since 2.6.0
  402      *
  403      * @param string       $html  HTML content for the image.
  404      * @param int          $id    Attachment ID.
  405      * @param string       $alt   Image description for the alt attribute.
  406      * @param string       $title Image description for the title attribute.
  407      * @param string       $align Part of the class name for aligning the image.
  408      * @param string|int[] $size  Requested image size. Can be any registered image size name, or
  409      *                            an array of width and height values in pixels (in that order).
  410      */
  411     return apply_filters( 'get_image_tag', $html, $id, $alt, $title, $align, $size );
  412 }
  413 
  414 /**
  415  * Calculates the new dimensions for a down-sampled image.
  416  *
  417  * If either width or height are empty, no constraint is applied on
  418  * that dimension.
  419  *
  420  * @since 2.5.0
  421  *
  422  * @param int $current_width  Current width of the image.
  423  * @param int $current_height Current height of the image.
  424  * @param int $max_width      Optional. Max width in pixels to constrain to. Default 0.
  425  * @param int $max_height     Optional. Max height in pixels to constrain to. Default 0.
  426  * @return int[] {
  427  *     An array of width and height values.
  428  *
  429  *     @type int $0 The width in pixels.
  430  *     @type int $1 The height in pixels.
  431  * }
  432  */
  433 function wp_constrain_dimensions( $current_width, $current_height, $max_width = 0, $max_height = 0 ) {
  434     if ( ! $max_width && ! $max_height ) {
  435         return array( $current_width, $current_height );
  436     }
  437 
  438     $width_ratio  = 1.0;
  439     $height_ratio = 1.0;
  440     $did_width    = false;
  441     $did_height   = false;
  442 
  443     if ( $max_width > 0 && $current_width > 0 && $current_width > $max_width ) {
  444         $width_ratio = $max_width / $current_width;
  445         $did_width   = true;
  446     }
  447 
  448     if ( $max_height > 0 && $current_height > 0 && $current_height > $max_height ) {
  449         $height_ratio = $max_height / $current_height;
  450         $did_height   = true;
  451     }
  452 
  453     // Calculate the larger/smaller ratios.
  454     $smaller_ratio = min( $width_ratio, $height_ratio );
  455     $larger_ratio  = max( $width_ratio, $height_ratio );
  456 
  457     if ( (int) round( $current_width * $larger_ratio ) > $max_width || (int) round( $current_height * $larger_ratio ) > $max_height ) {
  458         // The larger ratio is too big. It would result in an overflow.
  459         $ratio = $smaller_ratio;
  460     } else {
  461         // The larger ratio fits, and is likely to be a more "snug" fit.
  462         $ratio = $larger_ratio;
  463     }
  464 
  465     // Very small dimensions may result in 0, 1 should be the minimum.
  466     $w = max( 1, (int) round( $current_width * $ratio ) );
  467     $h = max( 1, (int) round( $current_height * $ratio ) );
  468 
  469     /*
  470      * Sometimes, due to rounding, we'll end up with a result like this:
  471      * 465x700 in a 177x177 box is 117x176... a pixel short.
  472      * We also have issues with recursive calls resulting in an ever-changing result.
  473      * Constraining to the result of a constraint should yield the original result.
  474      * Thus we look for dimensions that are one pixel shy of the max value and bump them up.
  475      */
  476 
  477     // Note: $did_width means it is possible $smaller_ratio == $width_ratio.
  478     if ( $did_width && $w === $max_width - 1 ) {
  479         $w = $max_width; // Round it up.
  480     }
  481 
  482     // Note: $did_height means it is possible $smaller_ratio == $height_ratio.
  483     if ( $did_height && $h === $max_height - 1 ) {
  484         $h = $max_height; // Round it up.
  485     }
  486 
  487     /**
  488      * Filters dimensions to constrain down-sampled images to.
  489      *
  490      * @since 4.1.0
  491      *
  492      * @param int[] $dimensions     {
  493      *     An array of width and height values.
  494      *
  495      *     @type int $0 The width in pixels.
  496      *     @type int $1 The height in pixels.
  497      * }
  498      * @param int   $current_width  The current width of the image.
  499      * @param int   $current_height The current height of the image.
  500      * @param int   $max_width      The maximum width permitted.
  501      * @param int   $max_height     The maximum height permitted.
  502      */
  503     return apply_filters( 'wp_constrain_dimensions', array( $w, $h ), $current_width, $current_height, $max_width, $max_height );
  504 }
  505 
  506 /**
  507  * Retrieves calculated resize dimensions for use in WP_Image_Editor.
  508  *
  509  * Calculates dimensions and coordinates for a resized image that fits
  510  * within a specified width and height.
  511  *
  512  * Cropping behavior is dependent on the value of $crop:
  513  * 1. If false (default), images will not be cropped.
  514  * 2. If an array in the form of array( x_crop_position, y_crop_position ):
  515  *    - x_crop_position accepts 'left' 'center', or 'right'.
  516  *    - y_crop_position accepts 'top', 'center', or 'bottom'.
  517  *    Images will be cropped to the specified dimensions within the defined crop area.
  518  * 3. If true, images will be cropped to the specified dimensions using center positions.
  519  *
  520  * @since 2.5.0
  521  *
  522  * @param int        $orig_w Original width in pixels.
  523  * @param int        $orig_h Original height in pixels.
  524  * @param int        $dest_w New width in pixels.
  525  * @param int        $dest_h New height in pixels.
  526  * @param bool|array $crop   Optional. Whether to crop image to specified width and height or resize.
  527  *                           An array can specify positioning of the crop area. Default false.
  528  * @return array|false Returned array matches parameters for `imagecopyresampled()`. False on failure.
  529  */
  530 function image_resize_dimensions( $orig_w, $orig_h, $dest_w, $dest_h, $crop = false ) {
  531 
  532     if ( $orig_w <= 0 || $orig_h <= 0 ) {
  533         return false;
  534     }
  535     // At least one of $dest_w or $dest_h must be specific.
  536     if ( $dest_w <= 0 && $dest_h <= 0 ) {
  537         return false;
  538     }
  539 
  540     /**
  541      * Filters whether to preempt calculating the image resize dimensions.
  542      *
  543      * Returning a non-null value from the filter will effectively short-circuit
  544      * image_resize_dimensions(), returning that value instead.
  545      *
  546      * @since 3.4.0
  547      *
  548      * @param null|mixed $null   Whether to preempt output of the resize dimensions.
  549      * @param int        $orig_w Original width in pixels.
  550      * @param int        $orig_h Original height in pixels.
  551      * @param int        $dest_w New width in pixels.
  552      * @param int        $dest_h New height in pixels.
  553      * @param bool|array $crop   Whether to crop image to specified width and height or resize.
  554      *                           An array can specify positioning of the crop area. Default false.
  555      */
  556     $output = apply_filters( 'image_resize_dimensions', null, $orig_w, $orig_h, $dest_w, $dest_h, $crop );
  557 
  558     if ( null !== $output ) {
  559         return $output;
  560     }
  561 
  562     // Stop if the destination size is larger than the original image dimensions.
  563     if ( empty( $dest_h ) ) {
  564         if ( $orig_w < $dest_w ) {
  565             return false;
  566         }
  567     } elseif ( empty( $dest_w ) ) {
  568         if ( $orig_h < $dest_h ) {
  569             return false;
  570         }
  571     } else {
  572         if ( $orig_w < $dest_w && $orig_h < $dest_h ) {
  573             return false;
  574         }
  575     }
  576 
  577     if ( $crop ) {
  578         /*
  579          * Crop the largest possible portion of the original image that we can size to $dest_w x $dest_h.
  580          * Note that the requested crop dimensions are used as a maximum bounding box for the original image.
  581          * If the original image's width or height is less than the requested width or height
  582          * only the greater one will be cropped.
  583          * For example when the original image is 600x300, and the requested crop dimensions are 400x400,
  584          * the resulting image will be 400x300.
  585          */
  586         $aspect_ratio = $orig_w / $orig_h;
  587         $new_w        = min( $dest_w, $orig_w );
  588         $new_h        = min( $dest_h, $orig_h );
  589 
  590         if ( ! $new_w ) {
  591             $new_w = (int) round( $new_h * $aspect_ratio );
  592         }
  593 
  594         if ( ! $new_h ) {
  595             $new_h = (int) round( $new_w / $aspect_ratio );
  596         }
  597 
  598         $size_ratio = max( $new_w / $orig_w, $new_h / $orig_h );
  599 
  600         $crop_w = round( $new_w / $size_ratio );
  601         $crop_h = round( $new_h / $size_ratio );
  602 
  603         if ( ! is_array( $crop ) || count( $crop ) !== 2 ) {
  604             $crop = array( 'center', 'center' );
  605         }
  606 
  607         list( $x, $y ) = $crop;
  608 
  609         if ( 'left' === $x ) {
  610             $s_x = 0;
  611         } elseif ( 'right' === $x ) {
  612             $s_x = $orig_w - $crop_w;
  613         } else {
  614             $s_x = floor( ( $orig_w - $crop_w ) / 2 );
  615         }
  616 
  617         if ( 'top' === $y ) {
  618             $s_y = 0;
  619         } elseif ( 'bottom' === $y ) {
  620             $s_y = $orig_h - $crop_h;
  621         } else {
  622             $s_y = floor( ( $orig_h - $crop_h ) / 2 );
  623         }
  624     } else {
  625         // Resize using $dest_w x $dest_h as a maximum bounding box.
  626         $crop_w = $orig_w;
  627         $crop_h = $orig_h;
  628 
  629         $s_x = 0;
  630         $s_y = 0;
  631 
  632         list( $new_w, $new_h ) = wp_constrain_dimensions( $orig_w, $orig_h, $dest_w, $dest_h );
  633     }
  634 
  635     if ( wp_fuzzy_number_match( $new_w, $orig_w ) && wp_fuzzy_number_match( $new_h, $orig_h ) ) {
  636         // The new size has virtually the same dimensions as the original image.
  637 
  638         /**
  639          * Filters whether to proceed with making an image sub-size with identical dimensions
  640          * with the original/source image. Differences of 1px may be due to rounding and are ignored.
  641          *
  642          * @since 5.3.0
  643          *
  644          * @param bool $proceed The filtered value.
  645          * @param int  $orig_w  Original image width.
  646          * @param int  $orig_h  Original image height.
  647          */
  648         $proceed = (bool) apply_filters( 'wp_image_resize_identical_dimensions', false, $orig_w, $orig_h );
  649 
  650         if ( ! $proceed ) {
  651             return false;
  652         }
  653     }
  654 
  655     // The return array matches the parameters to imagecopyresampled().
  656     // int dst_x, int dst_y, int src_x, int src_y, int dst_w, int dst_h, int src_w, int src_h
  657     return array( 0, 0, (int) $s_x, (int) $s_y, (int) $new_w, (int) $new_h, (int) $crop_w, (int) $crop_h );
  658 }
  659 
  660 /**
  661  * Resizes an image to make a thumbnail or intermediate size.
  662  *
  663  * The returned array has the file size, the image width, and image height. The
  664  * {@see 'image_make_intermediate_size'} filter can be used to hook in and change the
  665  * values of the returned array. The only parameter is the resized file path.
  666  *
  667  * @since 2.5.0
  668  *
  669  * @param string $file   File path.
  670  * @param int    $width  Image width.
  671  * @param int    $height Image height.
  672  * @param bool   $crop   Optional. Whether to crop image to specified width and height or resize.
  673  *                       Default false.
  674  * @return array|false Metadata array on success. False if no image was created.
  675  */
  676 function image_make_intermediate_size( $file, $width, $height, $crop = false ) {
  677     if ( $width || $height ) {
  678         $editor = wp_get_image_editor( $file );
  679 
  680         if ( is_wp_error( $editor ) || is_wp_error( $editor->resize( $width, $height, $crop ) ) ) {
  681             return false;
  682         }
  683 
  684         $resized_file = $editor->save();
  685 
  686         if ( ! is_wp_error( $resized_file ) && $resized_file ) {
  687             unset( $resized_file['path'] );
  688             return $resized_file;
  689         }
  690     }
  691     return false;
  692 }
  693 
  694 /**
  695  * Helper function to test if aspect ratios for two images match.
  696  *
  697  * @since 4.6.0
  698  *
  699  * @param int $source_width  Width of the first image in pixels.
  700  * @param int $source_height Height of the first image in pixels.
  701  * @param int $target_width  Width of the second image in pixels.
  702  * @param int $target_height Height of the second image in pixels.
  703  * @return bool True if aspect ratios match within 1px. False if not.
  704  */
  705 function wp_image_matches_ratio( $source_width, $source_height, $target_width, $target_height ) {
  706     /*
  707      * To test for varying crops, we constrain the dimensions of the larger image
  708      * to the dimensions of the smaller image and see if they match.
  709      */
  710     if ( $source_width > $target_width ) {
  711         $constrained_size = wp_constrain_dimensions( $source_width, $source_height, $target_width );
  712         $expected_size    = array( $target_width, $target_height );
  713     } else {
  714         $constrained_size = wp_constrain_dimensions( $target_width, $target_height, $source_width );
  715         $expected_size    = array( $source_width, $source_height );
  716     }
  717 
  718     // If the image dimensions are within 1px of the expected size, we consider it a match.
  719     $matched = ( wp_fuzzy_number_match( $constrained_size[0], $expected_size[0] ) && wp_fuzzy_number_match( $constrained_size[1], $expected_size[1] ) );
  720 
  721     return $matched;
  722 }
  723 
  724 /**
  725  * Retrieves the image's intermediate size (resized) path, width, and height.
  726  *
  727  * The $size parameter can be an array with the width and height respectively.
  728  * If the size matches the 'sizes' metadata array for width and height, then it
  729  * will be used. If there is no direct match, then the nearest image size larger
  730  * than the specified size will be used. If nothing is found, then the function
  731  * will break out and return false.
  732  *
  733  * The metadata 'sizes' is used for compatible sizes that can be used for the
  734  * parameter $size value.
  735  *
  736  * The url path will be given, when the $size parameter is a string.
  737  *
  738  * If you are passing an array for the $size, you should consider using
  739  * add_image_size() so that a cropped version is generated. It's much more
  740  * efficient than having to find the closest-sized image and then having the
  741  * browser scale down the image.
  742  *
  743  * @since 2.5.0
  744  *
  745  * @param int          $post_id Attachment ID.
  746  * @param string|int[] $size    Optional. Image size. Accepts any registered image size name, or an array
  747  *                              of width and height values in pixels (in that order). Default 'thumbnail'.
  748  * @return array|false {
  749  *     Array of file relative path, width, and height on success. Additionally includes absolute
  750  *     path and URL if registered size is passed to `$size` parameter. False on failure.
  751  *
  752  *     @type string $file   Path of image relative to uploads directory.
  753  *     @type int    $width  Width of image in pixels.
  754  *     @type int    $height Height of image in pixels.
  755  *     @type string $path   Absolute filesystem path of image.
  756  *     @type string $url    URL of image.
  757  * }
  758  */
  759 function image_get_intermediate_size( $post_id, $size = 'thumbnail' ) {
  760     $imagedata = wp_get_attachment_metadata( $post_id );
  761 
  762     if ( ! $size || ! is_array( $imagedata ) || empty( $imagedata['sizes'] ) ) {
  763         return false;
  764     }
  765 
  766     $data = array();
  767 
  768     // Find the best match when '$size' is an array.
  769     if ( is_array( $size ) ) {
  770         $candidates = array();
  771 
  772         if ( ! isset( $imagedata['file'] ) && isset( $imagedata['sizes']['full'] ) ) {
  773             $imagedata['height'] = $imagedata['sizes']['full']['height'];
  774             $imagedata['width']  = $imagedata['sizes']['full']['width'];
  775         }
  776 
  777         foreach ( $imagedata['sizes'] as $_size => $data ) {
  778             // If there's an exact match to an existing image size, short circuit.
  779             if ( (int) $data['width'] === (int) $size[0] && (int) $data['height'] === (int) $size[1] ) {
  780                 $candidates[ $data['width'] * $data['height'] ] = $data;
  781                 break;
  782             }
  783 
  784             // If it's not an exact match, consider larger sizes with the same aspect ratio.
  785             if ( $data['width'] >= $size[0] && $data['height'] >= $size[1] ) {
  786                 // If '0' is passed to either size, we test ratios against the original file.
  787                 if ( 0 === $size[0] || 0 === $size[1] ) {
  788                     $same_ratio = wp_image_matches_ratio( $data['width'], $data['height'], $imagedata['width'], $imagedata['height'] );
  789                 } else {
  790                     $same_ratio = wp_image_matches_ratio( $data['width'], $data['height'], $size[0], $size[1] );
  791                 }
  792 
  793                 if ( $same_ratio ) {
  794                     $candidates[ $data['width'] * $data['height'] ] = $data;
  795                 }
  796             }
  797         }
  798 
  799         if ( ! empty( $candidates ) ) {
  800             // Sort the array by size if we have more than one candidate.
  801             if ( 1 < count( $candidates ) ) {
  802                 ksort( $candidates );
  803             }
  804 
  805             $data = array_shift( $candidates );
  806             /*
  807             * When the size requested is smaller than the thumbnail dimensions, we
  808             * fall back to the thumbnail size to maintain backward compatibility with
  809             * pre 4.6 versions of WordPress.
  810             */
  811         } elseif ( ! empty( $imagedata['sizes']['thumbnail'] ) && $imagedata['sizes']['thumbnail']['width'] >= $size[0] && $imagedata['sizes']['thumbnail']['width'] >= $size[1] ) {
  812             $data = $imagedata['sizes']['thumbnail'];
  813         } else {
  814             return false;
  815         }
  816 
  817         // Constrain the width and height attributes to the requested values.
  818         list( $data['width'], $data['height'] ) = image_constrain_size_for_editor( $data['width'], $data['height'], $size );
  819 
  820     } elseif ( ! empty( $imagedata['sizes'][ $size ] ) ) {
  821         $data = $imagedata['sizes'][ $size ];
  822     }
  823 
  824     // If we still don't have a match at this point, return false.
  825     if ( empty( $data ) ) {
  826         return false;
  827     }
  828 
  829     // Include the full filesystem path of the intermediate file.
  830     if ( empty( $data['path'] ) && ! empty( $data['file'] ) && ! empty( $imagedata['file'] ) ) {
  831         $file_url     = wp_get_attachment_url( $post_id );
  832         $data['path'] = path_join( dirname( $imagedata['file'] ), $data['file'] );
  833         $data['url']  = path_join( dirname( $file_url ), $data['file'] );
  834     }
  835 
  836     /**
  837      * Filters the output of image_get_intermediate_size()
  838      *
  839      * @since 4.4.0
  840      *
  841      * @see image_get_intermediate_size()
  842      *
  843      * @param array        $data    Array of file relative path, width, and height on success. May also include
  844      *                              file absolute path and URL.
  845      * @param int          $post_id The ID of the image attachment.
  846      * @param string|int[] $size    Requested image size. Can be any registered image size name, or
  847      *                              an array of width and height values in pixels (in that order).
  848      */
  849     return apply_filters( 'image_get_intermediate_size', $data, $post_id, $size );
  850 }
  851 
  852 /**
  853  * Gets the available intermediate image size names.
  854  *
  855  * @since 3.0.0
  856  *
  857  * @return string[] An array of image size names.
  858  */
  859 function get_intermediate_image_sizes() {
  860     $default_sizes    = array( 'thumbnail', 'medium', 'medium_large', 'large' );
  861     $additional_sizes = wp_get_additional_image_sizes();
  862 
  863     if ( ! empty( $additional_sizes ) ) {
  864         $default_sizes = array_merge( $default_sizes, array_keys( $additional_sizes ) );
  865     }
  866 
  867     /**
  868      * Filters the list of intermediate image sizes.
  869      *
  870      * @since 2.5.0
  871      *
  872      * @param string[] $default_sizes An array of intermediate image size names. Defaults
  873      *                                are 'thumbnail', 'medium', 'medium_large', 'large'.
  874      */
  875     return apply_filters( 'intermediate_image_sizes', $default_sizes );
  876 }
  877 
  878 /**
  879  * Returns a normalized list of all currently registered image sub-sizes.
  880  *
  881  * @since 5.3.0
  882  * @uses wp_get_additional_image_sizes()
  883  * @uses get_intermediate_image_sizes()
  884  *
  885  * @return array Associative array of the registered image sub-sizes.
  886  */
  887 function wp_get_registered_image_subsizes() {
  888     $additional_sizes = wp_get_additional_image_sizes();
  889     $all_sizes        = array();
  890 
  891     foreach ( get_intermediate_image_sizes() as $size_name ) {
  892         $size_data = array(
  893             'width'  => 0,
  894             'height' => 0,
  895             'crop'   => false,
  896         );
  897 
  898         if ( isset( $additional_sizes[ $size_name ]['width'] ) ) {
  899             // For sizes added by plugins and themes.
  900             $size_data['width'] = (int) $additional_sizes[ $size_name ]['width'];
  901         } else {
  902             // For default sizes set in options.
  903             $size_data['width'] = (int) get_option( "{$size_name}_size_w" );
  904         }
  905 
  906         if ( isset( $additional_sizes[ $size_name ]['height'] ) ) {
  907             $size_data['height'] = (int) $additional_sizes[ $size_name ]['height'];
  908         } else {
  909             $size_data['height'] = (int) get_option( "{$size_name}_size_h" );
  910         }
  911 
  912         if ( empty( $size_data['width'] ) && empty( $size_data['height'] ) ) {
  913             // This size isn't set.
  914             continue;
  915         }
  916 
  917         if ( isset( $additional_sizes[ $size_name ]['crop'] ) ) {
  918             $size_data['crop'] = $additional_sizes[ $size_name ]['crop'];
  919         } else {
  920             $size_data['crop'] = get_option( "{$size_name}_crop" );
  921         }
  922 
  923         if ( ! is_array( $size_data['crop'] ) || empty( $size_data['crop'] ) ) {
  924             $size_data['crop'] = (bool) $size_data['crop'];
  925         }
  926 
  927         $all_sizes[ $size_name ] = $size_data;
  928     }
  929 
  930     return $all_sizes;
  931 }
  932 
  933 /**
  934  * Retrieves an image to represent an attachment.
  935  *
  936  * @since 2.5.0
  937  *
  938  * @param int          $attachment_id Image attachment ID.
  939  * @param string|int[] $size          Optional. Image size. Accepts any registered image size name, or an array of
  940  *                                    width and height values in pixels (in that order). Default 'thumbnail'.
  941  * @param bool         $icon          Optional. Whether the image should fall back to a mime type icon. Default false.
  942  * @return array|false {
  943  *     Array of image data, or boolean false if no image is available.
  944  *
  945  *     @type string $0 Image source URL.
  946  *     @type int    $1 Image width in pixels.
  947  *     @type int    $2 Image height in pixels.
  948  *     @type bool   $3 Whether the image is a resized image.
  949  * }
  950  */
  951 function wp_get_attachment_image_src( $attachment_id, $size = 'thumbnail', $icon = false ) {
  952     // Get a thumbnail or intermediate image if there is one.
  953     $image = image_downsize( $attachment_id, $size );
  954     if ( ! $image ) {
  955         $src = false;
  956 
  957         if ( $icon ) {
  958             $src = wp_mime_type_icon( $attachment_id );
  959 
  960             if ( $src ) {
  961                 /** This filter is documented in wp-includes/post.php */
  962                 $icon_dir = apply_filters( 'icon_dir', ABSPATH . WPINC . '/images/media' );
  963 
  964                 $src_file               = $icon_dir . '/' . wp_basename( $src );
  965                 list( $width, $height ) = wp_getimagesize( $src_file );
  966             }
  967         }
  968 
  969         if ( $src && $width && $height ) {
  970             $image = array( $src, $width, $height, false );
  971         }
  972     }
  973     /**
  974      * Filters the attachment image source result.
  975      *
  976      * @since 4.3.0
  977      *
  978      * @param array|false  $image         {
  979      *     Array of image data, or boolean false if no image is available.
  980      *
  981      *     @type string $0 Image source URL.
  982      *     @type int    $1 Image width in pixels.
  983      *     @type int    $2 Image height in pixels.
  984      *     @type bool   $3 Whether the image is a resized image.
  985      * }
  986      * @param int          $attachment_id Image attachment ID.
  987      * @param string|int[] $size          Requested image size. Can be any registered image size name, or
  988      *                                    an array of width and height values in pixels (in that order).
  989      * @param bool         $icon          Whether the image should be treated as an icon.
  990      */
  991     return apply_filters( 'wp_get_attachment_image_src', $image, $attachment_id, $size, $icon );
  992 }
  993 
  994 /**
  995  * Get an HTML img element representing an image attachment.
  996  *
  997  * While `$size` will accept an array, it is better to register a size with
  998  * add_image_size() so that a cropped version is generated. It's much more
  999  * efficient than having to find the closest-sized image and then having the
 1000  * browser scale down the image.
 1001  *
 1002  * @since 2.5.0
 1003  * @since 4.4.0 The `$srcset` and `$sizes` attributes were added.
 1004  * @since 5.5.0 The `$loading` attribute was added.
 1005  *
 1006  * @param int          $attachment_id Image attachment ID.
 1007  * @param string|int[] $size          Optional. Image size. Accepts any registered image size name, or an array
 1008  *                                    of width and height values in pixels (in that order). Default 'thumbnail'.
 1009  * @param bool         $icon          Optional. Whether the image should be treated as an icon. Default false.
 1010  * @param string|array $attr {
 1011  *     Optional. Attributes for the image markup.
 1012  *
 1013  *     @type string       $src     Image attachment URL.
 1014  *     @type string       $class   CSS class name or space-separated list of classes.
 1015  *                                 Default `attachment-$size_class size-$size_class`,
 1016  *                                 where `$size_class` is the image size being requested.
 1017  *     @type string       $alt     Image description for the alt attribute.
 1018  *     @type string       $srcset  The 'srcset' attribute value.
 1019  *     @type string       $sizes   The 'sizes' attribute value.
 1020  *     @type string|false $loading The 'loading' attribute value. Passing a value of false
 1021  *                                 will result in the attribute being omitted for the image.
 1022  *                                 Defaults to 'lazy', depending on wp_lazy_loading_enabled().
 1023  * }
 1024  * @return string HTML img element or empty string on failure.
 1025  */
 1026 function wp_get_attachment_image( $attachment_id, $size = 'thumbnail', $icon = false, $attr = '' ) {
 1027     $html  = '';
 1028     $image = wp_get_attachment_image_src( $attachment_id, $size, $icon );
 1029 
 1030     if ( $image ) {
 1031         list( $src, $width, $height ) = $image;
 1032 
 1033         $attachment = get_post( $attachment_id );
 1034         $hwstring   = image_hwstring( $width, $height );
 1035         $size_class = $size;
 1036 
 1037         if ( is_array( $size_class ) ) {
 1038             $size_class = implode( 'x', $size_class );
 1039         }
 1040 
 1041         $default_attr = array(
 1042             'src'   => $src,
 1043             'class' => "attachment-$size_class size-$size_class",
 1044             'alt'   => trim( strip_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) ),
 1045         );
 1046 
 1047         // Add `loading` attribute.
 1048         if ( wp_lazy_loading_enabled( 'img', 'wp_get_attachment_image' ) ) {
 1049             $default_attr['loading'] = 'lazy';
 1050         }
 1051 
 1052         $attr = wp_parse_args( $attr, $default_attr );
 1053 
 1054         // If the default value of `lazy` for the `loading` attribute is overridden
 1055         // to omit the attribute for this image, ensure it is not included.
 1056         if ( array_key_exists( 'loading', $attr ) && ! $attr['loading'] ) {
 1057             unset( $attr['loading'] );
 1058         }
 1059 
 1060         // Generate 'srcset' and 'sizes' if not already present.
 1061         if ( empty( $attr['srcset'] ) ) {
 1062             $image_meta = wp_get_attachment_metadata( $attachment_id );
 1063 
 1064             if ( is_array( $image_meta ) ) {
 1065                 $size_array = array( absint( $width ), absint( $height ) );
 1066                 $srcset     = wp_calculate_image_srcset( $size_array, $src, $image_meta, $attachment_id );
 1067                 $sizes      = wp_calculate_image_sizes( $size_array, $src, $image_meta, $attachment_id );
 1068 
 1069                 if ( $srcset && ( $sizes || ! empty( $attr['sizes'] ) ) ) {
 1070                     $attr['srcset'] = $srcset;
 1071 
 1072                     if ( empty( $attr['sizes'] ) ) {
 1073                         $attr['sizes'] = $sizes;
 1074                     }
 1075                 }
 1076             }
 1077         }
 1078 
 1079         /**
 1080          * Filters the list of attachment image attributes.
 1081          *
 1082          * @since 2.8.0
 1083          *
 1084          * @param string[]     $attr       Array of attribute values for the image markup, keyed by attribute name.
 1085          *                                 See wp_get_attachment_image().
 1086          * @param WP_Post      $attachment Image attachment post.
 1087          * @param string|int[] $size       Requested image size. Can be any registered image size name, or
 1088          *                                 an array of width and height values in pixels (in that order).
 1089          */
 1090         $attr = apply_filters( 'wp_get_attachment_image_attributes', $attr, $attachment, $size );
 1091 
 1092         $attr = array_map( 'esc_attr', $attr );
 1093         $html = rtrim( "<img $hwstring" );
 1094 
 1095         foreach ( $attr as $name => $value ) {
 1096             $html .= " $name=" . '"' . $value . '"';
 1097         }
 1098 
 1099         $html .= ' />';
 1100     }
 1101 
 1102     /**
 1103      * HTML img element representing an image attachment.
 1104      *
 1105      * @since 5.6.0
 1106      *
 1107      * @param string       $html          HTML img element or empty string on failure.
 1108      * @param int          $attachment_id Image attachment ID.
 1109      * @param string|int[] $size          Requested image size. Can be any registered image size name, or
 1110      *                                    an array of width and height values in pixels (in that order).
 1111      * @param bool         $icon          Whether the image should be treated as an icon.
 1112      * @param string[]     $attr          Array of attribute values for the image markup, keyed by attribute name.
 1113      *                                    See wp_get_attachment_image().
 1114      */
 1115     return apply_filters( 'wp_get_attachment_image', $html, $attachment_id, $size, $icon, $attr );
 1116 }
 1117 
 1118 /**
 1119  * Get the URL of an image attachment.
 1120  *
 1121  * @since 4.4.0
 1122  *
 1123  * @param int          $attachment_id Image attachment ID.
 1124  * @param string|int[] $size          Optional. Image size. Accepts any registered image size name, or an array of
 1125  *                                    width and height values in pixels (in that order). Default 'thumbnail'.
 1126  * @param bool         $icon          Optional. Whether the image should be treated as an icon. Default false.
 1127  * @return string|false Attachment URL or false if no image is available. If `$size` does not match
 1128  *                      any registered image size, the original image URL will be returned.
 1129  */
 1130 function wp_get_attachment_image_url( $attachment_id, $size = 'thumbnail', $icon = false ) {
 1131     $image = wp_get_attachment_image_src( $attachment_id, $size, $icon );
 1132     return isset( $image['0'] ) ? $image['0'] : false;
 1133 }
 1134 
 1135 /**
 1136  * Get the attachment path relative to the upload directory.
 1137  *
 1138  * @since 4.4.1
 1139  * @access private
 1140  *
 1141  * @param string $file Attachment file name.
 1142  * @return string Attachment path relative to the upload directory.
 1143  */
 1144 function _wp_get_attachment_relative_path( $file ) {
 1145     $dirname = dirname( $file );
 1146 
 1147     if ( '.' === $dirname ) {
 1148         return '';
 1149     }
 1150 
 1151     if ( false !== strpos( $dirname, 'wp-content/uploads' ) ) {
 1152         // Get the directory name relative to the upload directory (back compat for pre-2.7 uploads).
 1153         $dirname = substr( $dirname, strpos( $dirname, 'wp-content/uploads' ) + 18 );
 1154         $dirname = ltrim( $dirname, '/' );
 1155     }
 1156 
 1157     return $dirname;
 1158 }
 1159 
 1160 /**
 1161  * Get the image size as array from its meta data.
 1162  *
 1163  * Used for responsive images.
 1164  *
 1165  * @since 4.4.0
 1166  * @access private
 1167  *
 1168  * @param string $size_name  Image size. Accepts any registered image size name.
 1169  * @param array  $image_meta The image meta data.
 1170  * @return array|false {
 1171  *     Array of width and height or false if the size isn't present in the meta data.
 1172  *
 1173  *     @type int $0 Image width.
 1174  *     @type int $1 Image height.
 1175  * }
 1176  */
 1177 function _wp_get_image_size_from_meta( $size_name, $image_meta ) {
 1178     if ( 'full' === $size_name ) {
 1179         return array(
 1180             absint( $image_meta['width'] ),
 1181             absint( $image_meta['height'] ),
 1182         );
 1183     } elseif ( ! empty( $image_meta['sizes'][ $size_name ] ) ) {
 1184         return array(
 1185             absint( $image_meta['sizes'][ $size_name ]['width'] ),
 1186             absint( $image_meta['sizes'][ $size_name ]['height'] ),
 1187         );
 1188     }
 1189 
 1190     return false;
 1191 }
 1192 
 1193 /**
 1194  * Retrieves the value for an image attachment's 'srcset' attribute.
 1195  *
 1196  * @since 4.4.0
 1197  *
 1198  * @see wp_calculate_image_srcset()
 1199  *
 1200  * @param int          $attachment_id Image attachment ID.
 1201  * @param string|int[] $size          Optional. Image size. Accepts any registered image size name, or an array of
 1202  *                                    width and height values in pixels (in that order). Default 'medium'.
 1203  * @param array        $image_meta    Optional. The image meta data as returned by 'wp_get_attachment_metadata()'.
 1204  *                                    Default null.
 1205  * @return string|false A 'srcset' value string or false.
 1206  */
 1207 function wp_get_attachment_image_srcset( $attachment_id, $size = 'medium', $image_meta = null ) {
 1208     $image = wp_get_attachment_image_src( $attachment_id, $size );
 1209 
 1210     if ( ! $image ) {
 1211         return false;
 1212     }
 1213 
 1214     if ( ! is_array( $image_meta ) ) {
 1215         $image_meta = wp_get_attachment_metadata( $attachment_id );
 1216     }
 1217 
 1218     $image_src  = $image[0];
 1219     $size_array = array(
 1220         absint( $image[1] ),
 1221         absint( $image[2] ),
 1222     );
 1223 
 1224     return wp_calculate_image_srcset( $size_array, $image_src, $image_meta, $attachment_id );
 1225 }
 1226 
 1227 /**
 1228  * A helper function to calculate the image sources to include in a 'srcset' attribute.
 1229  *
 1230  * @since 4.4.0
 1231  *
 1232  * @param int[]  $size_array    {
 1233  *     An array of width and height values.
 1234  *
 1235  *     @type int $0 The width in pixels.
 1236  *     @type int $1 The height in pixels.
 1237  * }
 1238  * @param string $image_src     The 'src' of the image.
 1239  * @param array  $image_meta    The image meta data as returned by 'wp_get_attachment_metadata()'.
 1240  * @param int    $attachment_id Optional. The image attachment ID. Default 0.
 1241  * @return string|false The 'srcset' attribute value. False on error or when only one source exists.
 1242  */
 1243 function wp_calculate_image_srcset( $size_array, $image_src, $image_meta, $attachment_id = 0 ) {
 1244     /**
 1245      * Let plugins pre-filter the image meta to be able to fix inconsistencies in the stored data.
 1246      *
 1247      * @since 4.5.0
 1248      *
 1249      * @param array  $image_meta    The image meta data as returned by 'wp_get_attachment_metadata()'.
 1250      * @param int[]  $size_array    {
 1251      *     An array of requested width and height values.
 1252      *
 1253      *     @type int $0 The width in pixels.
 1254      *     @type int $1 The height in pixels.
 1255      * }
 1256      * @param string $image_src     The 'src' of the image.
 1257      * @param int    $attachment_id The image attachment ID or 0 if not supplied.
 1258      */
 1259     $image_meta = apply_filters( 'wp_calculate_image_srcset_meta', $image_meta, $size_array, $image_src, $attachment_id );
 1260 
 1261     if ( empty( $image_meta['sizes'] ) || ! isset( $image_meta['file'] ) || strlen( $image_meta['file'] ) < 4 ) {
 1262         return false;
 1263     }
 1264 
 1265     $image_sizes = $image_meta['sizes'];
 1266 
 1267     // Get the width and height of the image.
 1268     $image_width  = (int) $size_array[0];
 1269     $image_height = (int) $size_array[1];
 1270 
 1271     // Bail early if error/no width.
 1272     if ( $image_width < 1 ) {
 1273         return false;
 1274     }
 1275 
 1276     $image_basename = wp_basename( $image_meta['file'] );
 1277 
 1278     /*
 1279      * WordPress flattens animated GIFs into one frame when generating intermediate sizes.
 1280      * To avoid hiding animation in user content, if src is a full size GIF, a srcset attribute is not generated.
 1281      * If src is an intermediate size GIF, the full size is excluded from srcset to keep a flattened GIF from becoming animated.
 1282      */
 1283     if ( ! isset( $image_sizes['thumbnail']['mime-type'] ) || 'image/gif' !== $image_sizes['thumbnail']['mime-type'] ) {
 1284         $image_sizes[] = array(
 1285             'width'  => $image_meta['width'],
 1286             'height' => $image_meta['height'],
 1287             'file'   => $image_basename,
 1288         );
 1289     } elseif ( strpos( $image_src, $image_meta['file'] ) ) {
 1290         return false;
 1291     }
 1292 
 1293     // Retrieve the uploads sub-directory from the full size image.
 1294     $dirname = _wp_get_attachment_relative_path( $image_meta['file'] );
 1295 
 1296     if ( $dirname ) {
 1297         $dirname = trailingslashit( $dirname );
 1298     }
 1299 
 1300     $upload_dir    = wp_get_upload_dir();
 1301     $image_baseurl = trailingslashit( $upload_dir['baseurl'] ) . $dirname;
 1302 
 1303     /*
 1304      * If currently on HTTPS, prefer HTTPS URLs when we know they're supported by the domain
 1305      * (which is to say, when they share the domain name of the current request).
 1306      */
 1307     if ( is_ssl() && 'https' !== substr( $image_baseurl, 0, 5 ) && parse_url( $image_baseurl, PHP_URL_HOST ) === $_SERVER['HTTP_HOST'] ) {
 1308         $image_baseurl = set_url_scheme( $image_baseurl, 'https' );
 1309     }
 1310 
 1311     /*
 1312      * Images that have been edited in WordPress after being uploaded will
 1313      * contain a unique hash. Look for that hash and use it later to filter
 1314      * out images that are leftovers from previous versions.
 1315      */
 1316     $image_edited = preg_match( '/-e[0-9]{13}/', wp_basename( $image_src ), $image_edit_hash );
 1317 
 1318     /**
 1319      * Filters the maximum image width to be included in a 'srcset' attribute.
 1320      *
 1321      * @since 4.4.0
 1322      *
 1323      * @param int   $max_width  The maximum image width to be included in the 'srcset'. Default '2048'.
 1324      * @param int[] $size_array {
 1325      *     An array of requested width and height values.
 1326      *
 1327      *     @type int $0 The width in pixels.
 1328      *     @type int $1 The height in pixels.
 1329      * }
 1330      */
 1331     $max_srcset_image_width = apply_filters( 'max_srcset_image_width', 2048, $size_array );
 1332 
 1333     // Array to hold URL candidates.
 1334     $sources = array();
 1335 
 1336     /**
 1337      * To make sure the ID matches our image src, we will check to see if any sizes in our attachment
 1338      * meta match our $image_src. If no matches are found we don't return a srcset to avoid serving
 1339      * an incorrect image. See #35045.
 1340      */
 1341     $src_matched = false;
 1342 
 1343     /*
 1344      * Loop through available images. Only use images that are resized
 1345      * versions of the same edit.
 1346      */
 1347     foreach ( $image_sizes as $image ) {
 1348         $is_src = false;
 1349 
 1350         // Check if image meta isn't corrupted.
 1351         if ( ! is_array( $image ) ) {
 1352             continue;
 1353         }
 1354 
 1355         // If the file name is part of the `src`, we've confirmed a match.
 1356         if ( ! $src_matched && false !== strpos( $image_src, $dirname . $image['file'] ) ) {
 1357             $src_matched = true;
 1358             $is_src      = true;
 1359         }
 1360 
 1361         // Filter out images that are from previous edits.
 1362         if ( $image_edited && ! strpos( $image['file'], $image_edit_hash[0] ) ) {
 1363             continue;
 1364         }
 1365 
 1366         /*
 1367          * Filters out images that are wider than '$max_srcset_image_width' unless
 1368          * that file is in the 'src' attribute.
 1369          */
 1370         if ( $max_srcset_image_width && $image['width'] > $max_srcset_image_width && ! $is_src ) {
 1371             continue;
 1372         }
 1373 
 1374         // If the image dimensions are within 1px of the expected size, use it.
 1375         if ( wp_image_matches_ratio( $image_width, $image_height, $image['width'], $image['height'] ) ) {
 1376             // Add the URL, descriptor, and value to the sources array to be returned.
 1377             $source = array(
 1378                 'url'        => $image_baseurl . $image['file'],
 1379                 'descriptor' => 'w',
 1380                 'value'      => $image['width'],
 1381             );
 1382 
 1383             // The 'src' image has to be the first in the 'srcset', because of a bug in iOS8. See #35030.
 1384             if ( $is_src ) {
 1385                 $sources = array( $image['width'] => $source ) + $sources;
 1386             } else {
 1387                 $sources[ $image['width'] ] = $source;
 1388             }
 1389         }
 1390     }
 1391 
 1392     /**
 1393      * Filters an image's 'srcset' sources.
 1394      *
 1395      * @since 4.4.0
 1396      *
 1397      * @param array  $sources {
 1398      *     One or more arrays of source data to include in the 'srcset'.
 1399      *
 1400      *     @type array $width {
 1401      *         @type string $url        The URL of an image source.
 1402      *         @type string $descriptor The descriptor type used in the image candidate string,
 1403      *                                  either 'w' or 'x'.
 1404      *         @type int    $value      The source width if paired with a 'w' descriptor, or a
 1405      *                                  pixel density value if paired with an 'x' descriptor.
 1406      *     }
 1407      * }
 1408      * @param array $size_array     {
 1409      *     An array of requested width and height values.
 1410      *
 1411      *     @type int $0 The width in pixels.
 1412      *     @type int $1 The height in pixels.
 1413      * }
 1414      * @param string $image_src     The 'src' of the image.
 1415      * @param array  $image_meta    The image meta data as returned by 'wp_get_attachment_metadata()'.
 1416      * @param int    $attachment_id Image attachment ID or 0.
 1417      */
 1418     $sources = apply_filters( 'wp_calculate_image_srcset', $sources, $size_array, $image_src, $image_meta, $attachment_id );
 1419 
 1420     // Only return a 'srcset' value if there is more than one source.
 1421     if ( ! $src_matched || ! is_array( $sources ) || count( $sources ) < 2 ) {
 1422         return false;
 1423     }
 1424 
 1425     $srcset = '';
 1426 
 1427     foreach ( $sources as $source ) {
 1428         $srcset .= str_replace( ' ', '%20', $source['url'] ) . ' ' . $source['value'] . $source['descriptor'] . ', ';
 1429     }
 1430 
 1431     return rtrim( $srcset, ', ' );
 1432 }
 1433 
 1434 /**
 1435  * Retrieves the value for an image attachment's 'sizes' attribute.
 1436  *
 1437  * @since 4.4.0
 1438  *
 1439  * @see wp_calculate_image_sizes()
 1440  *
 1441  * @param int          $attachment_id Image attachment ID.
 1442  * @param string|int[] $size          Optional. Image size. Accepts any registered image size name, or an array of
 1443  *                                    width and height values in pixels (in that order). Default 'medium'.
 1444  * @param array        $image_meta    Optional. The image meta data as returned by 'wp_get_attachment_metadata()'.
 1445  *                                    Default null.
 1446  * @return string|false A valid source size value for use in a 'sizes' attribute or false.
 1447  */
 1448 function wp_get_attachment_image_sizes( $attachment_id, $size = 'medium', $image_meta = null ) {
 1449     $image = wp_get_attachment_image_src( $attachment_id, $size );
 1450 
 1451     if ( ! $image ) {
 1452         return false;
 1453     }
 1454 
 1455     if ( ! is_array( $image_meta ) ) {
 1456         $image_meta = wp_get_attachment_metadata( $attachment_id );
 1457     }
 1458 
 1459     $image_src  = $image[0];
 1460     $size_array = array(
 1461         absint( $image[1] ),
 1462         absint( $image[2] ),
 1463     );
 1464 
 1465     return wp_calculate_image_sizes( $size_array, $image_src, $image_meta, $attachment_id );
 1466 }
 1467 
 1468 /**
 1469  * Creates a 'sizes' attribute value for an image.
 1470  *
 1471  * @since 4.4.0
 1472  *
 1473  * @param string|int[] $size          Image size. Accepts any registered image size name, or an array of
 1474  *                                    width and height values in pixels (in that order).
 1475  * @param string       $image_src     Optional. The URL to the image file. Default null.
 1476  * @param array        $image_meta    Optional. The image meta data as returned by 'wp_get_attachment_metadata()'.
 1477  *                                    Default null.
 1478  * @param int          $attachment_id Optional. Image attachment ID. Either `$image_meta` or `$attachment_id`
 1479  *                                    is needed when using the image size name as argument for `$size`. Default 0.
 1480  * @return string|false A valid source size value for use in a 'sizes' attribute or false.
 1481  */
 1482 function wp_calculate_image_sizes( $size, $image_src = null, $image_meta = null, $attachment_id = 0 ) {
 1483     $width = 0;
 1484 
 1485     if ( is_array( $size ) ) {
 1486         $width = absint( $size[0] );
 1487     } elseif ( is_string( $size ) ) {
 1488         if ( ! $image_meta && $attachment_id ) {
 1489             $image_meta = wp_get_attachment_metadata( $attachment_id );
 1490         }
 1491 
 1492         if ( is_array( $image_meta ) ) {
 1493             $size_array = _wp_get_image_size_from_meta( $size, $image_meta );
 1494             if ( $size_array ) {
 1495                 $width = absint( $size_array[0] );
 1496             }
 1497         }
 1498     }
 1499 
 1500     if ( ! $width ) {
 1501         return false;
 1502     }
 1503 
 1504     // Setup the default 'sizes' attribute.
 1505     $sizes = sprintf( '(max-width: %1$dpx) 100vw, %1$dpx', $width );
 1506 
 1507     /**
 1508      * Filters the output of 'wp_calculate_image_sizes()'.
 1509      *
 1510      * @since 4.4.0
 1511      *
 1512      * @param string       $sizes         A source size value for use in a 'sizes' attribute.
 1513      * @param string|int[] $size          Requested image size. Can be any registered image size name, or
 1514      *                                    an array of width and height values in pixels (in that order).
 1515      * @param string|null  $image_src     The URL to the image file or null.
 1516      * @param array|null   $image_meta    The image meta data as returned by wp_get_attachment_metadata() or null.
 1517      * @param int          $attachment_id Image attachment ID of the original image or 0.
 1518      */
 1519     return apply_filters( 'wp_calculate_image_sizes', $sizes, $size, $image_src, $image_meta, $attachment_id );
 1520 }
 1521 
 1522 /**
 1523  * Determines if the image meta data is for the image source file.
 1524  *
 1525  * The image meta data is retrieved by attachment post ID. In some cases the post IDs may change.
 1526  * For example when the website is exported and imported at another website. Then the
 1527  * attachment post IDs that are in post_content for the exported website may not match
 1528  * the same attachments at the new website.
 1529  *
 1530  * @since 5.5.0
 1531  *
 1532  * @param string $image_location The full path or URI to the image file.
 1533  * @param array  $image_meta     The attachment meta data as returned by 'wp_get_attachment_metadata()'.
 1534  * @param int    $attachment_id  Optional. The image attachment ID. Default 0.
 1535  * @return bool Whether the image meta is for this image file.
 1536  */
 1537 function wp_image_file_matches_image_meta( $image_location, $image_meta, $attachment_id = 0 ) {
 1538     $match = false;
 1539 
 1540     // Ensure the $image_meta is valid.
 1541     if ( isset( $image_meta['file'] ) && strlen( $image_meta['file'] ) > 4 ) {
 1542         // Remove quiery args if image URI.
 1543         list( $image_location ) = explode( '?', $image_location );
 1544 
 1545         // Check if the relative image path from the image meta is at the end of $image_location.
 1546         if ( strrpos( $image_location, $image_meta['file'] ) === strlen( $image_location ) - strlen( $image_meta['file'] ) ) {
 1547             $match = true;
 1548         } else {
 1549             // Retrieve the uploads sub-directory from the full size image.
 1550             $dirname = _wp_get_attachment_relative_path( $image_meta['file'] );
 1551 
 1552             if ( $dirname ) {
 1553                 $dirname = trailingslashit( $dirname );
 1554             }
 1555 
 1556             if ( ! empty( $image_meta['original_image'] ) ) {
 1557                 $relative_path = $dirname . $image_meta['original_image'];
 1558 
 1559                 if ( strrpos( $image_location, $relative_path ) === strlen( $image_location ) - strlen( $relative_path ) ) {
 1560                     $match = true;
 1561                 }
 1562             }
 1563 
 1564             if ( ! $match && ! empty( $image_meta['sizes'] ) ) {
 1565                 foreach ( $image_meta['sizes'] as $image_size_data ) {
 1566                     $relative_path = $dirname . $image_size_data['file'];
 1567 
 1568                     if ( strrpos( $image_location, $relative_path ) === strlen( $image_location ) - strlen( $relative_path ) ) {
 1569                         $match = true;
 1570                         break;
 1571                     }
 1572                 }
 1573             }
 1574         }
 1575     }
 1576 
 1577     /**
 1578      * Filters whether an image path or URI matches image meta.
 1579      *
 1580      * @since 5.5.0
 1581      *
 1582      * @param bool   $match          Whether the image relative path from the image meta
 1583      *                               matches the end of the URI or path to the image file.
 1584      * @param string $image_location Full path or URI to the tested image file.
 1585      * @param array  $image_meta     The image meta data as returned by 'wp_get_attachment_metadata()'.
 1586      * @param int    $attachment_id  The image attachment ID or 0 if not supplied.
 1587      */
 1588     return apply_filters( 'wp_image_file_matches_image_meta', $match, $image_location, $image_meta, $attachment_id );
 1589 }
 1590 
 1591 /**
 1592  * Determines an image's width and height dimensions based on the source file.
 1593  *
 1594  * @since 5.5.0
 1595  *
 1596  * @param string $image_src     The image source file.
 1597  * @param array  $image_meta    The image meta data as returned by 'wp_get_attachment_metadata()'.
 1598  * @param int    $attachment_id Optional. The image attachment ID. Default 0.
 1599  * @return array|false Array with first element being the width and second element being the height,
 1600  *                     or false if dimensions cannot be determined.
 1601  */
 1602 function wp_image_src_get_dimensions( $image_src, $image_meta, $attachment_id = 0 ) {
 1603     $dimensions = false;
 1604 
 1605     // Is it a full size image?
 1606     if (
 1607         isset( $image_meta['file'] ) &&
 1608         strpos( $image_src, wp_basename( $image_meta['file'] ) ) !== false
 1609     ) {
 1610         $dimensions = array(
 1611             (int) $image_meta['width'],
 1612             (int) $image_meta['height'],
 1613         );
 1614     }
 1615 
 1616     if ( ! $dimensions && ! empty( $image_meta['sizes'] ) ) {
 1617         $src_filename = wp_basename( $image_src );
 1618 
 1619         foreach ( $image_meta['sizes'] as $image_size_data ) {
 1620             if ( $src_filename === $image_size_data['file'] ) {
 1621                 $dimensions = array(
 1622                     (int) $image_size_data['width'],
 1623                     (int) $image_size_data['height'],
 1624                 );
 1625 
 1626                 break;
 1627             }
 1628         }
 1629     }
 1630 
 1631     /**
 1632      * Filters the 'wp_image_src_get_dimensions' value.
 1633      *
 1634      * @since 5.7.0
 1635      *
 1636      * @param array|false $dimensions    Array with first element being the width
 1637      *                                   and second element being the height, or
 1638      *                                   false if dimensions could not be determined.
 1639      * @param string      $image_src     The image source file.
 1640      * @param array       $image_meta    The image meta data as returned by
 1641      *                                   'wp_get_attachment_metadata()'.
 1642      * @param int         $attachment_id The image attachment ID. Default 0.
 1643      */
 1644     return apply_filters( 'wp_image_src_get_dimensions', $dimensions, $image_src, $image_meta, $attachment_id );
 1645 }
 1646 
 1647 /**
 1648  * Adds 'srcset' and 'sizes' attributes to an existing 'img' element.
 1649  *
 1650  * @since 4.4.0
 1651  *
 1652  * @see wp_calculate_image_srcset()
 1653  * @see wp_calculate_image_sizes()
 1654  *
 1655  * @param string $image         An HTML 'img' element to be filtered.
 1656  * @param array  $image_meta    The image meta data as returned by 'wp_get_attachment_metadata()'.
 1657  * @param int    $attachment_id Image attachment ID.
 1658  * @return string Converted 'img' element with 'srcset' and 'sizes' attributes added.
 1659  */
 1660 function wp_image_add_srcset_and_sizes( $image, $image_meta, $attachment_id ) {
 1661     // Ensure the image meta exists.
 1662     if ( empty( $image_meta['sizes'] ) ) {
 1663         return $image;
 1664     }
 1665 
 1666     $image_src         = preg_match( '/src="([^"]+)"/', $image, $match_src ) ? $match_src[1] : '';
 1667     list( $image_src ) = explode( '?', $image_src );
 1668 
 1669     // Return early if we couldn't get the image source.
 1670     if ( ! $image_src ) {
 1671         return $image;
 1672     }
 1673 
 1674     // Bail early if an image has been inserted and later edited.
 1675     if ( preg_match( '/-e[0-9]{13}/', $image_meta['file'], $img_edit_hash ) &&
 1676         strpos( wp_basename( $image_src ), $img_edit_hash[0] ) === false ) {
 1677 
 1678         return $image;
 1679     }
 1680 
 1681     $width  = preg_match( '/ width="([0-9]+)"/', $image, $match_width ) ? (int) $match_width[1] : 0;
 1682     $height = preg_match( '/ height="([0-9]+)"/', $image, $match_height ) ? (int) $match_height[1] : 0;
 1683 
 1684     if ( $width && $height ) {
 1685         $size_array = array( $width, $height );
 1686     } else {
 1687         $size_array = wp_image_src_get_dimensions( $image_src, $image_meta, $attachment_id );
 1688         if ( ! $size_array ) {
 1689             return $image;
 1690         }
 1691     }
 1692 
 1693     $srcset = wp_calculate_image_srcset( $size_array, $image_src, $image_meta, $attachment_id );
 1694 
 1695     if ( $srcset ) {
 1696         // Check if there is already a 'sizes' attribute.
 1697         $sizes = strpos( $image, ' sizes=' );
 1698 
 1699         if ( ! $sizes ) {
 1700             $sizes = wp_calculate_image_sizes( $size_array, $image_src, $image_meta, $attachment_id );
 1701         }
 1702     }
 1703 
 1704     if ( $srcset && $sizes ) {
 1705         // Format the 'srcset' and 'sizes' string and escape attributes.
 1706         $attr = sprintf( ' srcset="%s"', esc_attr( $srcset ) );
 1707 
 1708         if ( is_string( $sizes ) ) {
 1709             $attr .= sprintf( ' sizes="%s"', esc_attr( $sizes ) );
 1710         }
 1711 
 1712         // Add the srcset and sizes attributes to the image markup.
 1713         return preg_replace( '/<img ([^>]+?)[\/ ]*>/', '<img $1' . $attr . ' />', $image );
 1714     }
 1715 
 1716     return $image;
 1717 }
 1718 
 1719 /**
 1720  * Determines whether to add the `loading` attribute to the specified tag in the specified context.
 1721  *
 1722  * @since 5.5.0
 1723  * @since 5.7.0 Now returns `true` by default for `iframe` tags.
 1724  *
 1725  * @param string $tag_name The tag name.
 1726  * @param string $context  Additional context, like the current filter name
 1727  *                         or the function name from where this was called.
 1728  * @return bool Whether to add the attribute.
 1729  */
 1730 function wp_lazy_loading_enabled( $tag_name, $context ) {
 1731     // By default add to all 'img' and 'iframe' tags.
 1732     // See https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-loading
 1733     // See https://html.spec.whatwg.org/multipage/iframe-embed-object.html#attr-iframe-loading
 1734     $default = ( 'img' === $tag_name || 'iframe' === $tag_name );
 1735 
 1736     /**
 1737      * Filters whether to add the `loading` attribute to the specified tag in the specified context.
 1738      *
 1739      * @since 5.5.0
 1740      *
 1741      * @param bool   $default  Default value.
 1742      * @param string $tag_name The tag name.
 1743      * @param string $context  Additional context, like the current filter name
 1744      *                         or the function name from where this was called.
 1745      */
 1746     return (bool) apply_filters( 'wp_lazy_loading_enabled', $default, $tag_name, $context );
 1747 }
 1748 
 1749 /**
 1750  * Filters specific tags in post content and modifies their markup.
 1751  *
 1752  * Modifies HTML tags in post content to include new browser and HTML technologies
 1753  * that may not have existed at the time of post creation. These modifications currently
 1754  * include adding `srcset`, `sizes`, and `loading` attributes to `img` HTML tags, as well
 1755  * as adding `loading` attributes to `iframe` HTML tags.
 1756  * Future similar optimizations should be added/expected here.
 1757  *
 1758  * @since 5.5.0
 1759  * @since 5.7.0 Now supports adding `loading` attributes to `iframe` tags.
 1760  *
 1761  * @see wp_img_tag_add_width_and_height_attr()
 1762  * @see wp_img_tag_add_srcset_and_sizes_attr()
 1763  * @see wp_img_tag_add_loading_attr()
 1764  * @see wp_iframe_tag_add_loading_attr()
 1765  *
 1766  * @param string $content The HTML content to be filtered.
 1767  * @param string $context Optional. Additional context to pass to the filters.
 1768  *                        Defaults to `current_filter()` when not set.
 1769  * @return string Converted content with images modified.
 1770  */
 1771 function wp_filter_content_tags( $content, $context = null ) {
 1772     if ( null === $context ) {
 1773         $context = current_filter();
 1774     }
 1775 
 1776     $add_img_loading_attr    = wp_lazy_loading_enabled( 'img', $context );
 1777     $add_iframe_loading_attr = wp_lazy_loading_enabled( 'iframe', $context );
 1778 
 1779     if ( ! preg_match_all( '/<(img|iframe)\s[^>]+>/', $content, $matches, PREG_SET_ORDER ) ) {
 1780         return $content;
 1781     }
 1782 
 1783     // List of the unique `img` tags found in $content.
 1784     $images = array();
 1785 
 1786     // List of the unique `iframe` tags found in $content.
 1787     $iframes = array();
 1788 
 1789     foreach ( $matches as $match ) {
 1790         list( $tag, $tag_name ) = $match;
 1791 
 1792         switch ( $tag_name ) {
 1793             case 'img':
 1794                 if ( preg_match( '/wp-image-([0-9]+)/i', $tag, $class_id ) ) {
 1795                     $attachment_id = absint( $class_id[1] );
 1796 
 1797                     if ( $attachment_id ) {
 1798                         // If exactly the same image tag is used more than once, overwrite it.
 1799                         // All identical tags will be replaced later with 'str_replace()'.
 1800                         $images[ $tag ] = $attachment_id;
 1801                         break;
 1802                     }
 1803                 }
 1804                 $images[ $tag ] = 0;
 1805                 break;
 1806             case 'iframe':
 1807                 $iframes[ $tag ] = 0;
 1808                 break;
 1809         }
 1810     }
 1811 
 1812     // Reduce the array to unique attachment IDs.
 1813     $attachment_ids = array_unique( array_filter( array_values( $images ) ) );
 1814 
 1815     if ( count( $attachment_ids ) > 1 ) {
 1816         /*
 1817          * Warm the object cache with post and meta information for all found
 1818          * images to avoid making individual database calls.
 1819          */
 1820         _prime_post_caches( $attachment_ids, false, true );
 1821     }
 1822 
 1823     foreach ( $images as $image => $attachment_id ) {
 1824         $filtered_image = $image;
 1825 
 1826         // Add 'width' and 'height' attributes if applicable.
 1827         if ( $attachment_id > 0 && false === strpos( $filtered_image, ' width=' ) && false === strpos( $filtered_image, ' height=' ) ) {
 1828             $filtered_image = wp_img_tag_add_width_and_height_attr( $filtered_image, $context, $attachment_id );
 1829         }
 1830 
 1831         // Add 'srcset' and 'sizes' attributes if applicable.
 1832         if ( $attachment_id > 0 && false === strpos( $filtered_image, ' srcset=' ) ) {
 1833             $filtered_image = wp_img_tag_add_srcset_and_sizes_attr( $filtered_image, $context, $attachment_id );
 1834         }
 1835 
 1836         // Add 'loading' attribute if applicable.
 1837         if ( $add_img_loading_attr && false === strpos( $filtered_image, ' loading=' ) ) {
 1838             $filtered_image = wp_img_tag_add_loading_attr( $filtered_image, $context );
 1839         }
 1840 
 1841         if ( $filtered_image !== $image ) {
 1842             $content = str_replace( $image, $filtered_image, $content );
 1843         }
 1844     }
 1845 
 1846     foreach ( $iframes as $iframe => $attachment_id ) {
 1847         $filtered_iframe = $iframe;
 1848 
 1849         // Add 'loading' attribute if applicable.
 1850         if ( $add_iframe_loading_attr && false === strpos( $filtered_iframe, ' loading=' ) ) {
 1851             $filtered_iframe = wp_iframe_tag_add_loading_attr( $filtered_iframe, $context );
 1852         }
 1853 
 1854         if ( $filtered_iframe !== $iframe ) {
 1855             $content = str_replace( $iframe, $filtered_iframe, $content );
 1856         }
 1857     }
 1858 
 1859     return $content;
 1860 }
 1861 
 1862 /**
 1863  * Adds `loading` attribute to an `img` HTML tag.
 1864  *
 1865  * @since 5.5.0
 1866  *
 1867  * @param string $image   The HTML `img` tag where the attribute should be added.
 1868  * @param string $context Additional context to pass to the filters.
 1869  * @return string Converted `img` tag with `loading` attribute added.
 1870  */
 1871 function wp_img_tag_add_loading_attr( $image, $context ) {
 1872     /**
 1873      * Filters the `loading` attribute value to add to an image. Default `lazy`.
 1874      *
 1875      * Returning `false` or an empty string will not add the attribute.
 1876      * Returning `true` will add the default value.
 1877      *
 1878      * @since 5.5.0
 1879      *
 1880      * @param string|bool $value   The `loading` attribute value. Returning a falsey value will result in
 1881      *                             the attribute being omitted for the image. Default 'lazy'.
 1882      * @param string      $image   The HTML `img` tag to be filtered.
 1883      * @param string      $context Additional context about how the function was called or where the img tag is.
 1884      */
 1885     $value = apply_filters( 'wp_img_tag_add_loading_attr', 'lazy', $image, $context );
 1886 
 1887     if ( $value ) {
 1888         if ( ! in_array( $value, array( 'lazy', 'eager' ), true ) ) {
 1889             $value = 'lazy';
 1890         }
 1891 
 1892         // Images should have source and dimension attributes for the `loading` attribute to be added.
 1893         if ( false === strpos( $image, ' src="' ) || false === strpos( $image, ' width="' ) || false === strpos( $image, ' height="' ) ) {
 1894             return $image;
 1895         }
 1896 
 1897         return str_replace( '<img', '<img loading="' . esc_attr( $value ) . '"', $image );
 1898     }
 1899 
 1900     return $image;
 1901 }
 1902 
 1903 /**
 1904  * Adds `width` and `height` attributes to an `img` HTML tag.
 1905  *
 1906  * @since 5.5.0
 1907  *
 1908  * @param string $image         The HTML `img` tag where the attribute should be added.
 1909  * @param string $context       Additional context to pass to the filters.
 1910  * @param int    $attachment_id Image attachment ID.
 1911  * @return string Converted 'img' element with 'width' and 'height' attributes added.
 1912  */
 1913 function wp_img_tag_add_width_and_height_attr( $image, $context, $attachment_id ) {
 1914     $image_src         = preg_match( '/src="([^"]+)"/', $image, $match_src ) ? $match_src[1] : '';
 1915     list( $image_src ) = explode( '?', $image_src );
 1916 
 1917     // Return early if we couldn't get the image source.
 1918     if ( ! $image_src ) {
 1919         return $image;
 1920     }
 1921 
 1922     /**
 1923      * Filters whether to add the missing `width` and `height` HTML attributes to the img tag. Default `true`.
 1924      *
 1925      * Returning anything else than `true` will not add the attributes.
 1926      *
 1927      * @since 5.5.0
 1928      *
 1929      * @param bool   $value         The filtered value, defaults to `true`.
 1930      * @param string $image         The HTML `img` tag where the attribute should be added.
 1931      * @param string $context       Additional context about how the function was called or where the img tag is.
 1932      * @param int    $attachment_id The image attachment ID.
 1933      */
 1934     $add = apply_filters( 'wp_img_tag_add_width_and_height_attr', true, $image, $context, $attachment_id );
 1935 
 1936     if ( true === $add ) {
 1937         $image_meta = wp_get_attachment_metadata( $attachment_id );
 1938         $size_array = wp_image_src_get_dimensions( $image_src, $image_meta, $attachment_id );
 1939 
 1940         if ( $size_array ) {
 1941             $hw = trim( image_hwstring( $size_array[0], $size_array[1] ) );
 1942             return str_replace( '<img', "<img {$hw}", $image );
 1943         }
 1944     }
 1945 
 1946     return $image;
 1947 }
 1948 
 1949 /**
 1950  * Adds `srcset` and `sizes` attributes to an existing `img` HTML tag.
 1951  *
 1952  * @since 5.5.0
 1953  *
 1954  * @param string $image         The HTML `img` tag where the attribute should be added.
 1955  * @param string $context       Additional context to pass to the filters.
 1956  * @param int    $attachment_id Image attachment ID.
 1957  * @return string Converted 'img' element with 'loading' attribute added.
 1958  */
 1959 function wp_img_tag_add_srcset_and_sizes_attr( $image, $context, $attachment_id ) {
 1960     /**
 1961      * Filters whether to add the `srcset` and `sizes` HTML attributes to the img tag. Default `true`.
 1962      *
 1963      * Returning anything else than `true` will not add the attributes.
 1964      *
 1965      * @since 5.5.0
 1966      *
 1967      * @param bool   $value         The filtered value, defaults to `true`.
 1968      * @param string $image         The HTML `img` tag where the attribute should be added.
 1969      * @param string $context       Additional context about how the function was called or where the img tag is.
 1970      * @param int    $attachment_id The image attachment ID.
 1971      */
 1972     $add = apply_filters( 'wp_img_tag_add_srcset_and_sizes_attr', true, $image, $context, $attachment_id );
 1973 
 1974     if ( true === $add ) {
 1975         $image_meta = wp_get_attachment_metadata( $attachment_id );
 1976         return wp_image_add_srcset_and_sizes( $image, $image_meta, $attachment_id );
 1977     }
 1978 
 1979     return $image;
 1980 }
 1981 
 1982 /**
 1983  * Adds `loading` attribute to an `iframe` HTML tag.
 1984  *
 1985  * @since 5.7.0
 1986  *
 1987  * @param string $iframe  The HTML `iframe` tag where the attribute should be added.
 1988  * @param string $context Additional context to pass to the filters.
 1989  * @return string Converted `iframe` tag with `loading` attribute added.
 1990  */
 1991 function wp_iframe_tag_add_loading_attr( $iframe, $context ) {
 1992     /**
 1993      * Filters the `loading` attribute value to add to an iframe. Default `lazy`.
 1994      *
 1995      * Returning `false` or an empty string will not add the attribute.
 1996      * Returning `true` will add the default value.
 1997      *
 1998      * @since 5.7.0
 1999      *
 2000      * @param string|bool $value   The `loading` attribute value. Returning a falsey value will result in
 2001      *                             the attribute being omitted for the iframe. Default 'lazy'.
 2002      * @param string      $iframe  The HTML `iframe` tag to be filtered.
 2003      * @param string      $context Additional context about how the function was called or where the iframe tag is.
 2004      */
 2005     $value = apply_filters( 'wp_iframe_tag_add_loading_attr', 'lazy', $iframe, $context );
 2006 
 2007     if ( $value ) {
 2008         if ( ! in_array( $value, array( 'lazy', 'eager' ), true ) ) {
 2009             $value = 'lazy';
 2010         }
 2011 
 2012         // Iframes should have source and dimension attributes for the `loading` attribute to be added.
 2013         if ( false === strpos( $iframe, ' src="' ) || false === strpos( $iframe, ' width="' ) || false === strpos( $iframe, ' height="' ) ) {
 2014             return $iframe;
 2015         }
 2016 
 2017         return str_replace( '<iframe', '<iframe loading="' . esc_attr( $value ) . '"', $iframe );
 2018     }
 2019 
 2020     return $iframe;
 2021 }
 2022 
 2023 /**
 2024  * Adds a 'wp-post-image' class to post thumbnails. Internal use only.
 2025  *
 2026  * Uses the {@see 'begin_fetch_post_thumbnail_html'} and {@see 'end_fetch_post_thumbnail_html'}
 2027  * action hooks to dynamically add/remove itself so as to only filter post thumbnails.
 2028  *
 2029  * @ignore
 2030  * @since 2.9.0
 2031  *
 2032  * @param string[] $attr Array of thumbnail attributes including src, class, alt, title, keyed by attribute name.
 2033  * @return string[] Modified array of attributes including the new 'wp-post-image' class.
 2034  */
 2035 function _wp_post_thumbnail_class_filter( $attr ) {
 2036     $attr['class'] .= ' wp-post-image';
 2037     return $attr;
 2038 }
 2039 
 2040 /**
 2041  * Adds '_wp_post_thumbnail_class_filter' callback to the 'wp_get_attachment_image_attributes'
 2042  * filter hook. Internal use only.
 2043  *
 2044  * @ignore
 2045  * @since 2.9.0
 2046  *
 2047  * @param string[] $attr Array of thumbnail attributes including src, class, alt, title, keyed by attribute name.
 2048  */
 2049 function _wp_post_thumbnail_class_filter_add( $attr ) {
 2050     add_filter( 'wp_get_attachment_image_attributes', '_wp_post_thumbnail_class_filter' );
 2051 }
 2052 
 2053 /**
 2054  * Removes the '_wp_post_thumbnail_class_filter' callback from the 'wp_get_attachment_image_attributes'
 2055  * filter hook. Internal use only.
 2056  *
 2057  * @ignore
 2058  * @since 2.9.0
 2059  *
 2060  * @param string[] $attr Array of thumbnail attributes including src, class, alt, title, keyed by attribute name.
 2061  */
 2062 function _wp_post_thumbnail_class_filter_remove( $attr ) {
 2063     remove_filter( 'wp_get_attachment_image_attributes', '_wp_post_thumbnail_class_filter' );
 2064 }
 2065 
 2066 add_shortcode( 'wp_caption', 'img_caption_shortcode' );
 2067 add_shortcode( 'caption', 'img_caption_shortcode' );
 2068 
 2069 /**
 2070  * Builds the Caption shortcode output.
 2071  *
 2072  * Allows a plugin to replace the content that would otherwise be returned. The
 2073  * filter is {@see 'img_caption_shortcode'} and passes an empty string, the attr
 2074  * parameter and the content parameter values.
 2075  *
 2076  * The supported attributes for the shortcode are 'id', 'caption_id', 'align',
 2077  * 'width', 'caption', and 'class'.
 2078  *
 2079  * @since 2.6.0
 2080  * @since 3.9.0 The `class` attribute was added.
 2081  * @since 5.1.0 The `caption_id` attribute was added.
 2082  *
 2083  * @param array  $attr {
 2084  *     Attributes of the caption shortcode.
 2085  *
 2086  *     @type string $id         ID of the image and caption container element, i.e. `<figure>` or `<div>`.
 2087  *     @type string $caption_id ID of the caption element, i.e. `<figcaption>` or `<p>`.
 2088  *     @type string $align      Class name that aligns the caption. Default 'alignnone'. Accepts 'alignleft',
 2089  *                              'aligncenter', alignright', 'alignnone'.
 2090  *     @type int    $width      The width of the caption, in pixels.
 2091  *     @type string $caption    The caption text.
 2092  *     @type string $class      Additional class name(s) added to the caption container.
 2093  * }
 2094  * @param string $content Shortcode content.
 2095  * @return string HTML content to display the caption.
 2096  */
 2097 function img_caption_shortcode( $attr, $content = null ) {
 2098     // New-style shortcode with the caption inside the shortcode with the link and image tags.
 2099     if ( ! isset( $attr['caption'] ) ) {
 2100         if ( preg_match( '#((?:<a [^>]+>\s*)?<img [^>]+>(?:\s*</a>)?)(.*)#is', $content, $matches ) ) {
 2101             $content         = $matches[1];
 2102             $attr['caption'] = trim( $matches[2] );
 2103         }
 2104     } elseif ( strpos( $attr['caption'], '<' ) !== false ) {
 2105         $attr['caption'] = wp_kses( $attr['caption'], 'post' );
 2106     }
 2107 
 2108     /**
 2109      * Filters the default caption shortcode output.
 2110      *
 2111      * If the filtered output isn't empty, it will be used instead of generating
 2112      * the default caption template.
 2113      *
 2114      * @since 2.6.0
 2115      *
 2116      * @see img_caption_shortcode()
 2117      *
 2118      * @param string $output  The caption output. Default empty.
 2119      * @param array  $attr    Attributes of the caption shortcode.
 2120      * @param string $content The image element, possibly wrapped in a hyperlink.
 2121      */
 2122     $output = apply_filters( 'img_caption_shortcode', '', $attr, $content );
 2123 
 2124     if ( ! empty( $output ) ) {
 2125         return $output;
 2126     }
 2127 
 2128     $atts = shortcode_atts(
 2129         array(
 2130             'id'         => '',
 2131             'caption_id' => '',
 2132             'align'      => 'alignnone',
 2133             'width'      => '',
 2134             'caption'    => '',
 2135             'class'      => '',
 2136         ),
 2137         $attr,
 2138         'caption'
 2139     );
 2140 
 2141     $atts['width'] = (int) $atts['width'];
 2142 
 2143     if ( $atts['width'] < 1 || empty( $atts['caption'] ) ) {
 2144         return $content;
 2145     }
 2146 
 2147     $id          = '';
 2148     $caption_id  = '';
 2149     $describedby = '';
 2150 
 2151     if ( $atts['id'] ) {
 2152         $atts['id'] = sanitize_html_class( $atts['id'] );
 2153         $id         = 'id="' . esc_attr( $atts['id'] ) . '" ';
 2154     }
 2155 
 2156     if ( $atts['caption_id'] ) {
 2157         $atts['caption_id'] = sanitize_html_class( $atts['caption_id'] );
 2158     } elseif ( $atts['id'] ) {
 2159         $atts['caption_id'] = 'caption-' . str_replace( '_', '-', $atts['id'] );
 2160     }
 2161 
 2162     if ( $atts['caption_id'] ) {
 2163         $caption_id  = 'id="' . esc_attr( $atts['caption_id'] ) . '" ';
 2164         $describedby = 'aria-describedby="' . esc_attr( $atts['caption_id'] ) . '" ';
 2165     }
 2166 
 2167     $class = trim( 'wp-caption ' . $atts['align'] . ' ' . $atts['class'] );
 2168 
 2169     $html5 = current_theme_supports( 'html5', 'caption' );
 2170     // HTML5 captions never added the extra 10px to the image width.
 2171     $width = $html5 ? $atts['width'] : ( 10 + $atts['width'] );
 2172 
 2173     /**
 2174      * Filters the width of an image's caption.
 2175      *
 2176      * By default, the caption is 10 pixels greater than the width of the image,
 2177      * to prevent post content from running up against a floated image.
 2178      *
 2179      * @since 3.7.0
 2180      *
 2181      * @see img_caption_shortcode()
 2182      *
 2183      * @param int    $width    Width of the caption in pixels. To remove this inline style,
 2184      *                         return zero.
 2185      * @param array  $atts     Attributes of the caption shortcode.
 2186      * @param string $content  The image element, possibly wrapped in a hyperlink.
 2187      */
 2188     $caption_width = apply_filters( 'img_caption_shortcode_width', $width, $atts, $content );
 2189 
 2190     $style = '';
 2191 
 2192     if ( $caption_width ) {
 2193         $style = 'style="width: ' . (int) $caption_width . 'px" ';
 2194     }
 2195 
 2196     if ( $html5 ) {
 2197         $html = sprintf(
 2198             '<figure %s%s%sclass="%s">%s%s</figure>',
 2199             $id,
 2200             $describedby,
 2201             $style,
 2202             esc_attr( $class ),
 2203             do_shortcode( $content ),
 2204             sprintf(
 2205                 '<figcaption %sclass="wp-caption-text">%s</figcaption>',
 2206                 $caption_id,
 2207                 $atts['caption']
 2208             )
 2209         );
 2210     } else {
 2211         $html = sprintf(
 2212             '<div %s%sclass="%s">%s%s</div>',
 2213             $id,
 2214             $style,
 2215             esc_attr( $class ),
 2216             str_replace( '<img ', '<img ' . $describedby, do_shortcode( $content ) ),
 2217             sprintf(
 2218                 '<p %sclass="wp-caption-text">%s</p>',
 2219                 $caption_id,
 2220                 $atts['caption']
 2221             )
 2222         );
 2223     }
 2224 
 2225     return $html;
 2226 }
 2227 
 2228 add_shortcode( 'gallery', 'gallery_shortcode' );
 2229 
 2230 /**
 2231  * Builds the Gallery shortcode output.
 2232  *
 2233  * This implements the functionality of the Gallery Shortcode for displaying
 2234  * WordPress images on a post.
 2235  *
 2236  * @since 2.5.0
 2237  *
 2238  * @param array $attr {
 2239  *     Attributes of the gallery shortcode.
 2240  *
 2241  *     @type string       $order      Order of the images in the gallery. Default 'ASC'. Accepts 'ASC', 'DESC'.
 2242  *     @type string       $orderby    The field to use when ordering the images. Default 'menu_order ID'.
 2243  *                                    Accepts any valid SQL ORDERBY statement.
 2244  *     @type int          $id         Post ID.
 2245  *     @type string       $itemtag    HTML tag to use for each image in the gallery.
 2246  *                                    Default 'dl', or 'figure' when the theme registers HTML5 gallery support.
 2247  *     @type string       $icontag    HTML tag to use for each image's icon.
 2248  *                                    Default 'dt', or 'div' when the theme registers HTML5 gallery support.
 2249  *     @type string       $captiontag HTML tag to use for each image's caption.
 2250  *                                    Default 'dd', or 'figcaption' when the theme registers HTML5 gallery support.
 2251  *     @type int          $columns    Number of columns of images to display. Default 3.
 2252  *     @type string|int[] $size       Size of the images to display. Accepts any registered image size name, or an array
 2253  *                                    of width and height values in pixels (in that order). Default 'thumbnail'.
 2254  *     @type string       $ids        A comma-separated list of IDs of attachments to display. Default empty.
 2255  *     @type string       $include    A comma-separated list of IDs of attachments to include. Default empty.
 2256  *     @type string       $exclude    A comma-separated list of IDs of attachments to exclude. Default empty.
 2257  *     @type string       $link       What to link each image to. Default empty (links to the attachment page).
 2258  *                                    Accepts 'file', 'none'.
 2259  * }
 2260  * @return string HTML content to display gallery.
 2261  */
 2262 function gallery_shortcode( $attr ) {
 2263     $post = get_post();
 2264 
 2265     static $instance = 0;
 2266     $instance++;
 2267 
 2268     if ( ! empty( $attr['ids'] ) ) {
 2269         // 'ids' is explicitly ordered, unless you specify otherwise.
 2270         if ( empty( $attr['orderby'] ) ) {
 2271             $attr['orderby'] = 'post__in';
 2272         }
 2273         $attr['include'] = $attr['ids'];
 2274     }
 2275 
 2276     /**
 2277      * Filters the default gallery shortcode output.
 2278      *
 2279      * If the filtered output isn't empty, it will be used instead of generating
 2280      * the default gallery template.
 2281      *
 2282      * @since 2.5.0
 2283      * @since 4.2.0 The `$instance` parameter was added.
 2284      *
 2285      * @see gallery_shortcode()
 2286      *
 2287      * @param string $output   The gallery output. Default empty.
 2288      * @param array  $attr     Attributes of the gallery shortcode.
 2289      * @param int    $instance Unique numeric ID of this gallery shortcode instance.
 2290      */
 2291     $output = apply_filters( 'post_gallery', '', $attr, $instance );
 2292 
 2293     if ( ! empty( $output ) ) {
 2294         return $output;
 2295     }
 2296 
 2297     $html5 = current_theme_supports( 'html5', 'gallery' );
 2298     $atts  = shortcode_atts(
 2299         array(
 2300             'order'      => 'ASC',
 2301             'orderby'    => 'menu_order ID',
 2302             'id'         => $post ? $post->ID : 0,
 2303             'itemtag'    => $html5 ? 'figure' : 'dl',
 2304             'icontag'    => $html5 ? 'div' : 'dt',
 2305             'captiontag' => $html5 ? 'figcaption' : 'dd',
 2306             'columns'    => 3,
 2307             'size'       => 'thumbnail',
 2308             'include'    => '',
 2309             'exclude'    => '',
 2310             'link'       => '',
 2311         ),
 2312         $attr,
 2313         'gallery'
 2314     );
 2315 
 2316     $id = (int) $atts['id'];
 2317 
 2318     if ( ! empty( $atts['include'] ) ) {
 2319         $_attachments = get_posts(
 2320             array(
 2321                 'include'        => $atts['include'],
 2322                 'post_status'    => 'inherit',
 2323                 'post_type'      => 'attachment',
 2324                 'post_mime_type' => 'image',
 2325                 'order'          => $atts['order'],
 2326                 'orderby'        => $atts['orderby'],
 2327             )
 2328         );
 2329 
 2330         $attachments = array();
 2331         foreach ( $_attachments as $key => $val ) {
 2332             $attachments[ $val->ID ] = $_attachments[ $key ];
 2333         }
 2334     } elseif ( ! empty( $atts['exclude'] ) ) {
 2335         $attachments = get_children(
 2336             array(
 2337                 'post_parent'    => $id,
 2338                 'exclude'        => $atts['exclude'],
 2339                 'post_status'    => 'inherit',
 2340                 'post_type'      => 'attachment',
 2341                 'post_mime_type' => 'image',
 2342                 'order'          => $atts['order'],
 2343                 'orderby'        => $atts['orderby'],
 2344             )
 2345         );
 2346     } else {
 2347         $attachments = get_children(
 2348             array(
 2349                 'post_parent'    => $id,
 2350                 'post_status'    => 'inherit',
 2351                 'post_type'      => 'attachment',
 2352                 'post_mime_type' => 'image',
 2353                 'order'          => $atts['order'],
 2354                 'orderby'        => $atts['orderby'],
 2355             )
 2356         );
 2357     }
 2358 
 2359     if ( empty( $attachments ) ) {
 2360         return '';
 2361     }
 2362 
 2363     if ( is_feed() ) {
 2364         $output = "\n";
 2365         foreach ( $attachments as $att_id => $attachment ) {
 2366             if ( ! empty( $atts['link'] ) ) {
 2367                 if ( 'none' === $atts['link'] ) {
 2368                     $output .= wp_get_attachment_image( $att_id, $atts['size'], false, $attr );
 2369                 } else {
 2370                     $output .= wp_get_attachment_link( $att_id, $atts['size'], false );
 2371                 }
 2372             } else {
 2373                 $output .= wp_get_attachment_link( $att_id, $atts['size'], true );
 2374             }
 2375             $output .= "\n";
 2376         }
 2377         return $output;
 2378     }
 2379 
 2380     $itemtag    = tag_escape( $atts['itemtag'] );
 2381     $captiontag = tag_escape( $atts['captiontag'] );
 2382     $icontag    = tag_escape( $atts['icontag'] );
 2383     $valid_tags = wp_kses_allowed_html( 'post' );
 2384     if ( ! isset( $valid_tags[ $itemtag ] ) ) {
 2385         $itemtag = 'dl';
 2386     }
 2387     if ( ! isset( $valid_tags[ $captiontag ] ) ) {
 2388         $captiontag = 'dd';
 2389     }
 2390     if ( ! isset( $valid_tags[ $icontag ] ) ) {
 2391         $icontag = 'dt';
 2392     }
 2393 
 2394     $columns   = (int) $atts['columns'];
 2395     $itemwidth = $columns > 0 ? floor( 100 / $columns ) : 100;
 2396     $float     = is_rtl() ? 'right' : 'left';
 2397 
 2398     $selector = "gallery-{$instance}";
 2399 
 2400     $gallery_style = '';
 2401 
 2402     /**
 2403      * Filters whether to print default gallery styles.
 2404      *
 2405      * @since 3.1.0
 2406      *
 2407      * @param bool $print Whether to print default gallery styles.
 2408      *                    Defaults to false if the theme supports HTML5 galleries.
 2409      *                    Otherwise, defaults to true.
 2410      */
 2411     if ( apply_filters( 'use_default_gallery_style', ! $html5 ) ) {
 2412         $type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"';
 2413 
 2414         $gallery_style = "
 2415         <style{$type_attr}>
 2416             #{$selector} {
 2417                 margin: auto;
 2418             }
 2419             #{$selector} .gallery-item {
 2420                 float: {$float};
 2421                 margin-top: 10px;
 2422                 text-align: center;
 2423                 width: {$itemwidth}%;
 2424             }
 2425             #{$selector} img {
 2426                 border: 2px solid #cfcfcf;
 2427             }
 2428             #{$selector} .gallery-caption {
 2429                 margin-left: 0;
 2430             }
 2431             /* see gallery_shortcode() in wp-includes/media.php */
 2432         </style>\n\t\t";
 2433     }
 2434 
 2435     $size_class  = sanitize_html_class( is_array( $atts['size'] ) ? implode( 'x', $atts['size'] ) : $atts['size'] );
 2436     $gallery_div = "<div id='$selector' class='gallery galleryid-{$id} gallery-columns-{$columns} gallery-size-{$size_class}'>";
 2437 
 2438     /**
 2439      * Filters the default gallery shortcode CSS styles.
 2440      *
 2441      * @since 2.5.0
 2442      *
 2443      * @param string $gallery_style Default CSS styles and opening HTML div container
 2444      *                              for the gallery shortcode output.
 2445      */
 2446     $output = apply_filters( 'gallery_style', $gallery_style . $gallery_div );
 2447 
 2448     $i = 0;
 2449 
 2450     foreach ( $attachments as $id => $attachment ) {
 2451 
 2452         $attr = ( trim( $attachment->post_excerpt ) ) ? array( 'aria-describedby' => "$selector-$id" ) : '';
 2453 
 2454         if ( ! empty( $atts['link'] ) && 'file' === $atts['link'] ) {
 2455             $image_output = wp_get_attachment_link( $id, $atts['size'], false, false, false, $attr );
 2456         } elseif ( ! empty( $atts['link'] ) && 'none' === $atts['link'] ) {
 2457             $image_output = wp_get_attachment_image( $id, $atts['size'], false, $attr );
 2458         } else {
 2459             $image_output = wp_get_attachment_link( $id, $atts['size'], true, false, false, $attr );
 2460         }
 2461 
 2462         $image_meta = wp_get_attachment_metadata( $id );
 2463 
 2464         $orientation = '';
 2465 
 2466         if ( isset( $image_meta['height'], $image_meta['width'] ) ) {
 2467             $orientation = ( $image_meta['height'] > $image_meta['width'] ) ? 'portrait' : 'landscape';
 2468         }
 2469 
 2470         $output .= "<{$itemtag} class='gallery-item'>";
 2471         $output .= "
 2472             <{$icontag} class='gallery-icon {$orientation}'>
 2473                 $image_output
 2474             </{$icontag}>";
 2475 
 2476         if ( $captiontag && trim( $attachment->post_excerpt ) ) {
 2477             $output .= "
 2478                 <{$captiontag} class='wp-caption-text gallery-caption' id='$selector-$id'>
 2479                 " . wptexturize( $attachment->post_excerpt ) . "
 2480                 </{$captiontag}>";
 2481         }
 2482 
 2483         $output .= "</{$itemtag}>";
 2484 
 2485         if ( ! $html5 && $columns > 0 && 0 === ++$i % $columns ) {
 2486             $output .= '<br style="clear: both" />';
 2487         }
 2488     }
 2489 
 2490     if ( ! $html5 && $columns > 0 && 0 !== $i % $columns ) {
 2491         $output .= "
 2492             <br style='clear: both' />";
 2493     }
 2494 
 2495     $output .= "
 2496         </div>\n";
 2497 
 2498     return $output;
 2499 }
 2500 
 2501 /**
 2502  * Outputs the templates used by playlists.
 2503  *
 2504  * @since 3.9.0
 2505  */
 2506 function wp_underscore_playlist_templates() {
 2507     ?>
 2508 <script type="text/html" id="tmpl-wp-playlist-current-item">
 2509     <# if ( data.thumb && data.thumb.src ) { #>
 2510         <img src="{{ data.thumb.src }}" alt="" />
 2511     <# } #>
 2512     <div class="wp-playlist-caption">
 2513         <span class="wp-playlist-item-meta wp-playlist-item-title">
 2514         <?php
 2515             /* translators: %s: Playlist item title. */
 2516             printf( _x( '&#8220;%s&#8221;', 'playlist item title' ), '{{ data.title }}' );
 2517         ?>
 2518         </span>
 2519         <# if ( data.meta.album ) { #><span class="wp-playlist-item-meta wp-playlist-item-album">{{ data.meta.album }}</span><# } #>
 2520         <# if ( data.meta.artist ) { #><span class="wp-playlist-item-meta wp-playlist-item-artist">{{ data.meta.artist }}</span><# } #>
 2521     </div>
 2522 </script>
 2523 <script type="text/html" id="tmpl-wp-playlist-item">
 2524     <div class="wp-playlist-item">
 2525         <a class="wp-playlist-caption" href="{{ data.src }}">
 2526             {{ data.index ? ( data.index + '. ' ) : '' }}
 2527             <# if ( data.caption ) { #>
 2528                 {{ data.caption }}
 2529             <# } else { #>
 2530                 <span class="wp-playlist-item-title">
 2531                 <?php
 2532                     /* translators: %s: Playlist item title. */
 2533                     printf( _x( '&#8220;%s&#8221;', 'playlist item title' ), '{{{ data.title }}}' );
 2534                 ?>
 2535                 </span>
 2536                 <# if ( data.artists && data.meta.artist ) { #>
 2537                 <span class="wp-playlist-item-artist"> &mdash; {{ data.meta.artist }}</span>
 2538                 <# } #>
 2539             <# } #>
 2540         </a>
 2541         <# if ( data.meta.length_formatted ) { #>
 2542         <div class="wp-playlist-item-length">{{ data.meta.length_formatted }}</div>
 2543         <# } #>
 2544     </div>
 2545 </script>
 2546     <?php
 2547 }
 2548 
 2549 /**
 2550  * Outputs and enqueue default scripts and styles for playlists.
 2551  *
 2552  * @since 3.9.0
 2553  *
 2554  * @param string $type Type of playlist. Accepts 'audio' or 'video'.
 2555  */
 2556 function wp_playlist_scripts( $type ) {
 2557     wp_enqueue_style( 'wp-mediaelement' );
 2558     wp_enqueue_script( 'wp-playlist' );
 2559     ?>
 2560 <!--[if lt IE 9]><script>document.createElement('<?php echo esc_js( $type ); ?>');</script><![endif]-->
 2561     <?php
 2562     add_action( 'wp_footer', 'wp_underscore_playlist_templates', 0 );
 2563     add_action( 'admin_footer', 'wp_underscore_playlist_templates', 0 );
 2564 }
 2565 
 2566 /**
 2567  * Builds the Playlist shortcode output.
 2568  *
 2569  * This implements the functionality of the playlist shortcode for displaying
 2570  * a collection of WordPress audio or video files in a post.
 2571  *
 2572  * @since 3.9.0
 2573  *
 2574  * @global int $content_width
 2575  *
 2576  * @param array $attr {
 2577  *     Array of default playlist attributes.
 2578  *
 2579  *     @type string  $type         Type of playlist to display. Accepts 'audio' or 'video'. Default 'audio'.
 2580  *     @type string  $order        Designates ascending or descending order of items in the playlist.
 2581  *                                 Accepts 'ASC', 'DESC'. Default 'ASC'.
 2582  *     @type string  $orderby      Any column, or columns, to sort the playlist. If $ids are
 2583  *                                 passed, this defaults to the order of the $ids array ('post__in').
 2584  *                                 Otherwise default is 'menu_order ID'.
 2585  *     @type int     $id           If an explicit $ids array is not present, this parameter
 2586  *                                 will determine which attachments are used for the playlist.
 2587  *                                 Default is the current post ID.
 2588  *     @type array   $ids          Create a playlist out of these explicit attachment IDs. If empty,
 2589  *                                 a playlist will be created from all $type attachments of $id.
 2590  *                                 Default empty.
 2591  *     @type array   $exclude      List of specific attachment IDs to exclude from the playlist. Default empty.
 2592  *     @type string  $style        Playlist style to use. Accepts 'light' or 'dark'. Default 'light'.
 2593  *     @type bool    $tracklist    Whether to show or hide the playlist. Default true.
 2594  *     @type bool    $tracknumbers Whether to show or hide the numbers next to entries in the playlist. Default true.
 2595  *     @type bool    $images       Show or hide the video or audio thumbnail (Featured Image/post
 2596  *                                 thumbnail). Default true.
 2597  *     @type bool    $artists      Whether to show or hide artist name in the playlist. Default true.
 2598  * }
 2599  *
 2600  * @return string Playlist output. Empty string if the passed type is unsupported.
 2601  */
 2602 function wp_playlist_shortcode( $attr ) {
 2603     global $content_width;
 2604     $post = get_post();
 2605 
 2606     static $instance = 0;
 2607     $instance++;
 2608 
 2609     if ( ! empty( $attr['ids'] ) ) {
 2610         // 'ids' is explicitly ordered, unless you specify otherwise.
 2611         if ( empty( $attr['orderby'] ) ) {
 2612             $attr['orderby'] = 'post__in';
 2613         }
 2614         $attr['include'] = $attr['ids'];
 2615     }
 2616 
 2617     /**
 2618      * Filters the playlist output.
 2619      *
 2620      * Returning a non-empty value from the filter will short-circuit generation
 2621      * of the default playlist output, returning the passed value instead.
 2622      *
 2623      * @since 3.9.0
 2624      * @since 4.2.0 The `$instance` parameter was added.
 2625      *
 2626      * @param string $output   Playlist output. Default empty.
 2627      * @param array  $attr     An array of shortcode attributes.
 2628      * @param int    $instance Unique numeric ID of this playlist shortcode instance.
 2629      */
 2630     $output = apply_filters( 'post_playlist', '', $attr, $instance );
 2631 
 2632     if ( ! empty( $output ) ) {
 2633         return $output;
 2634     }
 2635 
 2636     $atts = shortcode_atts(
 2637         array(
 2638             'type'         => 'audio',
 2639             'order'        => 'ASC',
 2640             'orderby'      => 'menu_order ID',
 2641             'id'           => $post ? $post->ID : 0,
 2642             'include'      => '',
 2643             'exclude'      => '',
 2644             'style'        => 'light',
 2645             'tracklist'    => true,
 2646             'tracknumbers' => true,
 2647             'images'       => true,
 2648             'artists'      => true,
 2649         ),
 2650         $attr,
 2651         'playlist'
 2652     );
 2653 
 2654     $id = (int) $atts['id'];
 2655 
 2656     if ( 'audio' !== $atts['type'] ) {
 2657         $atts['type'] = 'video';
 2658     }
 2659 
 2660     $args = array(
 2661         'post_status'    => 'inherit',
 2662         'post_type'      => 'attachment',
 2663         'post_mime_type' => $atts['type'],
 2664         'order'          => $atts['order'],
 2665         'orderby'        => $atts['orderby'],
 2666     );
 2667 
 2668     if ( ! empty( $atts['include'] ) ) {
 2669         $args['include'] = $atts['include'];
 2670         $_attachments    = get_posts( $args );
 2671 
 2672         $attachments = array();
 2673         foreach ( $_attachments as $key => $val ) {
 2674             $attachments[ $val->ID ] = $_attachments[ $key ];
 2675         }
 2676     } elseif ( ! empty( $atts['exclude'] ) ) {
 2677         $args['post_parent'] = $id;
 2678         $args['exclude']     = $atts['exclude'];
 2679         $attachments         = get_children( $args );
 2680     } else {
 2681         $args['post_parent'] = $id;
 2682         $attachments         = get_children( $args );
 2683     }
 2684 
 2685     if ( empty( $attachments ) ) {
 2686         return '';
 2687     }
 2688 
 2689     if ( is_feed() ) {
 2690         $output = "\n";
 2691         foreach ( $attachments as $att_id => $attachment ) {
 2692             $output .= wp_get_attachment_link( $att_id ) . "\n";
 2693         }
 2694         return $output;
 2695     }
 2696 
 2697     $outer = 22; // Default padding and border of wrapper.
 2698 
 2699     $default_width  = 640;
 2700     $default_height = 360;
 2701 
 2702     $theme_width  = empty( $content_width ) ? $default_width : ( $content_width - $outer );
 2703     $theme_height = empty( $content_width ) ? $default_height : round( ( $default_height * $theme_width ) / $default_width );
 2704 
 2705     $data = array(
 2706         'type'         => $atts['type'],
 2707         // Don't pass strings to JSON, will be truthy in JS.
 2708         'tracklist'    => wp_validate_boolean( $atts['tracklist'] ),
 2709         'tracknumbers' => wp_validate_boolean( $atts['tracknumbers'] ),
 2710         'images'       => wp_validate_boolean( $atts['images'] ),
 2711         'artists'      => wp_validate_boolean( $atts['artists'] ),
 2712     );
 2713 
 2714     $tracks = array();
 2715     foreach ( $attachments as $attachment ) {
 2716         $url   = wp_get_attachment_url( $attachment->ID );
 2717         $ftype = wp_check_filetype( $url, wp_get_mime_types() );
 2718         $track = array(
 2719             'src'         => $url,
 2720             'type'        => $ftype['type'],
 2721             'title'       => $attachment->post_title,
 2722             'caption'     => $attachment->post_excerpt,
 2723             'description' => $attachment->post_content,
 2724         );
 2725 
 2726         $track['meta'] = array();
 2727         $meta          = wp_get_attachment_metadata( $attachment->ID );
 2728         if ( ! empty( $meta ) ) {
 2729 
 2730             foreach ( wp_get_attachment_id3_keys( $attachment ) as $key => $label ) {
 2731                 if ( ! empty( $meta[ $key ] ) ) {
 2732                     $track['meta'][ $key ] = $meta[ $key ];
 2733                 }
 2734             }
 2735 
 2736             if ( 'video' === $atts['type'] ) {
 2737                 if ( ! empty( $meta['width'] ) && ! empty( $meta['height'] ) ) {
 2738                     $width        = $meta['width'];
 2739                     $height       = $meta['height'];
 2740                     $theme_height = round( ( $height * $theme_width ) / $width );
 2741                 } else {
 2742                     $width  = $default_width;
 2743                     $height = $default_height;
 2744                 }
 2745 
 2746                 $track['dimensions'] = array(
 2747                     'original' => compact( 'width', 'height' ),
 2748                     'resized'  => array(
 2749                         'width'  => $theme_width,
 2750                         'height' => $theme_height,
 2751                     ),
 2752                 );
 2753             }
 2754         }
 2755 
 2756         if ( $atts['images'] ) {
 2757             $thumb_id = get_post_thumbnail_id( $attachment->ID );
 2758             if ( ! empty( $thumb_id ) ) {
 2759                 list( $src, $width, $height ) = wp_get_attachment_image_src( $thumb_id, 'full' );
 2760                 $track['image']               = compact( 'src', 'width', 'height' );
 2761                 list( $src, $width, $height ) = wp_get_attachment_image_src( $thumb_id, 'thumbnail' );
 2762                 $track['thumb']               = compact( 'src', 'width', 'height' );
 2763             } else {
 2764                 $src            = wp_mime_type_icon( $attachment->ID );
 2765                 $width          = 48;
 2766                 $height         = 64;
 2767                 $track['image'] = compact( 'src', 'width', 'height' );
 2768                 $track['thumb'] = compact( 'src', 'width', 'height' );
 2769             }
 2770         }
 2771 
 2772         $tracks[] = $track;
 2773     }
 2774     $data['tracks'] = $tracks;
 2775 
 2776     $safe_type  = esc_attr( $atts['type'] );
 2777     $safe_style = esc_attr( $atts['style'] );
 2778 
 2779     ob_start();
 2780 
 2781     if ( 1 === $instance ) {
 2782         /**
 2783          * Prints and enqueues playlist scripts, styles, and JavaScript templates.
 2784          *
 2785          * @since 3.9.0
 2786          *
 2787          * @param string $type  Type of playlist. Possible values are 'audio' or 'video'.
 2788          * @param string $style The 'theme' for the playlist. Core provides 'light' and 'dark'.
 2789          */
 2790         do_action( 'wp_playlist_scripts', $atts['type'], $atts['style'] );
 2791     }
 2792     ?>
 2793 <div class="wp-playlist wp-<?php echo $safe_type; ?>-playlist wp-playlist-<?php echo $safe_style; ?>">
 2794     <?php if ( 'audio' === $atts['type'] ) : ?>
 2795         <div class="wp-playlist-current-item"></div>
 2796     <?php endif ?>
 2797     <<?php echo $safe_type; ?> controls="controls" preload="none" width="<?php echo (int) $theme_width; ?>"
 2798         <?php
 2799         if ( 'video' === $safe_type ) {
 2800             echo ' height="', (int) $theme_height, '"';
 2801         }
 2802         ?>
 2803     ></<?php echo $safe_type; ?>>
 2804     <div class="wp-playlist-next"></div>
 2805     <div class="wp-playlist-prev"></div>
 2806     <noscript>
 2807     <ol>
 2808         <?php
 2809         foreach ( $attachments as $att_id => $attachment ) {
 2810             printf( '<li>%s</li>', wp_get_attachment_link( $att_id ) );
 2811         }
 2812         ?>
 2813     </ol>
 2814     </noscript>
 2815     <script type="application/json" class="wp-playlist-script"><?php echo wp_json_encode( $data ); ?></script>
 2816 </div>
 2817     <?php
 2818     return ob_get_clean();
 2819 }
 2820 add_shortcode( 'playlist', 'wp_playlist_shortcode' );
 2821 
 2822 /**
 2823  * Provides a No-JS Flash fallback as a last resort for audio / video.
 2824  *
 2825  * @since 3.6.0
 2826  *
 2827  * @param string $url The media element URL.
 2828  * @return string Fallback HTML.
 2829  */
 2830 function wp_mediaelement_fallback( $url ) {
 2831     /**
 2832      * Filters the Mediaelement fallback output for no-JS.
 2833      *
 2834      * @since 3.6.0
 2835      *
 2836      * @param string $output Fallback output for no-JS.
 2837      * @param string $url    Media file URL.
 2838      */
 2839     return apply_filters( 'wp_mediaelement_fallback', sprintf( '<a href="%1$s">%1$s</a>', esc_url( $url ) ), $url );
 2840 }
 2841 
 2842 /**
 2843  * Returns a filtered list of supported audio formats.
 2844  *
 2845  * @since 3.6.0
 2846  *
 2847  * @return string[] Supported audio formats.
 2848  */
 2849 function wp_get_audio_extensions() {
 2850     /**
 2851      * Filters the list of supported audio formats.
 2852      *
 2853      * @since 3.6.0
 2854      *
 2855      * @param string[] $extensions An array of supported audio formats. Defaults are
 2856      *                            'mp3', 'ogg', 'flac', 'm4a', 'wav'.
 2857      */
 2858     return apply_filters( 'wp_audio_extensions', array( 'mp3', 'ogg', 'flac', 'm4a', 'wav' ) );
 2859 }
 2860 
 2861 /**
 2862  * Returns useful keys to use to lookup data from an attachment's stored metadata.
 2863  *
 2864  * @since 3.9.0
 2865  *
 2866  * @param WP_Post $attachment The current attachment, provided for context.
 2867  * @param string  $context    Optional. The context. Accepts 'edit', 'display'. Default 'display'.
 2868  * @return string[] Key/value pairs of field keys to labels.
 2869  */
 2870 function wp_get_attachment_id3_keys( $attachment, $context = 'display' ) {
 2871     $fields = array(
 2872         'artist' => __( 'Artist' ),
 2873         'album'  => __( 'Album' ),
 2874     );
 2875 
 2876     if ( 'display' === $context ) {
 2877         $fields['genre']            = __( 'Genre' );
 2878         $fields['year']             = __( 'Year' );
 2879         $fields['length_formatted'] = _x( 'Length', 'video or audio' );
 2880     } elseif ( 'js' === $context ) {
 2881         $fields['bitrate']      = __( 'Bitrate' );
 2882         $fields['bitrate_mode'] = __( 'Bitrate Mode' );
 2883     }
 2884 
 2885     /**
 2886      * Filters the editable list of keys to look up data from an attachment's metadata.
 2887      *
 2888      * @since 3.9.0
 2889      *
 2890      * @param array   $fields     Key/value pairs of field keys to labels.
 2891      * @param WP_Post $attachment Attachment object.
 2892      * @param string  $context    The context. Accepts 'edit', 'display'. Default 'display'.
 2893      */
 2894     return apply_filters( 'wp_get_attachment_id3_keys', $fields, $attachment, $context );
 2895 }
 2896 /**
 2897  * Builds the Audio shortcode output.
 2898  *
 2899  * This implements the functionality of the Audio Shortcode for displaying
 2900  * WordPress mp3s in a post.
 2901  *
 2902  * @since 3.6.0
 2903  *
 2904  * @param array  $attr {
 2905  *     Attributes of the audio shortcode.
 2906  *
 2907  *     @type string $src      URL to the source of the audio file. Default empty.
 2908  *     @type string $loop     The 'loop' attribute for the `<audio>` element. Default empty.
 2909  *     @type string $autoplay The 'autoplay' attribute for the `<audio>` element. Default empty.
 2910  *     @type string $preload  The 'preload' attribute for the `<audio>` element. Default 'none'.
 2911  *     @type string $class    The 'class' attribute for the `<audio>` element. Default 'wp-audio-shortcode'.
 2912  *     @type string $style    The 'style' attribute for the `<audio>` element. Default 'width: 100%;'.
 2913  * }
 2914  * @param string $content Shortcode content.
 2915  * @return string|void HTML content to display audio.
 2916  */
 2917 function wp_audio_shortcode( $attr, $content = '' ) {
 2918     $post_id = get_post() ? get_the_ID() : 0;
 2919 
 2920     static $instance = 0;
 2921     $instance++;
 2922 
 2923     /**
 2924      * Filters the default audio shortcode output.
 2925      *
 2926      * If the filtered output isn't empty, it will be used instead of generating the default audio template.
 2927      *
 2928      * @since 3.6.0
 2929      *
 2930      * @param string $html     Empty variable to be replaced with shortcode markup.
 2931      * @param array  $attr     Attributes of the shortcode. @see wp_audio_shortcode()
 2932      * @param string $content  Shortcode content.
 2933      * @param int    $instance Unique numeric ID of this audio shortcode instance.
 2934      */
 2935     $override = apply_filters( 'wp_audio_shortcode_override', '', $attr, $content, $instance );
 2936 
 2937     if ( '' !== $override ) {
 2938         return $override;
 2939     }
 2940 
 2941     $audio = null;
 2942 
 2943     $default_types = wp_get_audio_extensions();
 2944     $defaults_atts = array(
 2945         'src'      => '',
 2946         'loop'     => '',
 2947         'autoplay' => '',
 2948         'preload'  => 'none',
 2949         'class'    => 'wp-audio-shortcode',
 2950         'style'    => 'width: 100%;',
 2951     );
 2952     foreach ( $default_types as $type ) {
 2953         $defaults_atts[ $type ] = '';
 2954     }
 2955 
 2956     $atts = shortcode_atts( $defaults_atts, $attr, 'audio' );
 2957 
 2958     $primary = false;
 2959     if ( ! empty( $atts['src'] ) ) {
 2960         $type = wp_check_filetype( $atts['src'], wp_get_mime_types() );
 2961 
 2962         if ( ! in_array( strtolower( $type['ext'] ), $default_types, true ) ) {
 2963             return sprintf( '<a class="wp-embedded-audio" href="%s">%s</a>', esc_url( $atts['src'] ), esc_html( $atts['src'] ) );
 2964         }
 2965 
 2966         $primary = true;
 2967         array_unshift( $default_types, 'src' );
 2968     } else {
 2969         foreach ( $default_types as $ext ) {
 2970             if ( ! empty( $atts[ $ext ] ) ) {
 2971                 $type = wp_check_filetype( $atts[ $ext ], wp_get_mime_types() );
 2972 
 2973                 if ( strtolower( $type['ext'] ) === $ext ) {
 2974                     $primary = true;
 2975                 }
 2976             }
 2977         }
 2978     }
 2979 
 2980     if ( ! $primary ) {
 2981         $audios = get_attached_media( 'audio', $post_id );
 2982 
 2983         if ( empty( $audios ) ) {
 2984             return;
 2985         }
 2986 
 2987         $audio       = reset( $audios );
 2988         $atts['src'] = wp_get_attachment_url( $audio->ID );
 2989 
 2990         if ( empty( $atts['src'] ) ) {
 2991             return;
 2992         }
 2993 
 2994         array_unshift( $default_types, 'src' );
 2995     }
 2996 
 2997     /**
 2998      * Filters the media library used for the audio shortcode.
 2999      *
 3000      * @since 3.6.0
 3001      *
 3002      * @param string $library Media library used for the audio shortcode.
 3003      */
 3004     $library = apply_filters( 'wp_audio_shortcode_library', 'mediaelement' );
 3005 
 3006     if ( 'mediaelement' === $library && did_action( 'init' ) ) {
 3007         wp_enqueue_style( 'wp-mediaelement' );
 3008         wp_enqueue_script( 'wp-mediaelement' );
 3009     }
 3010 
 3011     /**
 3012      * Filters the class attribute for the audio shortcode output container.
 3013      *
 3014      * @since 3.6.0
 3015      * @since 4.9.0 The `$atts` parameter was added.
 3016      *
 3017      * @param string $class CSS class or list of space-separated classes.
 3018      * @param array  $atts  Array of audio shortcode attributes.
 3019      */
 3020     $atts['class'] = apply_filters( 'wp_audio_shortcode_class', $atts['class'], $atts );
 3021 
 3022     $html_atts = array(
 3023         'class'    => $atts['class'],
 3024         'id'       => sprintf( 'audio-%d-%d', $post_id, $instance ),
 3025         'loop'     => wp_validate_boolean( $atts['loop'] ),
 3026         'autoplay' => wp_validate_boolean( $atts['autoplay'] ),
 3027         'preload'  => $atts['preload'],
 3028         'style'    => $atts['style'],
 3029     );
 3030 
 3031     // These ones should just be omitted altogether if they are blank.
 3032     foreach ( array( 'loop', 'autoplay', 'preload' ) as $a ) {
 3033         if ( empty( $html_atts[ $a ] ) ) {
 3034             unset( $html_atts[ $a ] );
 3035         }
 3036     }
 3037 
 3038     $attr_strings = array();
 3039 
 3040     foreach ( $html_atts as $k => $v ) {
 3041         $attr_strings[] = $k . '="' . esc_attr( $v ) . '"';
 3042     }
 3043 
 3044     $html = '';
 3045 
 3046     if ( 'mediaelement' === $library && 1 === $instance ) {
 3047         $html .= "<!--[if lt IE 9]><script>document.createElement('audio');</script><![endif]-->\n";
 3048     }
 3049 
 3050     $html .= sprintf( '<audio %s controls="controls">', implode( ' ', $attr_strings ) );
 3051 
 3052     $fileurl = '';
 3053     $source  = '<source type="%s" src="%s" />';
 3054 
 3055     foreach ( $default_types as $fallback ) {
 3056         if ( ! empty( $atts[ $fallback ] ) ) {
 3057             if ( empty( $fileurl ) ) {
 3058                 $fileurl = $atts[ $fallback ];
 3059             }
 3060 
 3061             $type  = wp_check_filetype( $atts[ $fallback ], wp_get_mime_types() );
 3062             $url   = add_query_arg( '_', $instance, $atts[ $fallback ] );
 3063             $html .= sprintf( $source, $type['type'], esc_url( $url ) );
 3064         }
 3065     }
 3066 
 3067     if ( 'mediaelement' === $library ) {
 3068         $html .= wp_mediaelement_fallback( $fileurl );
 3069     }
 3070 
 3071     $html .= '</audio>';
 3072 
 3073     /**
 3074      * Filters the audio shortcode output.
 3075      *
 3076      * @since 3.6.0
 3077      *
 3078      * @param string $html    Audio shortcode HTML output.
 3079      * @param array  $atts    Array of audio shortcode attributes.
 3080      * @param string $audio   Audio file.
 3081      * @param int    $post_id Post ID.
 3082      * @param string $library Media library used for the audio shortcode.
 3083      */
 3084     return apply_filters( 'wp_audio_shortcode', $html, $atts, $audio, $post_id, $library );
 3085 }
 3086 add_shortcode( 'audio', 'wp_audio_shortcode' );
 3087 
 3088 /**
 3089  * Returns a filtered list of supported video formats.
 3090  *
 3091  * @since 3.6.0
 3092  *
 3093  * @return string[] List of supported video formats.
 3094  */
 3095 function wp_get_video_extensions() {
 3096     /**
 3097      * Filters the list of supported video formats.
 3098      *
 3099      * @since 3.6.0
 3100      *
 3101      * @param string[] $extensions An array of supported video formats. Defaults are
 3102      *                             'mp4', 'm4v', 'webm', 'ogv', 'flv'.
 3103      */
 3104     return apply_filters( 'wp_video_extensions', array( 'mp4', 'm4v', 'webm', 'ogv', 'flv' ) );
 3105 }
 3106 
 3107 /**
 3108  * Builds the Video shortcode output.
 3109  *
 3110  * This implements the functionality of the Video Shortcode for displaying
 3111  * WordPress mp4s in a post.
 3112  *
 3113  * @since 3.6.0
 3114  *
 3115  * @global int $content_width
 3116  *
 3117  * @param array  $attr {
 3118  *     Attributes of the shortcode.
 3119  *
 3120  *     @type string $src      URL to the source of the video file. Default empty.
 3121  *     @type int    $height   Height of the video embed in pixels. Default 360.
 3122  *     @type int    $width    Width of the video embed in pixels. Default $content_width or 640.
 3123  *     @type string $poster   The 'poster' attribute for the `<video>` element. Default empty.
 3124  *     @type string $loop     The 'loop' attribute for the `<video>` element. Default empty.
 3125  *     @type string $autoplay The 'autoplay' attribute for the `<video>` element. Default empty.
 3126  *     @type string $preload  The 'preload' attribute for the `<video>` element.
 3127  *                            Default 'metadata'.
 3128  *     @type string $class    The 'class' attribute for the `<video>` element.
 3129  *                            Default 'wp-video-shortcode'.
 3130  * }
 3131  * @param string $content Shortcode content.
 3132  * @return string|void HTML content to display video.
 3133  */
 3134 function wp_video_shortcode( $attr, $content = '' ) {
 3135     global $content_width;
 3136     $post_id = get_post() ? get_the_ID() : 0;
 3137 
 3138     static $instance = 0;
 3139     $instance++;
 3140 
 3141     /**
 3142      * Filters the default video shortcode output.
 3143      *
 3144      * If the filtered output isn't empty, it will be used instead of generating
 3145      * the default video template.
 3146      *
 3147      * @since 3.6.0
 3148      *
 3149      * @see wp_video_shortcode()
 3150      *
 3151      * @param string $html     Empty variable to be replaced with shortcode markup.
 3152      * @param array  $attr     Attributes of the shortcode. @see wp_video_shortcode()
 3153      * @param string $content  Video shortcode content.
 3154      * @param int    $instance Unique numeric ID of this video shortcode instance.
 3155      */
 3156     $override = apply_filters( 'wp_video_shortcode_override', '', $attr, $content, $instance );
 3157 
 3158     if ( '' !== $override ) {
 3159         return $override;
 3160     }
 3161 
 3162     $video = null;
 3163 
 3164     $default_types = wp_get_video_extensions();
 3165     $defaults_atts = array(
 3166         'src'      => '',
 3167         'poster'   => '',
 3168         'loop'     => '',
 3169         'autoplay' => '',
 3170         'preload'  => 'metadata',
 3171         'width'    => 640,
 3172         'height'   => 360,
 3173         'class'    => 'wp-video-shortcode',
 3174     );
 3175 
 3176     foreach ( $default_types as $type ) {
 3177         $defaults_atts[ $type ] = '';
 3178     }
 3179 
 3180     $atts = shortcode_atts( $defaults_atts, $attr, 'video' );
 3181 
 3182     if ( is_admin() ) {
 3183         // Shrink the video so it isn't huge in the admin.
 3184         if ( $atts['width'] > $defaults_atts['width'] ) {
 3185             $atts['height'] = round( ( $atts['height'] * $defaults_atts['width'] ) / $atts['width'] );
 3186             $atts['width']  = $defaults_atts['width'];
 3187         }
 3188     } else {
 3189         // If the video is bigger than the theme.
 3190         if ( ! empty( $content_width ) && $atts['width'] > $content_width ) {
 3191             $atts['height'] = round( ( $atts['height'] * $content_width ) / $atts['width'] );
 3192             $atts['width']  = $content_width;
 3193         }
 3194     }
 3195 
 3196     $is_vimeo      = false;
 3197     $is_youtube    = false;
 3198     $yt_pattern    = '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#';
 3199     $vimeo_pattern = '#^https?://(.+\.)?vimeo\.com/.*#';
 3200 
 3201     $primary = false;
 3202     if ( ! empty( $atts['src'] ) ) {
 3203         $is_vimeo   = ( preg_match( $vimeo_pattern, $atts['src'] ) );
 3204         $is_youtube = ( preg_match( $yt_pattern, $atts['src'] ) );
 3205 
 3206         if ( ! $is_youtube && ! $is_vimeo ) {
 3207             $type = wp_check_filetype( $atts['src'], wp_get_mime_types() );
 3208 
 3209             if ( ! in_array( strtolower( $type['ext'] ), $default_types, true ) ) {
 3210                 return sprintf( '<a class="wp-embedded-video" href="%s">%s</a>', esc_url( $atts['src'] ), esc_html( $atts['src'] ) );
 3211             }
 3212         }
 3213 
 3214         if ( $is_vimeo ) {
 3215             wp_enqueue_script( 'mediaelement-vimeo' );
 3216         }
 3217 
 3218         $primary = true;
 3219         array_unshift( $default_types, 'src' );
 3220     } else {
 3221         foreach ( $default_types as $ext ) {
 3222             if ( ! empty( $atts[ $ext ] ) ) {
 3223                 $type = wp_check_filetype( $atts[ $ext ], wp_get_mime_types() );
 3224                 if ( strtolower( $type['ext'] ) === $ext ) {
 3225                     $primary = true;
 3226                 }
 3227             }
 3228         }
 3229     }
 3230 
 3231     if ( ! $primary ) {
 3232         $videos = get_attached_media( 'video', $post_id );
 3233         if ( empty( $videos ) ) {
 3234             return;
 3235         }
 3236 
 3237         $video       = reset( $videos );
 3238         $atts['src'] = wp_get_attachment_url( $video->ID );
 3239         if ( empty( $atts['src'] ) ) {
 3240             return;
 3241         }
 3242 
 3243         array_unshift( $default_types, 'src' );
 3244     }
 3245 
 3246     /**
 3247      * Filters the media library used for the video shortcode.
 3248      *
 3249      * @since 3.6.0
 3250      *
 3251      * @param string $library Media library used for the video shortcode.
 3252      */
 3253     $library = apply_filters( 'wp_video_shortcode_library', 'mediaelement' );
 3254     if ( 'mediaelement' === $library && did_action( 'init' ) ) {
 3255         wp_enqueue_style( 'wp-mediaelement' );
 3256         wp_enqueue_script( 'wp-mediaelement' );
 3257         wp_enqueue_script( 'mediaelement-vimeo' );
 3258     }
 3259 
 3260     // MediaElement.js has issues with some URL formats for Vimeo and YouTube,
 3261     // so update the URL to prevent the ME.js player from breaking.
 3262     if ( 'mediaelement' === $library ) {
 3263         if ( $is_youtube ) {
 3264             // Remove `feature` query arg and force SSL - see #40866.
 3265             $atts['src'] = remove_query_arg( 'feature', $atts['src'] );
 3266             $atts['src'] = set_url_scheme( $atts['src'], 'https' );
 3267         } elseif ( $is_vimeo ) {
 3268             // Remove all query arguments and force SSL - see #40866.
 3269             $parsed_vimeo_url = wp_parse_url( $atts['src'] );
 3270             $vimeo_src        = 'https://' . $parsed_vimeo_url['host'] . $parsed_vimeo_url['path'];
 3271 
 3272             // Add loop param for mejs bug - see #40977, not needed after #39686.
 3273             $loop        = $atts['loop'] ? '1' : '0';
 3274             $atts['src'] = add_query_arg( 'loop', $loop, $vimeo_src );
 3275         }
 3276     }
 3277 
 3278     /**
 3279      * Filters the class attribute for the video shortcode output container.
 3280      *
 3281      * @since 3.6.0
 3282      * @since 4.9.0 The `$atts` parameter was added.
 3283      *
 3284      * @param string $class CSS class or list of space-separated classes.
 3285      * @param array  $atts  Array of video shortcode attributes.
 3286      */
 3287     $atts['class'] = apply_filters( 'wp_video_shortcode_class', $atts['class'], $atts );
 3288 
 3289     $html_atts = array(
 3290         'class'    => $atts['class'],
 3291         'id'       => sprintf( 'video-%d-%d', $post_id, $instance ),
 3292         'width'    => absint( $atts['width'] ),
 3293         'height'   => absint( $atts['height'] ),
 3294         'poster'   => esc_url( $atts['poster'] ),
 3295         'loop'     => wp_validate_boolean( $atts['loop'] ),
 3296         'autoplay' => wp_validate_boolean( $atts['autoplay'] ),
 3297         'preload'  => $atts['preload'],
 3298     );
 3299 
 3300     // These ones should just be omitted altogether if they are blank.
 3301     foreach ( array( 'poster', 'loop', 'autoplay', 'preload' ) as $a ) {
 3302         if ( empty( $html_atts[ $a ] ) ) {
 3303             unset( $html_atts[ $a ] );
 3304         }
 3305     }
 3306 
 3307     $attr_strings = array();
 3308     foreach ( $html_atts as $k => $v ) {
 3309         $attr_strings[] = $k . '="' . esc_attr( $v ) . '"';
 3310     }
 3311 
 3312     $html = '';
 3313 
 3314     if ( 'mediaelement' === $library && 1 === $instance ) {
 3315         $html .= "<!--[if lt IE 9]><script>document.createElement('video');</script><![endif]-->\n";
 3316     }
 3317 
 3318     $html .= sprintf( '<video %s controls="controls">', implode( ' ', $attr_strings ) );
 3319 
 3320     $fileurl = '';
 3321     $source  = '<source type="%s" src="%s" />';
 3322 
 3323     foreach ( $default_types as $fallback ) {
 3324         if ( ! empty( $atts[ $fallback ] ) ) {
 3325             if ( empty( $fileurl ) ) {
 3326                 $fileurl = $atts[ $fallback ];
 3327             }
 3328             if ( 'src' === $fallback && $is_youtube ) {
 3329                 $type = array( 'type' => 'video/youtube' );
 3330             } elseif ( 'src' === $fallback && $is_vimeo ) {
 3331                 $type = array( 'type' => 'video/vimeo' );
 3332             } else {
 3333                 $type = wp_check_filetype( $atts[ $fallback ], wp_get_mime_types() );
 3334             }
 3335             $url   = add_query_arg( '_', $instance, $atts[ $fallback ] );
 3336             $html .= sprintf( $source, $type['type'], esc_url( $url ) );
 3337         }
 3338     }
 3339 
 3340     if ( ! empty( $content ) ) {
 3341         if ( false !== strpos( $content, "\n" ) ) {
 3342             $content = str_replace( array( "\r\n", "\n", "\t" ), '', $content );
 3343         }
 3344         $html .= trim( $content );
 3345     }
 3346 
 3347     if ( 'mediaelement' === $library ) {
 3348         $html .= wp_mediaelement_fallback( $fileurl );
 3349     }
 3350     $html .= '</video>';
 3351 
 3352     $width_rule = '';
 3353     if ( ! empty( $atts['width'] ) ) {
 3354         $width_rule = sprintf( 'width: %dpx;', $atts['width'] );
 3355     }
 3356     $output = sprintf( '<div style="%s" class="wp-video">%s</div>', $width_rule, $html );
 3357 
 3358     /**
 3359      * Filters the output of the video shortcode.
 3360      *
 3361      * @since 3.6.0
 3362      *
 3363      * @param string $output  Video shortcode HTML output.
 3364      * @param array  $atts    Array of video shortcode attributes.
 3365      * @param string $video   Video file.
 3366      * @param int    $post_id Post ID.
 3367      * @param string $library Media library used for the video shortcode.
 3368      */
 3369     return apply_filters( 'wp_video_shortcode', $output, $atts, $video, $post_id, $library );
 3370 }
 3371 add_shortcode( 'video', 'wp_video_shortcode' );
 3372 
 3373 /**
 3374  * Displays previous image link that has the same post parent.
 3375  *
 3376  * @since 2.5.0
 3377  *
 3378  * @see adjacent_image_link()
 3379  *
 3380  * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array
 3381  *                           of width and height values in pixels (in that order). Default 'thumbnail'.
 3382  * @param string|false $text Optional. Link text. Default false.
 3383  */
 3384 function previous_image_link( $size = 'thumbnail', $text = false ) {
 3385     adjacent_image_link( true, $size, $text );
 3386 }
 3387 
 3388 /**
 3389  * Displays next image link that has the same post parent.
 3390  *
 3391  * @since 2.5.0
 3392  *
 3393  * @see adjacent_image_link()
 3394  *
 3395  * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array
 3396  *                           of width and height values in pixels (in that order). Default 'thumbnail'.
 3397  * @param string|false $text Optional. Link text. Default false.
 3398  */
 3399 function next_image_link( $size = 'thumbnail', $text = false ) {
 3400     adjacent_image_link( false, $size, $text );
 3401 }
 3402 
 3403 /**
 3404  * Displays next or previous image link that has the same post parent.
 3405  *
 3406  * Retrieves the current attachment object from the $post global.
 3407  *
 3408  * @since 2.5.0
 3409  *
 3410  * @param bool         $prev Optional. Whether to display the next (false) or previous (true) link. Default true.
 3411  * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array
 3412  *                           of width and height values in pixels (in that order). Default 'thumbnail'.
 3413  * @param bool         $text Optional. Link text. Default false.
 3414  */
 3415 function adjacent_image_link( $prev = true, $size = 'thumbnail', $text = false ) {
 3416     $post        = get_post();
 3417     $attachments = array_values(
 3418         get_children(
 3419             array(
 3420                 'post_parent'    => $post->post_parent,
 3421                 'post_status'    => 'inherit',
 3422                 'post_type'      => 'attachment',
 3423                 'post_mime_type' => 'image',
 3424                 'order'          => 'ASC',
 3425                 'orderby'        => 'menu_order ID',
 3426             )
 3427         )
 3428     );
 3429 
 3430     foreach ( $attachments as $k => $attachment ) {
 3431         if ( (int) $attachment->ID === (int) $post->ID ) {
 3432             break;
 3433         }
 3434     }
 3435 
 3436     $output        = '';
 3437     $attachment_id = 0;
 3438 
 3439     if ( $attachments ) {
 3440         $k = $prev ? $k - 1 : $k + 1;
 3441 
 3442         if ( isset( $attachments[ $k ] ) ) {
 3443             $attachment_id = $attachments[ $k ]->ID;
 3444             $attr          = array( 'alt' => get_the_title( $attachment_id ) );
 3445             $output        = wp_get_attachment_link( $attachment_id, $size, true, false, $text, $attr );
 3446         }
 3447     }
 3448 
 3449     $adjacent = $prev ? 'previous' : 'next';
 3450 
 3451     /**
 3452      * Filters the adjacent image link.
 3453      *
 3454      * The dynamic portion of the hook name, `$adjacent`, refers to the type of adjacency,
 3455      * either 'next', or 'previous'.
 3456      *
 3457      * @since 3.5.0
 3458      *
 3459      * @param string $output        Adjacent image HTML markup.
 3460      * @param int    $attachment_id Attachment ID
 3461      * @param string|int[] $size    Requested image size. Can be any registered image size name, or
 3462      *                              an array of width and height values in pixels (in that order).
 3463      * @param string $text          Link text.
 3464      */
 3465     echo apply_filters( "{$adjacent}_image_link", $output, $attachment_id, $size, $text );
 3466 }
 3467 
 3468 /**
 3469  * Retrieves taxonomies attached to given the attachment.
 3470  *
 3471  * @since 2.5.0
 3472  * @since 4.7.0 Introduced the `$output` parameter.
 3473  *
 3474  * @param int|array|object $attachment Attachment ID, data array, or data object.
 3475  * @param string           $output     Output type. 'names' to return an array of taxonomy names,
 3476  *                                     or 'objects' to return an array of taxonomy objects.
 3477  *                                     Default is 'names'.
 3478  * @return string[]|WP_Taxonomy[] List of taxonomies or taxonomy names. Empty array on failure.
 3479  */
 3480 function get_attachment_taxonomies( $attachment, $output = 'names' ) {
 3481     if ( is_int( $attachment ) ) {
 3482         $attachment = get_post( $attachment );
 3483     } elseif ( is_array( $attachment ) ) {
 3484         $attachment = (object) $attachment;
 3485     }
 3486 
 3487     if ( ! is_object( $attachment ) ) {
 3488         return array();
 3489     }
 3490 
 3491     $file     = get_attached_file( $attachment->ID );
 3492     $filename = wp_basename( $file );
 3493 
 3494     $objects = array( 'attachment' );
 3495 
 3496     if ( false !== strpos( $filename, '.' ) ) {
 3497         $objects[] = 'attachment:' . substr( $filename, strrpos( $filename, '.' ) + 1 );
 3498     }
 3499 
 3500     if ( ! empty( $attachment->post_mime_type ) ) {
 3501         $objects[] = 'attachment:' . $attachment->post_mime_type;
 3502 
 3503         if ( false !== strpos( $attachment->post_mime_type, '/' ) ) {
 3504             foreach ( explode( '/', $attachment->post_mime_type ) as $token ) {
 3505                 if ( ! empty( $token ) ) {
 3506                     $objects[] = "attachment:$token";
 3507                 }
 3508             }
 3509         }
 3510     }
 3511 
 3512     $taxonomies = array();
 3513 
 3514     foreach ( $objects as $object ) {
 3515         $taxes = get_object_taxonomies( $object, $output );
 3516 
 3517         if ( $taxes ) {
 3518             $taxonomies = array_merge( $taxonomies, $taxes );
 3519         }
 3520     }
 3521 
 3522     if ( 'names' === $output ) {
 3523         $taxonomies = array_unique( $taxonomies );
 3524     }
 3525 
 3526     return $taxonomies;
 3527 }
 3528 
 3529 /**
 3530  * Retrieves all of the taxonomies that are registered for attachments.
 3531  *
 3532  * Handles mime-type-specific taxonomies such as attachment:image and attachment:video.
 3533  *
 3534  * @since 3.5.0
 3535  *
 3536  * @see get_taxonomies()
 3537  *
 3538  * @param string $output Optional. The type of taxonomy output to return. Accepts 'names' or 'objects'.
 3539  *                       Default 'names'.
 3540  * @return string[]|WP_Taxonomy[] Array of names or objects of registered taxonomies for attachments.
 3541  */
 3542 function get_taxonomies_for_attachments( $output = 'names' ) {
 3543     $taxonomies = array();
 3544 
 3545     foreach ( get_taxonomies( array(), 'objects' ) as $taxonomy ) {
 3546         foreach ( $taxonomy->object_type as $object_type ) {
 3547             if ( 'attachment' === $object_type || 0 === strpos( $object_type, 'attachment:' ) ) {
 3548                 if ( 'names' === $output ) {
 3549                     $taxonomies[] = $taxonomy->name;
 3550                 } else {
 3551                     $taxonomies[ $taxonomy->name ] = $taxonomy;
 3552                 }
 3553                 break;
 3554             }
 3555         }
 3556     }
 3557 
 3558     return $taxonomies;
 3559 }
 3560 
 3561 /**
 3562  * Determines whether the value is an acceptable type for GD image functions.
 3563  *
 3564  * In PHP 8.0, the GD extension uses GdImage objects for its data structures.
 3565  * This function checks if the passed value is either a resource of type `gd`
 3566  * or a GdImage object instance. Any other type will return false.
 3567  *
 3568  * @since 5.6.0
 3569  *
 3570  * @param resource|GdImage|false $image A value to check the type for.
 3571  * @return bool True if $image is either a GD image resource or GdImage instance,
 3572  *              false otherwise.
 3573  */
 3574 function is_gd_image( $image ) {
 3575     if ( is_resource( $image ) && 'gd' === get_resource_type( $image )
 3576         || is_object( $image ) && $image instanceof GdImage
 3577     ) {
 3578         return true;
 3579     }
 3580 
 3581     return false;
 3582 }
 3583 
 3584 /**
 3585  * Create new GD image resource with transparency support
 3586  *
 3587  * @todo Deprecate if possible.
 3588  *
 3589  * @since 2.9.0
 3590  *
 3591  * @param int $width  Image width in pixels.
 3592  * @param int $height Image height in pixels.
 3593  * @return resource|GdImage|false The GD image resource or GdImage instance on success.
 3594  *                                False on failure.
 3595  */
 3596 function wp_imagecreatetruecolor( $width, $height ) {
 3597     $img = imagecreatetruecolor( $width, $height );
 3598 
 3599     if ( is_gd_image( $img )
 3600         && function_exists( 'imagealphablending' ) && function_exists( 'imagesavealpha' )
 3601     ) {
 3602         imagealphablending( $img, false );
 3603         imagesavealpha( $img, true );
 3604     }
 3605 
 3606     return $img;
 3607 }
 3608 
 3609 /**
 3610  * Based on a supplied width/height example, return the biggest possible dimensions based on the max width/height.
 3611  *
 3612  * @since 2.9.0
 3613  *
 3614  * @see wp_constrain_dimensions()
 3615  *
 3616  * @param int $example_width  The width of an example embed.
 3617  * @param int $example_height The height of an example embed.
 3618  * @param int $max_width      The maximum allowed width.
 3619  * @param int $max_height     The maximum allowed height.
 3620  * @return int[] {
 3621  *     An array of maximum width and height values.
 3622  *
 3623  *     @type int $0 The maximum width in pixels.
 3624  *     @type int $1 The maximum height in pixels.
 3625  * }
 3626  */
 3627 function wp_expand_dimensions( $example_width, $example_height, $max_width, $max_height ) {
 3628     $example_width  = (int) $example_width;
 3629     $example_height = (int) $example_height;
 3630     $max_width      = (int) $max_width;
 3631     $max_height     = (int) $max_height;
 3632 
 3633     return wp_constrain_dimensions( $example_width * 1000000, $example_height * 1000000, $max_width, $max_height );
 3634 }
 3635 
 3636 /**
 3637  * Determines the maximum upload size allowed in php.ini.
 3638  *
 3639  * @since 2.5.0
 3640  *
 3641  * @return int Allowed upload size.
 3642  */
 3643 function wp_max_upload_size() {
 3644     $u_bytes = wp_convert_hr_to_bytes( ini_get( 'upload_max_filesize' ) );
 3645     $p_bytes = wp_convert_hr_to_bytes( ini_get( 'post_max_size' ) );
 3646 
 3647     /**
 3648      * Filters the maximum upload size allowed in php.ini.
 3649      *
 3650      * @since 2.5.0
 3651      *
 3652      * @param int $size    Max upload size limit in bytes.
 3653      * @param int $u_bytes Maximum upload filesize in bytes.
 3654      * @param int $p_bytes Maximum size of POST data in bytes.
 3655      */
 3656     return apply_filters( 'upload_size_limit', min( $u_bytes, $p_bytes ), $u_bytes, $p_bytes );
 3657 }
 3658 
 3659 /**
 3660  * Returns a WP_Image_Editor instance and loads file into it.
 3661  *
 3662  * @since 3.5.0
 3663  *
 3664  * @param string $path Path to the file to load.
 3665  * @param array  $args Optional. Additional arguments for retrieving the image editor.
 3666  *                     Default empty array.
 3667  * @return WP_Image_Editor|WP_Error The WP_Image_Editor object on success,
 3668  *                                  a WP_Error object otherwise.
 3669  */
 3670 function wp_get_image_editor( $path, $args = array() ) {
 3671     $args['path'] = $path;
 3672 
 3673     if ( ! isset( $args['mime_type'] ) ) {
 3674         $file_info = wp_check_filetype( $args['path'] );
 3675 
 3676         // If $file_info['type'] is false, then we let the editor attempt to
 3677         // figure out the file type, rather than forcing a failure based on extension.
 3678         if ( isset( $file_info ) && $file_info['type'] ) {
 3679             $args['mime_type'] = $file_info['type'];
 3680         }
 3681     }
 3682 
 3683     $implementation = _wp_image_editor_choose( $args );
 3684 
 3685     if ( $implementation ) {
 3686         $editor = new $implementation( $path );
 3687         $loaded = $editor->load();
 3688 
 3689         if ( is_wp_error( $loaded ) ) {
 3690             return $loaded;
 3691         }
 3692 
 3693         return $editor;
 3694     }
 3695 
 3696     return new WP_Error( 'image_no_editor', __( 'No editor could be selected.' ) );
 3697 }
 3698 
 3699 /**
 3700  * Tests whether there is an editor that supports a given mime type or methods.
 3701  *
 3702  * @since 3.5.0
 3703  *
 3704  * @param string|array $args Optional. Array of arguments to retrieve the image editor supports.
 3705  *                           Default empty array.
 3706  * @return bool True if an eligible editor is found; false otherwise.
 3707  */
 3708 function wp_image_editor_supports( $args = array() ) {
 3709     return (bool) _wp_image_editor_choose( $args );
 3710 }
 3711 
 3712 /**
 3713  * Tests which editors are capable of supporting the request.
 3714  *
 3715  * @ignore
 3716  * @since 3.5.0
 3717  *
 3718  * @param array $args Optional. Array of arguments for choosing a capable editor. Default empty array.
 3719  * @return string|false Class name for the first editor that claims to support the request.
 3720  *                      False if no editor claims to support the request.
 3721  */
 3722 function _wp_image_editor_choose( $args = array() ) {
 3723     require_once ABSPATH . WPINC . '/class-wp-image-editor.php';
 3724     require_once ABSPATH . WPINC . '/class-wp-image-editor-gd.php';
 3725     require_once ABSPATH . WPINC . '/class-wp-image-editor-imagick.php';
 3726     /**
 3727      * Filters the list of image editing library classes.
 3728      *
 3729      * @since 3.5.0
 3730      *
 3731      * @param string[] $image_editors Array of available image editor class names. Defaults are
 3732      *                                'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD'.
 3733      */
 3734     $implementations = apply_filters( 'wp_image_editors', array( 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD' ) );
 3735 
 3736     foreach ( $implementations as $implementation ) {
 3737         if ( ! call_user_func( array( $implementation, 'test' ), $args ) ) {
 3738             continue;
 3739         }
 3740 
 3741         if ( isset( $args['mime_type'] ) &&
 3742             ! call_user_func(
 3743                 array( $implementation, 'supports_mime_type' ),
 3744                 $args['mime_type']
 3745             ) ) {
 3746             continue;
 3747         }
 3748 
 3749         if ( isset( $args['methods'] ) &&
 3750             array_diff( $args['methods'], get_class_methods( $implementation ) ) ) {
 3751 
 3752             continue;
 3753         }
 3754 
 3755         return $implementation;
 3756     }
 3757 
 3758     return false;
 3759 }
 3760 
 3761 /**
 3762  * Prints default Plupload arguments.
 3763  *
 3764  * @since 3.4.0
 3765  */
 3766 function wp_plupload_default_settings() {
 3767     $wp_scripts = wp_scripts();
 3768 
 3769     $data = $wp_scripts->get_data( 'wp-plupload', 'data' );
 3770     if ( $data && false !== strpos( $data, '_wpPluploadSettings' ) ) {
 3771         return;
 3772     }
 3773 
 3774     $max_upload_size    = wp_max_upload_size();
 3775     $allowed_extensions = array_keys( get_allowed_mime_types() );
 3776     $extensions         = array();
 3777     foreach ( $allowed_extensions as $extension ) {
 3778         $extensions = array_merge( $extensions, explode( '|', $extension ) );
 3779     }
 3780 
 3781     /*
 3782      * Since 4.9 the `runtimes` setting is hardcoded in our version of Plupload to `html5,html4`,
 3783      * and the `flash_swf_url` and `silverlight_xap_url` are not used.
 3784      */
 3785     $defaults = array(
 3786         'file_data_name' => 'async-upload', // Key passed to $_FILE.
 3787         'url'            => admin_url( 'async-upload.php', 'relative' ),
 3788         'filters'        => array(
 3789             'max_file_size' => $max_upload_size . 'b',
 3790             'mime_types'    => array( array( 'extensions' => implode( ',', $extensions ) ) ),
 3791         ),
 3792     );
 3793 
 3794     /*
 3795      * Currently only iOS Safari supports multiple files uploading,
 3796      * but iOS 7.x has a bug that prevents uploading of videos when enabled.
 3797      * See #29602.
 3798      */
 3799     if ( wp_is_mobile() && strpos( $_SERVER['HTTP_USER_AGENT'], 'OS 7_' ) !== false &&
 3800         strpos( $_SERVER['HTTP_USER_AGENT'], 'like Mac OS X' ) !== false ) {
 3801 
 3802         $defaults['multi_selection'] = false;
 3803     }
 3804 
 3805     /**
 3806      * Filters the Plupload default settings.
 3807      *
 3808      * @since 3.4.0
 3809      *
 3810      * @param array $defaults Default Plupload settings array.
 3811      */
 3812     $defaults = apply_filters( 'plupload_default_settings', $defaults );
 3813 
 3814     $params = array(
 3815         'action' => 'upload-attachment',
 3816     );
 3817 
 3818     /**
 3819      * Filters the Plupload default parameters.
 3820      *
 3821      * @since 3.4.0
 3822      *
 3823      * @param array $params Default Plupload parameters array.
 3824      */
 3825     $params = apply_filters( 'plupload_default_params', $params );
 3826 
 3827     $params['_wpnonce'] = wp_create_nonce( 'media-form' );
 3828 
 3829     $defaults['multipart_params'] = $params;
 3830 
 3831     $settings = array(
 3832         'defaults'      => $defaults,
 3833         'browser'       => array(
 3834             'mobile'    => wp_is_mobile(),
 3835             'supported' => _device_can_upload(),
 3836         ),
 3837         'limitExceeded' => is_multisite() && ! is_upload_space_available(),
 3838     );
 3839 
 3840     $script = 'var _wpPluploadSettings = ' . wp_json_encode( $settings ) . ';';
 3841 
 3842     if ( $data ) {
 3843         $script = "$data\n$script";
 3844     }
 3845 
 3846     $wp_scripts->add_data( 'wp-plupload', 'data', $script );
 3847 }
 3848 
 3849 /**
 3850  * Prepares an attachment post object for JS, where it is expected
 3851  * to be JSON-encoded and fit into an Attachment model.
 3852  *
 3853  * @since 3.5.0
 3854  *
 3855  * @param int|WP_Post $attachment Attachment ID or object.
 3856  * @return array|void {
 3857  *     Array of attachment details, or void if the parameter does not correspond to an attachment.
 3858  *
 3859  *     @type string $alt                   Alt text of the attachment.
 3860  *     @type string $author                ID of the attachment author, as a string.
 3861  *     @type string $authorName            Name of the attachment author.
 3862  *     @type string $caption               Caption for the attachment.
 3863  *     @type array  $compat                Containing item and meta.
 3864  *     @type string $context               Context, whether it's used as the site icon for example.
 3865  *     @type int    $date                  Uploaded date, timestamp in milliseconds.
 3866  *     @type string $dateFormatted         Formatted date (e.g. June 29, 2018).
 3867  *     @type string $description           Description of the attachment.
 3868  *     @type string $editLink              URL to the edit page for the attachment.
 3869  *     @type string $filename              File name of the attachment.
 3870  *     @type string $filesizeHumanReadable Filesize of the attachment in human readable format (e.g. 1 MB).
 3871  *     @type int    $filesizeInBytes       Filesize of the attachment in bytes.
 3872  *     @type int    $height                If the attachment is an image, represents the height of the image in pixels.
 3873  *     @type string $icon                  Icon URL of the attachment (e.g. /wp-includes/images/media/archive.png).
 3874  *     @type int    $id                    ID of the attachment.
 3875  *     @type string $link                  URL to the attachment.
 3876  *     @type int    $menuOrder             Menu order of the attachment post.
 3877  *     @type array  $meta                  Meta data for the attachment.
 3878  *     @type string $mime                  Mime type of the attachment (e.g. image/jpeg or application/zip).
 3879  *     @type int    $modified              Last modified, timestamp in milliseconds.
 3880  *     @type string $name                  Name, same as title of the attachment.
 3881  *     @type array  $nonces                Nonces for update, delete and edit.
 3882  *     @type string $orientation           If the attachment is an image, represents the image orientation
 3883  *                                         (landscape or portrait).
 3884  *     @type array  $sizes                 If the attachment is an image, contains an array of arrays
 3885  *                                         for the images sizes: thumbnail, medium, large, and full.
 3886  *     @type string $status                Post status of the attachment (usually 'inherit').
 3887  *     @type string $subtype               Mime subtype of the attachment (usually the last part, e.g. jpeg or zip).
 3888  *     @type string $title                 Title of the attachment (usually slugified file name without the extension).
 3889  *     @type string $type                  Type of the attachment (usually first part of the mime type, e.g. image).
 3890  *     @type int    $uploadedTo            Parent post to which the attachment was uploaded.
 3891  *     @type string $uploadedToLink        URL to the edit page of the parent post of the attachment.
 3892  *     @type string $uploadedToTitle       Post title of the parent of the attachment.
 3893  *     @type string $url                   Direct URL to the attachment file (from wp-content).
 3894  *     @type int    $width                 If the attachment is an image, represents the width of the image in pixels.
 3895  * }
 3896  *
 3897  */
 3898 function wp_prepare_attachment_for_js( $attachment ) {
 3899     $attachment = get_post( $attachment );
 3900 
 3901     if ( ! $attachment ) {
 3902         return;
 3903     }
 3904 
 3905     if ( 'attachment' !== $attachment->post_type ) {
 3906         return;
 3907     }
 3908 
 3909     $meta = wp_get_attachment_metadata( $attachment->ID );
 3910     if ( false !== strpos( $attachment->post_mime_type, '/' ) ) {
 3911         list( $type, $subtype ) = explode( '/', $attachment->post_mime_type );
 3912     } else {
 3913         list( $type, $subtype ) = array( $attachment->post_mime_type, '' );
 3914     }
 3915 
 3916     $attachment_url = wp_get_attachment_url( $attachment->ID );
 3917     $base_url       = str_replace( wp_basename( $attachment_url ), '', $attachment_url );
 3918 
 3919     $response = array(
 3920         'id'            => $attachment->ID,
 3921         'title'         => $attachment->post_title,
 3922         'filename'      => wp_basename( get_attached_file( $attachment->ID ) ),
 3923         'url'           => $attachment_url,
 3924         'link'          => get_attachment_link( $attachment->ID ),
 3925         'alt'           => get_post_meta( $attachment->ID, '_wp_attachment_image_alt', true ),
 3926         'author'        => $attachment->post_author,
 3927         'description'   => $attachment->post_content,
 3928         'caption'       => $attachment->post_excerpt,
 3929         'name'          => $attachment->post_name,
 3930         'status'        => $attachment->post_status,
 3931         'uploadedTo'    => $attachment->post_parent,
 3932         'date'          => strtotime( $attachment->post_date_gmt ) * 1000,
 3933         'modified'      => strtotime( $attachment->post_modified_gmt ) * 1000,
 3934         'menuOrder'     => $attachment->menu_order,
 3935         'mime'          => $attachment->post_mime_type,
 3936         'type'          => $type,
 3937         'subtype'       => $subtype,
 3938         'icon'          => wp_mime_type_icon( $attachment->ID ),
 3939         'dateFormatted' => mysql2date( __( 'F j, Y' ), $attachment->post_date ),
 3940         'nonces'        => array(
 3941             'update' => false,
 3942             'delete' => false,
 3943             'edit'   => false,
 3944         ),
 3945         'editLink'      => false,
 3946         'meta'          => false,
 3947     );
 3948 
 3949     $author = new WP_User( $attachment->post_author );
 3950 
 3951     if ( $author->exists() ) {
 3952         $author_name            = $author->display_name ? $author->display_name : $author->nickname;
 3953         $response['authorName'] = html_entity_decode( $author_name, ENT_QUOTES, get_bloginfo( 'charset' ) );
 3954         $response['authorLink'] = get_edit_user_link( $author->ID );
 3955     } else {
 3956         $response['authorName'] = __( '(no author)' );
 3957     }
 3958 
 3959     if ( $attachment->post_parent ) {
 3960         $post_parent = get_post( $attachment->post_parent );
 3961         if ( $post_parent ) {
 3962             $response['uploadedToTitle'] = $post_parent->post_title ? $post_parent->post_title : __( '(no title)' );
 3963             $response['uploadedToLink']  = get_edit_post_link( $attachment->post_parent, 'raw' );
 3964         }
 3965     }
 3966 
 3967     $attached_file = get_attached_file( $attachment->ID );
 3968 
 3969     if ( isset( $meta['filesize'] ) ) {
 3970         $bytes = $meta['filesize'];
 3971     } elseif ( file_exists( $attached_file ) ) {
 3972         $bytes = filesize( $attached_file );
 3973     } else {
 3974         $bytes = '';
 3975     }
 3976 
 3977     if ( $bytes ) {
 3978         $response['filesizeInBytes']       = $bytes;
 3979         $response['filesizeHumanReadable'] = size_format( $bytes );
 3980     }
 3981 
 3982     $context             = get_post_meta( $attachment->ID, '_wp_attachment_context', true );
 3983     $response['context'] = ( $context ) ? $context : '';
 3984 
 3985     if ( current_user_can( 'edit_post', $attachment->ID ) ) {
 3986         $response['nonces']['update'] = wp_create_nonce( 'update-post_' . $attachment->ID );
 3987         $response['nonces']['edit']   = wp_create_nonce( 'image_editor-' . $attachment->ID );
 3988         $response['editLink']         = get_edit_post_link( $attachment->ID, 'raw' );
 3989     }
 3990 
 3991     if ( current_user_can( 'delete_post', $attachment->ID ) ) {
 3992         $response['nonces']['delete'] = wp_create_nonce( 'delete-post_' . $attachment->ID );
 3993     }
 3994 
 3995     if ( $meta && ( 'image' === $type || ! empty( $meta['sizes'] ) ) ) {
 3996         $sizes = array();
 3997 
 3998         /** This filter is documented in wp-admin/includes/media.php */
 3999         $possible_sizes = apply_filters(
 4000             'image_size_names_choose',
 4001             array(
 4002                 'thumbnail' => __( 'Thumbnail' ),
 4003                 'medium'    => __( 'Medium' ),
 4004                 'large'     => __( 'Large' ),
 4005                 'full'      => __( 'Full Size' ),
 4006             )
 4007         );
 4008         unset( $possible_sizes['full'] );
 4009 
 4010         /*
 4011          * Loop through all potential sizes that may be chosen. Try to do this with some efficiency.
 4012          * First: run the image_downsize filter. If it returns something, we can use its data.
 4013          * If the filter does not return something, then image_downsize() is just an expensive way
 4014          * to check the image metadata, which we do second.
 4015          */
 4016         foreach ( $possible_sizes as $size => $label ) {
 4017 
 4018             /** This filter is documented in wp-includes/media.php */
 4019             $downsize = apply_filters( 'image_downsize', false, $attachment->ID, $size );
 4020 
 4021             if ( $downsize ) {
 4022                 if ( empty( $downsize[3] ) ) {
 4023                     continue;
 4024                 }
 4025 
 4026                 $sizes[ $size ] = array(
 4027                     'height'      => $downsize[2],
 4028                     'width'       => $downsize[1],
 4029                     'url'         => $downsize[0],
 4030                     'orientation' => $downsize[2] > $downsize[1] ? 'portrait' : 'landscape',
 4031                 );
 4032             } elseif ( isset( $meta['sizes'][ $size ] ) ) {
 4033                 // Nothing from the filter, so consult image metadata if we have it.
 4034                 $size_meta = $meta['sizes'][ $size ];
 4035 
 4036                 // We have the actual image size, but might need to further constrain it if content_width is narrower.
 4037                 // Thumbnail, medium, and full sizes are also checked against the site's height/width options.
 4038                 list( $width, $height ) = image_constrain_size_for_editor( $size_meta['width'], $size_meta['height'], $size, 'edit' );
 4039 
 4040                 $sizes[ $size ] = array(
 4041                     'height'      => $height,
 4042                     'width'       => $width,
 4043                     'url'         => $base_url . $size_meta['file'],
 4044                     'orientation' => $height > $width ? 'portrait' : 'landscape',
 4045                 );
 4046             }
 4047         }
 4048 
 4049         if ( 'image' === $type ) {
 4050             if ( ! empty( $meta['original_image'] ) ) {
 4051                 $response['originalImageURL']  = wp_get_original_image_url( $attachment->ID );
 4052                 $response['originalImageName'] = wp_basename( wp_get_original_image_path( $attachment->ID ) );
 4053             }
 4054 
 4055             $sizes['full'] = array( 'url' => $attachment_url );
 4056 
 4057             if ( isset( $meta['height'], $meta['width'] ) ) {
 4058                 $sizes['full']['height']      = $meta['height'];
 4059                 $sizes['full']['width']       = $meta['width'];
 4060                 $sizes['full']['orientation'] = $meta['height'] > $meta['width'] ? 'portrait' : 'landscape';
 4061             }
 4062 
 4063             $response = array_merge( $response, $sizes['full'] );
 4064         } elseif ( $meta['sizes']['full']['file'] ) {
 4065             $sizes['full'] = array(
 4066                 'url'         => $base_url . $meta['sizes']['full']['file'],
 4067                 'height'      => $meta['sizes']['full']['height'],
 4068                 'width'       => $meta['sizes']['full']['width'],
 4069                 'orientation' => $meta['sizes']['full']['height'] > $meta['sizes']['full']['width'] ? 'portrait' : 'landscape',
 4070             );
 4071         }
 4072 
 4073         $response = array_merge( $response, array( 'sizes' => $sizes ) );
 4074     }
 4075 
 4076     if ( $meta && 'video' === $type ) {
 4077         if ( isset( $meta['width'] ) ) {
 4078             $response['width'] = (int) $meta['width'];
 4079         }
 4080         if ( isset( $meta['height'] ) ) {
 4081             $response['height'] = (int) $meta['height'];
 4082         }
 4083     }
 4084 
 4085     if ( $meta && ( 'audio' === $type || 'video' === $type ) ) {
 4086         if ( isset( $meta['length_formatted'] ) ) {
 4087             $response['fileLength']              = $meta['length_formatted'];
 4088             $response['fileLengthHumanReadable'] = human_readable_duration( $meta['length_formatted'] );
 4089         }
 4090 
 4091         $response['meta'] = array();
 4092         foreach ( wp_get_attachment_id3_keys( $attachment, 'js' ) as $key => $label ) {
 4093             $response['meta'][ $key ] = false;
 4094 
 4095             if ( ! empty( $meta[ $key ] ) ) {
 4096                 $response['meta'][ $key ] = $meta[ $key ];
 4097             }
 4098         }
 4099 
 4100         $id = get_post_thumbnail_id( $attachment->ID );
 4101         if ( ! empty( $id ) ) {
 4102             list( $src, $width, $height ) = wp_get_attachment_image_src( $id, 'full' );
 4103             $response['image']            = compact( 'src', 'width', 'height' );
 4104             list( $src, $width, $height ) = wp_get_attachment_image_src( $id, 'thumbnail' );
 4105             $response['thumb']            = compact( 'src', 'width', 'height' );
 4106         } else {
 4107             $src               = wp_mime_type_icon( $attachment->ID );
 4108             $width             = 48;
 4109             $height            = 64;
 4110             $response['image'] = compact( 'src', 'width', 'height' );
 4111             $response['thumb'] = compact( 'src', 'width', 'height' );
 4112         }
 4113     }
 4114 
 4115     if ( function_exists( 'get_compat_media_markup' ) ) {
 4116         $response['compat'] = get_compat_media_markup( $attachment->ID, array( 'in_modal' => true ) );
 4117     }
 4118 
 4119     if ( function_exists( 'get_media_states' ) ) {
 4120         $media_states = get_media_states( $attachment );
 4121         if ( ! empty( $media_states ) ) {
 4122             $response['mediaStates'] = implode( ', ', $media_states );
 4123         }
 4124     }
 4125 
 4126     /**
 4127      * Filters the attachment data prepared for JavaScript.
 4128      *
 4129      * @since 3.5.0
 4130      *
 4131      * @param array       $response   Array of prepared attachment data. @see wp_prepare_attachment_for_js().
 4132      * @param WP_Post     $attachment Attachment object.
 4133      * @param array|false $meta       Array of attachment meta data, or false if there is none.
 4134      */
 4135     return apply_filters( 'wp_prepare_attachment_for_js', $response, $attachment, $meta );
 4136 }
 4137 
 4138 /**
 4139  * Enqueues all scripts, styles, settings, and templates necessary to use
 4140  * all media JS APIs.
 4141  *
 4142  * @since 3.5.0
 4143  *
 4144  * @global int       $content_width
 4145  * @global wpdb      $wpdb          WordPress database abstraction object.
 4146  * @global WP_Locale $wp_locale     WordPress date and time locale object.
 4147  *
 4148  * @param array $args {
 4149  *     Arguments for enqueuing media scripts.
 4150  *
 4151  *     @type int|WP_Post A post object or ID.
 4152  * }
 4153  */
 4154 function wp_enqueue_media( $args = array() ) {
 4155     // Enqueue me just once per page, please.
 4156     if ( did_action( 'wp_enqueue_media' ) ) {
 4157         return;
 4158     }
 4159 
 4160     global $content_width, $wpdb, $wp_locale;
 4161 
 4162     $defaults = array(
 4163         'post' => null,
 4164     );
 4165     $args     = wp_parse_args( $args, $defaults );
 4166 
 4167     // We're going to pass the old thickbox media tabs to `media_upload_tabs`
 4168     // to ensure plugins will work. We will then unset those tabs.
 4169     $tabs = array(
 4170         // handler action suffix => tab label
 4171         'type'     => '',
 4172         'type_url' => '',
 4173         'gallery'  => '',
 4174         'library'  => '',
 4175     );
 4176 
 4177     /** This filter is documented in wp-admin/includes/media.php */
 4178     $tabs = apply_filters( 'media_upload_tabs', $tabs );
 4179     unset( $tabs['type'], $tabs['type_url'], $tabs['gallery'], $tabs['library'] );
 4180 
 4181     $props = array(
 4182         'link'  => get_option( 'image_default_link_type' ), // DB default is 'file'.
 4183         'align' => get_option( 'image_default_align' ),     // Empty default.
 4184         'size'  => get_option( 'image_default_size' ),      // Empty default.
 4185     );
 4186 
 4187     $exts      = array_merge( wp_get_audio_extensions(), wp_get_video_extensions() );
 4188     $mimes     = get_allowed_mime_types();
 4189     $ext_mimes = array();
 4190     foreach ( $exts as $ext ) {
 4191         foreach ( $mimes as $ext_preg => $mime_match ) {
 4192             if ( preg_match( '#' . $ext . '#i', $ext_preg ) ) {
 4193                 $ext_mimes[ $ext ] = $mime_match;
 4194                 break;
 4195             }
 4196         }
 4197     }
 4198 
 4199     /**
 4200      * Allows showing or hiding the "Create Audio Playlist" button in the media library.
 4201      *
 4202      * By default, the "Create Audio Playlist" button will always be shown in
 4203      * the media library.  If this filter returns `null`, a query will be run
 4204      * to determine whether the media library contains any audio items.  This
 4205      * was the default behavior prior to version 4.8.0, but this query is
 4206      * expensive for large media libraries.
 4207      *
 4208      * @since 4.7.4
 4209      * @since 4.8.0 The filter's default value is `true` rather than `null`.
 4210      *
 4211      * @link https://core.trac.wordpress.org/ticket/31071
 4212      *
 4213      * @param bool|null $show Whether to show the button, or `null` to decide based
 4214      *                        on whether any audio files exist in the media library.
 4215      */
 4216     $show_audio_playlist = apply_filters( 'media_library_show_audio_playlist', true );
 4217     if ( null === $show_audio_playlist ) {
 4218         $show_audio_playlist = $wpdb->get_var(
 4219             "
 4220             SELECT ID
 4221             FROM $wpdb->posts
 4222             WHERE post_type = 'attachment'
 4223             AND post_mime_type LIKE 'audio%'
 4224             LIMIT 1
 4225         "
 4226         );
 4227     }
 4228 
 4229     /**
 4230      * Allows showing or hiding the "Create Video Playlist" button in the media library.
 4231      *
 4232      * By default, the "Create Video Playlist" button will always be shown in
 4233      * the media library.  If this filter returns `null`, a query will be run
 4234      * to determine whether the media library contains any video items.  This
 4235      * was the default behavior prior to version 4.8.0, but this query is
 4236      * expensive for large media libraries.
 4237      *
 4238      * @since 4.7.4
 4239      * @since 4.8.0 The filter's default value is `true` rather than `null`.
 4240      *
 4241      * @link https://core.trac.wordpress.org/ticket/31071
 4242      *
 4243      * @param bool|null $show Whether to show the button, or `null` to decide based
 4244      *                        on whether any video files exist in the media library.
 4245      */
 4246     $show_video_playlist = apply_filters( 'media_library_show_video_playlist', true );
 4247     if ( null === $show_video_playlist ) {
 4248         $show_video_playlist = $wpdb->get_var(
 4249             "
 4250             SELECT ID
 4251             FROM $wpdb->posts
 4252             WHERE post_type = 'attachment'
 4253             AND post_mime_type LIKE 'video%'
 4254             LIMIT 1
 4255         "
 4256         );
 4257     }
 4258 
 4259     /**
 4260      * Allows overriding the list of months displayed in the media library.
 4261      *
 4262      * By default (if this filter does not return an array), a query will be
 4263      * run to determine the months that have media items.  This query can be
 4264      * expensive for large media libraries, so it may be desirable for sites to
 4265      * override this behavior.
 4266      *
 4267      * @since 4.7.4
 4268      *
 4269      * @link https://core.trac.wordpress.org/ticket/31071
 4270      *
 4271      * @param array|null $months An array of objects with `month` and `year`
 4272      *                           properties, or `null` (or any other non-array value)
 4273      *                           for default behavior.
 4274      */
 4275     $months = apply_filters( 'media_library_months_with_files', null );
 4276     if ( ! is_array( $months ) ) {
 4277         $months = $wpdb->get_results(
 4278             $wpdb->prepare(
 4279                 "
 4280             SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month
 4281             FROM $wpdb->posts
 4282             WHERE post_type = %s
 4283             ORDER BY post_date DESC
 4284         ",
 4285                 'attachment'
 4286             )
 4287         );
 4288     }
 4289     foreach ( $months as $month_year ) {
 4290         $month_year->text = sprintf(
 4291             /* translators: 1: Month, 2: Year. */
 4292             __( '%1$s %2$d' ),
 4293             $wp_locale->get_month( $month_year->month ),
 4294             $month_year->year
 4295         );
 4296     }
 4297 
 4298     $settings = array(
 4299         'tabs'             => $tabs,
 4300         'tabUrl'           => add_query_arg( array( 'chromeless' => true ), admin_url( 'media-upload.php' ) ),
 4301         'mimeTypes'        => wp_list_pluck( get_post_mime_types(), 0 ),
 4302         /** This filter is documented in wp-admin/includes/media.php */
 4303         'captions'         => ! apply_filters( 'disable_captions', '' ),
 4304         'nonce'            => array(
 4305             'sendToEditor' => wp_create_nonce( 'media-send-to-editor' ),
 4306         ),
 4307         'post'             => array(
 4308             'id' => 0,
 4309         ),
 4310         'defaultProps'     => $props,
 4311         'attachmentCounts' => array(
 4312             'audio' => ( $show_audio_playlist ) ? 1 : 0,
 4313             'video' => ( $show_video_playlist ) ? 1 : 0,
 4314         ),
 4315         'oEmbedProxyUrl'   => rest_url( 'oembed/1.0/proxy' ),
 4316         'embedExts'        => $exts,
 4317         'embedMimes'       => $ext_mimes,
 4318         'contentWidth'     => $content_width,
 4319         'months'           => $months,
 4320         'mediaTrash'       => MEDIA_TRASH ? 1 : 0,
 4321     );
 4322 
 4323     $post = null;
 4324     if ( isset( $args['post'] ) ) {
 4325         $post             = get_post( $args['post'] );
 4326         $settings['post'] = array(
 4327             'id'    => $post->ID,
 4328             'nonce' => wp_create_nonce( 'update-post_' . $post->ID ),
 4329         );
 4330 
 4331         $thumbnail_support = current_theme_supports( 'post-thumbnails', $post->post_type ) && post_type_supports( $post->post_type, 'thumbnail' );
 4332         if ( ! $thumbnail_support && 'attachment' === $post->post_type && $post->post_mime_type ) {
 4333             if ( wp_attachment_is( 'audio', $post ) ) {
 4334                 $thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' );
 4335             } elseif ( wp_attachment_is( 'video', $post ) ) {
 4336                 $thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' );
 4337             }
 4338         }
 4339 
 4340         if ( $thumbnail_support ) {
 4341             $featured_image_id                   = get_post_meta( $post->ID, '_thumbnail_id', true );
 4342             $settings['post']['featuredImageId'] = $featured_image_id ? $featured_image_id : -1;
 4343         }
 4344     }
 4345 
 4346     if ( $post ) {
 4347         $post_type_object = get_post_type_object( $post->post_type );
 4348     } else {
 4349         $post_type_object = get_post_type_object( 'post' );
 4350     }
 4351 
 4352     $strings = array(
 4353         // Generic.
 4354         'mediaFrameDefaultTitle'      => __( 'Media' ),
 4355         'url'                         => __( 'URL' ),
 4356         'addMedia'                    => __( 'Add media' ),
 4357         'search'                      => __( 'Search' ),
 4358         'select'                      => __( 'Select' ),
 4359         'cancel'                      => __( 'Cancel' ),
 4360         'update'                      => __( 'Update' ),
 4361         'replace'                     => __( 'Replace' ),
 4362         'remove'                      => __( 'Remove' ),
 4363         'back'                        => __( 'Back' ),
 4364         /*
 4365          * translators: This is a would-be plural string used in the media manager.
 4366          * If there is not a word you can use in your language to avoid issues with the
 4367          * lack of plural support here, turn it into "selected: %d" then translate it.
 4368          */
 4369         'selected'                    => __( '%d selected' ),
 4370         'dragInfo'                    => __( 'Drag and drop to reorder media files.' ),
 4371 
 4372         // Upload.
 4373         'uploadFilesTitle'            => __( 'Upload files' ),
 4374         'uploadImagesTitle'           => __( 'Upload images' ),
 4375 
 4376         // Library.
 4377         'mediaLibraryTitle'           => __( 'Media Library' ),
 4378         'insertMediaTitle'            => __( 'Add media' ),
 4379         'createNewGallery'            => __( 'Create a new gallery' ),
 4380         'createNewPlaylist'           => __( 'Create a new playlist' ),
 4381         'createNewVideoPlaylist'      => __( 'Create a new video playlist' ),
 4382         'returnToLibrary'             => __( '&#8592; Go to library' ),
 4383         'allMediaItems'               => __( 'All media items' ),
 4384         'allDates'                    => __( 'All dates' ),
 4385         'noItemsFound'                => __( 'No items found.' ),
 4386         'insertIntoPost'              => $post_type_object->labels->insert_into_item,
 4387         'unattached'                  => __( 'Unattached' ),
 4388         'mine'                        => _x( 'Mine', 'media items' ),
 4389         'trash'                       => _x( 'Trash', 'noun' ),
 4390         'uploadedToThisPost'          => $post_type_object->labels->uploaded_to_this_item,
 4391         'warnDelete'                  => __( "You are about to permanently delete this item from your site.\nThis action cannot be undone.\n 'Cancel' to stop, 'OK' to delete." ),
 4392         'warnBulkDelete'              => __( "You are about to permanently delete these items from your site.\nThis action cannot be undone.\n 'Cancel' to stop, 'OK' to delete." ),
 4393         'warnBulkTrash'               => __( "You are about to trash these items.\n  'Cancel' to stop, 'OK' to delete." ),
 4394         'bulkSelect'                  => __( 'Bulk select' ),
 4395         'trashSelected'               => __( 'Move to Trash' ),
 4396         'restoreSelected'             => __( 'Restore from Trash' ),
 4397         'deletePermanently'           => __( 'Delete permanently' ),
 4398         'apply'                       => __( 'Apply' ),
 4399         'filterByDate'                => __( 'Filter by date' ),
 4400         'filterByType'                => __( 'Filter by type' ),
 4401         'searchLabel'                 => __( 'Search' ),
 4402         'searchMediaLabel'            => __( 'Search media' ),          // Backward compatibility pre-5.3.
 4403         'searchMediaPlaceholder'      => __( 'Search media items...' ), // Placeholder (no ellipsis), backward compatibility pre-5.3.
 4404         'mediaFound'                  => __( 'Number of media items found: %d' ),
 4405         'mediaFoundHasMoreResults'    => __( 'Number of media items displayed: %d. Scroll the page for more results.' ),
 4406         'noMedia'                     => __( 'No media items found.' ),
 4407         'noMediaTryNewSearch'         => __( 'No media items found. Try a different search.' ),
 4408 
 4409         // Library Details.
 4410         'attachmentDetails'           => __( 'Attachment details' ),
 4411 
 4412         // From URL.
 4413         'insertFromUrlTitle'          => __( 'Insert from URL' ),
 4414 
 4415         // Featured Images.
 4416         'setFeaturedImageTitle'       => $post_type_object->labels->featured_image,
 4417         'setFeaturedImage'            => $post_type_object->labels->set_featured_image,
 4418 
 4419         // Gallery.
 4420         'createGalleryTitle'          => __( 'Create gallery' ),
 4421         'editGalleryTitle'            => __( 'Edit gallery' ),
 4422         'cancelGalleryTitle'          => __( '&#8592; Cancel gallery' ),
 4423         'insertGallery'               => __( 'Insert gallery' ),
 4424         'updateGallery'               => __( 'Update gallery' ),
 4425         'addToGallery'                => __( 'Add to gallery' ),
 4426         'addToGalleryTitle'           => __( 'Add to gallery' ),
 4427         'reverseOrder'                => __( 'Reverse order' ),
 4428 
 4429         // Edit Image.
 4430         'imageDetailsTitle'           => __( 'Image details' ),
 4431         'imageReplaceTitle'           => __( 'Replace image' ),
 4432         'imageDetailsCancel'          => __( 'Cancel edit' ),
 4433         'editImage'                   => __( 'Edit image' ),
 4434 
 4435         // Crop Image.
 4436         'chooseImage'                 => __( 'Choose image' ),
 4437         'selectAndCrop'               => __( 'Select and crop' ),
 4438         'skipCropping'                => __( 'Skip cropping' ),
 4439         'cropImage'                   => __( 'Crop image' ),
 4440         'cropYourImage'               => __( 'Crop your image' ),
 4441         'cropping'                    => __( 'Cropping&hellip;' ),
 4442         /* translators: 1: Suggested width number, 2: Suggested height number. */
 4443         'suggestedDimensions'         => __( 'Suggested image dimensions: %1$s by %2$s pixels.' ),
 4444         'cropError'                   => __( 'There has been an error cropping your image.' ),
 4445 
 4446         // Edit Audio.
 4447         'audioDetailsTitle'           => __( 'Audio details' ),
 4448         'audioReplaceTitle'           => __( 'Replace audio' ),
 4449         'audioAddSourceTitle'         => __( 'Add audio source' ),
 4450         'audioDetailsCancel'          => __( 'Cancel edit' ),
 4451 
 4452         // Edit Video.
 4453         'videoDetailsTitle'           => __( 'Video details' ),
 4454         'videoReplaceTitle'           => __( 'Replace video' ),
 4455         'videoAddSourceTitle'         => __( 'Add video source' ),
 4456         'videoDetailsCancel'          => __( 'Cancel edit' ),
 4457         'videoSelectPosterImageTitle' => __( 'Select poster image' ),
 4458         'videoAddTrackTitle'          => __( 'Add subtitles' ),
 4459 
 4460         // Playlist.
 4461         'playlistDragInfo'            => __( 'Drag and drop to reorder tracks.' ),
 4462         'createPlaylistTitle'         => __( 'Create audio playlist' ),
 4463         'editPlaylistTitle'           => __( 'Edit audio playlist' ),
 4464         'cancelPlaylistTitle'         => __( '&#8592; Cancel audio playlist' ),
 4465         'insertPlaylist'              => __( 'Insert audio playlist' ),
 4466         'updatePlaylist'              => __( 'Update audio playlist' ),
 4467         'addToPlaylist'               => __( 'Add to audio playlist' ),
 4468         'addToPlaylistTitle'          => __( 'Add to Audio Playlist' ),
 4469 
 4470         // Video Playlist.
 4471         'videoPlaylistDragInfo'       => __( 'Drag and drop to reorder videos.' ),
 4472         'createVideoPlaylistTitle'    => __( 'Create video playlist' ),
 4473         'editVideoPlaylistTitle'      => __( 'Edit video playlist' ),
 4474         'cancelVideoPlaylistTitle'    => __( '&#8592; Cancel video playlist' ),
 4475         'insertVideoPlaylist'         => __( 'Insert video playlist' ),
 4476         'updateVideoPlaylist'         => __( 'Update video playlist' ),
 4477         'addToVideoPlaylist'          => __( 'Add to video playlist' ),
 4478         'addToVideoPlaylistTitle'     => __( 'Add to video Playlist' ),
 4479 
 4480         // Headings.
 4481         'filterAttachments'           => __( 'Filter media' ),
 4482         'attachmentsList'             => __( 'Media list' ),
 4483     );
 4484 
 4485     /**
 4486      * Filters the media view settings.
 4487      *
 4488      * @since 3.5.0
 4489      *
 4490      * @param array   $settings List of media view settings.
 4491      * @param WP_Post $post     Post object.
 4492      */
 4493     $settings = apply_filters( 'media_view_settings', $settings, $post );
 4494 
 4495     /**
 4496      * Filters the media view strings.
 4497      *
 4498      * @since 3.5.0
 4499      *
 4500      * @param string[] $strings Array of media view strings keyed by the name they'll be referenced by in JavaScript.
 4501      * @param WP_Post  $post    Post object.
 4502      */
 4503     $strings = apply_filters( 'media_view_strings', $strings, $post );
 4504 
 4505     $strings['settings'] = $settings;
 4506 
 4507     // Ensure we enqueue media-editor first, that way media-views
 4508     // is registered internally before we try to localize it. See #24724.
 4509     wp_enqueue_script( 'media-editor' );
 4510     wp_localize_script( 'media-views', '_wpMediaViewsL10n', $strings );
 4511 
 4512     wp_enqueue_script( 'media-audiovideo' );
 4513     wp_enqueue_style( 'media-views' );
 4514     if ( is_admin() ) {
 4515         wp_enqueue_script( 'mce-view' );
 4516         wp_enqueue_script( 'image-edit' );
 4517     }
 4518     wp_enqueue_style( 'imgareaselect' );
 4519     wp_plupload_default_settings();
 4520 
 4521     require_once ABSPATH . WPINC . '/media-template.php';
 4522     add_action( 'admin_footer', 'wp_print_media_templates' );
 4523     add_action( 'wp_footer', 'wp_print_media_templates' );
 4524     add_action( 'customize_controls_print_footer_scripts', 'wp_print_media_templates' );
 4525 
 4526     /**
 4527      * Fires at the conclusion of wp_enqueue_media().
 4528      *
 4529      * @since 3.5.0
 4530      */
 4531     do_action( 'wp_enqueue_media' );
 4532 }
 4533 
 4534 /**
 4535  * Retrieves media attached to the passed post.
 4536  *
 4537  * @since 3.6.0
 4538  *
 4539  * @param string      $type Mime type.
 4540  * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
 4541  * @return WP_Post[] Array of media attached to the given post.
 4542  */
 4543 function get_attached_media( $type, $post = 0 ) {
 4544     $post = get_post( $post );
 4545 
 4546     if ( ! $post ) {
 4547         return array();
 4548     }
 4549 
 4550     $args = array(
 4551         'post_parent'    => $post->ID,
 4552         'post_type'      => 'attachment',
 4553         'post_mime_type' => $type,
 4554         'posts_per_page' => -1,
 4555         'orderby'        => 'menu_order',
 4556         'order'          => 'ASC',
 4557     );
 4558 
 4559     /**
 4560      * Filters arguments used to retrieve media attached to the given post.
 4561      *
 4562      * @since 3.6.0
 4563      *
 4564      * @param array   $args Post query arguments.
 4565      * @param string  $type Mime type of the desired media.
 4566      * @param WP_Post $post Post object.
 4567      */
 4568     $args = apply_filters( 'get_attached_media_args', $args, $type, $post );
 4569 
 4570     $children = get_children( $args );
 4571 
 4572     /**
 4573      * Filters the list of media attached to the given post.
 4574      *
 4575      * @since 3.6.0
 4576      *
 4577      * @param WP_Post[] $children Array of media attached to the given post.
 4578      * @param string    $type     Mime type of the media desired.
 4579      * @param WP_Post   $post     Post object.
 4580      */
 4581     return (array) apply_filters( 'get_attached_media', $children, $type, $post );
 4582 }
 4583 
 4584 /**
 4585  * Check the content HTML for a audio, video, object, embed, or iframe tags.
 4586  *
 4587  * @since 3.6.0
 4588  *
 4589  * @param string   $content A string of HTML which might contain media elements.
 4590  * @param string[] $types   An array of media types: 'audio', 'video', 'object', 'embed', or 'iframe'.
 4591  * @return string[] Array of found HTML media elements.
 4592  */
 4593 function get_media_embedded_in_content( $content, $types = null ) {
 4594     $html = array();
 4595 
 4596     /**
 4597      * Filters the embedded media types that are allowed to be returned from the content blob.
 4598      *
 4599      * @since 4.2.0
 4600      *
 4601      * @param string[] $allowed_media_types An array of allowed media types. Default media types are
 4602      *                                      'audio', 'video', 'object', 'embed', and 'iframe'.
 4603      */
 4604     $allowed_media_types = apply_filters( 'media_embedded_in_content_allowed_types', array( 'audio', 'video', 'object', 'embed', 'iframe' ) );
 4605 
 4606     if ( ! empty( $types ) ) {
 4607         if ( ! is_array( $types ) ) {
 4608             $types = array( $types );
 4609         }
 4610 
 4611         $allowed_media_types = array_intersect( $allowed_media_types, $types );
 4612     }
 4613 
 4614     $tags = implode( '|', $allowed_media_types );
 4615 
 4616     if ( preg_match_all( '#<(?P<tag>' . $tags . ')[^<]*?(?:>[\s\S]*?<\/(?P=tag)>|\s*\/>)#', $content, $matches ) ) {
 4617         foreach ( $matches[0] as $match ) {
 4618             $html[] = $match;
 4619         }
 4620     }
 4621 
 4622     return $html;
 4623 }
 4624 
 4625 /**
 4626  * Retrieves galleries from the passed post's content.
 4627  *
 4628  * @since 3.6.0
 4629  *
 4630  * @param int|WP_Post $post Post ID or object.
 4631  * @param bool        $html Optional. Whether to return HTML or data in the array. Default true.
 4632  * @return array A list of arrays, each containing gallery data and srcs parsed
 4633  *               from the expanded shortcode.
 4634  */
 4635 function get_post_galleries( $post, $html = true ) {
 4636     $post = get_post( $post );
 4637 
 4638     if ( ! $post ) {
 4639         return array();
 4640     }
 4641 
 4642     if ( ! has_shortcode( $post->post_content, 'gallery' ) ) {
 4643         return array();
 4644     }
 4645 
 4646     $galleries = array();
 4647     if ( preg_match_all( '/' . get_shortcode_regex() . '/s', $post->post_content, $matches, PREG_SET_ORDER ) ) {
 4648         foreach ( $matches as $shortcode ) {
 4649             if ( 'gallery' === $shortcode[2] ) {
 4650                 $srcs = array();
 4651 
 4652                 $shortcode_attrs = shortcode_parse_atts( $shortcode[3] );
 4653                 if ( ! is_array( $shortcode_attrs ) ) {
 4654                     $shortcode_attrs = array();
 4655                 }
 4656 
 4657                 // Specify the post ID of the gallery we're viewing if the shortcode doesn't reference another post already.
 4658                 if ( ! isset( $shortcode_attrs['id'] ) ) {
 4659                     $shortcode[3] .= ' id="' . (int) $post->ID . '"';
 4660                 }
 4661 
 4662                 $gallery = do_shortcode_tag( $shortcode );
 4663                 if ( $html ) {
 4664                     $galleries[] = $gallery;
 4665                 } else {
 4666                     preg_match_all( '#src=([\'"])(.+?)\1#is', $gallery, $src, PREG_SET_ORDER );
 4667                     if ( ! empty( $src ) ) {
 4668                         foreach ( $src as $s ) {
 4669                             $srcs[] = $s[2];
 4670                         }
 4671                     }
 4672 
 4673                     $galleries[] = array_merge(
 4674                         $shortcode_attrs,
 4675                         array(
 4676                             'src' => array_values( array_unique( $srcs ) ),
 4677                         )
 4678                     );
 4679                 }
 4680             }
 4681         }
 4682     }
 4683 
 4684     /**
 4685      * Filters the list of all found galleries in the given post.
 4686      *
 4687      * @since 3.6.0
 4688      *
 4689      * @param array   $galleries Associative array of all found post galleries.
 4690      * @param WP_Post $post      Post object.
 4691      */
 4692     return apply_filters( 'get_post_galleries', $galleries, $post );
 4693 }
 4694 
 4695 /**
 4696  * Check a specified post's content for gallery and, if present, return the first
 4697  *
 4698  * @since 3.6.0
 4699  *
 4700  * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
 4701  * @param bool        $html Optional. Whether to return HTML or data. Default is true.
 4702  * @return string|array Gallery data and srcs parsed from the expanded shortcode.
 4703  */
 4704 function get_post_gallery( $post = 0, $html = true ) {
 4705     $galleries = get_post_galleries( $post, $html );
 4706     $gallery   = reset( $galleries );
 4707 
 4708     /**
 4709      * Filters the first-found post gallery.
 4710      *
 4711      * @since 3.6.0
 4712      *
 4713      * @param array       $gallery   The first-found post gallery.
 4714      * @param int|WP_Post $post      Post ID or object.
 4715      * @param array       $galleries Associative array of all found post galleries.
 4716      */
 4717     return apply_filters( 'get_post_gallery', $gallery, $post, $galleries );
 4718 }
 4719 
 4720 /**
 4721  * Retrieve the image srcs from galleries from a post's content, if present
 4722  *
 4723  * @since 3.6.0
 4724  *
 4725  * @see get_post_galleries()
 4726  *
 4727  * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global `$post`.
 4728  * @return array A list of lists, each containing image srcs parsed.
 4729  *               from an expanded shortcode
 4730  */
 4731 function get_post_galleries_images( $post = 0 ) {
 4732     $galleries = get_post_galleries( $post, false );
 4733     return wp_list_pluck( $galleries, 'src' );
 4734 }
 4735 
 4736 /**
 4737  * Checks a post's content for galleries and return the image srcs for the first found gallery
 4738  *
 4739  * @since 3.6.0
 4740  *
 4741  * @see get_post_gallery()
 4742  *
 4743  * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global `$post`.
 4744  * @return string[] A list of a gallery's image srcs in order.
 4745  */
 4746 function get_post_gallery_images( $post = 0 ) {
 4747     $gallery = get_post_gallery( $post, false );
 4748     return empty( $gallery['src'] ) ? array() : $gallery['src'];
 4749 }
 4750 
 4751 /**
 4752  * Maybe attempts to generate attachment metadata, if missing.
 4753  *
 4754  * @since 3.9.0
 4755  *
 4756  * @param WP_Post $attachment Attachment object.
 4757  */
 4758 function wp_maybe_generate_attachment_metadata( $attachment ) {
 4759     if ( empty( $attachment ) || empty( $attachment->ID ) ) {
 4760         return;
 4761     }
 4762 
 4763     $attachment_id = (int) $attachment->ID;
 4764     $file          = get_attached_file( $attachment_id );
 4765     $meta          = wp_get_attachment_metadata( $attachment_id );
 4766 
 4767     if ( empty( $meta ) && file_exists( $file ) ) {
 4768         $_meta = get_post_meta( $attachment_id );
 4769         $_lock = 'wp_generating_att_' . $attachment_id;
 4770 
 4771         if ( ! array_key_exists( '_wp_attachment_metadata', $_meta ) && ! get_transient( $_lock ) ) {
 4772             set_transient( $_lock, $file );
 4773             wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) );
 4774             delete_transient( $_lock );
 4775         }
 4776     }
 4777 }
 4778 
 4779 /**
 4780  * Tries to convert an attachment URL into a post ID.
 4781  *
 4782  * @since 4.0.0
 4783  *
 4784  * @global wpdb $wpdb WordPress database abstraction object.
 4785  *
 4786  * @param string $url The URL to resolve.
 4787  * @return int The found post ID, or 0 on failure.
 4788  */
 4789 function attachment_url_to_postid( $url ) {
 4790     global $wpdb;
 4791 
 4792     $dir  = wp_get_upload_dir();
 4793     $path = $url;
 4794 
 4795     $site_url   = parse_url( $dir['url'] );
 4796     $image_path = parse_url( $path );
 4797 
 4798     // Force the protocols to match if needed.
 4799     if ( isset( $image_path['scheme'] ) && ( $image_path['scheme'] !== $site_url['scheme'] ) ) {
 4800         $path = str_replace( $image_path['scheme'], $site_url['scheme'], $path );
 4801     }
 4802 
 4803     if ( 0 === strpos( $path, $dir['baseurl'] . '/' ) ) {
 4804         $path = substr( $path, strlen( $dir['baseurl'] . '/' ) );
 4805     }
 4806 
 4807     $sql = $wpdb->prepare(
 4808         "SELECT post_id, meta_value FROM $wpdb->postmeta WHERE meta_key = '_wp_attached_file' AND meta_value = %s",
 4809         $path
 4810     );
 4811 
 4812     $results = $wpdb->get_results( $sql );
 4813     $post_id = null;
 4814 
 4815     if ( $results ) {
 4816         // Use the first available result, but prefer a case-sensitive match, if exists.
 4817         $post_id = reset( $results )->post_id;
 4818 
 4819         if ( count( $results ) > 1 ) {
 4820             foreach ( $results as $result ) {
 4821                 if ( $path === $result->meta_value ) {
 4822                     $post_id = $result->post_id;
 4823                     break;
 4824                 }
 4825             }
 4826         }
 4827     }
 4828 
 4829     /**
 4830      * Filters an attachment ID found by URL.
 4831      *
 4832      * @since 4.2.0
 4833      *
 4834      * @param int|null $post_id The post_id (if any) found by the function.
 4835      * @param string   $url     The URL being looked up.
 4836      */
 4837     return (int) apply_filters( 'attachment_url_to_postid', $post_id, $url );
 4838 }
 4839 
 4840 /**
 4841  * Returns the URLs for CSS files used in an iframe-sandbox'd TinyMCE media view.
 4842  *
 4843  * @since 4.0.0
 4844  *
 4845  * @return string[] The relevant CSS file URLs.
 4846  */
 4847 function wpview_media_sandbox_styles() {
 4848     $version        = 'ver=' . get_bloginfo( 'version' );
 4849     $mediaelement   = includes_url( "js/mediaelement/mediaelementplayer-legacy.min.css?$version" );
 4850     $wpmediaelement = includes_url( "js/mediaelement/wp-mediaelement.css?$version" );
 4851 
 4852     return array( $mediaelement, $wpmediaelement );
 4853 }
 4854 
 4855 /**
 4856  * Registers the personal data exporter for media.
 4857  *
 4858  * @param array[] $exporters An array of personal data exporters, keyed by their ID.
 4859  * @return array[] Updated array of personal data exporters.
 4860  */
 4861 function wp_register_media_personal_data_exporter( $exporters ) {
 4862     $exporters['wordpress-media'] = array(
 4863         'exporter_friendly_name' => __( 'WordPress Media' ),
 4864         'callback'               => 'wp_media_personal_data_exporter',
 4865     );
 4866 
 4867     return $exporters;
 4868 }
 4869 
 4870 /**
 4871  * Finds and exports attachments associated with an email address.
 4872  *
 4873  * @since 4.9.6
 4874  *
 4875  * @param string $email_address The attachment owner email address.
 4876  * @param int    $page          Attachment page.
 4877  * @return array An array of personal data.
 4878  */
 4879 function wp_media_personal_data_exporter( $email_address, $page = 1 ) {
 4880     // Limit us to 50 attachments at a time to avoid timing out.
 4881     $number = 50;
 4882     $page   = (int) $page;
 4883 
 4884     $data_to_export = array();
 4885 
 4886     $user = get_user_by( 'email', $email_address );
 4887     if ( false === $user ) {
 4888         return array(
 4889             'data' => $data_to_export,
 4890             'done' => true,
 4891         );
 4892     }
 4893 
 4894     $post_query = new WP_Query(
 4895         array(
 4896             'author'         => $user->ID,
 4897             'posts_per_page' => $number,
 4898             'paged'          => $page,
 4899             'post_type'      => 'attachment',
 4900             'post_status'    => 'any',
 4901             'orderby'        => 'ID',
 4902             'order'          => 'ASC',
 4903         )
 4904     );
 4905 
 4906     foreach ( (array) $post_query->posts as $post ) {
 4907         $attachment_url = wp_get_attachment_url( $post->ID );
 4908 
 4909         if ( $attachment_url ) {
 4910             $post_data_to_export = array(
 4911                 array(
 4912                     'name'  => __( 'URL' ),
 4913                     'value' => $attachment_url,
 4914                 ),
 4915             );
 4916 
 4917             $data_to_export[] = array(
 4918                 'group_id'          => 'media',
 4919                 'group_label'       => __( 'Media' ),
 4920                 'group_description' => __( 'User&#8217;s media data.' ),
 4921                 'item_id'           => "post-{$post->ID}",
 4922                 'data'              => $post_data_to_export,
 4923             );
 4924         }
 4925     }
 4926 
 4927     $done = $post_query->max_num_pages <= $page;
 4928 
 4929     return array(
 4930         'data' => $data_to_export,
 4931         'done' => $done,
 4932     );
 4933 }
 4934 
 4935 /**
 4936  * Add additional default image sub-sizes.
 4937  *
 4938  * These sizes are meant to enhance the way WordPress displays images on the front-end on larger,
 4939  * high-density devices. They make it possible to generate more suitable `srcset` and `sizes` attributes
 4940  * when the users upload large images.
 4941  *
 4942  * The sizes can be changed or removed by themes and plugins but that is not recommended.
 4943  * The size "names" reflect the image dimensions, so changing the sizes would be quite misleading.
 4944  *
 4945  * @since 5.3.0
 4946  * @access private
 4947  */
 4948 function _wp_add_additional_image_sizes() {
 4949     // 2x medium_large size.
 4950     add_image_size( '1536x1536', 1536, 1536 );
 4951     // 2x large size.
 4952     add_image_size( '2048x2048', 2048, 2048 );
 4953 }
 4954 
 4955 /**
 4956  * Callback to enable showing of the user error when uploading .heic images.
 4957  *
 4958  * @since 5.5.0
 4959  *
 4960  * @param array[] $plupload_settings The settings for Plupload.js.
 4961  * @return array[] Modified settings for Plupload.js.
 4962  */
 4963 function wp_show_heic_upload_error( $plupload_settings ) {
 4964     $plupload_settings['heic_upload_error'] = true;
 4965     return $plupload_settings;
 4966 }
 4967 
 4968 /**
 4969  * Allows PHP's getimagesize() to be debuggable when necessary.
 4970  *
 4971  * @since 5.7.0
 4972  *
 4973  * @param string $filename  The file path.
 4974  * @param array  $imageinfo Extended image information, passed by reference.
 4975  * @return array|false Array of image information or false on failure.
 4976  */
 4977 function wp_getimagesize( $filename, &$imageinfo = array() ) {
 4978     if (
 4979         // Skip when running unit tests.
 4980         ! defined( 'WP_RUN_CORE_TESTS' )
 4981         &&
 4982         // Return without silencing errors when in debug mode.
 4983         defined( 'WP_DEBUG' ) && WP_DEBUG
 4984     ) {
 4985         return getimagesize( $filename, $imageinfo );
 4986     }
 4987 
 4988     /*
 4989      * Silencing notice and warning is intentional.
 4990      *
 4991      * getimagesize() has a tendency to generate errors, such as
 4992      * "corrupt JPEG data: 7191 extraneous bytes before marker",
 4993      * even when it's able to provide image size information.
 4994      *
 4995      * See https://core.trac.wordpress.org/ticket/42480
 4996      *
 4997      * phpcs:ignore WordPress.PHP.NoSilencedErrors
 4998      */
 4999     return @getimagesize( $filename, $imageinfo );
 5000 }