Wordpress 3.7
[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 = max ( 1, $w * $ratio );
471         $h2 = max ( 1, $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                                         /** This filter is documented in wp-admin/custom-header.php */
503                                         $delpath = apply_filters('wp_delete_file', $file);
504                                         @unlink($delpath);
505                                 }
506                         } elseif ( isset( $meta['width'], $meta['height'] ) ) {
507                                 $backup_sizes["full-$suffix"] = array('width' => $meta['width'], 'height' => $meta['height'], 'file' => $parts['basename']);
508                         }
509                 }
510
511                 $restored_file = path_join($parts['dirname'], $data['file']);
512                 $restored = update_attached_file($post_id, $restored_file);
513
514                 $meta['file'] = _wp_relative_upload_path( $restored_file );
515                 $meta['width'] = $data['width'];
516                 $meta['height'] = $data['height'];
517         }
518
519         foreach ( $default_sizes as $default_size ) {
520                 if ( isset($backup_sizes["$default_size-orig"]) ) {
521                         $data = $backup_sizes["$default_size-orig"];
522                         if ( isset($meta['sizes'][$default_size]) && $meta['sizes'][$default_size]['file'] != $data['file'] ) {
523                                 if ( defined('IMAGE_EDIT_OVERWRITE') && IMAGE_EDIT_OVERWRITE ) {
524                                         // delete only if it's edited image
525                                         if ( preg_match('/-e[0-9]{13}-/', $meta['sizes'][$default_size]['file']) ) {
526                                                 /** This filter is documented in wp-admin/custom-header.php */
527                                                 $delpath = apply_filters( 'wp_delete_file', path_join($parts['dirname'], $meta['sizes'][$default_size]['file']) );
528                                                 @unlink($delpath);
529                                         }
530                                 } else {
531                                         $backup_sizes["$default_size-{$suffix}"] = $meta['sizes'][$default_size];
532                                 }
533                         }
534
535                         $meta['sizes'][$default_size] = $data;
536                 } else {
537                         unset($meta['sizes'][$default_size]);
538                 }
539         }
540
541         if ( !wp_update_attachment_metadata($post_id, $meta) || !update_post_meta( $post_id, '_wp_attachment_backup_sizes', $backup_sizes) ) {
542                 $msg->error = __('Cannot save image metadata.');
543                 return $msg;
544         }
545
546         if ( !$restored )
547                 $msg->error = __('Image metadata is inconsistent.');
548         else
549                 $msg->msg = __('Image restored successfully.');
550
551         return $msg;
552 }
553
554 /**
555  * Saves image to post along with enqueued changes
556  * in $_REQUEST['history']
557  *
558  * @param int $post_id
559  * @return \stdClass
560  */
561 function wp_save_image( $post_id ) {
562         $return = new stdClass;
563         $success = $delete = $scaled = $nocrop = false;
564         $post = get_post( $post_id );
565
566         $img = wp_get_image_editor( _load_image_to_edit_path( $post_id, 'full' ) );
567         if ( is_wp_error( $img ) ) {
568                 $return->error = esc_js( __('Unable to create new image.') );
569                 return $return;
570         }
571
572         $fwidth = !empty($_REQUEST['fwidth']) ? intval($_REQUEST['fwidth']) : 0;
573         $fheight = !empty($_REQUEST['fheight']) ? intval($_REQUEST['fheight']) : 0;
574         $target = !empty($_REQUEST['target']) ? preg_replace('/[^a-z0-9_-]+/i', '', $_REQUEST['target']) : '';
575         $scale = !empty($_REQUEST['do']) && 'scale' == $_REQUEST['do'];
576
577         if ( $scale && $fwidth > 0 && $fheight > 0 ) {
578                 $size = $img->get_size();
579                 $sX = $size['width'];
580                 $sY = $size['height'];
581
582                 // check if it has roughly the same w / h ratio
583                 $diff = round($sX / $sY, 2) - round($fwidth / $fheight, 2);
584                 if ( -0.1 < $diff && $diff < 0.1 ) {
585                         // scale the full size image
586                         if ( $img->resize( $fwidth, $fheight ) )
587                                 $scaled = true;
588                 }
589
590                 if ( !$scaled ) {
591                         $return->error = esc_js( __('Error while saving the scaled image. Please reload the page and try again.') );
592                         return $return;
593                 }
594         } elseif ( !empty($_REQUEST['history']) ) {
595                 $changes = json_decode( wp_unslash($_REQUEST['history']) );
596                 if ( $changes )
597                         $img = image_edit_apply_changes($img, $changes);
598         } else {
599                 $return->error = esc_js( __('Nothing to save, the image has not changed.') );
600                 return $return;
601         }
602
603         $meta = wp_get_attachment_metadata($post_id);
604         $backup_sizes = get_post_meta( $post->ID, '_wp_attachment_backup_sizes', true );
605
606         if ( !is_array($meta) ) {
607                 $return->error = esc_js( __('Image data does not exist. Please re-upload the image.') );
608                 return $return;
609         }
610
611         if ( !is_array($backup_sizes) )
612                 $backup_sizes = array();
613
614         // generate new filename
615         $path = get_attached_file($post_id);
616         $path_parts = pathinfo( $path );
617         $filename = $path_parts['filename'];
618         $suffix = time() . rand(100, 999);
619
620         if ( defined('IMAGE_EDIT_OVERWRITE') && IMAGE_EDIT_OVERWRITE &&
621                 isset($backup_sizes['full-orig']) && $backup_sizes['full-orig']['file'] != $path_parts['basename'] ) {
622
623                 if ( 'thumbnail' == $target )
624                         $new_path = "{$path_parts['dirname']}/{$filename}-temp.{$path_parts['extension']}";
625                 else
626                         $new_path = $path;
627         } else {
628                 while( true ) {
629                         $filename = preg_replace( '/-e([0-9]+)$/', '', $filename );
630                         $filename .= "-e{$suffix}";
631                         $new_filename = "{$filename}.{$path_parts['extension']}";
632                         $new_path = "{$path_parts['dirname']}/$new_filename";
633                         if ( file_exists($new_path) )
634                                 $suffix++;
635                         else
636                                 break;
637                 }
638         }
639
640         // save the full-size file, also needed to create sub-sizes
641         if ( !wp_save_image_file($new_path, $img, $post->post_mime_type, $post_id) ) {
642                 $return->error = esc_js( __('Unable to save the image.') );
643                 return $return;
644         }
645
646         if ( 'nothumb' == $target || 'all' == $target || 'full' == $target || $scaled ) {
647                 $tag = false;
648                 if ( isset($backup_sizes['full-orig']) ) {
649                         if ( ( !defined('IMAGE_EDIT_OVERWRITE') || !IMAGE_EDIT_OVERWRITE ) && $backup_sizes['full-orig']['file'] != $path_parts['basename'] )
650                                 $tag = "full-$suffix";
651                 } else {
652                         $tag = 'full-orig';
653                 }
654
655                 if ( $tag )
656                         $backup_sizes[$tag] = array('width' => $meta['width'], 'height' => $meta['height'], 'file' => $path_parts['basename']);
657
658                 $success = update_attached_file( $post_id, $new_path );
659
660                 $meta['file'] = _wp_relative_upload_path( $new_path );
661
662                 $size = $img->get_size();
663                 $meta['width'] = $size['width'];
664                 $meta['height'] = $size['height'];
665
666                 if ( $success && ('nothumb' == $target || 'all' == $target) ) {
667                         $sizes = get_intermediate_image_sizes();
668                         if ( 'nothumb' == $target )
669                                 $sizes = array_diff( $sizes, array('thumbnail') );
670                 }
671
672                 $return->fw = $meta['width'];
673                 $return->fh = $meta['height'];
674         } elseif ( 'thumbnail' == $target ) {
675                 $sizes = array( 'thumbnail' );
676                 $success = $delete = $nocrop = true;
677         }
678
679         if ( isset( $sizes ) ) {
680                 $_sizes = array();
681
682                 foreach ( $sizes as $size ) {
683                         $tag = false;
684                         if ( isset( $meta['sizes'][$size] ) ) {
685                                 if ( isset($backup_sizes["$size-orig"]) ) {
686                                         if ( ( !defined('IMAGE_EDIT_OVERWRITE') || !IMAGE_EDIT_OVERWRITE ) && $backup_sizes["$size-orig"]['file'] != $meta['sizes'][$size]['file'] )
687                                                 $tag = "$size-$suffix";
688                                 } else {
689                                         $tag = "$size-orig";
690                                 }
691
692                                 if ( $tag )
693                                         $backup_sizes[$tag] = $meta['sizes'][$size];
694                         }
695
696                         $crop = $nocrop ? false : get_option("{$size}_crop");
697                         $_sizes[ $size ] = array( 'width' => get_option("{$size}_size_w"), 'height' => get_option("{$size}_size_h"), 'crop' => $crop );
698                 }
699
700                 $meta['sizes'] = array_merge( $meta['sizes'], $img->multi_resize( $_sizes ) );
701         }
702
703         unset( $img );
704
705         if ( $success ) {
706                 wp_update_attachment_metadata( $post_id, $meta );
707                 update_post_meta( $post_id, '_wp_attachment_backup_sizes', $backup_sizes);
708
709                 if ( $target == 'thumbnail' || $target == 'all' || $target == 'full' ) {
710                         // Check if it's an image edit from attachment edit screen
711                         if ( ! empty( $_REQUEST['context'] ) && 'edit-attachment' == $_REQUEST['context'] ) {
712                                 $thumb_url = wp_get_attachment_image_src( $post_id, array( 900, 600 ), true );
713                                 $return->thumbnail = $thumb_url[0];
714                         } else {
715                                 $file_url = wp_get_attachment_url($post_id);
716                                 if ( $thumb = $meta['sizes']['thumbnail'] )
717                                         $return->thumbnail = path_join( dirname($file_url), $thumb['file'] );
718                                 else
719                                         $return->thumbnail = "$file_url?w=128&h=128";
720                         }
721                 }
722         } else {
723                 $delete = true;
724         }
725
726         if ( $delete ) {
727                 /** This filter is documented in wp-admin/custom-header.php */
728                 $delpath = apply_filters('wp_delete_file', $new_path);
729                 @unlink( $delpath );
730         }
731
732         $return->msg = esc_js( __('Image saved') );
733         return $return;
734 }