Wordpress 3.6
[autoinstalls/wordpress.git] / wp-admin / includes / image-edit.php
1 <?php
2 /**
3  * WordPress Image Editor
4  *
5  * @package WordPress
6  * @subpackage Administration
7  */
8
9 function wp_image_editor($post_id, $msg = false) {
10         $nonce = wp_create_nonce("image_editor-$post_id");
11         $meta = wp_get_attachment_metadata($post_id);
12         $thumb = image_get_intermediate_size($post_id, 'thumbnail');
13         $sub_sizes = isset($meta['sizes']) && is_array($meta['sizes']);
14         $note = '';
15
16         if ( isset( $meta['width'], $meta['height'] ) )
17                 $big = max( $meta['width'], $meta['height'] );
18         else
19                 die( __('Image data does not exist. Please re-upload the image.') );
20
21         $sizer = $big > 400 ? 400 / $big : 1;
22
23         $backup_sizes = get_post_meta( $post_id, '_wp_attachment_backup_sizes', true );
24         $can_restore = false;
25         if ( ! empty( $backup_sizes ) && isset( $backup_sizes['full-orig'], $meta['file'] ) )
26                 $can_restore = $backup_sizes['full-orig']['file'] != basename( $meta['file'] );
27
28         if ( $msg ) {
29                 if ( isset($msg->error) )
30                         $note = "<div class='error'><p>$msg->error</p></div>";
31                 elseif ( isset($msg->msg) )
32                         $note = "<div class='updated'><p>$msg->msg</p></div>";
33         }
34
35         ?>
36         <div class="imgedit-wrap">
37         <?php echo $note; ?>
38         <table id="imgedit-panel-<?php echo $post_id; ?>"><tbody>
39         <tr><td>
40         <div class="imgedit-menu">
41                 <div onclick="imageEdit.crop(<?php echo "$post_id, '$nonce'"; ?>, this)" class="imgedit-crop disabled" title="<?php esc_attr_e( 'Crop' ); ?>"></div><?php
42
43         // On some setups GD library does not provide imagerotate() - Ticket #11536
44         if ( wp_image_editor_supports( array( 'mime_type' => get_post_mime_type( $post_id ), 'methods' => array( 'rotate' ) ) ) ) { ?>
45                 <div class="imgedit-rleft"  onclick="imageEdit.rotate( 90, <?php echo "$post_id, '$nonce'"; ?>, this)" title="<?php esc_attr_e( 'Rotate counter-clockwise' ); ?>"></div>
46                 <div class="imgedit-rright" onclick="imageEdit.rotate(-90, <?php echo "$post_id, '$nonce'"; ?>, this)" title="<?php esc_attr_e( 'Rotate clockwise' ); ?>"></div>
47 <?php } else {
48                 $note_no_rotate = esc_attr__('Image rotation is not supported by your web host.');
49 ?>
50             <div class="imgedit-rleft disabled"  title="<?php echo $note_no_rotate; ?>"></div>
51             <div class="imgedit-rright disabled" title="<?php echo $note_no_rotate; ?>"></div>
52 <?php } ?>
53
54                 <div onclick="imageEdit.flip(1, <?php echo "$post_id, '$nonce'"; ?>, this)" class="imgedit-flipv" title="<?php esc_attr_e( 'Flip vertically' ); ?>"></div>
55                 <div onclick="imageEdit.flip(2, <?php echo "$post_id, '$nonce'"; ?>, this)" class="imgedit-fliph" title="<?php esc_attr_e( 'Flip horizontally' ); ?>"></div>
56
57                 <div id="image-undo-<?php echo $post_id; ?>" onclick="imageEdit.undo(<?php echo "$post_id, '$nonce'"; ?>, this)" class="imgedit-undo disabled" title="<?php esc_attr_e( 'Undo' ); ?>"></div>
58                 <div id="image-redo-<?php echo $post_id; ?>" onclick="imageEdit.redo(<?php echo "$post_id, '$nonce'"; ?>, this)" class="imgedit-redo disabled" title="<?php esc_attr_e( 'Redo' ); ?>"></div>
59                 <br class="clear" />
60         </div>
61
62         <input type="hidden" id="imgedit-sizer-<?php echo $post_id; ?>" value="<?php echo $sizer; ?>" />
63         <input type="hidden" id="imgedit-minthumb-<?php echo $post_id; ?>" value="<?php echo ( get_option('thumbnail_size_w') . ':' . get_option('thumbnail_size_h') ); ?>" />
64         <input type="hidden" id="imgedit-history-<?php echo $post_id; ?>" value="" />
65         <input type="hidden" id="imgedit-undone-<?php echo $post_id; ?>" value="0" />
66         <input type="hidden" id="imgedit-selection-<?php echo $post_id; ?>" value="" />
67         <input type="hidden" id="imgedit-x-<?php echo $post_id; ?>" value="<?php echo isset( $meta['width'] ) ? $meta['width'] : 0; ?>" />
68         <input type="hidden" id="imgedit-y-<?php echo $post_id; ?>" value="<?php echo isset( $meta['height'] ) ? $meta['height'] : 0; ?>" />
69
70         <div id="imgedit-crop-<?php echo $post_id; ?>" class="imgedit-crop-wrap">
71         <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&amp;_ajax_nonce=<?php echo $nonce; ?>&amp;postid=<?php echo $post_id; ?>&amp;rand=<?php echo rand(1, 99999); ?>" />
72         </div>
73
74         <div class="imgedit-submit">
75                 <input type="button" onclick="imageEdit.close(<?php echo $post_id; ?>, 1)" class="button" value="<?php esc_attr_e( 'Cancel' ); ?>" />
76                 <input type="button" onclick="imageEdit.save(<?php echo "$post_id, '$nonce'"; ?>)" disabled="disabled" class="button-primary imgedit-submit-btn" value="<?php esc_attr_e( 'Save' ); ?>" />
77         </div>
78         </td>
79
80         <td class="imgedit-settings">
81         <div class="imgedit-group">
82         <div class="imgedit-group-top">
83                 <a class="imgedit-help-toggle" onclick="imageEdit.toggleHelp(this);return false;" href="#"><strong><?php _e('Scale Image'); ?></strong></a>
84                 <div class="imgedit-help">
85                 <p><?php _e('You can proportionally scale the original image. For best results the scaling should be done before performing any other operations on it like crop, rotate, etc. Note that images can only be scaled down, not up.'); ?></p>
86                 <?php if ( isset( $meta['width'], $meta['height'] ) ): ?>
87                 <p><?php printf( __('Original dimensions %s'), $meta['width'] . '&times;' . $meta['height'] ); ?></p>
88                 <?php endif ?>
89                 <div class="imgedit-submit">
90                 <span class="nowrap"><input type="text" id="imgedit-scale-width-<?php echo $post_id; ?>" onkeyup="imageEdit.scaleChanged(<?php echo $post_id; ?>, 1)" onblur="imageEdit.scaleChanged(<?php echo $post_id; ?>, 1)" style="width:4em;" value="<?php echo isset( $meta['width'] ) ? $meta['width'] : 0; ?>" />&times;<input type="text" id="imgedit-scale-height-<?php echo $post_id; ?>" onkeyup="imageEdit.scaleChanged(<?php echo $post_id; ?>, 0)" onblur="imageEdit.scaleChanged(<?php echo $post_id; ?>, 0)" style="width:4em;" value="<?php echo isset( $meta['height'] ) ? $meta['height'] : 0; ?>" />
91                 <span class="imgedit-scale-warn" id="imgedit-scale-warn-<?php echo $post_id; ?>">!</span></span>
92                 <input type="button" onclick="imageEdit.action(<?php echo "$post_id, '$nonce'"; ?>, 'scale')" class="button-primary" value="<?php esc_attr_e( 'Scale' ); ?>" />
93                 </div>
94                 </div>
95         </div>
96
97 <?php if ( $can_restore ) { ?>
98
99         <div class="imgedit-group-top">
100                 <a class="imgedit-help-toggle" onclick="imageEdit.toggleHelp(this);return false;" href="#"><strong><?php _e('Restore Original Image'); ?></strong></a>
101                 <div class="imgedit-help">
102                 <p><?php _e('Discard any changes and restore the original image.');
103
104                 if ( !defined('IMAGE_EDIT_OVERWRITE') || !IMAGE_EDIT_OVERWRITE )
105                         echo ' '.__('Previously edited copies of the image will not be deleted.');
106
107                 ?></p>
108                 <div class="imgedit-submit">
109                 <input type="button" onclick="imageEdit.action(<?php echo "$post_id, '$nonce'"; ?>, 'restore')" class="button-primary" value="<?php esc_attr_e( 'Restore image' ); ?>" <?php echo $can_restore; ?> />
110                 </div>
111                 </div>
112         </div>
113
114 <?php } ?>
115
116         </div>
117
118         <div class="imgedit-group">
119         <div class="imgedit-group-top">
120                 <strong><?php _e('Image Crop'); ?></strong>
121                 <a class="imgedit-help-toggle" onclick="imageEdit.toggleHelp(this);return false;" href="#"><?php _e('(help)'); ?></a>
122                 <div class="imgedit-help">
123                 <p><?php _e('The image can be cropped by clicking on it and dragging to select the desired part. While dragging the dimensions of the selection are displayed below.'); ?></p>
124
125                 <p><strong><?php _e('Crop Aspect Ratio'); ?></strong><br />
126                 <?php _e('You can specify the crop selection aspect ratio then hold down the Shift key while dragging to lock it. The values can be 1:1 (square), 4:3, 16:9, etc. If there is a selection, specifying aspect ratio will set it immediately.'); ?></p>
127
128                 <p><strong><?php _e('Crop Selection'); ?></strong><br />
129                 <?php _e('Once started, the selection can be adjusted by entering new values (in pixels). Note that these values are scaled to approximately match the original image dimensions. The minimum selection size equals the thumbnail size as set in the Media settings.'); ?></p>
130                 </div>
131         </div>
132
133         <p>
134                 <?php _e('Aspect ratio:'); ?>
135                 <span  class="nowrap">
136                 <input type="text" id="imgedit-crop-width-<?php echo $post_id; ?>" onkeyup="imageEdit.setRatioSelection(<?php echo $post_id; ?>, 0, this)" style="width:3em;" />
137                 :
138                 <input type="text" id="imgedit-crop-height-<?php echo $post_id; ?>" onkeyup="imageEdit.setRatioSelection(<?php echo $post_id; ?>, 1, this)" style="width:3em;" />
139                 </span>
140         </p>
141
142         <p id="imgedit-crop-sel-<?php echo $post_id; ?>">
143                 <?php _e('Selection:'); ?>
144                 <span  class="nowrap">
145                 <input type="text" id="imgedit-sel-width-<?php echo $post_id; ?>" onkeyup="imageEdit.setNumSelection(<?php echo $post_id; ?>)" style="width:4em;" />
146                 :
147                 <input type="text" id="imgedit-sel-height-<?php echo $post_id; ?>" onkeyup="imageEdit.setNumSelection(<?php echo $post_id; ?>)" style="width:4em;" />
148                 </span>
149         </p>
150         </div>
151
152         <?php if ( $thumb && $sub_sizes ) {
153                 $thumb_img = wp_constrain_dimensions( $thumb['width'], $thumb['height'], 160, 120 );
154         ?>
155
156         <div class="imgedit-group imgedit-applyto">
157         <div class="imgedit-group-top">
158                 <strong><?php _e('Thumbnail Settings'); ?></strong>
159                 <a class="imgedit-help-toggle" onclick="imageEdit.toggleHelp(this);return false;" href="#"><?php _e('(help)'); ?></a>
160                 <p class="imgedit-help"><?php _e('The thumbnail image can be cropped differently. For example it can be square or contain only a portion of the original image to showcase it better. Here you can select whether to apply changes to all image sizes or make the thumbnail different.'); ?></p>
161         </div>
162
163         <p>
164                 <img src="<?php echo $thumb['url']; ?>" width="<?php echo $thumb_img[0]; ?>" height="<?php echo $thumb_img[1]; ?>" class="imgedit-size-preview" alt="" /><br /><?php _e('Current thumbnail'); ?>
165         </p>
166
167         <p id="imgedit-save-target-<?php echo $post_id; ?>">
168                 <strong><?php _e('Apply changes to:'); ?></strong><br />
169
170                 <label class="imgedit-label">
171                 <input type="radio" name="imgedit-target-<?php echo $post_id; ?>" value="all" checked="checked" />
172                 <?php _e('All image sizes'); ?></label>
173
174                 <label class="imgedit-label">
175                 <input type="radio" name="imgedit-target-<?php echo $post_id; ?>" value="thumbnail" />
176                 <?php _e('Thumbnail'); ?></label>
177
178                 <label class="imgedit-label">
179                 <input type="radio" name="imgedit-target-<?php echo $post_id; ?>" value="nothumb" />
180                 <?php _e('All sizes except thumbnail'); ?></label>
181         </p>
182         </div>
183
184         <?php } ?>
185
186         </td></tr>
187         </tbody></table>
188         <div class="imgedit-wait" id="imgedit-wait-<?php echo $post_id; ?>"></div>
189         <script type="text/javascript">jQuery( function() { imageEdit.init(<?php echo $post_id; ?>); });</script>
190         <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>
191         </div>
192 <?php
193 }
194
195 /**
196  * Streams image in WP_Image_Editor to browser.
197  * Provided for backcompat reasons
198  *
199  * @param WP_Image_Editor $image
200  * @param string $mime_type
201  * @param int $post_id
202  * @return boolean
203  */
204 function wp_stream_image( $image, $mime_type, $post_id ) {
205         if ( $image instanceof WP_Image_Editor ) {
206                 $image = apply_filters('image_editor_save_pre', $image, $post_id);
207
208                 if ( is_wp_error( $image->stream( $mime_type ) ) )
209                         return false;
210
211                 return true;
212         } else {
213                 _deprecated_argument( __FUNCTION__, '3.5', __( '$image needs to be an WP_Image_Editor object' ) );
214
215                 $image = apply_filters('image_save_pre', $image, $post_id);
216
217                 switch ( $mime_type ) {
218                         case 'image/jpeg':
219                                 header( 'Content-Type: image/jpeg' );
220                                 return imagejpeg( $image, null, 90 );
221                         case 'image/png':
222                                 header( 'Content-Type: image/png' );
223                                 return imagepng( $image );
224                         case 'image/gif':
225                                 header( 'Content-Type: image/gif' );
226                                 return imagegif( $image );
227                         default:
228                                 return false;
229                 }
230         }
231 }
232
233 /**
234  * Saves Image to File
235  *
236  * @param string $filename
237  * @param WP_Image_Editor $image
238  * @param string $mime_type
239  * @param int $post_id
240  * @return boolean
241  */
242 function wp_save_image_file( $filename, $image, $mime_type, $post_id ) {
243         if ( $image instanceof WP_Image_Editor ) {
244                 $image = apply_filters('image_editor_save_pre', $image, $post_id);
245                 $saved = apply_filters('wp_save_image_editor_file', null, $filename, $image, $mime_type, $post_id);
246
247                 if ( null !== $saved )
248                         return $saved;
249
250                 return $image->save( $filename, $mime_type );
251         } else {
252                 _deprecated_argument( __FUNCTION__, '3.5', __( '$image needs to be an WP_Image_Editor object' ) );
253
254                 $image = apply_filters('image_save_pre', $image, $post_id);
255                 $saved = apply_filters('wp_save_image_file', null, $filename, $image, $mime_type, $post_id);
256
257                 if ( null !== $saved )
258                         return $saved;
259
260                 switch ( $mime_type ) {
261                         case 'image/jpeg':
262                                 return imagejpeg( $image, $filename, apply_filters( 'jpeg_quality', 90, 'edit_image' ) );
263                         case 'image/png':
264                                 return imagepng( $image, $filename );
265                         case 'image/gif':
266                                 return imagegif( $image, $filename );
267                         default:
268                                 return false;
269                 }
270         }
271 }
272
273 function _image_get_preview_ratio($w, $h) {
274         $max = max($w, $h);
275         return $max > 400 ? (400 / $max) : 1;
276 }
277
278 // @TODO: Returns GD resource, but is NOT public
279 function _rotate_image_resource($img, $angle) {
280         _deprecated_function( __FUNCTION__, '3.5', __( 'Use WP_Image_Editor::rotate' ) );
281         if ( function_exists('imagerotate') ) {
282                 $rotated = imagerotate($img, $angle, 0);
283                 if ( is_resource($rotated) ) {
284                         imagedestroy($img);
285                         $img = $rotated;
286                 }
287         }
288         return $img;
289 }
290
291 /**
292  * @TODO: Only used within image_edit_apply_changes
293  *                and receives/returns GD Resource.
294  *                Consider removal.
295  *
296  * @param GD_Resource $img
297  * @param boolean $horz
298  * @param boolean $vert
299  * @return GD_Resource
300  */
301 function _flip_image_resource($img, $horz, $vert) {
302         _deprecated_function( __FUNCTION__, '3.5', __( 'Use WP_Image_Editor::flip' ) );
303         $w = imagesx($img);
304         $h = imagesy($img);
305         $dst = wp_imagecreatetruecolor($w, $h);
306         if ( is_resource($dst) ) {
307                 $sx = $vert ? ($w - 1) : 0;
308                 $sy = $horz ? ($h - 1) : 0;
309                 $sw = $vert ? -$w : $w;
310                 $sh = $horz ? -$h : $h;
311
312                 if ( imagecopyresampled($dst, $img, 0, 0, $sx, $sy, $w, $h, $sw, $sh) ) {
313                         imagedestroy($img);
314                         $img = $dst;
315                 }
316         }
317         return $img;
318 }
319
320 /**
321  * @TODO: Only used within image_edit_apply_changes
322  *                and receives/returns GD Resource.
323  *                Consider removal.
324  *
325  * @param GD_Resource $img
326  * @param float $x
327  * @param float $y
328  * @param float $w
329  * @param float $h
330  * @return GD_Resource
331  */
332 function _crop_image_resource($img, $x, $y, $w, $h) {
333         $dst = wp_imagecreatetruecolor($w, $h);
334         if ( is_resource($dst) ) {
335                 if ( imagecopy($dst, $img, 0, 0, $x, $y, $w, $h) ) {
336                         imagedestroy($img);
337                         $img = $dst;
338                 }
339         }
340         return $img;
341 }
342
343 /**
344  * Performs group of changes on Editor specified.
345  *
346  * @param WP_Image_Editor $image
347  * @param type $changes
348  * @return WP_Image_Editor
349  */
350 function image_edit_apply_changes( $image, $changes ) {
351         if ( is_resource( $image ) )
352                 _deprecated_argument( __FUNCTION__, '3.5', __( '$image needs to be an WP_Image_Editor object' ) );
353
354         if ( !is_array($changes) )
355                 return $image;
356
357         // expand change operations
358         foreach ( $changes as $key => $obj ) {
359                 if ( isset($obj->r) ) {
360                         $obj->type = 'rotate';
361                         $obj->angle = $obj->r;
362                         unset($obj->r);
363                 } elseif ( isset($obj->f) ) {
364                         $obj->type = 'flip';
365                         $obj->axis = $obj->f;
366                         unset($obj->f);
367                 } elseif ( isset($obj->c) ) {
368                         $obj->type = 'crop';
369                         $obj->sel = $obj->c;
370                         unset($obj->c);
371                 }
372                 $changes[$key] = $obj;
373         }
374
375         // combine operations
376         if ( count($changes) > 1 ) {
377                 $filtered = array($changes[0]);
378                 for ( $i = 0, $j = 1; $j < count($changes); $j++ ) {
379                         $combined = false;
380                         if ( $filtered[$i]->type == $changes[$j]->type ) {
381                                 switch ( $filtered[$i]->type ) {
382                                         case 'rotate':
383                                                 $filtered[$i]->angle += $changes[$j]->angle;
384                                                 $combined = true;
385                                                 break;
386                                         case 'flip':
387                                                 $filtered[$i]->axis ^= $changes[$j]->axis;
388                                                 $combined = true;
389                                                 break;
390                                 }
391                         }
392                         if ( !$combined )
393                                 $filtered[++$i] = $changes[$j];
394                 }
395                 $changes = $filtered;
396                 unset($filtered);
397         }
398
399         // image resource before applying the changes
400         if ( $image instanceof WP_Image_Editor )
401                 $image = apply_filters('wp_image_editor_before_change', $image, $changes);
402         elseif ( is_resource( $image ) )
403                 $image = apply_filters('image_edit_before_change', $image, $changes);
404
405         foreach ( $changes as $operation ) {
406                 switch ( $operation->type ) {
407                         case 'rotate':
408                                 if ( $operation->angle != 0 ) {
409                                         if ( $image instanceof WP_Image_Editor )
410                                                 $image->rotate( $operation->angle );
411                                         else
412                                                 $image = _rotate_image_resource( $image, $operation->angle );
413                                 }
414                                 break;
415                         case 'flip':
416                                 if ( $operation->axis != 0 )
417                                         if ( $image instanceof WP_Image_Editor )
418                                                 $image->flip( ($operation->axis & 1) != 0, ($operation->axis & 2) != 0 );
419                                         else
420                                                 $image = _flip_image_resource( $image, ( $operation->axis & 1 ) != 0, ( $operation->axis & 2 ) != 0 );
421                                 break;
422                         case 'crop':
423                                 $sel = $operation->sel;
424
425                                 if ( $image instanceof WP_Image_Editor ) {
426                                         $size = $image->get_size();
427                                         $w = $size['width'];
428                                         $h = $size['height'];
429
430                                         $scale = 1 / _image_get_preview_ratio( $w, $h ); // discard preview scaling
431                                         $image->crop( $sel->x * $scale, $sel->y * $scale, $sel->w * $scale, $sel->h * $scale );
432                                 } else {
433                                         $scale = 1 / _image_get_preview_ratio( imagesx( $image ), imagesy( $image ) ); // discard preview scaling
434                                         $image = _crop_image_resource( $image, $sel->x * $scale, $sel->y * $scale, $sel->w * $scale, $sel->h * $scale );
435                                 }
436                                 break;
437                 }
438         }
439
440         return $image;
441 }
442
443
444 /**
445  * Streams image in post to browser, along with enqueued changes
446  * in $_REQUEST['history']
447  *
448  * @param int $post_id
449  * @return boolean
450  */
451 function stream_preview_image( $post_id ) {
452         $post = get_post( $post_id );
453         @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) );
454
455         $img = wp_get_image_editor( _load_image_to_edit_path( $post_id ) );
456
457     if ( is_wp_error( $img ) )
458         return false;
459
460         $changes = !empty($_REQUEST['history']) ? json_decode( wp_unslash($_REQUEST['history']) ) : null;
461         if ( $changes )
462                 $img = image_edit_apply_changes( $img, $changes );
463
464         // scale the image
465         $size = $img->get_size();
466         $w = $size['width'];
467         $h = $size['height'];
468
469         $ratio = _image_get_preview_ratio( $w, $h );
470         $w2 = $w * $ratio;
471         $h2 = $h * $ratio;
472
473         if ( is_wp_error( $img->resize( $w2, $h2 ) ) )
474                 return false;
475
476         return wp_stream_image( $img, $post->post_mime_type, $post_id );
477 }
478
479 function wp_restore_image($post_id) {
480         $meta = wp_get_attachment_metadata($post_id);
481         $file = get_attached_file($post_id);
482         $backup_sizes = get_post_meta( $post_id, '_wp_attachment_backup_sizes', true );
483         $restored = false;
484         $msg = new stdClass;
485
486         if ( !is_array($backup_sizes) ) {
487                 $msg->error = __('Cannot load image metadata.');
488                 return $msg;
489         }
490
491         $parts = pathinfo($file);
492         $suffix = time() . rand(100, 999);
493         $default_sizes = get_intermediate_image_sizes();
494
495         if ( isset($backup_sizes['full-orig']) && is_array($backup_sizes['full-orig']) ) {
496                 $data = $backup_sizes['full-orig'];
497
498                 if ( $parts['basename'] != $data['file'] ) {
499                         if ( defined('IMAGE_EDIT_OVERWRITE') && IMAGE_EDIT_OVERWRITE ) {
500                                 // delete only if it's edited image
501                                 if ( preg_match('/-e[0-9]{13}\./', $parts['basename']) ) {
502                                         $delpath = apply_filters('wp_delete_file', $file);
503                                         @unlink($delpath);
504                                 }
505                         } elseif ( isset( $meta['width'], $meta['height'] ) ) {
506                                 $backup_sizes["full-$suffix"] = array('width' => $meta['width'], 'height' => $meta['height'], 'file' => $parts['basename']);
507                         }
508                 }
509
510                 $restored_file = path_join($parts['dirname'], $data['file']);
511                 $restored = update_attached_file($post_id, $restored_file);
512
513                 $meta['file'] = _wp_relative_upload_path( $restored_file );
514                 $meta['width'] = $data['width'];
515                 $meta['height'] = $data['height'];
516         }
517
518         foreach ( $default_sizes as $default_size ) {
519                 if ( isset($backup_sizes["$default_size-orig"]) ) {
520                         $data = $backup_sizes["$default_size-orig"];
521                         if ( isset($meta['sizes'][$default_size]) && $meta['sizes'][$default_size]['file'] != $data['file'] ) {
522                                 if ( defined('IMAGE_EDIT_OVERWRITE') && IMAGE_EDIT_OVERWRITE ) {
523                                         // delete only if it's edited image
524                                         if ( preg_match('/-e[0-9]{13}-/', $meta['sizes'][$default_size]['file']) ) {
525                                                 $delpath = apply_filters( 'wp_delete_file', path_join($parts['dirname'], $meta['sizes'][$default_size]['file']) );
526                                                 @unlink($delpath);
527                                         }
528                                 } else {
529                                         $backup_sizes["$default_size-{$suffix}"] = $meta['sizes'][$default_size];
530                                 }
531                         }
532
533                         $meta['sizes'][$default_size] = $data;
534                 } else {
535                         unset($meta['sizes'][$default_size]);
536                 }
537         }
538
539         if ( !wp_update_attachment_metadata($post_id, $meta) || !update_post_meta( $post_id, '_wp_attachment_backup_sizes', $backup_sizes) ) {
540                 $msg->error = __('Cannot save image metadata.');
541                 return $msg;
542         }
543
544         if ( !$restored )
545                 $msg->error = __('Image metadata is inconsistent.');
546         else
547                 $msg->msg = __('Image restored successfully.');
548
549         return $msg;
550 }
551
552 /**
553  * Saves image to post along with enqueued changes
554  * in $_REQUEST['history']
555  *
556  * @param int $post_id
557  * @return \stdClass
558  */
559 function wp_save_image( $post_id ) {
560         $return = new stdClass;
561         $success = $delete = $scaled = $nocrop = false;
562         $post = get_post( $post_id );
563
564         $img = wp_get_image_editor( _load_image_to_edit_path( $post_id, 'full' ) );
565         if ( is_wp_error( $img ) ) {
566                 $return->error = esc_js( __('Unable to create new image.') );
567                 return $return;
568         }
569
570         $fwidth = !empty($_REQUEST['fwidth']) ? intval($_REQUEST['fwidth']) : 0;
571         $fheight = !empty($_REQUEST['fheight']) ? intval($_REQUEST['fheight']) : 0;
572         $target = !empty($_REQUEST['target']) ? preg_replace('/[^a-z0-9_-]+/i', '', $_REQUEST['target']) : '';
573         $scale = !empty($_REQUEST['do']) && 'scale' == $_REQUEST['do'];
574
575         if ( $scale && $fwidth > 0 && $fheight > 0 ) {
576                 $size = $img->get_size();
577                 $sX = $size['width'];
578                 $sY = $size['height'];
579
580                 // check if it has roughly the same w / h ratio
581                 $diff = round($sX / $sY, 2) - round($fwidth / $fheight, 2);
582                 if ( -0.1 < $diff && $diff < 0.1 ) {
583                         // scale the full size image
584                         if ( $img->resize( $fwidth, $fheight ) )
585                                 $scaled = true;
586                 }
587
588                 if ( !$scaled ) {
589                         $return->error = esc_js( __('Error while saving the scaled image. Please reload the page and try again.') );
590                         return $return;
591                 }
592         } elseif ( !empty($_REQUEST['history']) ) {
593                 $changes = json_decode( wp_unslash($_REQUEST['history']) );
594                 if ( $changes )
595                         $img = image_edit_apply_changes($img, $changes);
596         } else {
597                 $return->error = esc_js( __('Nothing to save, the image has not changed.') );
598                 return $return;
599         }
600
601         $meta = wp_get_attachment_metadata($post_id);
602         $backup_sizes = get_post_meta( $post->ID, '_wp_attachment_backup_sizes', true );
603
604         if ( !is_array($meta) ) {
605                 $return->error = esc_js( __('Image data does not exist. Please re-upload the image.') );
606                 return $return;
607         }
608
609         if ( !is_array($backup_sizes) )
610                 $backup_sizes = array();
611
612         // generate new filename
613         $path = get_attached_file($post_id);
614         $path_parts = pathinfo( $path );
615         $filename = $path_parts['filename'];
616         $suffix = time() . rand(100, 999);
617
618         if ( defined('IMAGE_EDIT_OVERWRITE') && IMAGE_EDIT_OVERWRITE &&
619                 isset($backup_sizes['full-orig']) && $backup_sizes['full-orig']['file'] != $path_parts['basename'] ) {
620
621                 if ( 'thumbnail' == $target )
622                         $new_path = "{$path_parts['dirname']}/{$filename}-temp.{$path_parts['extension']}";
623                 else
624                         $new_path = $path;
625         } else {
626                 while( true ) {
627                         $filename = preg_replace( '/-e([0-9]+)$/', '', $filename );
628                         $filename .= "-e{$suffix}";
629                         $new_filename = "{$filename}.{$path_parts['extension']}";
630                         $new_path = "{$path_parts['dirname']}/$new_filename";
631                         if ( file_exists($new_path) )
632                                 $suffix++;
633                         else
634                                 break;
635                 }
636         }
637
638         // save the full-size file, also needed to create sub-sizes
639         if ( !wp_save_image_file($new_path, $img, $post->post_mime_type, $post_id) ) {
640                 $return->error = esc_js( __('Unable to save the image.') );
641                 return $return;
642         }
643
644         if ( 'nothumb' == $target || 'all' == $target || 'full' == $target || $scaled ) {
645                 $tag = false;
646                 if ( isset($backup_sizes['full-orig']) ) {
647                         if ( ( !defined('IMAGE_EDIT_OVERWRITE') || !IMAGE_EDIT_OVERWRITE ) && $backup_sizes['full-orig']['file'] != $path_parts['basename'] )
648                                 $tag = "full-$suffix";
649                 } else {
650                         $tag = 'full-orig';
651                 }
652
653                 if ( $tag )
654                         $backup_sizes[$tag] = array('width' => $meta['width'], 'height' => $meta['height'], 'file' => $path_parts['basename']);
655
656                 $success = update_attached_file( $post_id, $new_path );
657
658                 $meta['file'] = _wp_relative_upload_path( $new_path );
659
660                 $size = $img->get_size();
661                 $meta['width'] = $size['width'];
662                 $meta['height'] = $size['height'];
663
664                 if ( $success && ('nothumb' == $target || 'all' == $target) ) {
665                         $sizes = get_intermediate_image_sizes();
666                         if ( 'nothumb' == $target )
667                                 $sizes = array_diff( $sizes, array('thumbnail') );
668                 }
669
670                 $return->fw = $meta['width'];
671                 $return->fh = $meta['height'];
672         } elseif ( 'thumbnail' == $target ) {
673                 $sizes = array( 'thumbnail' );
674                 $success = $delete = $nocrop = true;
675         }
676
677         if ( isset( $sizes ) ) {
678                 $_sizes = array();
679
680                 foreach ( $sizes as $size ) {
681                         $tag = false;
682                         if ( isset( $meta['sizes'][$size] ) ) {
683                                 if ( isset($backup_sizes["$size-orig"]) ) {
684                                         if ( ( !defined('IMAGE_EDIT_OVERWRITE') || !IMAGE_EDIT_OVERWRITE ) && $backup_sizes["$size-orig"]['file'] != $meta['sizes'][$size]['file'] )
685                                                 $tag = "$size-$suffix";
686                                 } else {
687                                         $tag = "$size-orig";
688                                 }
689
690                                 if ( $tag )
691                                         $backup_sizes[$tag] = $meta['sizes'][$size];
692                         }
693
694                         $crop = $nocrop ? false : get_option("{$size}_crop");
695                         $_sizes[ $size ] = array( 'width' => get_option("{$size}_size_w"), 'height' => get_option("{$size}_size_h"), 'crop' => $crop );
696                 }
697
698                 $meta['sizes'] = array_merge( $meta['sizes'], $img->multi_resize( $_sizes ) );
699         }
700
701         unset( $img );
702
703         if ( $success ) {
704                 wp_update_attachment_metadata( $post_id, $meta );
705                 update_post_meta( $post_id, '_wp_attachment_backup_sizes', $backup_sizes);
706
707                 if ( $target == 'thumbnail' || $target == 'all' || $target == 'full' ) {
708                         // Check if it's an image edit from attachment edit screen
709                         if ( ! empty( $_REQUEST['context'] ) && 'edit-attachment' == $_REQUEST['context'] ) {
710                                 $thumb_url = wp_get_attachment_image_src( $post_id, array( 900, 600 ), true );
711                                 $return->thumbnail = $thumb_url[0];
712                         } else {
713                                 $file_url = wp_get_attachment_url($post_id);
714                                 if ( $thumb = $meta['sizes']['thumbnail'] )
715                                         $return->thumbnail = path_join( dirname($file_url), $thumb['file'] );
716                                 else
717                                         $return->thumbnail = "$file_url?w=128&h=128";
718                         }
719                 }
720         } else {
721                 $delete = true;
722         }
723
724         if ( $delete ) {
725                 $delpath = apply_filters('wp_delete_file', $new_path);
726                 @unlink( $delpath );
727         }
728
729         $return->msg = esc_js( __('Image saved') );
730         return $return;
731 }