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