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