[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-admin/includes/ -> image.php (source)

   1  <?php
   2  /**
   3   * File contains all the administration image manipulation functions.
   4   *
   5   * @package WordPress
   6   * @subpackage Administration
   7   */
   8  
   9  /**
  10   * Crops an image to a given size.
  11   *
  12   * @since 2.1.0
  13   *
  14   * @param string|int   $src      The source file or Attachment ID.
  15   * @param int          $src_x    The start x position to crop from.
  16   * @param int          $src_y    The start y position to crop from.
  17   * @param int          $src_w    The width to crop.
  18   * @param int          $src_h    The height to crop.
  19   * @param int          $dst_w    The destination width.
  20   * @param int          $dst_h    The destination height.
  21   * @param bool|false   $src_abs  Optional. If the source crop points are absolute.
  22   * @param string|false $dst_file Optional. The destination file to write to.
  23   * @return string|WP_Error New filepath on success, WP_Error on failure.
  24   */
  25  function wp_crop_image( $src, $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h, $src_abs = false, $dst_file = false ) {
  26      $src_file = $src;
  27      if ( is_numeric( $src ) ) { // Handle int as attachment ID.
  28          $src_file = get_attached_file( $src );
  29  
  30          if ( ! file_exists( $src_file ) ) {
  31              // If the file doesn't exist, attempt a URL fopen on the src link.
  32              // This can occur with certain file replication plugins.
  33              $src = _load_image_to_edit_path( $src, 'full' );
  34          } else {
  35              $src = $src_file;
  36          }
  37      }
  38  
  39      $editor = wp_get_image_editor( $src );
  40      if ( is_wp_error( $editor ) ) {
  41          return $editor;
  42      }
  43  
  44      $src = $editor->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h, $src_abs );
  45      if ( is_wp_error( $src ) ) {
  46          return $src;
  47      }
  48  
  49      if ( ! $dst_file ) {
  50          $dst_file = str_replace( wp_basename( $src_file ), 'cropped-' . wp_basename( $src_file ), $src_file );
  51      }
  52  
  53      /*
  54       * The directory containing the original file may no longer exist when
  55       * using a replication plugin.
  56       */
  57      wp_mkdir_p( dirname( $dst_file ) );
  58  
  59      $dst_file = dirname( $dst_file ) . '/' . wp_unique_filename( dirname( $dst_file ), wp_basename( $dst_file ) );
  60  
  61      $result = $editor->save( $dst_file );
  62      if ( is_wp_error( $result ) ) {
  63          return $result;
  64      }
  65  
  66      if ( ! empty( $result['path'] ) ) {
  67          return $result['path'];
  68      }
  69  
  70      return $dst_file;
  71  }
  72  
  73  /**
  74   * Compares the existing image sub-sizes (as saved in the attachment meta)
  75   * to the currently registered image sub-sizes, and returns the difference.
  76   *
  77   * Registered sub-sizes that are larger than the image are skipped.
  78   *
  79   * @since 5.3.0
  80   * @since 6.1.0 The $mime_type parameter was added.
  81   *
  82   * @param int    $attachment_id The image attachment post ID.
  83   * @param string $mime_type     Optional. The mime type to check for missing sizes. Default is the primary image mime.
  84   * @return array[] Associative array of arrays of image sub-size information for
  85   *                 missing image sizes, keyed by image size name.
  86   */
  87  function wp_get_missing_image_subsizes( $attachment_id, $mime_type = '' ) {
  88      if ( ! wp_attachment_is_image( $attachment_id ) ) {
  89          return array();
  90      }
  91  
  92      $primary_mime_type = get_post_mime_type( get_post( $attachment_id ) );
  93      if ( ! $mime_type ) {
  94          $mime_type = $primary_mime_type;
  95      }
  96  
  97      $registered_sizes = wp_get_registered_image_subsizes();
  98      $image_meta       = wp_get_attachment_metadata( $attachment_id );
  99  
 100      // Meta error?
 101      if ( empty( $image_meta ) ) {
 102          return $registered_sizes;
 103      }
 104  
 105      // Use the originally uploaded image dimensions as full_width and full_height.
 106      if ( ! empty( $image_meta['original_image'] ) ) {
 107          $image_file = wp_get_original_image_path( $attachment_id );
 108          $imagesize  = wp_getimagesize( $image_file );
 109      }
 110  
 111      if ( ! empty( $imagesize ) ) {
 112          $full_width  = $imagesize[0];
 113          $full_height = $imagesize[1];
 114      } else {
 115          $full_width  = (int) $image_meta['width'];
 116          $full_height = (int) $image_meta['height'];
 117      }
 118  
 119      $possible_sizes = array();
 120  
 121      // Skip registered sizes that are too large for the uploaded image.
 122      foreach ( $registered_sizes as $size_name => $size_data ) {
 123          if ( image_resize_dimensions( $full_width, $full_height, $size_data['width'], $size_data['height'], $size_data['crop'] ) ) {
 124              $possible_sizes[ $size_name ] = $size_data;
 125          }
 126      }
 127  
 128      if ( empty( $image_meta['sizes'] ) ) {
 129          $image_meta['sizes'] = array();
 130      }
 131  
 132      /*
 133       * Remove sizes that already exist. Only checks for matching "size names".
 134       * It is possible that the dimensions for a particular size name have changed.
 135       * For example the user has changed the values on the Settings -> Media screen.
 136       * However we keep the old sub-sizes with the previous dimensions
 137       * as the image may have been used in an older post.
 138       */
 139      $missing_sizes = array();
 140      foreach ( $possible_sizes as $size_name => $size_data ) {
 141          if ( ! isset( $image_meta['sizes'][ $size_name ] ) ) {
 142              $missing_sizes[ $size_name ] = $size_data;
 143              continue;
 144          }
 145  
 146          if ( ( isset( $size_data['mime-type'] ) && $size_data['mime-type'] === $mime_type ) || isset( $size_data['sources'][ $mime_type ] ) ) {
 147              continue;
 148          }
 149  
 150          $missing_sizes[ $size_name ] = $size_data;
 151      }
 152  
 153      // Filter secondary mime types to those sizes that are enabled.
 154      if ( $primary_mime_type !== $mime_type ) {
 155          $missing_sizes = _wp_filter_image_sizes_additional_mime_type_support( $missing_sizes, $attachment_id );
 156      }
 157  
 158      /**
 159       * Filters the array of missing image sub-sizes for an uploaded image.
 160       *
 161       * @since 5.3.0
 162       * @since 6.1.0 The $mime_type filter parameter was added.
 163       *
 164       * @param array[] $missing_sizes Associative array of arrays of image sub-size information for
 165       *                               missing image sizes, keyed by image size name.
 166       * @param array   $image_meta    The image meta data.
 167       * @param int     $attachment_id The image attachment post ID.
 168       * @param string  $mime_type     The image mime type to get missing sizes for.
 169       */
 170      return apply_filters( 'wp_get_missing_image_subsizes', $missing_sizes, $image_meta, $attachment_id, $mime_type );
 171  }
 172  
 173  /**
 174   * If any of the currently registered image sub-sizes are missing,
 175   * create them and update the image meta data.
 176   *
 177   * @since 5.3.0
 178   * @since 6.1.0 Now supports additional mime types, creating the additional sub-sizes and 'full' sized images.
 179   *
 180   * @param int $attachment_id The image attachment post ID.
 181   * @return array|WP_Error The updated image meta data array or WP_Error object
 182   *                        if both the image meta and the attached file are missing.
 183   */
 184  function wp_update_image_subsizes( $attachment_id ) {
 185      $image_meta = wp_get_attachment_metadata( $attachment_id );
 186      $image_file = wp_get_original_image_path( $attachment_id );
 187  
 188      if ( empty( $image_meta ) || ! is_array( $image_meta ) ) {
 189          // Previously failed upload?
 190          // If there is an uploaded file, make all sub-sizes and generate all of the attachment meta.
 191          if ( ! empty( $image_file ) ) {
 192              $image_meta = wp_create_image_subsizes( $image_file, $attachment_id );
 193          } else {
 194              return new WP_Error( 'invalid_attachment', __( 'The attached file cannot be found.' ) );
 195          }
 196      } else {
 197          // Get the primary and additional mime types to generate.
 198          list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $image_file, $attachment_id );
 199  
 200          // Generate missing 'full' image files for additional mime types.
 201          if ( ! empty( $additional_mime_types ) ) {
 202              if ( isset( $image_meta['sources'] ) ) {
 203                  $missing_mime_types = array_diff( $additional_mime_types, array_keys( $image_meta['sources'] ) );
 204              } else {
 205                  $missing_mime_types = $additional_mime_types;
 206              }
 207              if ( ! empty( $missing_mime_types ) ) {
 208                  $image_meta = _wp_make_additional_mime_types( $missing_mime_types, $image_file, $image_meta, $attachment_id );
 209              }
 210          }
 211  
 212          // Generate missing image sub-sizes for each mime type.
 213          $all_mime_types = array_merge( array( $primary_mime_type ), $additional_mime_types );
 214          foreach ( $all_mime_types as $mime_type ) {
 215              $missing_sizes = wp_get_missing_image_subsizes( $attachment_id, $mime_type );
 216  
 217              if ( empty( $missing_sizes ) ) {
 218                  continue;
 219              }
 220  
 221              // This also updates the image meta.
 222              $image_meta = _wp_make_subsizes( $missing_sizes, $image_file, $image_meta, $attachment_id, $mime_type );
 223          }
 224      }
 225  
 226      /** This filter is documented in wp-admin/includes/image.php */
 227      $image_meta = apply_filters( 'wp_generate_attachment_metadata', $image_meta, $attachment_id, 'update' );
 228  
 229      // Save the updated metadata.
 230      wp_update_attachment_metadata( $attachment_id, $image_meta );
 231  
 232      return $image_meta;
 233  }
 234  
 235  /**
 236   * Updates the attached file and image meta data when the original image was edited.
 237   *
 238   * @since 5.3.0
 239   * @since 6.0.0 The `$filesize` value was added to the returned array.
 240   * @access private
 241   *
 242   * @param array  $saved_data    The data returned from WP_Image_Editor after successfully saving an image.
 243   * @param string $original_file Path to the original file.
 244   * @param array  $image_meta    The image meta data.
 245   * @param int    $attachment_id The attachment post ID.
 246   * @return array The updated image meta data.
 247   */
 248  function _wp_image_meta_replace_original( $saved_data, $original_file, $image_meta, $attachment_id ) {
 249      $new_file = $saved_data['path'];
 250  
 251      // Update the attached file meta.
 252      update_attached_file( $attachment_id, $new_file );
 253  
 254      // Width and height of the new image.
 255      $image_meta['width']  = $saved_data['width'];
 256      $image_meta['height'] = $saved_data['height'];
 257  
 258      // Make the file path relative to the upload dir.
 259      $image_meta['file'] = _wp_relative_upload_path( $new_file );
 260  
 261      // Add image file size.
 262      $image_meta['filesize'] = wp_filesize( $new_file );
 263  
 264      // Store the original image file name in image_meta.
 265      $image_meta['original_image'] = wp_basename( $original_file );
 266  
 267      return $image_meta;
 268  }
 269  
 270  /**
 271   * Creates image mime variations and sub-sizes, adds the new data to the image meta `sizes` array, and updates the image metadata.
 272   *
 273   * Intended for use after an image is uploaded. Saves/updates the image metadata after each
 274   * sub-size is created. If there was an error, it is added to the returned image metadata array.
 275   *
 276   * @since 5.3.0
 277   * @since 6.1.0 Generates sub-sizes in alternate mime types based on the `wp_image_mime_transforms` filter.
 278   *
 279   * @param string $file          Full path to the image file.
 280   * @param int    $attachment_id Attachment ID to process.
 281   * @return array The image attachment meta data.
 282   */
 283  function wp_create_image_subsizes( $file, $attachment_id ) {
 284      $imagesize = wp_getimagesize( $file );
 285  
 286      if ( empty( $imagesize ) ) {
 287          // File is not an image.
 288          return array();
 289      }
 290  
 291      // Default image meta.
 292      $image_meta = array(
 293          'width'    => $imagesize[0],
 294          'height'   => $imagesize[1],
 295          'file'     => _wp_relative_upload_path( $file ),
 296          'filesize' => wp_filesize( $file ),
 297          'sizes'    => array(),
 298          'sources'  => array(),
 299      );
 300  
 301      // Fetch additional metadata from EXIF/IPTC.
 302      $exif_meta = wp_read_image_metadata( $file );
 303  
 304      if ( $exif_meta ) {
 305          $image_meta['image_meta'] = $exif_meta;
 306      }
 307  
 308      // Get the primary and additional mime types to generate.
 309      list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $file, $attachment_id );
 310  
 311      list( $editor, $resized, $rotated ) = _wp_maybe_scale_and_rotate_image( $file, $attachment_id, $imagesize, $exif_meta, $primary_mime_type );
 312      if ( is_wp_error( $editor ) ) {
 313          return $image_meta;
 314      }
 315      $suffix = _wp_get_image_suffix( $resized, $rotated );
 316  
 317      // Save image only if either it was modified or if the primary mime type is different from the original.
 318      if ( ! empty( $suffix ) || $primary_mime_type !== $imagesize['mime'] ) {
 319          $saved = $editor->save( $editor->generate_filename( $suffix ) );
 320  
 321          if ( ! is_wp_error( $saved ) ) {
 322              $image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id );
 323  
 324              // If the image was rotated update the stored EXIF data.
 325              if ( true === $rotated && ! empty( $image_meta['image_meta']['orientation'] ) ) {
 326                  $image_meta['image_meta']['orientation'] = 1;
 327              }
 328          } else {
 329              // TODO: Log errors.
 330          }
 331      }
 332  
 333      // Set 'sources' for the primary mime type.
 334      $image_meta['sources'][ $primary_mime_type ] = _wp_get_sources_from_meta( $image_meta );
 335  
 336      /*
 337       * Initial save of the new metadata.
 338       * At this point the file was uploaded and moved to the uploads directory
 339       * but the image sub-sizes haven't been created yet and the `sizes` array is empty.
 340       */
 341      wp_update_attachment_metadata( $attachment_id, $image_meta );
 342  
 343      if ( ! empty( $additional_mime_types ) ) {
 344          // Use the original file's exif_meta orientation and size information for secondary mime generation to ensure
 345          // sub-sized images are correctly scaled and rotated.
 346  
 347          // Save data.
 348          $saved_meta                = array();
 349          $saved_meta['orientation'] = $image_meta['image_meta']['orientation'];
 350          $saved_meta['width']       = $image_meta['width'];
 351          $saved_meta['height']      = $image_meta['height'];
 352  
 353          // Temporarily set the image meta to the original file's meta.
 354          $image_meta['image_meta']['orientation'] = $exif_meta['orientation'];
 355          $image_meta['width']                     = $imagesize[0];
 356          $image_meta['height']                    = $imagesize[1];
 357  
 358          $image_meta = _wp_make_additional_mime_types( $additional_mime_types, $file, $image_meta, $attachment_id );
 359  
 360          // Restore the saved meta data.
 361          $image_meta['image_meta']['orientation'] = $saved_meta['orientation'];
 362          $image_meta['width']                     = $saved_meta['width'];
 363          $image_meta['height']                    = $saved_meta['height'];
 364  
 365      }
 366  
 367      $new_sizes = wp_get_registered_image_subsizes();
 368  
 369      /**
 370       * Filters the image sizes automatically generated when uploading an image.
 371       *
 372       * @since 2.9.0
 373       * @since 4.4.0 Added the `$image_meta` argument.
 374       * @since 5.3.0 Added the `$attachment_id` argument.
 375       *
 376       * @param array $new_sizes     Associative array of image sizes to be created.
 377       * @param array $image_meta    The image meta data: width, height, file, sizes, etc.
 378       * @param int   $attachment_id The attachment post ID for the image.
 379       */
 380      $new_sizes = apply_filters( 'intermediate_image_sizes_advanced', $new_sizes, $image_meta, $attachment_id );
 381  
 382      $image_meta = _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id, $primary_mime_type );
 383  
 384      // Filter secondary mime types to those sizes that are enabled.
 385      $new_sizes = _wp_filter_image_sizes_additional_mime_type_support( $new_sizes, $attachment_id );
 386  
 387      foreach ( $additional_mime_types as $additional_mime_type ) {
 388          $image_meta = _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id, $additional_mime_type );
 389      }
 390  
 391      return $image_meta;
 392  }
 393  
 394  /**
 395   * Returns a WP_Image_Editor instance where the image file has been scaled and rotated as necessary.
 396   *
 397   * @since 6.1.0
 398   * @access private
 399   *
 400   * @param string     $file          Full path to the image file.
 401   * @param int        $attachment_id Attachment ID.
 402   * @param array      $imagesize     {
 403   *     Indexed array of the image width and height in pixels.
 404   *
 405   *     @type int $0 The image width.
 406   *     @type int $1 The image height.
 407   * }
 408   * @param array|null $exif_meta EXIF metadata if extracted from the image file.
 409   * @param string     $mime_type Output mime type.
 410   * @return array Array with three entries: The WP_Image_Editor instance, whether the image was resized, and whether the
 411   *               image was rotated (booleans). Each entry can alternatively be a WP_Error in case something went wrong.
 412   */
 413  function _wp_maybe_scale_and_rotate_image( $file, $attachment_id, $imagesize, $exif_meta, $mime_type ) {
 414      $resized = false;
 415      $rotated = false;
 416  
 417      $editor = wp_get_image_editor( $file, array( 'mime_type' => $mime_type ) );
 418      if ( is_wp_error( $editor ) ) {
 419          // This image cannot be edited.
 420          return array( $editor, $resized, $rotated );
 421      }
 422  
 423      if ( ! empty( $mime_type ) ) {
 424          $editor->set_output_mime_type( $mime_type );
 425      }
 426  
 427      // Do not scale (large) PNG images. May result in sub-sizes that have greater file size than the original. See #48736.
 428      if ( 'image/png' !== $mime_type ) {
 429          /**
 430           * Filters the "BIG image" threshold value.
 431           *
 432           * If the original image width or height is above the threshold, it will be scaled down. The threshold is
 433           * used as max width and max height. The scaled down image will be used as the largest available size, including
 434           * the `_wp_attached_file` post meta value.
 435           *
 436           * Returning `false` from the filter callback will disable the scaling.
 437           *
 438           * @since 5.3.0
 439           *
 440           * @param int    $threshold     The threshold value in pixels. Default 2560.
 441           * @param array  $imagesize     {
 442           *     Indexed array of the image width and height in pixels.
 443           *
 444           *     @type int $0 The image width.
 445           *     @type int $1 The image height.
 446           * }
 447           * @param string $file          Full path to the uploaded image file.
 448           * @param int    $attachment_id Attachment post ID.
 449           */
 450          $threshold = (int) apply_filters( 'big_image_size_threshold', 2560, $imagesize, $file, $attachment_id );
 451  
 452          // If the original image's dimensions are over the threshold,
 453          // scale the image and use it as the "full" size.
 454          if ( $threshold && ( $imagesize[0] > $threshold || $imagesize[1] > $threshold ) ) {
 455              // Resize the image.
 456              $resized = $editor->resize( $threshold, $threshold );
 457  
 458              // If there is EXIF data, rotate according to EXIF Orientation.
 459              if ( ! is_wp_error( $resized ) && is_array( $exif_meta ) ) {
 460                  $rotated = $editor->maybe_exif_rotate();
 461              }
 462          } elseif ( ! empty( $exif_meta['orientation'] ) && 1 !== (int) $exif_meta['orientation'] ) {
 463              // Rotate the whole original image if there is EXIF data and "orientation" is not 1.
 464              $rotated = $editor->maybe_exif_rotate();
 465          }
 466      }
 467  
 468      return array( $editor, $resized, $rotated );
 469  }
 470  
 471  /**
 472   * Gets the suffix to use for image files based on resizing and rotating.
 473   *
 474   * @since 6.1.0
 475   * @access private
 476   *
 477   * @param bool|WP_Error Whether the image was resized, or an error if resizing failed.
 478   * @param bool|WP_Error Whether the image was rotated, or an error if rotating failed.
 479   * @return string The suffix to use for the file name, or empty string if none.
 480   */
 481  function _wp_get_image_suffix( $resized, $rotated ) {
 482      if ( $resized && ! is_wp_error( $resized ) ) {
 483          // Append "-scaled" to the image file name. It will look like "my_image-scaled.jpg".
 484          // This doesn't affect the sub-sizes names as they are generated from the original image (for best quality).
 485          return 'scaled';
 486      }
 487  
 488      if ( true === $rotated ) {
 489          // Append `-rotated` to the image file name.
 490          return 'rotated';
 491      }
 492  
 493      if ( is_wp_error( $resized ) || is_wp_error( $rotated ) ) {
 494          // TODO: Log errors.
 495      }
 496      return '';
 497  }
 498  
 499  /**
 500   * Gets a sources array element from a meta.
 501   *
 502   * @since 6.1.0
 503   * @access private
 504   *
 505   * @param array $meta The meta to get the source from.
 506   * @return array The source array element.
 507   */
 508  function _wp_get_sources_from_meta( $meta ) {
 509      return array(
 510          'file'     => isset( $meta['file'] ) ? wp_basename( $meta['file'] ) : '',
 511          'filesize' => isset( $meta['filesize'] ) ? $meta['filesize'] : wp_filesize( $meta['path'] ),
 512      );
 513  }
 514  
 515  /**
 516   * Low-level function to create image sub-sizes.
 517   *
 518   * Updates the image meta after each sub-size is created.
 519   * Errors are stored in the returned image metadata array.
 520   *
 521   * @since 5.3.0
 522   * @since 6.1.0 The $mime_type parameter was added.
 523   * @access private
 524   *
 525   * @param array  $new_sizes       Array defining what sizes to create.
 526   * @param string $file            Full path to the image file.
 527   * @param array  $image_meta      The attachment meta data array.
 528   * @param int    $attachment_id   Attachment ID to process.
 529   * @param string $mime_type       Optional. The mime type to check for missing sizes. Default is the image mime of $file.
 530   * @return array The attachment meta data with updated `sizes` array. Includes an array of errors encountered while resizing.
 531   */
 532  function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id, $mime_type = '' ) {
 533      if ( empty( $image_meta ) || ! is_array( $image_meta ) ) {
 534          // Not an image attachment.
 535          return array();
 536      }
 537  
 538      if ( ! $mime_type ) {
 539          $mime_type = wp_get_image_mime( $file );
 540      }
 541  
 542      // Check if any of the new sizes already exist.
 543      if ( isset( $image_meta['sizes'] ) && is_array( $image_meta['sizes'] ) ) {
 544          foreach ( $image_meta['sizes'] as $size_name => $size_meta ) {
 545              /*
 546               * Only checks "size name" so we don't override existing images even if the dimensions
 547               * don't match the currently defined size with the same name.
 548               * To change the behavior, unset changed/mismatched sizes in the `sizes` array in image meta.
 549               */
 550              if ( array_key_exists( $size_name, $new_sizes ) ) {
 551                  // Unset the size if it is either the required mime type already exists either as main mime type or
 552                  // within sources.
 553                  if ( $size_meta['mime-type'] === $mime_type || isset( $size_meta['sources'][ $mime_type ] ) ) {
 554                      unset( $new_sizes[ $size_name ] );
 555                  }
 556              }
 557          }
 558      } else {
 559          $image_meta['sizes'] = array();
 560      }
 561  
 562      if ( empty( $new_sizes ) ) {
 563          // Nothing to do...
 564          return $image_meta;
 565      }
 566  
 567      /*
 568       * Sort the image sub-sizes in order of priority when creating them.
 569       * This ensures there is an appropriate sub-size the user can access immediately
 570       * even when there was an error and not all sub-sizes were created.
 571       */
 572      $priority = array(
 573          'medium'       => null,
 574          'large'        => null,
 575          'thumbnail'    => null,
 576          'medium_large' => null,
 577      );
 578  
 579      $new_sizes = array_filter( array_merge( $priority, $new_sizes ) );
 580  
 581      $editor = wp_get_image_editor( $file, array( 'mime_type' => $mime_type ) );
 582  
 583      if ( is_wp_error( $editor ) ) {
 584          // The image cannot be edited.
 585          return $image_meta;
 586      }
 587  
 588      $editor->set_output_mime_type( $mime_type );
 589  
 590      // If stored EXIF data exists, rotate the source image before creating sub-sizes.
 591      if ( ! empty( $image_meta['image_meta'] ) ) {
 592          $rotated = $editor->maybe_exif_rotate();
 593  
 594          if ( is_wp_error( $rotated ) ) {
 595              // TODO: Log errors.
 596          }
 597      }
 598  
 599      if ( method_exists( $editor, 'make_subsize' ) ) {
 600          foreach ( $new_sizes as $new_size_name => $new_size_data ) {
 601              $new_size_meta = $editor->make_subsize( $new_size_data );
 602  
 603              if ( is_wp_error( $new_size_meta ) ) {
 604                  // TODO: Log errors.
 605              } else {
 606                  // Save the size meta value.
 607                  if ( ! isset( $image_meta['sizes'][ $new_size_name ] ) ) {
 608                      $image_meta['sizes'][ $new_size_name ] = $new_size_meta;
 609                  } else {
 610                      // Remove any newly generated images that are larger than the primary mime type.
 611                      $new_size     = isset( $new_size_meta['filesize'] ) ? $new_size_meta['filesize'] : 0;
 612                      $primary_size = isset( $image_meta['sizes'][ $new_size_name ]['filesize'] ) ? $image_meta['sizes'][ $new_size_name ]['filesize'] : 0;
 613  
 614                      if ( $new_size && $primary_size && $new_size >= $primary_size ) {
 615                          wp_delete_file( dirname( $file ) . '/' . $new_size_meta['file'] );
 616                          continue;
 617                      }
 618                  }
 619                  if ( ! isset( $image_meta['sizes'][ $new_size_name ]['sources'] ) ) {
 620                      $image_meta['sizes'][ $new_size_name ]['sources'] = array();
 621                  }
 622                  $image_meta['sizes'][ $new_size_name ]['sources'][ $mime_type ] = _wp_get_sources_from_meta( $new_size_meta );
 623                  wp_update_attachment_metadata( $attachment_id, $image_meta );
 624              }
 625          }
 626      } else {
 627          // Fall back to `$editor->multi_resize()`.
 628          $created_sizes = $editor->multi_resize( $new_sizes );
 629  
 630          if ( ! empty( $created_sizes ) ) {
 631              foreach ( $created_sizes as $created_size_name => $created_size_meta ) {
 632  
 633                  // Primary mime type is set in 'sizes' array.
 634                  if ( ! isset( $image_meta['sizes'][ $created_size_name ] ) ) {
 635                      $image_meta['sizes'][ $created_size_name ] = $created_size_meta;
 636                  } else {
 637                      // Remove any newly generated images that are larger than the primary mime type.
 638                      $new_size     = isset( $created_size_meta['filesize'] ) ? $created_size_meta['filesize'] : 0;
 639                      $primary_size = isset( $image_meta['sizes'][ $created_size_name ]['filesize'] ) ? $image_meta['sizes'][ $created_size_name ]['filesize'] : 0;
 640  
 641                      if ( $new_size && $primary_size && $new_size >= $primary_size ) {
 642                          wp_delete_file( dirname( $file ) . '/' . $created_size_meta['file'] );
 643                          continue;
 644                      }
 645                  }
 646                  if ( ! isset( $image_meta['sizes'][ $created_size_name ]['sources'] ) ) {
 647                      $image_meta['sizes'][ $created_size_name ]['sources'] = array();
 648                  }
 649                  $image_meta['sizes'][ $created_size_name ]['sources'][ $mime_type ] = _wp_get_sources_from_meta( $created_size_meta );
 650              }
 651              wp_update_attachment_metadata( $attachment_id, $image_meta );
 652          }
 653      }
 654  
 655      return $image_meta;
 656  }
 657  
 658  /**
 659   * Filters the list of image size objects that support secondary mime type output.
 660   *
 661   * @since 6.1.0
 662   *
 663   * @param array $sizes         Associative array of image sizes.
 664   * @param int   $attachment_id Attachment ID.
 665   * @return array $sizes Filtered $sizes with only those that support secondary mime type output.
 666   */
 667  function _wp_filter_image_sizes_additional_mime_type_support( $sizes, $attachment_id ) {
 668  
 669      // Include only the core sizes that do not rely on add_image_size(). Additional image sizes are opt-in.
 670      $enabled_sizes = array(
 671          'thumbnail'      => true,
 672          'medium'         => true,
 673          'medium_large'   => true,
 674          'large'          => true,
 675          'post-thumbnail' => true,
 676      );
 677  
 678      /**
 679       * Filter the sizes that support secondary mime type output. Developers can use this
 680       * to control the output of additional mime type sub-sized images.
 681       *
 682       * @since 6.1.0
 683       *
 684       * @param array $enabled_sizes Map of size names and whether they support secondary mime type output.
 685       * @param int   $attachment_id Attachment ID.
 686       */
 687      $enabled_sizes = apply_filters( 'wp_image_sizes_with_additional_mime_type_support', $enabled_sizes, $attachment_id );
 688  
 689      // Filter supported sizes to only include enabled sizes.
 690      return array_intersect_key( $sizes, array_filter( $enabled_sizes ) );
 691  }
 692  
 693  /**
 694   * Low-level function to create full-size images in additional mime types.
 695   *
 696   * Updates the image meta after each mime type image is created.
 697   *
 698   * @since 6.1.0
 699   * @access private
 700   *
 701   * @param array  $new_mime_types Array defining what mime types to create.
 702   * @param string $file           Full path to the image file.
 703   * @param array  $image_meta     The attachment meta data array.
 704   * @param int    $attachment_id  Attachment ID to process.
 705   * @return array The attachment meta data with updated `sizes` array. Includes an array of errors encountered while resizing.
 706   */
 707  function _wp_make_additional_mime_types( $new_mime_types, $file, $image_meta, $attachment_id ) {
 708      $imagesize          = array(
 709          $image_meta['width'],
 710          $image_meta['height'],
 711      );
 712      $exif_meta          = isset( $image_meta['image_meta'] ) ? $image_meta['image_meta'] : null;
 713      $original_file_size = isset( $image_meta['filesize'] ) ? $image_meta['filesize'] : wp_filesize( $file );
 714  
 715      foreach ( $new_mime_types as $mime_type ) {
 716          list( $editor, $resized, $rotated ) = _wp_maybe_scale_and_rotate_image( $file, $attachment_id, $imagesize, $exif_meta, $mime_type );
 717          if ( is_wp_error( $editor ) ) {
 718              // The image cannot be edited.
 719              continue;
 720          }
 721  
 722          $suffix    = _wp_get_image_suffix( $resized, $rotated );
 723          $extension = wp_get_default_extension_for_mime_type( $mime_type );
 724  
 725          $saved = $editor->save( $editor->generate_filename( $suffix, null, $extension ) );
 726  
 727          if ( is_wp_error( $saved ) ) {
 728              // TODO: Log errors.
 729          } else {
 730              // If the saved image is larger than the original, discard it.
 731              $filesize = isset( $saved['filesize'] ) ? $saved['filesize'] : wp_filesize( $saved['path'] );
 732              if ( $filesize && $original_file_size && $filesize > $original_file_size ) {
 733                  wp_delete_file( $saved['path'] );
 734                  continue;
 735              }
 736              $image_meta['sources'][ $mime_type ] = _wp_get_sources_from_meta( $saved );
 737              wp_update_attachment_metadata( $attachment_id, $image_meta );
 738          }
 739      }
 740  
 741      return $image_meta;
 742  }
 743  
 744  /**
 745   * Generates attachment meta data and create image sub-sizes for images.
 746   *
 747   * @since 2.1.0
 748   * @since 6.0.0 The `$filesize` value was added to the returned array.
 749   *
 750   * @param int    $attachment_id Attachment ID to process.
 751   * @param string $file          Filepath of the attached image.
 752   * @return array Metadata for attachment.
 753   */
 754  function wp_generate_attachment_metadata( $attachment_id, $file ) {
 755      $attachment = get_post( $attachment_id );
 756  
 757      $metadata  = array();
 758      $support   = false;
 759      $mime_type = get_post_mime_type( $attachment );
 760  
 761      if ( preg_match( '!^image/!', $mime_type ) && file_is_displayable_image( $file ) ) {
 762          // Make thumbnails and other intermediate sizes.
 763          $metadata = wp_create_image_subsizes( $file, $attachment_id );
 764      } elseif ( wp_attachment_is( 'video', $attachment ) ) {
 765          $metadata = wp_read_video_metadata( $file );
 766          $support  = current_theme_supports( 'post-thumbnails', 'attachment:video' ) || post_type_supports( 'attachment:video', 'thumbnail' );
 767      } elseif ( wp_attachment_is( 'audio', $attachment ) ) {
 768          $metadata = wp_read_audio_metadata( $file );
 769          $support  = current_theme_supports( 'post-thumbnails', 'attachment:audio' ) || post_type_supports( 'attachment:audio', 'thumbnail' );
 770      }
 771  
 772      /*
 773       * wp_read_video_metadata() and wp_read_audio_metadata() return `false`
 774       * if the attachment does not exist in the local filesystem,
 775       * so make sure to convert the value to an array.
 776       */
 777      if ( ! is_array( $metadata ) ) {
 778          $metadata = array();
 779      }
 780  
 781      if ( $support && ! empty( $metadata['image']['data'] ) ) {
 782          // Check for existing cover.
 783          $hash   = md5( $metadata['image']['data'] );
 784          $posts  = get_posts(
 785              array(
 786                  'fields'         => 'ids',
 787                  'post_type'      => 'attachment',
 788                  'post_mime_type' => $metadata['image']['mime'],
 789                  'post_status'    => 'inherit',
 790                  'posts_per_page' => 1,
 791                  'meta_key'       => '_cover_hash',
 792                  'meta_value'     => $hash,
 793              )
 794          );
 795          $exists = reset( $posts );
 796  
 797          if ( ! empty( $exists ) ) {
 798              update_post_meta( $attachment_id, '_thumbnail_id', $exists );
 799          } else {
 800              $ext = '.jpg';
 801              switch ( $metadata['image']['mime'] ) {
 802                  case 'image/gif':
 803                      $ext = '.gif';
 804                      break;
 805                  case 'image/png':
 806                      $ext = '.png';
 807                      break;
 808                  case 'image/webp':
 809                      $ext = '.webp';
 810                      break;
 811              }
 812              $basename = str_replace( '.', '-', wp_basename( $file ) ) . '-image' . $ext;
 813              $uploaded = wp_upload_bits( $basename, '', $metadata['image']['data'] );
 814              if ( false === $uploaded['error'] ) {
 815                  $image_attachment = array(
 816                      'post_mime_type' => $metadata['image']['mime'],
 817                      'post_type'      => 'attachment',
 818                      'post_content'   => '',
 819                  );
 820                  /**
 821                   * Filters the parameters for the attachment thumbnail creation.
 822                   *
 823                   * @since 3.9.0
 824                   *
 825                   * @param array $image_attachment An array of parameters to create the thumbnail.
 826                   * @param array $metadata         Current attachment metadata.
 827                   * @param array $uploaded         {
 828                   *     Information about the newly-uploaded file.
 829                   *
 830                   *     @type string $file Filename of the newly-uploaded file.
 831                   *     @type string $url  URL of the uploaded file.
 832                   *     @type string $type File type.
 833                   * }
 834                   */
 835                  $image_attachment = apply_filters( 'attachment_thumbnail_args', $image_attachment, $metadata, $uploaded );
 836  
 837                  $sub_attachment_id = wp_insert_attachment( $image_attachment, $uploaded['file'] );
 838                  add_post_meta( $sub_attachment_id, '_cover_hash', $hash );
 839                  $attach_data = wp_generate_attachment_metadata( $sub_attachment_id, $uploaded['file'] );
 840                  wp_update_attachment_metadata( $sub_attachment_id, $attach_data );
 841                  update_post_meta( $attachment_id, '_thumbnail_id', $sub_attachment_id );
 842              }
 843          }
 844      } elseif ( 'application/pdf' === $mime_type ) {
 845          // Try to create image thumbnails for PDFs.
 846  
 847          $fallback_sizes = array(
 848              'thumbnail',
 849              'medium',
 850              'large',
 851          );
 852  
 853          /**
 854           * Filters the image sizes generated for non-image mime types.
 855           *
 856           * @since 4.7.0
 857           *
 858           * @param string[] $fallback_sizes An array of image size names.
 859           * @param array    $metadata       Current attachment metadata.
 860           */
 861          $fallback_sizes = apply_filters( 'fallback_intermediate_image_sizes', $fallback_sizes, $metadata );
 862  
 863          $registered_sizes = wp_get_registered_image_subsizes();
 864          $merged_sizes     = array_intersect_key( $registered_sizes, array_flip( $fallback_sizes ) );
 865  
 866          // Force thumbnails to be soft crops.
 867          if ( isset( $merged_sizes['thumbnail'] ) && is_array( $merged_sizes['thumbnail'] ) ) {
 868              $merged_sizes['thumbnail']['crop'] = false;
 869          }
 870  
 871          // Only load PDFs in an image editor if we're processing sizes.
 872          if ( ! empty( $merged_sizes ) ) {
 873              $editor = wp_get_image_editor( $file );
 874  
 875              if ( ! is_wp_error( $editor ) ) { // No support for this type of file.
 876                  /*
 877                   * PDFs may have the same file filename as JPEGs.
 878                   * Ensure the PDF preview image does not overwrite any JPEG images that already exist.
 879                   */
 880                  $dirname      = dirname( $file ) . '/';
 881                  $ext          = '.' . pathinfo( $file, PATHINFO_EXTENSION );
 882                  $preview_file = $dirname . wp_unique_filename( $dirname, wp_basename( $file, $ext ) . '-pdf.jpg' );
 883  
 884                  $uploaded = $editor->save( $preview_file, 'image/jpeg' );
 885                  unset( $editor );
 886  
 887                  // Resize based on the full size image, rather than the source.
 888                  if ( ! is_wp_error( $uploaded ) ) {
 889                      $image_file = $uploaded['path'];
 890                      unset( $uploaded['path'] );
 891  
 892                      $metadata['sizes'] = array(
 893                          'full' => $uploaded,
 894                      );
 895  
 896                      // Save the meta data before any image post-processing errors could happen.
 897                      wp_update_attachment_metadata( $attachment_id, $metadata );
 898  
 899                      // Create sub-sizes saving the image meta after each.
 900                      $metadata = _wp_make_subsizes( $merged_sizes, $image_file, $metadata, $attachment_id, '' );
 901                  }
 902              }
 903          }
 904      }
 905  
 906      // Remove the blob of binary data from the array.
 907      unset( $metadata['image']['data'] );
 908  
 909      // Capture file size for cases where it has not been captured yet, such as PDFs.
 910      if ( ! isset( $metadata['filesize'] ) && file_exists( $file ) ) {
 911          $metadata['filesize'] = wp_filesize( $file );
 912      }
 913  
 914      /**
 915       * Filters the generated attachment meta data.
 916       *
 917       * @since 2.1.0
 918       * @since 5.3.0 The `$context` parameter was added.
 919       *
 920       * @param array  $metadata      An array of attachment meta data.
 921       * @param int    $attachment_id Current attachment ID.
 922       * @param string $context       Additional context. Can be 'create' when metadata was initially created for new attachment
 923       *                              or 'update' when the metadata was updated.
 924       */
 925      return apply_filters( 'wp_generate_attachment_metadata', $metadata, $attachment_id, 'create' );
 926  }
 927  
 928  /**
 929   * Converts a fraction string to a decimal.
 930   *
 931   * @since 2.5.0
 932   *
 933   * @param string $str Fraction string.
 934   * @return int|float Returns calculated fraction or integer 0 on invalid input.
 935   */
 936  function wp_exif_frac2dec( $str ) {
 937      if ( ! is_scalar( $str ) || is_bool( $str ) ) {
 938          return 0;
 939      }
 940  
 941      if ( ! is_string( $str ) ) {
 942          return $str; // This can only be an integer or float, so this is fine.
 943      }
 944  
 945      // Fractions passed as a string must contain a single `/`.
 946      if ( substr_count( $str, '/' ) !== 1 ) {
 947          if ( is_numeric( $str ) ) {
 948              return (float) $str;
 949          }
 950  
 951          return 0;
 952      }
 953  
 954      list( $numerator, $denominator ) = explode( '/', $str );
 955  
 956      // Both the numerator and the denominator must be numbers.
 957      if ( ! is_numeric( $numerator ) || ! is_numeric( $denominator ) ) {
 958          return 0;
 959      }
 960  
 961      // The denominator must not be zero.
 962      if ( 0 == $denominator ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison -- Deliberate loose comparison.
 963          return 0;
 964      }
 965  
 966      return $numerator / $denominator;
 967  }
 968  
 969  /**
 970   * Converts the exif date format to a unix timestamp.
 971   *
 972   * @since 2.5.0
 973   *
 974   * @param string $str A date string expected to be in Exif format (Y:m:d H:i:s).
 975   * @return int|false The unix timestamp, or false on failure.
 976   */
 977  function wp_exif_date2ts( $str ) {
 978      list( $date, $time ) = explode( ' ', trim( $str ) );
 979      list( $y, $m, $d )   = explode( ':', $date );
 980  
 981      return strtotime( "{$y}-{$m}-{$d} {$time}" );
 982  }
 983  
 984  /**
 985   * Gets extended image metadata, exif or iptc as available.
 986   *
 987   * Retrieves the EXIF metadata aperture, credit, camera, caption, copyright, iso
 988   * created_timestamp, focal_length, shutter_speed, and title.
 989   *
 990   * The IPTC metadata that is retrieved is APP13, credit, byline, created date
 991   * and time, caption, copyright, and title. Also includes FNumber, Model,
 992   * DateTimeDigitized, FocalLength, ISOSpeedRatings, and ExposureTime.
 993   *
 994   * @todo Try other exif libraries if available.
 995   * @since 2.5.0
 996   *
 997   * @param string $file
 998   * @return array|false Image metadata array on success, false on failure.
 999   */
1000  function wp_read_image_metadata( $file ) {
1001      if ( ! file_exists( $file ) ) {
1002          return false;
1003      }
1004  
1005      list( , , $image_type ) = wp_getimagesize( $file );
1006  
1007      /*
1008       * EXIF contains a bunch of data we'll probably never need formatted in ways
1009       * that are difficult to use. We'll normalize it and just extract the fields
1010       * that are likely to be useful. Fractions and numbers are converted to
1011       * floats, dates to unix timestamps, and everything else to strings.
1012       */
1013      $meta = array(
1014          'aperture'          => 0,
1015          'credit'            => '',
1016          'camera'            => '',
1017          'caption'           => '',
1018          'created_timestamp' => 0,
1019          'copyright'         => '',
1020          'focal_length'      => 0,
1021          'iso'               => 0,
1022          'shutter_speed'     => 0,
1023          'title'             => '',
1024          'orientation'       => 0,
1025          'keywords'          => array(),
1026      );
1027  
1028      $iptc = array();
1029      $info = array();
1030      /*
1031       * Read IPTC first, since it might contain data not available in exif such
1032       * as caption, description etc.
1033       */
1034      if ( is_callable( 'iptcparse' ) ) {
1035          wp_getimagesize( $file, $info );
1036  
1037          if ( ! empty( $info['APP13'] ) ) {
1038              // Don't silence errors when in debug mode, unless running unit tests.
1039              if ( defined( 'WP_DEBUG' ) && WP_DEBUG
1040                  && ! defined( 'WP_RUN_CORE_TESTS' )
1041              ) {
1042                  $iptc = iptcparse( $info['APP13'] );
1043              } else {
1044                  // phpcs:ignore WordPress.PHP.NoSilencedErrors -- Silencing notice and warning is intentional. See https://core.trac.wordpress.org/ticket/42480
1045                  $iptc = @iptcparse( $info['APP13'] );
1046              }
1047  
1048              if ( ! is_array( $iptc ) ) {
1049                  $iptc = array();
1050              }
1051  
1052              // Headline, "A brief synopsis of the caption".
1053              if ( ! empty( $iptc['2#105'][0] ) ) {
1054                  $meta['title'] = trim( $iptc['2#105'][0] );
1055                  /*
1056                  * Title, "Many use the Title field to store the filename of the image,
1057                  * though the field may be used in many ways".
1058                  */
1059              } elseif ( ! empty( $iptc['2#005'][0] ) ) {
1060                  $meta['title'] = trim( $iptc['2#005'][0] );
1061              }
1062  
1063              if ( ! empty( $iptc['2#120'][0] ) ) { // Description / legacy caption.
1064                  $caption = trim( $iptc['2#120'][0] );
1065  
1066                  mbstring_binary_safe_encoding();
1067                  $caption_length = strlen( $caption );
1068                  reset_mbstring_encoding();
1069  
1070                  if ( empty( $meta['title'] ) && $caption_length < 80 ) {
1071                      // Assume the title is stored in 2:120 if it's short.
1072                      $meta['title'] = $caption;
1073                  }
1074  
1075                  $meta['caption'] = $caption;
1076              }
1077  
1078              if ( ! empty( $iptc['2#110'][0] ) ) { // Credit.
1079                  $meta['credit'] = trim( $iptc['2#110'][0] );
1080              } elseif ( ! empty( $iptc['2#080'][0] ) ) { // Creator / legacy byline.
1081                  $meta['credit'] = trim( $iptc['2#080'][0] );
1082              }
1083  
1084              if ( ! empty( $iptc['2#055'][0] ) && ! empty( $iptc['2#060'][0] ) ) { // Created date and time.
1085                  $meta['created_timestamp'] = strtotime( $iptc['2#055'][0] . ' ' . $iptc['2#060'][0] );
1086              }
1087  
1088              if ( ! empty( $iptc['2#116'][0] ) ) { // Copyright.
1089                  $meta['copyright'] = trim( $iptc['2#116'][0] );
1090              }
1091  
1092              if ( ! empty( $iptc['2#025'][0] ) ) { // Keywords array.
1093                  $meta['keywords'] = array_values( $iptc['2#025'] );
1094              }
1095          }
1096      }
1097  
1098      $exif = array();
1099  
1100      /**
1101       * Filters the image types to check for exif data.
1102       *
1103       * @since 2.5.0
1104       *
1105       * @param int[] $image_types Array of image types to check for exif data. Each value
1106       *                           is usually one of the `IMAGETYPE_*` constants.
1107       */
1108      $exif_image_types = apply_filters( 'wp_read_image_metadata_types', array( IMAGETYPE_JPEG, IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM ) );
1109  
1110      if ( is_callable( 'exif_read_data' ) && in_array( $image_type, $exif_image_types, true ) ) {
1111          // Don't silence errors when in debug mode, unless running unit tests.
1112          if ( defined( 'WP_DEBUG' ) && WP_DEBUG
1113              && ! defined( 'WP_RUN_CORE_TESTS' )
1114          ) {
1115              $exif = exif_read_data( $file );
1116          } else {
1117              // phpcs:ignore WordPress.PHP.NoSilencedErrors -- Silencing notice and warning is intentional. See https://core.trac.wordpress.org/ticket/42480
1118              $exif = @exif_read_data( $file );
1119          }
1120  
1121          if ( ! is_array( $exif ) ) {
1122              $exif = array();
1123          }
1124  
1125          if ( ! empty( $exif['ImageDescription'] ) ) {
1126              mbstring_binary_safe_encoding();
1127              $description_length = strlen( $exif['ImageDescription'] );
1128              reset_mbstring_encoding();
1129  
1130              if ( empty( $meta['title'] ) && $description_length < 80 ) {
1131                  // Assume the title is stored in ImageDescription.
1132                  $meta['title'] = trim( $exif['ImageDescription'] );
1133              }
1134  
1135              if ( empty( $meta['caption'] ) && ! empty( $exif['COMPUTED']['UserComment'] ) ) {
1136                  $meta['caption'] = trim( $exif['COMPUTED']['UserComment'] );
1137              }
1138  
1139              if ( empty( $meta['caption'] ) ) {
1140                  $meta['caption'] = trim( $exif['ImageDescription'] );
1141              }
1142          } elseif ( empty( $meta['caption'] ) && ! empty( $exif['Comments'] ) ) {
1143              $meta['caption'] = trim( $exif['Comments'] );
1144          }
1145  
1146          if ( empty( $meta['credit'] ) ) {
1147              if ( ! empty( $exif['Artist'] ) ) {
1148                  $meta['credit'] = trim( $exif['Artist'] );
1149              } elseif ( ! empty( $exif['Author'] ) ) {
1150                  $meta['credit'] = trim( $exif['Author'] );
1151              }
1152          }
1153  
1154          if ( empty( $meta['copyright'] ) && ! empty( $exif['Copyright'] ) ) {
1155              $meta['copyright'] = trim( $exif['Copyright'] );
1156          }
1157          if ( ! empty( $exif['FNumber'] ) && is_scalar( $exif['FNumber'] ) ) {
1158              $meta['aperture'] = round( wp_exif_frac2dec( $exif['FNumber'] ), 2 );
1159          }
1160          if ( ! empty( $exif['Model'] ) ) {
1161              $meta['camera'] = trim( $exif['Model'] );
1162          }
1163          if ( empty( $meta['created_timestamp'] ) && ! empty( $exif['DateTimeDigitized'] ) ) {
1164              $meta['created_timestamp'] = wp_exif_date2ts( $exif['DateTimeDigitized'] );
1165          }
1166          if ( ! empty( $exif['FocalLength'] ) ) {
1167              $meta['focal_length'] = (string) $exif['FocalLength'];
1168              if ( is_scalar( $exif['FocalLength'] ) ) {
1169                  $meta['focal_length'] = (string) wp_exif_frac2dec( $exif['FocalLength'] );
1170              }
1171          }
1172          if ( ! empty( $exif['ISOSpeedRatings'] ) ) {
1173              $meta['iso'] = is_array( $exif['ISOSpeedRatings'] ) ? reset( $exif['ISOSpeedRatings'] ) : $exif['ISOSpeedRatings'];
1174              $meta['iso'] = trim( $meta['iso'] );
1175          }
1176          if ( ! empty( $exif['ExposureTime'] ) ) {
1177              $meta['shutter_speed'] = (string) $exif['ExposureTime'];
1178              if ( is_scalar( $exif['ExposureTime'] ) ) {
1179                  $meta['shutter_speed'] = (string) wp_exif_frac2dec( $exif['ExposureTime'] );
1180              }
1181          }
1182          if ( ! empty( $exif['Orientation'] ) ) {
1183              $meta['orientation'] = $exif['Orientation'];
1184          }
1185      }
1186  
1187      foreach ( array( 'title', 'caption', 'credit', 'copyright', 'camera', 'iso' ) as $key ) {
1188          if ( $meta[ $key ] && ! seems_utf8( $meta[ $key ] ) ) {
1189              $meta[ $key ] = utf8_encode( $meta[ $key ] );
1190          }
1191      }
1192  
1193      foreach ( $meta['keywords'] as $key => $keyword ) {
1194          if ( ! seems_utf8( $keyword ) ) {
1195              $meta['keywords'][ $key ] = utf8_encode( $keyword );
1196          }
1197      }
1198  
1199      $meta = wp_kses_post_deep( $meta );
1200  
1201      /**
1202       * Filters the array of meta data read from an image's exif data.
1203       *
1204       * @since 2.5.0
1205       * @since 4.4.0 The `$iptc` parameter was added.
1206       * @since 5.0.0 The `$exif` parameter was added.
1207       *
1208       * @param array  $meta       Image meta data.
1209       * @param string $file       Path to image file.
1210       * @param int    $image_type Type of image, one of the `IMAGETYPE_XXX` constants.
1211       * @param array  $iptc       IPTC data.
1212       * @param array  $exif       EXIF data.
1213       */
1214      return apply_filters( 'wp_read_image_metadata', $meta, $file, $image_type, $iptc, $exif );
1215  
1216  }
1217  
1218  /**
1219   * Validates that file is an image.
1220   *
1221   * @since 2.5.0
1222   *
1223   * @param string $path File path to test if valid image.
1224   * @return bool True if valid image, false if not valid image.
1225   */
1226  function file_is_valid_image( $path ) {
1227      $size = wp_getimagesize( $path );
1228      return ! empty( $size );
1229  }
1230  
1231  /**
1232   * Validates that file is suitable for displaying within a web page.
1233   *
1234   * @since 2.5.0
1235   *
1236   * @param string $path File path to test.
1237   * @return bool True if suitable, false if not suitable.
1238   */
1239  function file_is_displayable_image( $path ) {
1240      $displayable_image_types = array( IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_BMP, IMAGETYPE_ICO, IMAGETYPE_WEBP );
1241  
1242      $info = wp_getimagesize( $path );
1243      if ( empty( $info ) ) {
1244          $result = false;
1245      } elseif ( ! in_array( $info[2], $displayable_image_types, true ) ) {
1246          $result = false;
1247      } else {
1248          $result = true;
1249      }
1250  
1251      /**
1252       * Filters whether the current image is displayable in the browser.
1253       *
1254       * @since 2.5.0
1255       *
1256       * @param bool   $result Whether the image can be displayed. Default true.
1257       * @param string $path   Path to the image.
1258       */
1259      return apply_filters( 'file_is_displayable_image', $result, $path );
1260  }
1261  
1262  /**
1263   * Loads an image resource for editing.
1264   *
1265   * @since 2.9.0
1266   *
1267   * @param int          $attachment_id Attachment ID.
1268   * @param string       $mime_type     Image mime type.
1269   * @param string|int[] $size          Optional. Image size. Accepts any registered image size name, or an array
1270   *                                    of width and height values in pixels (in that order). Default 'full'.
1271   * @return resource|GdImage|false The resulting image resource or GdImage instance on success,
1272   *                                false on failure.
1273   */
1274  function load_image_to_edit( $attachment_id, $mime_type, $size = 'full' ) {
1275      $filepath = _load_image_to_edit_path( $attachment_id, $size );
1276      if ( empty( $filepath ) ) {
1277          return false;
1278      }
1279  
1280      switch ( $mime_type ) {
1281          case 'image/jpeg':
1282              $image = imagecreatefromjpeg( $filepath );
1283              break;
1284          case 'image/png':
1285              $image = imagecreatefrompng( $filepath );
1286              break;
1287          case 'image/gif':
1288              $image = imagecreatefromgif( $filepath );
1289              break;
1290          case 'image/webp':
1291              $image = false;
1292              if ( function_exists( 'imagecreatefromwebp' ) ) {
1293                  $image = imagecreatefromwebp( $filepath );
1294              }
1295              break;
1296          default:
1297              $image = false;
1298              break;
1299      }
1300  
1301      if ( is_gd_image( $image ) ) {
1302          /**
1303           * Filters the current image being loaded for editing.
1304           *
1305           * @since 2.9.0
1306           *
1307           * @param resource|GdImage $image         Current image.
1308           * @param int              $attachment_id Attachment ID.
1309           * @param string|int[]     $size          Requested image size. Can be any registered image size name, or
1310           *                                        an array of width and height values in pixels (in that order).
1311           */
1312          $image = apply_filters( 'load_image_to_edit', $image, $attachment_id, $size );
1313  
1314          if ( function_exists( 'imagealphablending' ) && function_exists( 'imagesavealpha' ) ) {
1315              imagealphablending( $image, false );
1316              imagesavealpha( $image, true );
1317          }
1318      }
1319  
1320      return $image;
1321  }
1322  
1323  /**
1324   * Retrieves the path or URL of an attachment's attached file.
1325   *
1326   * If the attached file is not present on the local filesystem (usually due to replication plugins),
1327   * then the URL of the file is returned if `allow_url_fopen` is supported.
1328   *
1329   * @since 3.4.0
1330   * @access private
1331   *
1332   * @param int          $attachment_id Attachment ID.
1333   * @param string|int[] $size          Optional. Image size. Accepts any registered image size name, or an array
1334   *                                    of width and height values in pixels (in that order). Default 'full'.
1335   * @return string|false File path or URL on success, false on failure.
1336   */
1337  function _load_image_to_edit_path( $attachment_id, $size = 'full' ) {
1338      $filepath = get_attached_file( $attachment_id );
1339  
1340      if ( $filepath && file_exists( $filepath ) ) {
1341          if ( 'full' !== $size ) {
1342              $data = image_get_intermediate_size( $attachment_id, $size );
1343  
1344              if ( $data ) {
1345                  $filepath = path_join( dirname( $filepath ), $data['file'] );
1346  
1347                  /**
1348                   * Filters the path to an attachment's file when editing the image.
1349                   *
1350                   * The filter is evaluated for all image sizes except 'full'.
1351                   *
1352                   * @since 3.1.0
1353                   *
1354                   * @param string       $path          Path to the current image.
1355                   * @param int          $attachment_id Attachment ID.
1356                   * @param string|int[] $size          Requested image size. Can be any registered image size name, or
1357                   *                                    an array of width and height values in pixels (in that order).
1358                   */
1359                  $filepath = apply_filters( 'load_image_to_edit_filesystempath', $filepath, $attachment_id, $size );
1360              }
1361          }
1362      } elseif ( function_exists( 'fopen' ) && ini_get( 'allow_url_fopen' ) ) {
1363          /**
1364           * Filters the path to an attachment's URL when editing the image.
1365           *
1366           * The filter is only evaluated if the file isn't stored locally and `allow_url_fopen` is enabled on the server.
1367           *
1368           * @since 3.1.0
1369           *
1370           * @param string|false $image_url     Current image URL.
1371           * @param int          $attachment_id Attachment ID.
1372           * @param string|int[] $size          Requested image size. Can be any registered image size name, or
1373           *                                    an array of width and height values in pixels (in that order).
1374           */
1375          $filepath = apply_filters( 'load_image_to_edit_attachmenturl', wp_get_attachment_url( $attachment_id ), $attachment_id, $size );
1376      }
1377  
1378      /**
1379       * Filters the returned path or URL of the current image.
1380       *
1381       * @since 2.9.0
1382       *
1383       * @param string|false $filepath      File path or URL to current image, or false.
1384       * @param int          $attachment_id Attachment ID.
1385       * @param string|int[] $size          Requested image size. Can be any registered image size name, or
1386       *                                    an array of width and height values in pixels (in that order).
1387       */
1388      return apply_filters( 'load_image_to_edit_path', $filepath, $attachment_id, $size );
1389  }
1390  
1391  /**
1392   * Copies an existing image file.
1393   *
1394   * @since 3.4.0
1395   * @access private
1396   *
1397   * @param int $attachment_id Attachment ID.
1398   * @return string|false New file path on success, false on failure.
1399   */
1400  function _copy_image_file( $attachment_id ) {
1401      $dst_file = get_attached_file( $attachment_id );
1402      $src_file = $dst_file;
1403  
1404      if ( ! file_exists( $src_file ) ) {
1405          $src_file = _load_image_to_edit_path( $attachment_id );
1406      }
1407  
1408      if ( $src_file ) {
1409          $dst_file = str_replace( wp_basename( $dst_file ), 'copy-' . wp_basename( $dst_file ), $dst_file );
1410          $dst_file = dirname( $dst_file ) . '/' . wp_unique_filename( dirname( $dst_file ), wp_basename( $dst_file ) );
1411  
1412          /*
1413           * The directory containing the original file may no longer
1414           * exist when using a replication plugin.
1415           */
1416          wp_mkdir_p( dirname( $dst_file ) );
1417  
1418          if ( ! copy( $src_file, $dst_file ) ) {
1419              $dst_file = false;
1420          }
1421      } else {
1422          $dst_file = false;
1423      }
1424  
1425      return $dst_file;
1426  }
1427  
1428  /**
1429   * Returns an array with the list of valid mime types that a specific mime type should be converted into.
1430   * For example an `image/jpeg` should be converted into an `image/jpeg` and `image/webp`. The first type
1431   * is considered the primary output type for this image.
1432   *
1433   * Called for each uploaded image to determine the list of mime types that should be converted into. Then,
1434   * called again for each image size as they are generated to check if the image should be converted into the mime type
1435   * for that size.
1436   *
1437   * @since 6.1.0
1438   *
1439   * @param int    $attachment_id The attachment ID.
1440   * @return array An array of valid mime types, where the key is the source file mime type and the list of mime types to
1441   *               generate.
1442   */
1443  function wp_upload_image_mime_transforms( $attachment_id ) {
1444      $default_image_mime_transforms = array(
1445          'image/jpeg' => array( 'image/jpeg', 'image/webp' ),
1446          'image/webp' => array( 'image/webp', 'image/jpeg' ),
1447      );
1448      $image_mime_transforms         = $default_image_mime_transforms;
1449  
1450      /**
1451       * Filters the output mime types for a given input mime type and image size.
1452       *
1453       * @since 6.1.0
1454       *
1455       * @param array $image_mime_transforms A map with the valid mime transforms where the key is the source file mime type
1456       *                                      and the value is one or more mime file types to generate.
1457       * @param int   $attachment_id         The ID of the attachment where the hook was dispatched.
1458       */
1459      $image_mime_transforms = apply_filters( 'wp_upload_image_mime_transforms', $image_mime_transforms, $attachment_id );
1460  
1461      if ( ! is_array( $image_mime_transforms ) ) {
1462          return $default_image_mime_transforms;
1463      }
1464  
1465      return array_map(
1466          function( $transforms_list ) {
1467              return (array) $transforms_list;
1468          },
1469          $image_mime_transforms
1470      );
1471  }
1472  
1473  /**
1474   * Extracts the primary and additional mime output types for an image from the $image_mime_transforms.
1475   *
1476   * @since 6.1.0
1477   * @access private
1478   *
1479   * @param string $file          Full path to the image file.
1480   * @param int    $attachment_id Attachment ID to process.
1481   * @return array An array with two entries, the primary mime type and the list of additional mime types.
1482   */
1483  function _wp_get_primary_and_additional_mime_types( $file, $attachment_id ) {
1484      $image_mime_transforms = wp_upload_image_mime_transforms( $attachment_id );
1485      $original_mime_type    = wp_get_image_mime( $file );
1486      $output_mime_types     = isset( $image_mime_transforms[ $original_mime_type ] ) ? $image_mime_transforms[ $original_mime_type ] : array( $original_mime_type );
1487  
1488      // Exclude any output mime types that the system doesn't support.
1489      $output_mime_types = array_values(
1490          array_filter(
1491              $output_mime_types,
1492              function( $mime_type ) {
1493                  return wp_image_editor_supports(
1494                      array(
1495                          'mime_type' => $mime_type,
1496                      )
1497                  );
1498              }
1499          )
1500      );
1501  
1502      // Handle an empty value for $output_mime_types: only output the original type.
1503      if ( empty( $output_mime_types ) ) {
1504          return array( $original_mime_type, array() );
1505      }
1506  
1507      // Use original mime type as primary mime type, or alternatively the first one.
1508      $primary_mime_type_key = array_search( $original_mime_type, $output_mime_types, true );
1509      if ( false === $primary_mime_type_key ) {
1510          $primary_mime_type_key = 0;
1511      }
1512      // Split output mime types into primary mime type and additional mime types.
1513      $additional_mime_types     = $output_mime_types;
1514      list( $primary_mime_type ) = array_splice( $additional_mime_types, $primary_mime_type_key, 1 );
1515  
1516      return array(
1517          $primary_mime_type,
1518          $additional_mime_types,
1519      );
1520  }


Generated : Fri Aug 19 08:20:02 2022 Cross-referenced by PHPXref