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