[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

   1  <?php
   2  /**
   3   * WordPress Image Editor
   4   *
   5   * @package WordPress
   6   * @subpackage Administration
   7   */
   8  
   9  /**
  10   * Loads the WP image-editing interface.
  11   *
  12   * @since 2.9.0
  13   *
  14   * @param int          $post_id Attachment post ID.
  15   * @param false|object $msg     Optional. Message to display for image editor updates or errors.
  16   *                              Default false.
  17   */
  18  function wp_image_editor( $post_id, $msg = false ) {
  19      $nonce     = wp_create_nonce( "image_editor-$post_id" );
  20      $meta      = wp_get_attachment_metadata( $post_id );
  21      $thumb     = image_get_intermediate_size( $post_id, 'thumbnail' );
  22      $sub_sizes = isset( $meta['sizes'] ) && is_array( $meta['sizes'] );
  23      $note      = '';
  24  
  25      if ( isset( $meta['width'], $meta['height'] ) ) {
  26          $big = max( $meta['width'], $meta['height'] );
  27      } else {
  28          die( __( 'Image data does not exist. Please re-upload the image.' ) );
  29      }
  30  
  31      $sizer = $big > 600 ? 600 / $big : 1;
  32  
  33      $backup_sizes = get_post_meta( $post_id, '_wp_attachment_backup_sizes', true );
  34      $can_restore  = false;
  35  
  36      if ( ! empty( $backup_sizes ) && isset( $backup_sizes['full-orig'], $meta['file'] ) ) {
  37          $can_restore = wp_basename( $meta['file'] ) !== $backup_sizes['full-orig']['file'];
  38      }
  39  
  40      if ( $msg ) {
  41          if ( isset( $msg->error ) ) {
  42              $note = "<div class='notice notice-error' role='alert'><p>$msg->error</p></div>";
  43          } elseif ( isset( $msg->msg ) ) {
  44              $note = "<div class='notice notice-success' role='alert'><p>$msg->msg</p></div>";
  45          }
  46      }
  47  
  48      /**
  49       * Shows the settings in the Image Editor that allow selecting to edit only the thumbnail of an image.
  50       *
  51       * @since 6.3.0
  52       *
  53       * @param bool $show Whether to show the settings in the Image Editor. Default false.
  54       */
  55      $edit_thumbnails_separately = (bool) apply_filters( 'image_edit_thumbnails_separately', false );
  56  
  57      ?>
  58      <div class="imgedit-wrap wp-clearfix">
  59      <div id="imgedit-panel-<?php echo $post_id; ?>">
  60      <?php echo $note; ?>
  61      <div class="imgedit-panel-content imgedit-panel-tools wp-clearfix">
  62          <div class="imgedit-menu wp-clearfix">
  63              <button type="button" onclick="imageEdit.toggleCropTool( <?php echo "$post_id, '$nonce'"; ?>, this );" aria-expanded="false" aria-controls="imgedit-crop" class="imgedit-crop button disabled" disabled><?php esc_html_e( 'Crop' ); ?></button>
  64              <button type="button" class="imgedit-scale button" onclick="imageEdit.toggleControls(this);" aria-expanded="false" aria-controls="imgedit-scale"><?php echo esc_html_x( 'Scale', 'verb' ); ?></button>
  65              <div class="imgedit-rotate-menu-container">
  66                  <button type="button" aria-controls="imgedit-rotate-menu" class="imgedit-rotate button" aria-expanded="false" onclick="imageEdit.togglePopup(this)" onblur="imageEdit.monitorPopup()"><?php esc_html_e( 'Image Rotation' ); ?></button>
  67                  <div id="imgedit-rotate-menu" class="imgedit-popup-menu">
  68              <?php
  69              // On some setups GD library does not provide imagerotate() - Ticket #11536.
  70              if ( wp_image_editor_supports(
  71                  array(
  72                      'mime_type' => get_post_mime_type( $post_id ),
  73                      'methods'   => array( 'rotate' ),
  74                  )
  75              ) ) {
  76                  $note_no_rotate = '';
  77                  ?>
  78                      <button type="button" class="imgedit-rleft button" onkeydown="imageEdit.browsePopup(event, this)" onclick="imageEdit.rotate( 90, <?php echo "$post_id, '$nonce'"; ?>, this)" onblur="imageEdit.monitorPopup()"><?php esc_html_e( 'Rotate 90&deg; left' ); ?></button>
  79                      <button type="button" class="imgedit-rright button" onkeydown="imageEdit.browsePopup(event, this)" onclick="imageEdit.rotate(-90, <?php echo "$post_id, '$nonce'"; ?>, this)" onblur="imageEdit.monitorPopup()"><?php esc_html_e( 'Rotate 90&deg; right' ); ?></button>
  80                      <button type="button" class="imgedit-rfull button" onkeydown="imageEdit.browsePopup(event, this)" onclick="imageEdit.rotate(180, <?php echo "$post_id, '$nonce'"; ?>, this)" onblur="imageEdit.monitorPopup()"><?php esc_html_e( 'Rotate 180&deg;' ); ?></button>
  81                  <?php
  82              } else {
  83                  $note_no_rotate = '<p class="note-no-rotate"><em>' . __( 'Image rotation is not supported by your web host.' ) . '</em></p>';
  84                  ?>
  85                      <button type="button" class="imgedit-rleft button disabled" disabled></button>
  86                      <button type="button" class="imgedit-rright button disabled" disabled></button>
  87                  <?php
  88              }
  89              ?>
  90                      <hr />
  91                      <button type="button" onkeydown="imageEdit.browsePopup(event, this)" onclick="imageEdit.flip(1, <?php echo "$post_id, '$nonce'"; ?>, this)" onblur="imageEdit.monitorPopup()" class="imgedit-flipv button"><?php esc_html_e( 'Flip vertical' ); ?></button>
  92                      <button type="button" onkeydown="imageEdit.browsePopup(event, this)" onclick="imageEdit.flip(2, <?php echo "$post_id, '$nonce'"; ?>, this)" onblur="imageEdit.monitorPopup()" class="imgedit-fliph button"><?php esc_html_e( 'Flip horizontal' ); ?></button>
  93                      <?php echo $note_no_rotate; ?>
  94                  </div>
  95              </div>
  96          </div>
  97          <div class="imgedit-submit imgedit-menu">
  98              <button type="button" id="image-undo-<?php echo $post_id; ?>" onclick="imageEdit.undo(<?php echo "$post_id, '$nonce'"; ?>, this)" class="imgedit-undo button disabled" disabled><?php esc_html_e( 'Undo' ); ?></button>
  99              <button type="button" id="image-redo-<?php echo $post_id; ?>" onclick="imageEdit.redo(<?php echo "$post_id, '$nonce'"; ?>, this)" class="imgedit-redo button disabled" disabled><?php esc_html_e( 'Redo' ); ?></button>
 100              <button type="button" onclick="imageEdit.close(<?php echo $post_id; ?>, 1)" class="button imgedit-cancel-btn"><?php esc_html_e( 'Cancel Editing' ); ?></button>
 101              <button type="button" onclick="imageEdit.save(<?php echo "$post_id, '$nonce'"; ?>)" disabled="disabled" class="button button-primary imgedit-submit-btn"><?php esc_html_e( 'Save Edits' ); ?></button>
 102          </div>
 103      </div>
 104  
 105      <div class="imgedit-panel-content wp-clearfix">
 106          <div class="imgedit-tools">
 107              <input type="hidden" id="imgedit-nonce-<?php echo $post_id; ?>" value="<?php echo $nonce; ?>" />
 108              <input type="hidden" id="imgedit-sizer-<?php echo $post_id; ?>" value="<?php echo $sizer; ?>" />
 109              <input type="hidden" id="imgedit-history-<?php echo $post_id; ?>" value="" />
 110              <input type="hidden" id="imgedit-undone-<?php echo $post_id; ?>" value="0" />
 111              <input type="hidden" id="imgedit-selection-<?php echo $post_id; ?>" value="" />
 112              <input type="hidden" id="imgedit-x-<?php echo $post_id; ?>" value="<?php echo $meta['width'] ?? 0; ?>" />
 113              <input type="hidden" id="imgedit-y-<?php echo $post_id; ?>" value="<?php echo $meta['height'] ?? 0; ?>" />
 114  
 115              <div id="imgedit-crop-<?php echo $post_id; ?>" class="imgedit-crop-wrap">
 116              <div class="imgedit-crop-grid"></div>
 117              <img id="image-preview-<?php echo $post_id; ?>" onload="imageEdit.imgLoaded('<?php echo $post_id; ?>')"
 118                  src="<?php echo esc_url( admin_url( 'admin-ajax.php', 'relative' ) ) . '?action=imgedit-preview&amp;_ajax_nonce=' . $nonce . '&amp;postid=' . $post_id . '&amp;rand=' . rand( 1, 99999 ); ?>" alt="" />
 119              </div>
 120          </div>
 121          <div class="imgedit-settings">
 122              <div class="imgedit-tool-active">
 123                  <div class="imgedit-group">
 124                  <div id="imgedit-scale" tabindex="-1" class="imgedit-group-controls">
 125                      <div class="imgedit-group-top">
 126                          <h2><?php _e( 'Scale Image' ); ?></h2>
 127                          <button type="button" class="dashicons dashicons-editor-help imgedit-help-toggle" onclick="imageEdit.toggleHelp(this);" aria-expanded="false"><span class="screen-reader-text">
 128                          <?php
 129                          /* translators: Hidden accessibility text. */
 130                          esc_html_e( 'Scale Image Help' );
 131                          ?>
 132                          </span></button>
 133                          <div class="imgedit-help">
 134                          <p><?php _e( 'You can proportionally scale the original image. For best results, scaling should be done before you crop, flip, or rotate. Images can only be scaled down, not up.' ); ?></p>
 135                          </div>
 136                          <?php if ( isset( $meta['width'], $meta['height'] ) ) : ?>
 137                          <p>
 138                              <?php
 139                              printf(
 140                                  /* translators: %s: Image width and height in pixels. */
 141                                  __( 'Original dimensions %s' ),
 142                                  '<span class="imgedit-original-dimensions">' . $meta['width'] . ' &times; ' . $meta['height'] . '</span>'
 143                              );
 144                              ?>
 145                          </p>
 146                          <?php endif; ?>
 147                          <div class="imgedit-submit">
 148                          <fieldset class="imgedit-scale-controls">
 149                              <legend><?php _e( 'New dimensions:' ); ?></legend>
 150                              <div class="nowrap">
 151                              <label for="imgedit-scale-width-<?php echo $post_id; ?>" class="screen-reader-text">
 152                              <?php
 153                              /* translators: Hidden accessibility text. */
 154                              _e( 'Width' );
 155                              ?>
 156                              </label>
 157                              <input type="number" step="1" min="0" max="<?php echo $meta['width'] ?? ''; ?>" aria-describedby="imgedit-scale-warn-<?php echo $post_id; ?>"  id="imgedit-scale-width-<?php echo $post_id; ?>" onkeyup="imageEdit.scaleChanged(<?php echo $post_id; ?>, 1, this)" onblur="imageEdit.scaleChanged(<?php echo $post_id; ?>, 1, this)" value="<?php echo $meta['width'] ?? 0; ?>" />
 158                              <span class="imgedit-separator" aria-hidden="true">&times;</span>
 159                              <label for="imgedit-scale-height-<?php echo $post_id; ?>" class="screen-reader-text">
 160                              <?php
 161                              /* translators: Hidden accessibility text. */
 162                              _e( 'Height' );
 163                              ?>
 164                              </label>
 165                              <input type="number" step="1" min="0" max="<?php echo $meta['height'] ?? ''; ?>" aria-describedby="imgedit-scale-warn-<?php echo $post_id; ?>" id="imgedit-scale-height-<?php echo $post_id; ?>" onkeyup="imageEdit.scaleChanged(<?php echo $post_id; ?>, 0, this)" onblur="imageEdit.scaleChanged(<?php echo $post_id; ?>, 0, this)" value="<?php echo $meta['height'] ?? 0; ?>" />
 166                              <button id="imgedit-scale-button" type="button" onclick="imageEdit.action(<?php echo "$post_id, '$nonce'"; ?>, 'scale')" class="button button-primary"><?php echo esc_html_x( 'Scale', 'verb' ); ?></button>
 167                              </div>
 168                              <span class="imgedit-scale-warn" id="imgedit-scale-warn-<?php echo $post_id; ?>"><span class="dashicons dashicons-warning" aria-hidden="true"></span><?php esc_html_e( 'Images cannot be scaled to a size larger than the original.' ); ?></span>
 169                          </fieldset>
 170                          </div>
 171                      </div>
 172                  </div>
 173              </div>
 174  
 175          <?php if ( $can_restore ) { ?>
 176                  <div class="imgedit-group">
 177                  <div class="imgedit-group-top">
 178                      <h2><button type="button" onclick="imageEdit.toggleHelp(this);" class="button-link" aria-expanded="false"><?php _e( 'Restore original image' ); ?> <span class="dashicons dashicons-arrow-down imgedit-help-toggle"></span></button></h2>
 179                      <div class="imgedit-help imgedit-restore">
 180                      <p>
 181                      <?php
 182                      _e( 'Discard any changes and restore the original image.' );
 183                      if ( ! defined( 'IMAGE_EDIT_OVERWRITE' ) || ! IMAGE_EDIT_OVERWRITE ) {
 184                          echo ' ' . __( 'Previously edited copies of the image will not be deleted.' );
 185                      }
 186                      ?>
 187                      </p>
 188                      <div class="imgedit-submit">
 189                          <input type="button" onclick="imageEdit.action(<?php echo "$post_id, '$nonce'"; ?>, 'restore')" class="button button-primary" value="<?php esc_attr_e( 'Restore image' ); ?>" <?php echo $can_restore; ?> />
 190                      </div>
 191                  </div>
 192              </div>
 193              </div>
 194          <?php } ?>
 195              <div class="imgedit-group">
 196                  <div id="imgedit-crop" tabindex="-1" class="imgedit-group-controls">
 197                  <div class="imgedit-group-top">
 198                      <h2><?php _e( 'Crop Image' ); ?></h2>
 199                      <button type="button" class="dashicons dashicons-editor-help imgedit-help-toggle" onclick="imageEdit.toggleHelp(this);" aria-expanded="false"><span class="screen-reader-text">
 200                      <?php
 201                      /* translators: Hidden accessibility text. */
 202                      _e( 'Image Crop Help' );
 203                      ?>
 204                      </span></button>
 205                      <div class="imgedit-help">
 206                          <p><?php _e( 'To crop the image, click on it and drag to make your selection.' ); ?></p>
 207                          <p><strong><?php _e( 'Crop Aspect Ratio' ); ?></strong><br />
 208                          <?php _e( 'The aspect ratio is the relationship between the width and height. You can preserve the aspect ratio by holding down the shift key while resizing your selection. Use the input box to specify the aspect ratio, e.g. 1:1 (square), 4:3, 16:9, etc.' ); ?></p>
 209  
 210                          <p><strong><?php _e( 'Crop Selection' ); ?></strong><br />
 211                          <?php _e( 'Once you have made your selection, you can adjust it by entering the size in pixels. The minimum selection size is the thumbnail size as set in the Media settings.' ); ?></p>
 212                      </div>
 213                  </div>
 214                  <fieldset class="imgedit-crop-ratio">
 215                      <legend><?php _e( 'Aspect ratio:' ); ?></legend>
 216                      <div class="nowrap">
 217                      <label for="imgedit-crop-width-<?php echo $post_id; ?>" class="screen-reader-text">
 218                      <?php
 219                      /* translators: Hidden accessibility text. */
 220                      _e( 'crop ratio width' );
 221                      ?>
 222                      </label>
 223                      <input type="number" step="1" min="1" id="imgedit-crop-width-<?php echo $post_id; ?>" onkeyup="imageEdit.setRatioSelection(<?php echo $post_id; ?>, 0, this)" onblur="imageEdit.setRatioSelection(<?php echo $post_id; ?>, 0, this)" />
 224                      <span class="imgedit-separator" aria-hidden="true">:</span>
 225                      <label for="imgedit-crop-height-<?php echo $post_id; ?>" class="screen-reader-text">
 226                      <?php
 227                      /* translators: Hidden accessibility text. */
 228                      _e( 'crop ratio height' );
 229                      ?>
 230                      </label>
 231                      <input  type="number" step="1" min="0" id="imgedit-crop-height-<?php echo $post_id; ?>" onkeyup="imageEdit.setRatioSelection(<?php echo $post_id; ?>, 1, this)" onblur="imageEdit.setRatioSelection(<?php echo $post_id; ?>, 1, this)" />
 232                      </div>
 233                  </fieldset>
 234                  <fieldset id="imgedit-crop-sel-<?php echo $post_id; ?>" class="imgedit-crop-sel">
 235                      <legend><?php _e( 'Selection:' ); ?></legend>
 236                      <div class="nowrap">
 237                      <label for="imgedit-sel-width-<?php echo $post_id; ?>" class="screen-reader-text">
 238                      <?php
 239                      /* translators: Hidden accessibility text. */
 240                      _e( 'selection width' );
 241                      ?>
 242                      </label>
 243                      <input  type="number" step="1" min="0" id="imgedit-sel-width-<?php echo $post_id; ?>" onkeyup="imageEdit.setNumSelection(<?php echo $post_id; ?>, this)" onblur="imageEdit.setNumSelection(<?php echo $post_id; ?>, this)" />
 244                      <span class="imgedit-separator" aria-hidden="true">&times;</span>
 245                      <label for="imgedit-sel-height-<?php echo $post_id; ?>" class="screen-reader-text">
 246                      <?php
 247                      /* translators: Hidden accessibility text. */
 248                      _e( 'selection height' );
 249                      ?>
 250                      </label>
 251                      <input  type="number" step="1" min="0" id="imgedit-sel-height-<?php echo $post_id; ?>" onkeyup="imageEdit.setNumSelection(<?php echo $post_id; ?>, this)" onblur="imageEdit.setNumSelection(<?php echo $post_id; ?>, this)" />
 252                      </div>
 253                  </fieldset>
 254                  <fieldset id="imgedit-crop-sel-<?php echo $post_id; ?>" class="imgedit-crop-sel">
 255                      <legend><?php _e( 'Starting Coordinates:' ); ?></legend>
 256                      <div class="nowrap">
 257                      <label for="imgedit-start-x-<?php echo $post_id; ?>" class="screen-reader-text">
 258                      <?php
 259                      /* translators: Hidden accessibility text. */
 260                      _e( 'horizontal start position' );
 261                      ?>
 262                      </label>
 263                      <input  type="number" step="1" min="0" id="imgedit-start-x-<?php echo $post_id; ?>" onkeyup="imageEdit.setNumSelection(<?php echo $post_id; ?>, this)" onblur="imageEdit.setNumSelection(<?php echo $post_id; ?>, this)" value="0" />
 264                      <span class="imgedit-separator" aria-hidden="true">&times;</span>
 265                      <label for="imgedit-start-y-<?php echo $post_id; ?>" class="screen-reader-text">
 266                      <?php
 267                      /* translators: Hidden accessibility text. */
 268                      _e( 'vertical start position' );
 269                      ?>
 270                      </label>
 271                      <input  type="number" step="1" min="0" id="imgedit-start-y-<?php echo $post_id; ?>" onkeyup="imageEdit.setNumSelection(<?php echo $post_id; ?>, this)" onblur="imageEdit.setNumSelection(<?php echo $post_id; ?>, this)" value="0" />
 272                      </div>
 273                  </fieldset>
 274                  <div class="imgedit-crop-apply imgedit-menu container">
 275                      <button class="button button-primary" type="button" onclick="imageEdit.handleCropToolClick( <?php echo "$post_id, '$nonce'"; ?>, this );" class="imgedit-crop-apply button"><?php esc_html_e( 'Apply Crop' ); ?></button> <button type="button" onclick="imageEdit.handleCropToolClick( <?php echo "$post_id, '$nonce'"; ?>, this );" class="imgedit-crop-clear button" disabled="disabled"><?php esc_html_e( 'Clear Crop' ); ?></button>
 276                  </div>
 277              </div>
 278          </div>
 279      </div>
 280  
 281      <?php
 282      if ( $edit_thumbnails_separately && $thumb && $sub_sizes ) {
 283          $thumb_img = wp_constrain_dimensions( $thumb['width'], $thumb['height'], 160, 120 );
 284          ?>
 285  
 286      <div class="imgedit-group imgedit-applyto">
 287          <div class="imgedit-group-top">
 288              <h2><?php _e( 'Thumbnail Settings' ); ?></h2>
 289              <button type="button" class="dashicons dashicons-editor-help imgedit-help-toggle" onclick="imageEdit.toggleHelp(this);" aria-expanded="false"><span class="screen-reader-text">
 290              <?php
 291              /* translators: Hidden accessibility text. */
 292              esc_html_e( 'Thumbnail Settings Help' );
 293              ?>
 294              </span></button>
 295              <div class="imgedit-help">
 296              <p><?php _e( 'You can edit the image while preserving the thumbnail. For example, you may wish to have a square thumbnail that displays just a section of the image.' ); ?></p>
 297              </div>
 298          </div>
 299          <div class="imgedit-thumbnail-preview-group">
 300              <figure class="imgedit-thumbnail-preview">
 301                  <img src="<?php echo esc_url( $thumb['url'] ); ?>" width="<?php echo esc_attr( $thumb_img[0] ); ?>" height="<?php echo esc_attr( $thumb_img[1] ); ?>" class="imgedit-size-preview" alt="" draggable="false" />
 302                  <figcaption class="imgedit-thumbnail-preview-caption"><?php _e( 'Current thumbnail' ); ?></figcaption>
 303              </figure>
 304              <div id="imgedit-save-target-<?php echo $post_id; ?>" class="imgedit-save-target">
 305              <fieldset>
 306                  <legend><?php _e( 'Apply changes to:' ); ?></legend>
 307  
 308                  <span class="imgedit-label">
 309                      <input type="radio" id="imgedit-target-all" name="imgedit-target-<?php echo $post_id; ?>" value="all" checked="checked" />
 310                      <label for="imgedit-target-all"><?php _e( 'All image sizes' ); ?></label>
 311                  </span>
 312  
 313                  <span class="imgedit-label">
 314                      <input type="radio" id="imgedit-target-thumbnail" name="imgedit-target-<?php echo $post_id; ?>" value="thumbnail" />
 315                      <label for="imgedit-target-thumbnail"><?php _e( 'Thumbnail' ); ?></label>
 316                  </span>
 317  
 318                  <span class="imgedit-label">
 319                      <input type="radio" id="imgedit-target-nothumb" name="imgedit-target-<?php echo $post_id; ?>" value="nothumb" />
 320                      <label for="imgedit-target-nothumb"><?php _e( 'All sizes except thumbnail' ); ?></label>
 321                  </span>
 322  
 323                  </fieldset>
 324              </div>
 325          </div>
 326      </div>
 327      <?php } ?>
 328          </div>
 329      </div>
 330  
 331      </div>
 332  
 333      <div class="imgedit-wait" id="imgedit-wait-<?php echo $post_id; ?>"></div>
 334      <div class="hidden" id="imgedit-leaving-<?php echo $post_id; ?>"><?php _e( "There are unsaved changes that will be lost. 'OK' to continue, 'Cancel' to return to the Image Editor." ); ?></div>
 335      </div>
 336      <?php
 337  }
 338  
 339  /**
 340   * Streams image in WP_Image_Editor to browser.
 341   *
 342   * @since 2.9.0
 343   *
 344   * @param WP_Image_Editor $image         The image editor instance.
 345   * @param string          $mime_type     The mime type of the image.
 346   * @param int             $attachment_id The image's attachment post ID.
 347   * @return bool True on success, false on failure.
 348   */
 349  function wp_stream_image( $image, $mime_type, $attachment_id ) {
 350      if ( $image instanceof WP_Image_Editor ) {
 351  
 352          /**
 353           * Filters the WP_Image_Editor instance for the image to be streamed to the browser.
 354           *
 355           * @since 3.5.0
 356           *
 357           * @param WP_Image_Editor $image         The image editor instance.
 358           * @param int             $attachment_id The attachment post ID.
 359           */
 360          $image = apply_filters( 'image_editor_save_pre', $image, $attachment_id );
 361  
 362          if ( is_wp_error( $image->stream( $mime_type ) ) ) {
 363              return false;
 364          }
 365  
 366          return true;
 367      } else {
 368          /* translators: 1: $image, 2: WP_Image_Editor */
 369          _deprecated_argument( __FUNCTION__, '3.5.0', sprintf( __( '%1$s needs to be a %2$s object.' ), '$image', 'WP_Image_Editor' ) );
 370  
 371          /**
 372           * Filters the GD image resource to be streamed to the browser.
 373           *
 374           * @since 2.9.0
 375           * @deprecated 3.5.0 Use {@see 'image_editor_save_pre'} instead.
 376           *
 377           * @param resource|GdImage $image         Image resource to be streamed.
 378           * @param int              $attachment_id The attachment post ID.
 379           */
 380          $image = apply_filters_deprecated( 'image_save_pre', array( $image, $attachment_id ), '3.5.0', 'image_editor_save_pre' );
 381  
 382          switch ( $mime_type ) {
 383              case 'image/jpeg':
 384                  header( 'Content-Type: image/jpeg' );
 385                  return imagejpeg( $image, null, 90 );
 386              case 'image/png':
 387                  header( 'Content-Type: image/png' );
 388                  return imagepng( $image );
 389              case 'image/gif':
 390                  header( 'Content-Type: image/gif' );
 391                  return imagegif( $image );
 392              case 'image/webp':
 393                  if ( function_exists( 'imagewebp' ) ) {
 394                      header( 'Content-Type: image/webp' );
 395                      return imagewebp( $image, null, 90 );
 396                  }
 397                  return false;
 398              case 'image/avif':
 399                  if ( function_exists( 'imageavif' ) ) {
 400                      header( 'Content-Type: image/avif' );
 401                      return imageavif( $image, null, 90 );
 402                  }
 403                  return false;
 404              default:
 405                  return false;
 406          }
 407      }
 408  }
 409  
 410  /**
 411   * Saves image to file.
 412   *
 413   * @since 2.9.0
 414   * @since 3.5.0 The `$image` parameter expects a `WP_Image_Editor` instance.
 415   * @since 6.0.0 The `$filesize` value was added to the returned array.
 416   *
 417   * @param string          $filename  Name of the file to be saved.
 418   * @param WP_Image_Editor $image     The image editor instance.
 419   * @param string          $mime_type The mime type of the image.
 420   * @param int             $post_id   Attachment post ID.
 421   * @return array|WP_Error|bool {
 422   *     Array on success or WP_Error if the file failed to save.
 423   *     When called with a deprecated value for the `$image` parameter,
 424   *     i.e. a non-`WP_Image_Editor` image resource or `GdImage` instance,
 425   *     the function will return true on success, false on failure.
 426   *
 427   *     @type string $path      Path to the image file.
 428   *     @type string $file      Name of the image file.
 429   *     @type int    $width     Image width.
 430   *     @type int    $height    Image height.
 431   *     @type string $mime-type The mime type of the image.
 432   *     @type int    $filesize  File size of the image.
 433   * }
 434   */
 435  function wp_save_image_file( $filename, $image, $mime_type, $post_id ) {
 436      if ( $image instanceof WP_Image_Editor ) {
 437  
 438          /** This filter is documented in wp-admin/includes/image-edit.php */
 439          $image = apply_filters( 'image_editor_save_pre', $image, $post_id );
 440  
 441          /**
 442           * Filters whether to skip saving the image file.
 443           *
 444           * Returning a non-null value will short-circuit the save method,
 445           * returning that value instead.
 446           *
 447           * @since 3.5.0
 448           *
 449           * @param bool|null       $override  Value to return instead of saving. Default null.
 450           * @param string          $filename  Name of the file to be saved.
 451           * @param WP_Image_Editor $image     The image editor instance.
 452           * @param string          $mime_type The mime type of the image.
 453           * @param int             $post_id   Attachment post ID.
 454           */
 455          $saved = apply_filters( 'wp_save_image_editor_file', null, $filename, $image, $mime_type, $post_id );
 456  
 457          if ( null !== $saved ) {
 458              return $saved;
 459          }
 460  
 461          return $image->save( $filename, $mime_type );
 462      } else {
 463          /* translators: 1: $image, 2: WP_Image_Editor */
 464          _deprecated_argument( __FUNCTION__, '3.5.0', sprintf( __( '%1$s needs to be a %2$s object.' ), '$image', 'WP_Image_Editor' ) );
 465  
 466          /** This filter is documented in wp-admin/includes/image-edit.php */
 467          $image = apply_filters_deprecated( 'image_save_pre', array( $image, $post_id ), '3.5.0', 'image_editor_save_pre' );
 468  
 469          /**
 470           * Filters whether to skip saving the image file.
 471           *
 472           * Returning a non-null value will short-circuit the save method,
 473           * returning that value instead.
 474           *
 475           * @since 2.9.0
 476           * @deprecated 3.5.0 Use {@see 'wp_save_image_editor_file'} instead.
 477           *
 478           * @param bool|null        $override  Value to return instead of saving. Default null.
 479           * @param string           $filename  Name of the file to be saved.
 480           * @param resource|GdImage $image     Image resource or GdImage instance.
 481           * @param string           $mime_type The mime type of the image.
 482           * @param int              $post_id   Attachment post ID.
 483           */
 484          $saved = apply_filters_deprecated(
 485              'wp_save_image_file',
 486              array( null, $filename, $image, $mime_type, $post_id ),
 487              '3.5.0',
 488              'wp_save_image_editor_file'
 489          );
 490  
 491          if ( null !== $saved ) {
 492              return $saved;
 493          }
 494  
 495          switch ( $mime_type ) {
 496              case 'image/jpeg':
 497                  /** This filter is documented in wp-includes/class-wp-image-editor.php */
 498                  return imagejpeg( $image, $filename, apply_filters( 'jpeg_quality', 90, 'edit_image' ) );
 499              case 'image/png':
 500                  return imagepng( $image, $filename );
 501              case 'image/gif':
 502                  return imagegif( $image, $filename );
 503              case 'image/webp':
 504                  if ( function_exists( 'imagewebp' ) ) {
 505                      return imagewebp( $image, $filename );
 506                  }
 507                  return false;
 508              case 'image/avif':
 509                  if ( function_exists( 'imageavif' ) ) {
 510                      return imageavif( $image, $filename );
 511                  }
 512                  return false;
 513              default:
 514                  return false;
 515          }
 516      }
 517  }
 518  
 519  /**
 520   * Image preview ratio. Internal use only.
 521   *
 522   * @since 2.9.0
 523   *
 524   * @ignore
 525   * @param int $w Image width in pixels.
 526   * @param int $h Image height in pixels.
 527   * @return float|int Image preview ratio.
 528   */
 529  function _image_get_preview_ratio( $w, $h ) {
 530      $max = max( $w, $h );
 531      return $max > 600 ? ( 600 / $max ) : 1;
 532  }
 533  
 534  /**
 535   * Returns an image resource. Internal use only.
 536   *
 537   * @since 2.9.0
 538   * @deprecated 3.5.0 Use WP_Image_Editor::rotate()
 539   * @see WP_Image_Editor::rotate()
 540   *
 541   * @ignore
 542   * @param resource|GdImage $img   Image resource.
 543   * @param float|int        $angle Image rotation angle, in degrees.
 544   * @return resource|GdImage|false GD image resource or GdImage instance, false otherwise.
 545   */
 546  function _rotate_image_resource( $img, $angle ) {
 547      _deprecated_function( __FUNCTION__, '3.5.0', 'WP_Image_Editor::rotate()' );
 548  
 549      if ( function_exists( 'imagerotate' ) ) {
 550          $rotated = imagerotate( $img, $angle, 0 );
 551  
 552          if ( is_gd_image( $rotated ) ) {
 553              if ( PHP_VERSION_ID < 80000 ) { // imagedestroy() has no effect as of PHP 8.0.
 554                  imagedestroy( $img );
 555              }
 556  
 557              $img = $rotated;
 558          }
 559      }
 560  
 561      return $img;
 562  }
 563  
 564  /**
 565   * Flips an image resource. Internal use only.
 566   *
 567   * @since 2.9.0
 568   * @deprecated 3.5.0 Use WP_Image_Editor::flip()
 569   * @see WP_Image_Editor::flip()
 570   *
 571   * @ignore
 572   * @param resource|GdImage $img  Image resource or GdImage instance.
 573   * @param bool             $horz Whether to flip horizontally.
 574   * @param bool             $vert Whether to flip vertically.
 575   * @return resource|GdImage (maybe) flipped image resource or GdImage instance.
 576   */
 577  function _flip_image_resource( $img, $horz, $vert ) {
 578      _deprecated_function( __FUNCTION__, '3.5.0', 'WP_Image_Editor::flip()' );
 579  
 580      $w   = imagesx( $img );
 581      $h   = imagesy( $img );
 582      $dst = wp_imagecreatetruecolor( $w, $h );
 583  
 584      if ( is_gd_image( $dst ) ) {
 585          $sx = $vert ? ( $w - 1 ) : 0;
 586          $sy = $horz ? ( $h - 1 ) : 0;
 587          $sw = $vert ? -$w : $w;
 588          $sh = $horz ? -$h : $h;
 589  
 590          if ( imagecopyresampled( $dst, $img, 0, 0, $sx, $sy, $w, $h, $sw, $sh ) ) {
 591              if ( PHP_VERSION_ID < 80000 ) { // imagedestroy() has no effect as of PHP 8.0.
 592                  imagedestroy( $img );
 593              }
 594  
 595              $img = $dst;
 596          }
 597      }
 598  
 599      return $img;
 600  }
 601  
 602  /**
 603   * Crops an image resource. Internal use only.
 604   *
 605   * @since 2.9.0
 606   *
 607   * @ignore
 608   * @param resource|GdImage $img Image resource or GdImage instance.
 609   * @param float            $x   Source point x-coordinate.
 610   * @param float            $y   Source point y-coordinate.
 611   * @param float            $w   Source width.
 612   * @param float            $h   Source height.
 613   * @return resource|GdImage (maybe) cropped image resource or GdImage instance.
 614   */
 615  function _crop_image_resource( $img, $x, $y, $w, $h ) {
 616      $dst = wp_imagecreatetruecolor( $w, $h );
 617  
 618      if ( is_gd_image( $dst ) ) {
 619          if ( imagecopy( $dst, $img, 0, 0, $x, $y, $w, $h ) ) {
 620              if ( PHP_VERSION_ID < 80000 ) { // imagedestroy() has no effect as of PHP 8.0.
 621                  imagedestroy( $img );
 622              }
 623  
 624              $img = $dst;
 625          }
 626      }
 627  
 628      return $img;
 629  }
 630  
 631  /**
 632   * Performs group of changes on Editor specified.
 633   *
 634   * @since 2.9.0
 635   *
 636   * @param WP_Image_Editor $image   WP_Image_Editor instance.
 637   * @param array           $changes Array of change operations.
 638   * @return WP_Image_Editor WP_Image_Editor instance with changes applied.
 639   */
 640  function image_edit_apply_changes( $image, $changes ) {
 641      if ( is_gd_image( $image ) ) {
 642          /* translators: 1: $image, 2: WP_Image_Editor */
 643          _deprecated_argument( __FUNCTION__, '3.5.0', sprintf( __( '%1$s needs to be a %2$s object.' ), '$image', 'WP_Image_Editor' ) );
 644      }
 645  
 646      if ( ! is_array( $changes ) ) {
 647          return $image;
 648      }
 649  
 650      // Expand change operations.
 651      foreach ( $changes as $key => $obj ) {
 652          if ( isset( $obj->r ) ) {
 653              $obj->type  = 'rotate';
 654              $obj->angle = $obj->r;
 655              unset( $obj->r );
 656          } elseif ( isset( $obj->f ) ) {
 657              $obj->type = 'flip';
 658              $obj->axis = $obj->f;
 659              unset( $obj->f );
 660          } elseif ( isset( $obj->c ) ) {
 661              $obj->type = 'crop';
 662              $obj->sel  = $obj->c;
 663              unset( $obj->c );
 664          }
 665  
 666          $changes[ $key ] = $obj;
 667      }
 668  
 669      // Combine operations.
 670      if ( count( $changes ) > 1 ) {
 671          $filtered = array( $changes[0] );
 672  
 673          for ( $i = 0, $j = 1, $c = count( $changes ); $j < $c; $j++ ) {
 674              $combined = false;
 675  
 676              if ( $filtered[ $i ]->type === $changes[ $j ]->type ) {
 677                  switch ( $filtered[ $i ]->type ) {
 678                      case 'rotate':
 679                          $filtered[ $i ]->angle += $changes[ $j ]->angle;
 680                          $combined               = true;
 681                          break;
 682                      case 'flip':
 683                          $filtered[ $i ]->axis ^= $changes[ $j ]->axis;
 684                          $combined              = true;
 685                          break;
 686                  }
 687              }
 688  
 689              if ( ! $combined ) {
 690                  $filtered[ ++$i ] = $changes[ $j ];
 691              }
 692          }
 693  
 694          $changes = $filtered;
 695          unset( $filtered );
 696      }
 697  
 698      // Image resource before applying the changes.
 699      if ( $image instanceof WP_Image_Editor ) {
 700  
 701          /**
 702           * Filters the WP_Image_Editor instance before applying changes to the image.
 703           *
 704           * @since 3.5.0
 705           *
 706           * @param WP_Image_Editor $image   WP_Image_Editor instance.
 707           * @param array           $changes Array of change operations.
 708           */
 709          $image = apply_filters( 'wp_image_editor_before_change', $image, $changes );
 710      } elseif ( is_gd_image( $image ) ) {
 711  
 712          /**
 713           * Filters the GD image resource before applying changes to the image.
 714           *
 715           * @since 2.9.0
 716           * @deprecated 3.5.0 Use {@see 'wp_image_editor_before_change'} instead.
 717           *
 718           * @param resource|GdImage $image   GD image resource or GdImage instance.
 719           * @param array            $changes Array of change operations.
 720           */
 721          $image = apply_filters_deprecated( 'image_edit_before_change', array( $image, $changes ), '3.5.0', 'wp_image_editor_before_change' );
 722      }
 723  
 724      foreach ( $changes as $operation ) {
 725          switch ( $operation->type ) {
 726              case 'rotate':
 727                  if ( 0 !== $operation->angle ) {
 728                      if ( $image instanceof WP_Image_Editor ) {
 729                          $image->rotate( $operation->angle );
 730                      } else {
 731                          $image = _rotate_image_resource( $image, $operation->angle );
 732                      }
 733                  }
 734                  break;
 735              case 'flip':
 736                  if ( 0 !== $operation->axis ) {
 737                      if ( $image instanceof WP_Image_Editor ) {
 738                          $image->flip( ( $operation->axis & 1 ) !== 0, ( $operation->axis & 2 ) !== 0 );
 739                      } else {
 740                          $image = _flip_image_resource( $image, ( $operation->axis & 1 ) !== 0, ( $operation->axis & 2 ) !== 0 );
 741                      }
 742                  }
 743                  break;
 744              case 'crop':
 745                  $sel = $operation->sel;
 746  
 747                  if ( $image instanceof WP_Image_Editor ) {
 748                      $size = $image->get_size();
 749                      $w    = $size['width'];
 750                      $h    = $size['height'];
 751  
 752                      $scale = $sel->r ?? 1 / _image_get_preview_ratio( $w, $h ); // Discard preview scaling.
 753                      $image->crop( (int) ( $sel->x * $scale ), (int) ( $sel->y * $scale ), (int) ( $sel->w * $scale ), (int) ( $sel->h * $scale ) );
 754                  } else {
 755                      $scale = $sel->r ?? 1 / _image_get_preview_ratio( imagesx( $image ), imagesy( $image ) ); // Discard preview scaling.
 756                      $image = _crop_image_resource( $image, $sel->x * $scale, $sel->y * $scale, $sel->w * $scale, $sel->h * $scale );
 757                  }
 758                  break;
 759          }
 760      }
 761  
 762      return $image;
 763  }
 764  
 765  
 766  /**
 767   * Streams image in post to browser, along with enqueued changes
 768   * in `$_REQUEST['history']`.
 769   *
 770   * @since 2.9.0
 771   *
 772   * @param int $post_id Attachment post ID.
 773   * @return bool True on success, false on failure.
 774   */
 775  function stream_preview_image( $post_id ) {
 776      $post = get_post( $post_id );
 777  
 778      wp_raise_memory_limit( 'admin' );
 779  
 780      $img = wp_get_image_editor( _load_image_to_edit_path( $post_id ) );
 781  
 782      if ( is_wp_error( $img ) ) {
 783          return false;
 784      }
 785  
 786      $changes = ! empty( $_REQUEST['history'] ) ? json_decode( wp_unslash( $_REQUEST['history'] ) ) : null;
 787      if ( $changes ) {
 788          $img = image_edit_apply_changes( $img, $changes );
 789      }
 790  
 791      // Scale the image.
 792      $size = $img->get_size();
 793      $w    = $size['width'];
 794      $h    = $size['height'];
 795  
 796      $ratio = _image_get_preview_ratio( $w, $h );
 797      $w2    = max( 1, $w * $ratio );
 798      $h2    = max( 1, $h * $ratio );
 799  
 800      if ( is_wp_error( $img->resize( $w2, $h2 ) ) ) {
 801          return false;
 802      }
 803  
 804      return wp_stream_image( $img, $post->post_mime_type, $post_id );
 805  }
 806  
 807  /**
 808   * Restores the metadata for a given attachment.
 809   *
 810   * @since 2.9.0
 811   *
 812   * @param int $post_id Attachment post ID.
 813   * @return stdClass Image restoration message object.
 814   */
 815  function wp_restore_image( $post_id ) {
 816      $meta             = wp_get_attachment_metadata( $post_id );
 817      $file             = get_attached_file( $post_id );
 818      $backup_sizes     = get_post_meta( $post_id, '_wp_attachment_backup_sizes', true );
 819      $old_backup_sizes = $backup_sizes;
 820      $restored         = false;
 821      $msg              = new stdClass();
 822  
 823      if ( ! is_array( $meta ) || ! is_array( $backup_sizes ) ) {
 824          $msg->error = __( 'Cannot load image metadata.' );
 825          return $msg;
 826      }
 827  
 828      $meta['sizes'] ??= array();
 829  
 830      $parts         = pathinfo( $file );
 831      $suffix        = time() . rand( 100, 999 );
 832      $default_sizes = get_intermediate_image_sizes();
 833  
 834      if ( isset( $backup_sizes['full-orig'] ) && is_array( $backup_sizes['full-orig'] ) ) {
 835          $data = $backup_sizes['full-orig'];
 836  
 837          if ( $parts['basename'] !== $data['file'] ) {
 838              if ( defined( 'IMAGE_EDIT_OVERWRITE' ) && IMAGE_EDIT_OVERWRITE ) {
 839                  // Delete only if it's an edited image.
 840                  if ( preg_match( '/-e[0-9]{13}\./', $parts['basename'] ) ) {
 841                      wp_delete_file( $file );
 842                  }
 843              } elseif ( isset( $meta['width'], $meta['height'] ) ) {
 844                  $backup_sizes[ "full-$suffix" ] = array(
 845                      'width'    => $meta['width'],
 846                      'height'   => $meta['height'],
 847                      'filesize' => $meta['filesize'],
 848                      'file'     => $parts['basename'],
 849                  );
 850              }
 851          }
 852  
 853          $restored_file = path_join( $parts['dirname'], $data['file'] );
 854          $restored      = update_attached_file( $post_id, $restored_file );
 855  
 856          $meta['file']   = _wp_relative_upload_path( $restored_file );
 857          $meta['width']  = $data['width'];
 858          $meta['height'] = $data['height'];
 859          if ( isset( $data['filesize'] ) ) {
 860              /*
 861               * Restore the original filesize if it was backed up.
 862               *
 863               * See https://core.trac.wordpress.org/ticket/59684.
 864               */
 865              $meta['filesize'] = $data['filesize'];
 866          }
 867      }
 868  
 869      foreach ( $default_sizes as $default_size ) {
 870          if ( isset( $backup_sizes[ "$default_size-orig" ] ) ) {
 871              $data = $backup_sizes[ "$default_size-orig" ];
 872  
 873              if ( isset( $meta['sizes'][ $default_size ] ) && $meta['sizes'][ $default_size ]['file'] !== $data['file'] ) {
 874                  if ( defined( 'IMAGE_EDIT_OVERWRITE' ) && IMAGE_EDIT_OVERWRITE ) {
 875                      // Delete only if it's an edited image.
 876                      if ( preg_match( '/-e[0-9]{13}-/', $meta['sizes'][ $default_size ]['file'] ) ) {
 877                          $delete_file = path_join( $parts['dirname'], $meta['sizes'][ $default_size ]['file'] );
 878                          wp_delete_file( $delete_file );
 879                      }
 880                  } else {
 881                      $backup_sizes[ "$default_size-{$suffix}" ] = $meta['sizes'][ $default_size ];
 882                  }
 883              }
 884  
 885              $meta['sizes'][ $default_size ] = $data;
 886          } else {
 887              unset( $meta['sizes'][ $default_size ] );
 888          }
 889      }
 890  
 891      if ( ! wp_update_attachment_metadata( $post_id, $meta )
 892          || ( $old_backup_sizes !== $backup_sizes && ! update_post_meta( $post_id, '_wp_attachment_backup_sizes', $backup_sizes ) )
 893      ) {
 894          $msg->error = __( 'Cannot save image metadata.' );
 895          return $msg;
 896      }
 897  
 898      if ( ! $restored ) {
 899          $msg->error = __( 'Image metadata is inconsistent.' );
 900      } else {
 901          $msg->msg = __( 'Image restored successfully.' );
 902  
 903          if ( defined( 'IMAGE_EDIT_OVERWRITE' ) && IMAGE_EDIT_OVERWRITE ) {
 904              delete_post_meta( $post_id, '_wp_attachment_backup_sizes' );
 905          }
 906      }
 907  
 908      return $msg;
 909  }
 910  
 911  /**
 912   * Saves image to post, along with enqueued changes
 913   * in `$_REQUEST['history']`.
 914   *
 915   * @since 2.9.0
 916   *
 917   * @param int $post_id Attachment post ID.
 918   * @return stdClass
 919   */
 920  function wp_save_image( $post_id ) {
 921      $_wp_additional_image_sizes = wp_get_additional_image_sizes();
 922  
 923      $return  = new stdClass();
 924      $success = false;
 925      $delete  = false;
 926      $scaled  = false;
 927      $nocrop  = false;
 928      $post    = get_post( $post_id );
 929  
 930      $img = wp_get_image_editor( _load_image_to_edit_path( $post_id, 'full' ) );
 931  
 932      if ( is_wp_error( $img ) ) {
 933          $return->error = esc_js( __( 'Unable to create new image.' ) );
 934          return $return;
 935      }
 936  
 937      $full_width  = ! empty( $_REQUEST['fwidth'] ) ? (int) $_REQUEST['fwidth'] : 0;
 938      $full_height = ! empty( $_REQUEST['fheight'] ) ? (int) $_REQUEST['fheight'] : 0;
 939      $target      = ! empty( $_REQUEST['target'] ) ? preg_replace( '/[^a-z0-9_-]+/i', '', $_REQUEST['target'] ) : '';
 940      $scale       = ! empty( $_REQUEST['do'] ) && 'scale' === $_REQUEST['do'];
 941  
 942      /** This filter is documented in wp-admin/includes/image-edit.php */
 943      $edit_thumbnails_separately = (bool) apply_filters( 'image_edit_thumbnails_separately', false );
 944  
 945      if ( $scale ) {
 946          $size            = $img->get_size();
 947          $original_width  = $size['width'];
 948          $original_height = $size['height'];
 949  
 950          if ( $full_width > $original_width || $full_height > $original_height ) {
 951              $return->error = esc_js( __( 'Images cannot be scaled to a size larger than the original.' ) );
 952              return $return;
 953          }
 954  
 955          if ( $full_width > 0 && $full_height > 0 ) {
 956              // Check if it has roughly the same w / h ratio.
 957              $diff = round( $original_width / $original_height, 2 ) - round( $full_width / $full_height, 2 );
 958              if ( -0.1 < $diff && $diff < 0.1 ) {
 959                  // Scale the full size image.
 960                  if ( $img->resize( $full_width, $full_height ) ) {
 961                      $scaled = true;
 962                  }
 963              }
 964  
 965              if ( ! $scaled ) {
 966                  $return->error = esc_js( __( 'Error while saving the scaled image. Please reload the page and try again.' ) );
 967                  return $return;
 968              }
 969          }
 970      } elseif ( ! empty( $_REQUEST['history'] ) ) {
 971          $changes = json_decode( wp_unslash( $_REQUEST['history'] ) );
 972          if ( $changes ) {
 973              $img = image_edit_apply_changes( $img, $changes );
 974          }
 975      } else {
 976          $return->error = esc_js( __( 'Nothing to save, the image has not changed.' ) );
 977          return $return;
 978      }
 979  
 980      $meta         = wp_get_attachment_metadata( $post_id );
 981      $backup_sizes = get_post_meta( $post->ID, '_wp_attachment_backup_sizes', true );
 982  
 983      if ( ! is_array( $meta ) ) {
 984          $return->error = esc_js( __( 'Image data does not exist. Please re-upload the image.' ) );
 985          return $return;
 986      }
 987  
 988      $meta['sizes'] ??= array();
 989  
 990      if ( ! is_array( $backup_sizes ) ) {
 991          $backup_sizes = array();
 992      }
 993  
 994      // Generate new filename.
 995      $path = get_attached_file( $post_id );
 996  
 997      $basename = pathinfo( $path, PATHINFO_BASENAME );
 998      $dirname  = pathinfo( $path, PATHINFO_DIRNAME );
 999      $ext      = pathinfo( $path, PATHINFO_EXTENSION );
1000      $filename = pathinfo( $path, PATHINFO_FILENAME );
1001      $suffix   = time() . rand( 100, 999 );
1002  
1003      if ( defined( 'IMAGE_EDIT_OVERWRITE' ) && IMAGE_EDIT_OVERWRITE
1004          && isset( $backup_sizes['full-orig'] ) && $backup_sizes['full-orig']['file'] !== $basename
1005      ) {
1006  
1007          if ( $edit_thumbnails_separately && 'thumbnail' === $target ) {
1008              $new_path = "{$dirname}/{$filename}-temp.{$ext}";
1009          } else {
1010              $new_path = $path;
1011          }
1012      } else {
1013          while ( true ) {
1014              $filename     = preg_replace( '/-e([0-9]+)$/', '', $filename );
1015              $filename    .= "-e{$suffix}";
1016              $new_filename = "{$filename}.{$ext}";
1017              $new_path     = "{$dirname}/$new_filename";
1018  
1019              if ( file_exists( $new_path ) ) {
1020                  ++$suffix;
1021              } else {
1022                  break;
1023              }
1024          }
1025      }
1026  
1027      $saved_image = wp_save_image_file( $new_path, $img, $post->post_mime_type, $post_id );
1028      // Save the full-size file, also needed to create sub-sizes.
1029      if ( ! $saved_image ) {
1030          $return->error = esc_js( __( 'Unable to save the image.' ) );
1031          return $return;
1032      }
1033  
1034      if ( 'nothumb' === $target || 'all' === $target || 'full' === $target || $scaled ) {
1035          $tag = false;
1036  
1037          if ( isset( $backup_sizes['full-orig'] ) ) {
1038              if ( ( ! defined( 'IMAGE_EDIT_OVERWRITE' ) || ! IMAGE_EDIT_OVERWRITE )
1039                  && $backup_sizes['full-orig']['file'] !== $basename
1040              ) {
1041                  $tag = "full-$suffix";
1042              }
1043          } else {
1044              $tag = 'full-orig';
1045          }
1046  
1047          if ( $tag ) {
1048              $backup_sizes[ $tag ] = array(
1049                  'width'    => $meta['width'],
1050                  'height'   => $meta['height'],
1051                  'filesize' => $meta['filesize'],
1052                  'file'     => $basename,
1053              );
1054          }
1055  
1056          $success = ( $path === $new_path ) || update_attached_file( $post_id, $new_path );
1057  
1058          $meta['file'] = _wp_relative_upload_path( $new_path );
1059  
1060          $size             = $img->get_size();
1061          $meta['width']    = $size['width'];
1062          $meta['height']   = $size['height'];
1063          $meta['filesize'] = $saved_image['filesize'];
1064  
1065          if ( $success && ( 'nothumb' === $target || 'all' === $target ) ) {
1066              $sizes = get_intermediate_image_sizes();
1067  
1068              if ( $edit_thumbnails_separately && 'nothumb' === $target ) {
1069                  $sizes = array_diff( $sizes, array( 'thumbnail' ) );
1070              }
1071          }
1072  
1073          $return->fw = $meta['width'];
1074          $return->fh = $meta['height'];
1075      } elseif ( $edit_thumbnails_separately && 'thumbnail' === $target ) {
1076          $sizes   = array( 'thumbnail' );
1077          $success = true;
1078          $delete  = true;
1079          $nocrop  = true;
1080      }
1081  
1082      /*
1083       * We need to remove any existing resized image files because
1084       * a new crop or rotate could generate different sizes (and hence, filenames),
1085       * keeping the new resized images from overwriting the existing image files.
1086       * https://core.trac.wordpress.org/ticket/32171
1087       */
1088      if ( defined( 'IMAGE_EDIT_OVERWRITE' ) && IMAGE_EDIT_OVERWRITE && ! empty( $meta['sizes'] ) ) {
1089          foreach ( $meta['sizes'] as $size ) {
1090              if ( ! empty( $size['file'] ) && preg_match( '/-e[0-9]{13}-/', $size['file'] ) ) {
1091                  $delete_file = path_join( $dirname, $size['file'] );
1092                  wp_delete_file( $delete_file );
1093              }
1094          }
1095      }
1096  
1097      if ( isset( $sizes ) ) {
1098          $_sizes = array();
1099  
1100          foreach ( $sizes as $size ) {
1101              $tag = false;
1102  
1103              if ( isset( $meta['sizes'][ $size ] ) ) {
1104                  if ( isset( $backup_sizes[ "$size-orig" ] ) ) {
1105                      if ( ( ! defined( 'IMAGE_EDIT_OVERWRITE' ) || ! IMAGE_EDIT_OVERWRITE )
1106                          && $backup_sizes[ "$size-orig" ]['file'] !== $meta['sizes'][ $size ]['file']
1107                      ) {
1108                          $tag = "$size-$suffix";
1109                      }
1110                  } else {
1111                      $tag = "$size-orig";
1112                  }
1113  
1114                  if ( $tag ) {
1115                      $backup_sizes[ $tag ] = $meta['sizes'][ $size ];
1116                  }
1117              }
1118  
1119              if ( isset( $_wp_additional_image_sizes[ $size ] ) ) {
1120                  $width  = (int) $_wp_additional_image_sizes[ $size ]['width'];
1121                  $height = (int) $_wp_additional_image_sizes[ $size ]['height'];
1122                  $crop   = ( $nocrop ) ? false : $_wp_additional_image_sizes[ $size ]['crop'];
1123              } else {
1124                  $height = get_option( "{$size}_size_h" );
1125                  $width  = get_option( "{$size}_size_w" );
1126                  $crop   = ( $nocrop ) ? false : get_option( "{$size}_crop" );
1127              }
1128  
1129              $_sizes[ $size ] = array(
1130                  'width'  => $width,
1131                  'height' => $height,
1132                  'crop'   => $crop,
1133              );
1134          }
1135  
1136          $meta['sizes'] = array_merge( $meta['sizes'], $img->multi_resize( $_sizes ) );
1137      }
1138  
1139      unset( $img );
1140  
1141      if ( $success ) {
1142          wp_update_attachment_metadata( $post_id, $meta );
1143          update_post_meta( $post_id, '_wp_attachment_backup_sizes', $backup_sizes );
1144  
1145          if ( 'thumbnail' === $target || 'all' === $target || 'full' === $target ) {
1146              // Check if it's an image edit from attachment edit screen.
1147              if ( ! empty( $_REQUEST['context'] ) && 'edit-attachment' === $_REQUEST['context'] ) {
1148                  $thumb_url = wp_get_attachment_image_src( $post_id, array( 900, 600 ), true );
1149  
1150                  $return->thumbnail = $thumb_url[0];
1151              } else {
1152                  $file_url = wp_get_attachment_url( $post_id );
1153  
1154                  if ( ! empty( $meta['sizes']['thumbnail'] ) ) {
1155                      $thumb             = $meta['sizes']['thumbnail'];
1156                      $return->thumbnail = path_join( dirname( $file_url ), $thumb['file'] );
1157                  } else {
1158                      $return->thumbnail = "$file_url?w=128&h=128";
1159                  }
1160              }
1161          }
1162      } else {
1163          $delete = true;
1164      }
1165  
1166      if ( $delete ) {
1167          wp_delete_file( $new_path );
1168      }
1169  
1170      $return->msg = esc_js( __( 'Image saved' ) );
1171  
1172      return $return;
1173  }


Generated : Sat Aug 8 08:20:21 2026 Cross-referenced by PHPXref