3 * WordPress Image Editor
6 * @subpackage Administration
10 * Loads the WP image-editing interface.
12 * @param int $post_id Post ID.
13 * @param bool|object $msg Optional. Message to display for image editor updates or errors.
16 function wp_image_editor($post_id, $msg = false) {
17 $nonce = wp_create_nonce("image_editor-$post_id");
18 $meta = wp_get_attachment_metadata($post_id);
19 $thumb = image_get_intermediate_size($post_id, 'thumbnail');
20 $sub_sizes = isset($meta['sizes']) && is_array($meta['sizes']);
23 if ( isset( $meta['width'], $meta['height'] ) )
24 $big = max( $meta['width'], $meta['height'] );
26 die( __('Image data does not exist. Please re-upload the image.') );
28 $sizer = $big > 400 ? 400 / $big : 1;
30 $backup_sizes = get_post_meta( $post_id, '_wp_attachment_backup_sizes', true );
32 if ( ! empty( $backup_sizes ) && isset( $backup_sizes['full-orig'], $meta['file'] ) )
33 $can_restore = $backup_sizes['full-orig']['file'] != basename( $meta['file'] );
36 if ( isset($msg->error) )
37 $note = "<div class='error'><p>$msg->error</p></div>";
38 elseif ( isset($msg->msg) )
39 $note = "<div class='updated'><p>$msg->msg</p></div>";
43 <div class="imgedit-wrap wp-clearfix">
44 <div id="imgedit-panel-<?php echo $post_id; ?>">
46 <div class="imgedit-settings">
47 <div class="imgedit-group">
48 <div class="imgedit-group-top">
49 <h2><?php _e( 'Scale Image' ); ?></h2>
50 <button type="button" class="dashicons dashicons-editor-help imgedit-help-toggle" onclick="imageEdit.toggleHelp(this);return false;" aria-expanded="false"><span class="screen-reader-text"><?php esc_html_e( 'Scale Image Help' ); ?></span></button>
51 <div class="imgedit-help">
52 <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>
54 <?php if ( isset( $meta['width'], $meta['height'] ) ): ?>
55 <p><?php printf( __('Original dimensions %s'), $meta['width'] . ' × ' . $meta['height'] ); ?></p>
57 <div class="imgedit-submit">
59 <fieldset class="imgedit-scale">
60 <legend><?php _e( 'New dimensions:' ); ?></legend>
62 <label><span class="screen-reader-text"><?php _e( 'scale width' ); ?></span>
63 <input type="text" 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 isset( $meta['width'] ) ? $meta['width'] : 0; ?>" />
65 <span class="imgedit-separator">×</span>
66 <label><span class="screen-reader-text"><?php _e( 'scale height' ); ?></span>
67 <input type="text" 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 isset( $meta['height'] ) ? $meta['height'] : 0; ?>" />
69 <span class="imgedit-scale-warn" id="imgedit-scale-warn-<?php echo $post_id; ?>">!</span>
70 <input id="imgedit-scale-button" type="button" onclick="imageEdit.action(<?php echo "$post_id, '$nonce'"; ?>, 'scale')" class="button button-primary" value="<?php esc_attr_e( 'Scale' ); ?>" />
78 <?php if ( $can_restore ) { ?>
80 <div class="imgedit-group">
81 <div class="imgedit-group-top">
82 <h2><button type="button" onclick="imageEdit.toggleHelp(this);" class="button-link"><?php _e( 'Restore Original Image' ); ?> <span class="dashicons dashicons-arrow-down imgedit-help-toggle"></span></button></h2>
83 <div class="imgedit-help">
84 <p><?php _e('Discard any changes and restore the original image.');
86 if ( !defined('IMAGE_EDIT_OVERWRITE') || !IMAGE_EDIT_OVERWRITE )
87 echo ' '.__('Previously edited copies of the image will not be deleted.');
90 <div class="imgedit-submit">
91 <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; ?> />
99 <div class="imgedit-group">
100 <div class="imgedit-group-top">
101 <h2><?php _e( 'Image Crop' ); ?></h2>
102 <button type="button" class="dashicons dashicons-editor-help imgedit-help-toggle" onclick="imageEdit.toggleHelp(this);return false;" aria-expanded="false"><span class="screen-reader-text"><?php esc_html_e( 'Image Crop Help' ); ?></span></button>
104 <div class="imgedit-help">
105 <p><?php _e('To crop the image, click on it and drag to make your selection.'); ?></p>
107 <p><strong><?php _e('Crop Aspect Ratio'); ?></strong><br />
108 <?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>
110 <p><strong><?php _e('Crop Selection'); ?></strong><br />
111 <?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>
115 <fieldset class="imgedit-crop-ratio">
116 <legend><?php _e( 'Aspect ratio:' ); ?></legend>
118 <label><span class="screen-reader-text"><?php _e( 'crop ratio width' ); ?></span>
119 <input type="text" 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)" />
121 <span class="imgedit-separator">:</span>
122 <label><span class="screen-reader-text"><?php _e( 'crop ratio height' ); ?></span>
123 <input type="text" 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)" />
128 <fieldset id="imgedit-crop-sel-<?php echo $post_id; ?>" class="imgedit-crop-sel">
129 <legend><?php _e( 'Selection:' ); ?></legend>
131 <label><span class="screen-reader-text"><?php _e( 'selection width' ); ?></span>
132 <input type="text" id="imgedit-sel-width-<?php echo $post_id; ?>" onkeyup="imageEdit.setNumSelection(<?php echo $post_id; ?>, this)" onblur="imageEdit.setNumSelection(<?php echo $post_id; ?>, this)" />
134 <span class="imgedit-separator">×</span>
135 <label><span class="screen-reader-text"><?php _e( 'selection height' ); ?></span>
136 <input type="text" id="imgedit-sel-height-<?php echo $post_id; ?>" onkeyup="imageEdit.setNumSelection(<?php echo $post_id; ?>, this)" onblur="imageEdit.setNumSelection(<?php echo $post_id; ?>, this)" />
143 <?php if ( $thumb && $sub_sizes ) {
144 $thumb_img = wp_constrain_dimensions( $thumb['width'], $thumb['height'], 160, 120 );
147 <div class="imgedit-group imgedit-applyto">
148 <div class="imgedit-group-top">
149 <h2><?php _e( 'Thumbnail Settings' ); ?></h2>
150 <button type="button" class="dashicons dashicons-editor-help imgedit-help-toggle" onclick="imageEdit.toggleHelp(this);return false;" aria-expanded="false"><span class="screen-reader-text"><?php esc_html_e( 'Thumbnail Settings Help' ); ?></span></button>
151 <p class="imgedit-help"><?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>
154 <figure class="imgedit-thumbnail-preview">
155 <img src="<?php echo $thumb['url']; ?>" width="<?php echo $thumb_img[0]; ?>" height="<?php echo $thumb_img[1]; ?>" class="imgedit-size-preview" alt="" draggable="false" />
156 <figcaption class="imgedit-thumbnail-preview-caption"><?php _e( 'Current thumbnail' ); ?></figcaption>
159 <div id="imgedit-save-target-<?php echo $post_id; ?>" class="imgedit-save-target">
161 <legend><strong><?php _e( 'Apply changes to:' ); ?></strong></legend>
163 <label class="imgedit-label">
164 <input type="radio" name="imgedit-target-<?php echo $post_id; ?>" value="all" checked="checked" />
165 <?php _e('All image sizes'); ?></label>
167 <label class="imgedit-label">
168 <input type="radio" name="imgedit-target-<?php echo $post_id; ?>" value="thumbnail" />
169 <?php _e('Thumbnail'); ?></label>
171 <label class="imgedit-label">
172 <input type="radio" name="imgedit-target-<?php echo $post_id; ?>" value="nothumb" />
173 <?php _e('All sizes except thumbnail'); ?></label>
182 <div class="imgedit-panel-content wp-clearfix">
184 <div class="imgedit-menu wp-clearfix">
185 <button type="button" onclick="imageEdit.crop(<?php echo "$post_id, '$nonce'"; ?>, this)" class="imgedit-crop button disabled" disabled><span class="screen-reader-text"><?php esc_html_e( 'Crop' ); ?></span></button><?php
187 // On some setups GD library does not provide imagerotate() - Ticket #11536
188 if ( wp_image_editor_supports( array( 'mime_type' => get_post_mime_type( $post_id ), 'methods' => array( 'rotate' ) ) ) ) {
189 $note_no_rotate = '';
191 <button type="button" class="imgedit-rleft button" onclick="imageEdit.rotate( 90, <?php echo "$post_id, '$nonce'"; ?>, this)"><span class="screen-reader-text"><?php esc_html_e( 'Rotate counter-clockwise' ); ?></span></button>
192 <button type="button" class="imgedit-rright button" onclick="imageEdit.rotate(-90, <?php echo "$post_id, '$nonce'"; ?>, this)"><span class="screen-reader-text"><?php esc_html_e( 'Rotate clockwise' ); ?></span></button>
194 $note_no_rotate = '<p class="note-no-rotate"><em>' . __( 'Image rotation is not supported by your web host.' ) . '</em></p>';
196 <button type="button" class="imgedit-rleft button disabled" disabled></button>
197 <button type="button" class="imgedit-rright button disabled" disabled></button>
200 <button type="button" onclick="imageEdit.flip(1, <?php echo "$post_id, '$nonce'"; ?>, this)" class="imgedit-flipv button"><span class="screen-reader-text"><?php esc_html_e( 'Flip vertically' ); ?></span></button>
201 <button type="button" onclick="imageEdit.flip(2, <?php echo "$post_id, '$nonce'"; ?>, this)" class="imgedit-fliph button"><span class="screen-reader-text"><?php esc_html_e( 'Flip horizontally' ); ?></span></button>
203 <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><span class="screen-reader-text"><?php esc_html_e( 'Undo' ); ?></span></button>
204 <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><span class="screen-reader-text"><?php esc_html_e( 'Redo' ); ?></span></button>
205 <?php echo $note_no_rotate; ?>
208 <input type="hidden" id="imgedit-sizer-<?php echo $post_id; ?>" value="<?php echo $sizer; ?>" />
209 <input type="hidden" id="imgedit-history-<?php echo $post_id; ?>" value="" />
210 <input type="hidden" id="imgedit-undone-<?php echo $post_id; ?>" value="0" />
211 <input type="hidden" id="imgedit-selection-<?php echo $post_id; ?>" value="" />
212 <input type="hidden" id="imgedit-x-<?php echo $post_id; ?>" value="<?php echo isset( $meta['width'] ) ? $meta['width'] : 0; ?>" />
213 <input type="hidden" id="imgedit-y-<?php echo $post_id; ?>" value="<?php echo isset( $meta['height'] ) ? $meta['height'] : 0; ?>" />
215 <div id="imgedit-crop-<?php echo $post_id; ?>" class="imgedit-crop-wrap">
216 <img id="image-preview-<?php echo $post_id; ?>" onload="imageEdit.imgLoaded('<?php echo $post_id; ?>')" src="<?php echo admin_url( 'admin-ajax.php', 'relative' ); ?>?action=imgedit-preview&_ajax_nonce=<?php echo $nonce; ?>&postid=<?php echo $post_id; ?>&rand=<?php echo rand(1, 99999); ?>" alt="" />
219 <div class="imgedit-submit">
220 <input type="button" onclick="imageEdit.close(<?php echo $post_id; ?>, 1)" class="button imgedit-cancel-btn" value="<?php esc_attr_e( 'Cancel' ); ?>" />
221 <input type="button" onclick="imageEdit.save(<?php echo "$post_id, '$nonce'"; ?>)" disabled="disabled" class="button button-primary imgedit-submit-btn" value="<?php esc_attr_e( 'Save' ); ?>" />
226 <div class="imgedit-wait" id="imgedit-wait-<?php echo $post_id; ?>"></div>
227 <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>
233 * Streams image in WP_Image_Editor to browser.
234 * Provided for backcompat reasons
236 * @param WP_Image_Editor $image
237 * @param string $mime_type
238 * @param int $post_id
241 function wp_stream_image( $image, $mime_type, $post_id ) {
242 if ( $image instanceof WP_Image_Editor ) {
245 * Filters the WP_Image_Editor instance for the image to be streamed to the browser.
249 * @param WP_Image_Editor $image WP_Image_Editor instance.
250 * @param int $post_id Post ID.
252 $image = apply_filters( 'image_editor_save_pre', $image, $post_id );
254 if ( is_wp_error( $image->stream( $mime_type ) ) )
259 _deprecated_argument( __FUNCTION__, '3.5.0', __( '$image needs to be an WP_Image_Editor object' ) );
262 * Filters the GD image resource to be streamed to the browser.
265 * @deprecated 3.5.0 Use image_editor_save_pre instead.
267 * @param resource $image Image resource to be streamed.
268 * @param int $post_id Post ID.
270 $image = apply_filters( 'image_save_pre', $image, $post_id );
272 switch ( $mime_type ) {
274 header( 'Content-Type: image/jpeg' );
275 return imagejpeg( $image, null, 90 );
277 header( 'Content-Type: image/png' );
278 return imagepng( $image );
280 header( 'Content-Type: image/gif' );
281 return imagegif( $image );
289 * Saves Image to File
291 * @param string $filename
292 * @param WP_Image_Editor $image
293 * @param string $mime_type
294 * @param int $post_id
297 function wp_save_image_file( $filename, $image, $mime_type, $post_id ) {
298 if ( $image instanceof WP_Image_Editor ) {
300 /** This filter is documented in wp-admin/includes/image-edit.php */
301 $image = apply_filters( 'image_editor_save_pre', $image, $post_id );
304 * Filters whether to skip saving the image file.
306 * Returning a non-null value will short-circuit the save method,
307 * returning that value instead.
311 * @param mixed $override Value to return instead of saving. Default null.
312 * @param string $filename Name of the file to be saved.
313 * @param WP_Image_Editor $image WP_Image_Editor instance.
314 * @param string $mime_type Image mime type.
315 * @param int $post_id Post ID.
317 $saved = apply_filters( 'wp_save_image_editor_file', null, $filename, $image, $mime_type, $post_id );
319 if ( null !== $saved )
322 return $image->save( $filename, $mime_type );
324 _deprecated_argument( __FUNCTION__, '3.5.0', __( '$image needs to be an WP_Image_Editor object' ) );
326 /** This filter is documented in wp-admin/includes/image-edit.php */
327 $image = apply_filters( 'image_save_pre', $image, $post_id );
330 * Filters whether to skip saving the image file.
332 * Returning a non-null value will short-circuit the save method,
333 * returning that value instead.
336 * @deprecated 3.5.0 Use wp_save_image_editor_file instead.
338 * @param mixed $override Value to return instead of saving. Default null.
339 * @param string $filename Name of the file to be saved.
340 * @param WP_Image_Editor $image WP_Image_Editor instance.
341 * @param string $mime_type Image mime type.
342 * @param int $post_id Post ID.
344 $saved = apply_filters( 'wp_save_image_file', null, $filename, $image, $mime_type, $post_id );
346 if ( null !== $saved )
349 switch ( $mime_type ) {
352 /** This filter is documented in wp-includes/class-wp-image-editor.php */
353 return imagejpeg( $image, $filename, apply_filters( 'jpeg_quality', 90, 'edit_image' ) );
355 return imagepng( $image, $filename );
357 return imagegif( $image, $filename );
365 * Image preview ratio. Internal use only.
370 * @param int $w Image width in pixels.
371 * @param int $h Image height in pixels.
372 * @return float|int Image preview ratio.
374 function _image_get_preview_ratio($w, $h) {
376 return $max > 400 ? (400 / $max) : 1;
380 * Returns an image resource. Internal use only.
385 * @param resource $img Image resource.
386 * @param float|int $angle Image rotation angle, in degrees.
387 * @return resource|false GD image resource, false otherwise.
389 function _rotate_image_resource($img, $angle) {
390 _deprecated_function( __FUNCTION__, '3.5.0', 'WP_Image_Editor::rotate()' );
391 if ( function_exists('imagerotate') ) {
392 $rotated = imagerotate($img, $angle, 0);
393 if ( is_resource($rotated) ) {
402 * Flips an image resource. Internal use only.
407 * @param resource $img Image resource.
408 * @param bool $horz Whether to flip horizontally.
409 * @param bool $vert Whether to flip vertically.
410 * @return resource (maybe) flipped image resource.
412 function _flip_image_resource($img, $horz, $vert) {
413 _deprecated_function( __FUNCTION__, '3.5.0', 'WP_Image_Editor::flip()' );
416 $dst = wp_imagecreatetruecolor($w, $h);
417 if ( is_resource($dst) ) {
418 $sx = $vert ? ($w - 1) : 0;
419 $sy = $horz ? ($h - 1) : 0;
420 $sw = $vert ? -$w : $w;
421 $sh = $horz ? -$h : $h;
423 if ( imagecopyresampled($dst, $img, 0, 0, $sx, $sy, $w, $h, $sw, $sh) ) {
432 * Crops an image resource. Internal use only.
437 * @param resource $img Image resource.
438 * @param float $x Source point x-coordinate.
439 * @param float $y Source point y-cooredinate.
440 * @param float $w Source width.
441 * @param float $h Source height.
442 * @return resource (maybe) cropped image resource.
444 function _crop_image_resource($img, $x, $y, $w, $h) {
445 $dst = wp_imagecreatetruecolor($w, $h);
446 if ( is_resource($dst) ) {
447 if ( imagecopy($dst, $img, 0, 0, $x, $y, $w, $h) ) {
456 * Performs group of changes on Editor specified.
460 * @param WP_Image_Editor $image WP_Image_Editor instance.
461 * @param array $changes Array of change operations.
462 * @return WP_Image_Editor WP_Image_Editor instance with changes applied.
464 function image_edit_apply_changes( $image, $changes ) {
465 if ( is_resource( $image ) )
466 _deprecated_argument( __FUNCTION__, '3.5.0', __( '$image needs to be an WP_Image_Editor object' ) );
468 if ( !is_array($changes) )
471 // Expand change operations.
472 foreach ( $changes as $key => $obj ) {
473 if ( isset($obj->r) ) {
474 $obj->type = 'rotate';
475 $obj->angle = $obj->r;
477 } elseif ( isset($obj->f) ) {
479 $obj->axis = $obj->f;
481 } elseif ( isset($obj->c) ) {
486 $changes[$key] = $obj;
489 // Combine operations.
490 if ( count($changes) > 1 ) {
491 $filtered = array($changes[0]);
492 for ( $i = 0, $j = 1, $c = count( $changes ); $j < $c; $j++ ) {
494 if ( $filtered[$i]->type == $changes[$j]->type ) {
495 switch ( $filtered[$i]->type ) {
497 $filtered[$i]->angle += $changes[$j]->angle;
501 $filtered[$i]->axis ^= $changes[$j]->axis;
507 $filtered[++$i] = $changes[$j];
509 $changes = $filtered;
513 // Image resource before applying the changes.
514 if ( $image instanceof WP_Image_Editor ) {
517 * Filters the WP_Image_Editor instance before applying changes to the image.
521 * @param WP_Image_Editor $image WP_Image_Editor instance.
522 * @param array $changes Array of change operations.
524 $image = apply_filters( 'wp_image_editor_before_change', $image, $changes );
525 } elseif ( is_resource( $image ) ) {
528 * Filters the GD image resource before applying changes to the image.
531 * @deprecated 3.5.0 Use wp_image_editor_before_change instead.
533 * @param resource $image GD image resource.
534 * @param array $changes Array of change operations.
536 $image = apply_filters( 'image_edit_before_change', $image, $changes );
539 foreach ( $changes as $operation ) {
540 switch ( $operation->type ) {
542 if ( $operation->angle != 0 ) {
543 if ( $image instanceof WP_Image_Editor )
544 $image->rotate( $operation->angle );
546 $image = _rotate_image_resource( $image, $operation->angle );
550 if ( $operation->axis != 0 )
551 if ( $image instanceof WP_Image_Editor )
552 $image->flip( ($operation->axis & 1) != 0, ($operation->axis & 2) != 0 );
554 $image = _flip_image_resource( $image, ( $operation->axis & 1 ) != 0, ( $operation->axis & 2 ) != 0 );
557 $sel = $operation->sel;
559 if ( $image instanceof WP_Image_Editor ) {
560 $size = $image->get_size();
562 $h = $size['height'];
564 $scale = 1 / _image_get_preview_ratio( $w, $h ); // discard preview scaling
565 $image->crop( $sel->x * $scale, $sel->y * $scale, $sel->w * $scale, $sel->h * $scale );
567 $scale = 1 / _image_get_preview_ratio( imagesx( $image ), imagesy( $image ) ); // discard preview scaling
568 $image = _crop_image_resource( $image, $sel->x * $scale, $sel->y * $scale, $sel->w * $scale, $sel->h * $scale );
579 * Streams image in post to browser, along with enqueued changes
580 * in $_REQUEST['history']
582 * @param int $post_id
585 function stream_preview_image( $post_id ) {
586 $post = get_post( $post_id );
588 wp_raise_memory_limit( 'admin' );
590 $img = wp_get_image_editor( _load_image_to_edit_path( $post_id ) );
592 if ( is_wp_error( $img ) ) {
596 $changes = !empty($_REQUEST['history']) ? json_decode( wp_unslash($_REQUEST['history']) ) : null;
598 $img = image_edit_apply_changes( $img, $changes );
601 $size = $img->get_size();
603 $h = $size['height'];
605 $ratio = _image_get_preview_ratio( $w, $h );
606 $w2 = max ( 1, $w * $ratio );
607 $h2 = max ( 1, $h * $ratio );
609 if ( is_wp_error( $img->resize( $w2, $h2 ) ) )
612 return wp_stream_image( $img, $post->post_mime_type, $post_id );
616 * Restores the metadata for a given attachment.
620 * @param int $post_id Attachment post ID.
621 * @return stdClass Image restoration message object.
623 function wp_restore_image($post_id) {
624 $meta = wp_get_attachment_metadata($post_id);
625 $file = get_attached_file($post_id);
626 $backup_sizes = $old_backup_sizes = get_post_meta( $post_id, '_wp_attachment_backup_sizes', true );
630 if ( !is_array($backup_sizes) ) {
631 $msg->error = __('Cannot load image metadata.');
635 $parts = pathinfo($file);
636 $suffix = time() . rand(100, 999);
637 $default_sizes = get_intermediate_image_sizes();
639 if ( isset($backup_sizes['full-orig']) && is_array($backup_sizes['full-orig']) ) {
640 $data = $backup_sizes['full-orig'];
642 if ( $parts['basename'] != $data['file'] ) {
643 if ( defined('IMAGE_EDIT_OVERWRITE') && IMAGE_EDIT_OVERWRITE ) {
645 // Delete only if it's an edited image.
646 if ( preg_match('/-e[0-9]{13}\./', $parts['basename']) ) {
647 wp_delete_file( $file );
649 } elseif ( isset( $meta['width'], $meta['height'] ) ) {
650 $backup_sizes["full-$suffix"] = array('width' => $meta['width'], 'height' => $meta['height'], 'file' => $parts['basename']);
654 $restored_file = path_join($parts['dirname'], $data['file']);
655 $restored = update_attached_file($post_id, $restored_file);
657 $meta['file'] = _wp_relative_upload_path( $restored_file );
658 $meta['width'] = $data['width'];
659 $meta['height'] = $data['height'];
662 foreach ( $default_sizes as $default_size ) {
663 if ( isset($backup_sizes["$default_size-orig"]) ) {
664 $data = $backup_sizes["$default_size-orig"];
665 if ( isset($meta['sizes'][$default_size]) && $meta['sizes'][$default_size]['file'] != $data['file'] ) {
666 if ( defined('IMAGE_EDIT_OVERWRITE') && IMAGE_EDIT_OVERWRITE ) {
668 // Delete only if it's an edited image.
669 if ( preg_match('/-e[0-9]{13}-/', $meta['sizes'][$default_size]['file']) ) {
670 $delete_file = path_join( $parts['dirname'], $meta['sizes'][$default_size]['file'] );
671 wp_delete_file( $delete_file );
674 $backup_sizes["$default_size-{$suffix}"] = $meta['sizes'][$default_size];
678 $meta['sizes'][$default_size] = $data;
680 unset($meta['sizes'][$default_size]);
684 if ( ! wp_update_attachment_metadata( $post_id, $meta ) ||
685 ( $old_backup_sizes !== $backup_sizes && ! update_post_meta( $post_id, '_wp_attachment_backup_sizes', $backup_sizes ) ) ) {
687 $msg->error = __('Cannot save image metadata.');
692 $msg->error = __('Image metadata is inconsistent.');
694 $msg->msg = __('Image restored successfully.');
700 * Saves image to post along with enqueued changes
701 * in $_REQUEST['history']
703 * @param int $post_id
706 function wp_save_image( $post_id ) {
707 $_wp_additional_image_sizes = wp_get_additional_image_sizes();
709 $return = new stdClass;
710 $success = $delete = $scaled = $nocrop = false;
711 $post = get_post( $post_id );
713 $img = wp_get_image_editor( _load_image_to_edit_path( $post_id, 'full' ) );
714 if ( is_wp_error( $img ) ) {
715 $return->error = esc_js( __('Unable to create new image.') );
719 $fwidth = !empty($_REQUEST['fwidth']) ? intval($_REQUEST['fwidth']) : 0;
720 $fheight = !empty($_REQUEST['fheight']) ? intval($_REQUEST['fheight']) : 0;
721 $target = !empty($_REQUEST['target']) ? preg_replace('/[^a-z0-9_-]+/i', '', $_REQUEST['target']) : '';
722 $scale = !empty($_REQUEST['do']) && 'scale' == $_REQUEST['do'];
724 if ( $scale && $fwidth > 0 && $fheight > 0 ) {
725 $size = $img->get_size();
726 $sX = $size['width'];
727 $sY = $size['height'];
729 // Check if it has roughly the same w / h ratio.
730 $diff = round($sX / $sY, 2) - round($fwidth / $fheight, 2);
731 if ( -0.1 < $diff && $diff < 0.1 ) {
732 // Scale the full size image.
733 if ( $img->resize( $fwidth, $fheight ) )
738 $return->error = esc_js( __('Error while saving the scaled image. Please reload the page and try again.') );
741 } elseif ( !empty($_REQUEST['history']) ) {
742 $changes = json_decode( wp_unslash($_REQUEST['history']) );
744 $img = image_edit_apply_changes($img, $changes);
746 $return->error = esc_js( __('Nothing to save, the image has not changed.') );
750 $meta = wp_get_attachment_metadata($post_id);
751 $backup_sizes = get_post_meta( $post->ID, '_wp_attachment_backup_sizes', true );
753 if ( !is_array($meta) ) {
754 $return->error = esc_js( __('Image data does not exist. Please re-upload the image.') );
758 if ( !is_array($backup_sizes) )
759 $backup_sizes = array();
761 // Generate new filename.
762 $path = get_attached_file( $post_id );
764 $basename = pathinfo( $path, PATHINFO_BASENAME );
765 $dirname = pathinfo( $path, PATHINFO_DIRNAME );
766 $ext = pathinfo( $path, PATHINFO_EXTENSION );
767 $filename = pathinfo( $path, PATHINFO_FILENAME );
768 $suffix = time() . rand(100, 999);
770 if ( defined('IMAGE_EDIT_OVERWRITE') && IMAGE_EDIT_OVERWRITE &&
771 isset($backup_sizes['full-orig']) && $backup_sizes['full-orig']['file'] != $basename ) {
773 if ( 'thumbnail' == $target ) {
774 $new_path = "{$dirname}/{$filename}-temp.{$ext}";
780 $filename = preg_replace( '/-e([0-9]+)$/', '', $filename );
781 $filename .= "-e{$suffix}";
782 $new_filename = "{$filename}.{$ext}";
783 $new_path = "{$dirname}/$new_filename";
784 if ( file_exists($new_path) ) {
792 // Save the full-size file, also needed to create sub-sizes.
793 if ( !wp_save_image_file($new_path, $img, $post->post_mime_type, $post_id) ) {
794 $return->error = esc_js( __('Unable to save the image.') );
798 if ( 'nothumb' === $target || 'all' === $target || 'full' === $target || $scaled ) {
800 if ( isset( $backup_sizes['full-orig'] ) ) {
801 if ( ( ! defined( 'IMAGE_EDIT_OVERWRITE' ) || ! IMAGE_EDIT_OVERWRITE ) && $backup_sizes['full-orig']['file'] !== $basename ) {
802 $tag = "full-$suffix";
809 $backup_sizes[$tag] = array('width' => $meta['width'], 'height' => $meta['height'], 'file' => $basename );
811 $success = ( $path === $new_path ) || update_attached_file( $post_id, $new_path );
813 $meta['file'] = _wp_relative_upload_path( $new_path );
815 $size = $img->get_size();
816 $meta['width'] = $size['width'];
817 $meta['height'] = $size['height'];
819 if ( $success && ('nothumb' == $target || 'all' == $target) ) {
820 $sizes = get_intermediate_image_sizes();
821 if ( 'nothumb' == $target )
822 $sizes = array_diff( $sizes, array('thumbnail') );
825 $return->fw = $meta['width'];
826 $return->fh = $meta['height'];
827 } elseif ( 'thumbnail' == $target ) {
828 $sizes = array( 'thumbnail' );
829 $success = $delete = $nocrop = true;
833 * We need to remove any existing resized image files because
834 * a new crop or rotate could generate different sizes (and hence, filenames),
835 * keeping the new resized images from overwriting the existing image files.
836 * https://core.trac.wordpress.org/ticket/32171
838 if ( defined( 'IMAGE_EDIT_OVERWRITE' ) && IMAGE_EDIT_OVERWRITE && ! empty( $meta['sizes'] ) ) {
839 foreach ( $meta['sizes'] as $size ) {
840 if ( ! empty( $size['file'] ) && preg_match( '/-e[0-9]{13}-/', $size['file'] ) ) {
841 $delete_file = path_join( $dirname, $size['file'] );
842 wp_delete_file( $delete_file );
847 if ( isset( $sizes ) ) {
850 foreach ( $sizes as $size ) {
852 if ( isset( $meta['sizes'][$size] ) ) {
853 if ( isset($backup_sizes["$size-orig"]) ) {
854 if ( ( !defined('IMAGE_EDIT_OVERWRITE') || !IMAGE_EDIT_OVERWRITE ) && $backup_sizes["$size-orig"]['file'] != $meta['sizes'][$size]['file'] )
855 $tag = "$size-$suffix";
861 $backup_sizes[$tag] = $meta['sizes'][$size];
864 if ( isset( $_wp_additional_image_sizes[ $size ] ) ) {
865 $width = intval( $_wp_additional_image_sizes[ $size ]['width'] );
866 $height = intval( $_wp_additional_image_sizes[ $size ]['height'] );
867 $crop = ( $nocrop ) ? false : $_wp_additional_image_sizes[ $size ]['crop'];
869 $height = get_option( "{$size}_size_h" );
870 $width = get_option( "{$size}_size_w" );
871 $crop = ( $nocrop ) ? false : get_option( "{$size}_crop" );
874 $_sizes[ $size ] = array( 'width' => $width, 'height' => $height, 'crop' => $crop );
877 $meta['sizes'] = array_merge( $meta['sizes'], $img->multi_resize( $_sizes ) );
883 wp_update_attachment_metadata( $post_id, $meta );
884 update_post_meta( $post_id, '_wp_attachment_backup_sizes', $backup_sizes);
886 if ( $target == 'thumbnail' || $target == 'all' || $target == 'full' ) {
887 // Check if it's an image edit from attachment edit screen
888 if ( ! empty( $_REQUEST['context'] ) && 'edit-attachment' == $_REQUEST['context'] ) {
889 $thumb_url = wp_get_attachment_image_src( $post_id, array( 900, 600 ), true );
890 $return->thumbnail = $thumb_url[0];
892 $file_url = wp_get_attachment_url($post_id);
893 if ( ! empty( $meta['sizes']['thumbnail'] ) && $thumb = $meta['sizes']['thumbnail'] ) {
894 $return->thumbnail = path_join( dirname($file_url), $thumb['file'] );
896 $return->thumbnail = "$file_url?w=128&h=128";
905 wp_delete_file( $new_path );
908 $return->msg = esc_js( __('Image saved') );