WordPress 4.0
[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-history-<?php echo $post_id; ?>" value="" />
174                 <input type="hidden" id="imgedit-undone-<?php echo $post_id; ?>" value="0" />
175                 <input type="hidden" id="imgedit-selection-<?php echo $post_id; ?>" value="" />
176                 <input type="hidden" id="imgedit-x-<?php echo $post_id; ?>" value="<?php echo isset( $meta['width'] ) ? $meta['width'] : 0; ?>" />
177                 <input type="hidden" id="imgedit-y-<?php echo $post_id; ?>" value="<?php echo isset( $meta['height'] ) ? $meta['height'] : 0; ?>" />
178
179                 <div id="imgedit-crop-<?php echo $post_id; ?>" class="imgedit-crop-wrap">
180                 <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); ?>" />
181                 </div>
182
183                 <div class="imgedit-submit">
184                         <input type="button" onclick="imageEdit.close(<?php echo $post_id; ?>, 1)" class="button" value="<?php esc_attr_e( 'Cancel' ); ?>" />
185                         <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' ); ?>" />
186                 </div>
187         </div>
188
189         </div>
190         <div class="imgedit-wait" id="imgedit-wait-<?php echo $post_id; ?>"></div>
191         <script type="text/javascript">jQuery( function() { imageEdit.init(<?php echo $post_id; ?>); });</script>
192         <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>
193         </div>
194 <?php
195 }
196
197 /**
198  * Streams image in WP_Image_Editor to browser.
199  * Provided for backcompat reasons
200  *
201  * @param WP_Image_Editor $image
202  * @param string $mime_type
203  * @param int $post_id
204  * @return boolean
205  */
206 function wp_stream_image( $image, $mime_type, $post_id ) {
207         if ( $image instanceof WP_Image_Editor ) {
208
209                 /**
210                  * Filter the WP_Image_Editor instance for the image to be streamed to the browser.
211                  *
212                  * @since 3.5.0
213                  *
214                  * @param WP_Image_Editor $image   WP_Image_Editor instance.
215                  * @param int             $post_id Post ID.
216                  */
217                 $image = apply_filters( 'image_editor_save_pre', $image, $post_id );
218
219                 if ( is_wp_error( $image->stream( $mime_type ) ) )
220                         return false;
221
222                 return true;
223         } else {
224                 _deprecated_argument( __FUNCTION__, '3.5', __( '$image needs to be an WP_Image_Editor object' ) );
225
226                 /**
227                  * Filter the GD image resource to be streamed to the browser.
228                  *
229                  * @since 2.9.0
230                  * @deprecated 3.5.0 Use image_editor_save_pre instead.
231                  *
232                  * @param resource $image   Image resource to be streamed.
233                  * @param int      $post_id Post ID.
234                  */
235                 $image = apply_filters( 'image_save_pre', $image, $post_id );
236
237                 switch ( $mime_type ) {
238                         case 'image/jpeg':
239                                 header( 'Content-Type: image/jpeg' );
240                                 return imagejpeg( $image, null, 90 );
241                         case 'image/png':
242                                 header( 'Content-Type: image/png' );
243                                 return imagepng( $image );
244                         case 'image/gif':
245                                 header( 'Content-Type: image/gif' );
246                                 return imagegif( $image );
247                         default:
248                                 return false;
249                 }
250         }
251 }
252
253 /**
254  * Saves Image to File
255  *
256  * @param string $filename
257  * @param WP_Image_Editor $image
258  * @param string $mime_type
259  * @param int $post_id
260  * @return boolean
261  */
262 function wp_save_image_file( $filename, $image, $mime_type, $post_id ) {
263         if ( $image instanceof WP_Image_Editor ) {
264
265                 /** This filter is documented in wp-admin/includes/image-edit.php */
266                 $image = apply_filters( 'image_editor_save_pre', $image, $post_id );
267
268                 /**
269                  * Filter whether to skip saving the image file.
270                  *
271                  * Returning a non-null value will short-circuit the save method,
272                  * returning that value instead.
273                  *
274                  * @since 3.5.0
275                  *
276                  * @param mixed           $override  Value to return instead of saving. Default null.
277                  * @param string          $filename  Name of the file to be saved.
278                  * @param WP_Image_Editor $image     WP_Image_Editor instance.
279                  * @param string          $mime_type Image mime type.
280                  * @param int             $post_id   Post ID.
281                  */
282                 $saved = apply_filters( 'wp_save_image_editor_file', null, $filename, $image, $mime_type, $post_id );
283
284                 if ( null !== $saved )
285                         return $saved;
286
287                 return $image->save( $filename, $mime_type );
288         } else {
289                 _deprecated_argument( __FUNCTION__, '3.5', __( '$image needs to be an WP_Image_Editor object' ) );
290
291                 /** This filter is documented in wp-admin/includes/image-edit.php */
292                 $image = apply_filters( 'image_save_pre', $image, $post_id );
293
294                 /**
295                  * Filter whether to skip saving the image file.
296                  *
297                  * Returning a non-null value will short-circuit the save method,
298                  * returning that value instead.
299                  *
300                  * @since 2.9.0
301                  * @deprecated 3.5.0 Use wp_save_image_editor_file instead.
302                  *
303                  * @param mixed           $override  Value to return instead of saving. Default null.
304                  * @param string          $filename  Name of the file to be saved.
305                  * @param WP_Image_Editor $image     WP_Image_Editor instance.
306                  * @param string          $mime_type Image mime type.
307                  * @param int             $post_id   Post ID.
308                  */
309                 $saved = apply_filters( 'wp_save_image_file', null, $filename, $image, $mime_type, $post_id );
310
311                 if ( null !== $saved )
312                         return $saved;
313
314                 switch ( $mime_type ) {
315                         case 'image/jpeg':
316
317                                 /** This filter is documented in wp-includes/class-wp-image-editor.php */
318                                 return imagejpeg( $image, $filename, apply_filters( 'jpeg_quality', 90, 'edit_image' ) );
319                         case 'image/png':
320                                 return imagepng( $image, $filename );
321                         case 'image/gif':
322                                 return imagegif( $image, $filename );
323                         default:
324                                 return false;
325                 }
326         }
327 }
328
329 function _image_get_preview_ratio($w, $h) {
330         $max = max($w, $h);
331         return $max > 400 ? (400 / $max) : 1;
332 }
333
334 // @TODO: Returns GD resource, but is NOT public
335 function _rotate_image_resource($img, $angle) {
336         _deprecated_function( __FUNCTION__, '3.5', __( 'Use WP_Image_Editor::rotate' ) );
337         if ( function_exists('imagerotate') ) {
338                 $rotated = imagerotate($img, $angle, 0);
339                 if ( is_resource($rotated) ) {
340                         imagedestroy($img);
341                         $img = $rotated;
342                 }
343         }
344         return $img;
345 }
346
347 /**
348  * @TODO: Only used within image_edit_apply_changes
349  *                and receives/returns GD Resource.
350  *                Consider removal.
351  *
352  * @param GD_Resource $img
353  * @param boolean $horz
354  * @param boolean $vert
355  * @return GD_Resource
356  */
357 function _flip_image_resource($img, $horz, $vert) {
358         _deprecated_function( __FUNCTION__, '3.5', __( 'Use WP_Image_Editor::flip' ) );
359         $w = imagesx($img);
360         $h = imagesy($img);
361         $dst = wp_imagecreatetruecolor($w, $h);
362         if ( is_resource($dst) ) {
363                 $sx = $vert ? ($w - 1) : 0;
364                 $sy = $horz ? ($h - 1) : 0;
365                 $sw = $vert ? -$w : $w;
366                 $sh = $horz ? -$h : $h;
367
368                 if ( imagecopyresampled($dst, $img, 0, 0, $sx, $sy, $w, $h, $sw, $sh) ) {
369                         imagedestroy($img);
370                         $img = $dst;
371                 }
372         }
373         return $img;
374 }
375
376 /**
377  * @TODO: Only used within image_edit_apply_changes
378  *                and receives/returns GD Resource.
379  *                Consider removal.
380  *
381  * @param GD_Resource $img
382  * @param float $x
383  * @param float $y
384  * @param float $w
385  * @param float $h
386  * @return GD_Resource
387  */
388 function _crop_image_resource($img, $x, $y, $w, $h) {
389         $dst = wp_imagecreatetruecolor($w, $h);
390         if ( is_resource($dst) ) {
391                 if ( imagecopy($dst, $img, 0, 0, $x, $y, $w, $h) ) {
392                         imagedestroy($img);
393                         $img = $dst;
394                 }
395         }
396         return $img;
397 }
398
399 /**
400  * Performs group of changes on Editor specified.
401  *
402  * @param WP_Image_Editor $image
403  * @param type $changes
404  * @return WP_Image_Editor
405  */
406 function image_edit_apply_changes( $image, $changes ) {
407         if ( is_resource( $image ) )
408                 _deprecated_argument( __FUNCTION__, '3.5', __( '$image needs to be an WP_Image_Editor object' ) );
409
410         if ( !is_array($changes) )
411                 return $image;
412
413         // Expand change operations.
414         foreach ( $changes as $key => $obj ) {
415                 if ( isset($obj->r) ) {
416                         $obj->type = 'rotate';
417                         $obj->angle = $obj->r;
418                         unset($obj->r);
419                 } elseif ( isset($obj->f) ) {
420                         $obj->type = 'flip';
421                         $obj->axis = $obj->f;
422                         unset($obj->f);
423                 } elseif ( isset($obj->c) ) {
424                         $obj->type = 'crop';
425                         $obj->sel = $obj->c;
426                         unset($obj->c);
427                 }
428                 $changes[$key] = $obj;
429         }
430
431         // Combine operations.
432         if ( count($changes) > 1 ) {
433                 $filtered = array($changes[0]);
434                 for ( $i = 0, $j = 1; $j < count($changes); $j++ ) {
435                         $combined = false;
436                         if ( $filtered[$i]->type == $changes[$j]->type ) {
437                                 switch ( $filtered[$i]->type ) {
438                                         case 'rotate':
439                                                 $filtered[$i]->angle += $changes[$j]->angle;
440                                                 $combined = true;
441                                                 break;
442                                         case 'flip':
443                                                 $filtered[$i]->axis ^= $changes[$j]->axis;
444                                                 $combined = true;
445                                                 break;
446                                 }
447                         }
448                         if ( !$combined )
449                                 $filtered[++$i] = $changes[$j];
450                 }
451                 $changes = $filtered;
452                 unset($filtered);
453         }
454
455         // Image resource before applying the changes.
456         if ( $image instanceof WP_Image_Editor ) {
457
458                 /**
459                  * Filter the WP_Image_Editor instance before applying changes to the image.
460                  *
461                  * @since 3.5.0
462                  *
463                  * @param WP_Image_Editor $image   WP_Image_Editor instance.
464                  * @param array           $changes Array of change operations.
465                  */
466                 $image = apply_filters( 'wp_image_editor_before_change', $image, $changes );
467         } elseif ( is_resource( $image ) ) {
468
469                 /**
470                  * Filter the GD image resource before applying changes to the image.
471                  *
472                  * @since 2.9.0
473                  * @deprecated 3.5.0 Use wp_image_editor_before_change instead.
474                  *
475                  * @param resource $image   GD image resource.
476                  * @param array    $changes Array of change operations.
477                  */
478                 $image = apply_filters( 'image_edit_before_change', $image, $changes );
479         }
480
481         foreach ( $changes as $operation ) {
482                 switch ( $operation->type ) {
483                         case 'rotate':
484                                 if ( $operation->angle != 0 ) {
485                                         if ( $image instanceof WP_Image_Editor )
486                                                 $image->rotate( $operation->angle );
487                                         else
488                                                 $image = _rotate_image_resource( $image, $operation->angle );
489                                 }
490                                 break;
491                         case 'flip':
492                                 if ( $operation->axis != 0 )
493                                         if ( $image instanceof WP_Image_Editor )
494                                                 $image->flip( ($operation->axis & 1) != 0, ($operation->axis & 2) != 0 );
495                                         else
496                                                 $image = _flip_image_resource( $image, ( $operation->axis & 1 ) != 0, ( $operation->axis & 2 ) != 0 );
497                                 break;
498                         case 'crop':
499                                 $sel = $operation->sel;
500
501                                 if ( $image instanceof WP_Image_Editor ) {
502                                         $size = $image->get_size();
503                                         $w = $size['width'];
504                                         $h = $size['height'];
505
506                                         $scale = 1 / _image_get_preview_ratio( $w, $h ); // discard preview scaling
507                                         $image->crop( $sel->x * $scale, $sel->y * $scale, $sel->w * $scale, $sel->h * $scale );
508                                 } else {
509                                         $scale = 1 / _image_get_preview_ratio( imagesx( $image ), imagesy( $image ) ); // discard preview scaling
510                                         $image = _crop_image_resource( $image, $sel->x * $scale, $sel->y * $scale, $sel->w * $scale, $sel->h * $scale );
511                                 }
512                                 break;
513                 }
514         }
515
516         return $image;
517 }
518
519
520 /**
521  * Streams image in post to browser, along with enqueued changes
522  * in $_REQUEST['history']
523  *
524  * @param int $post_id
525  * @return boolean
526  */
527 function stream_preview_image( $post_id ) {
528         $post = get_post( $post_id );
529
530         /** This filter is documented in wp-admin/admin.php */
531         @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) );
532
533         $img = wp_get_image_editor( _load_image_to_edit_path( $post_id ) );
534
535     if ( is_wp_error( $img ) )
536         return false;
537
538         $changes = !empty($_REQUEST['history']) ? json_decode( wp_unslash($_REQUEST['history']) ) : null;
539         if ( $changes )
540                 $img = image_edit_apply_changes( $img, $changes );
541
542         // Scale the image.
543         $size = $img->get_size();
544         $w = $size['width'];
545         $h = $size['height'];
546
547         $ratio = _image_get_preview_ratio( $w, $h );
548         $w2 = max ( 1, $w * $ratio );
549         $h2 = max ( 1, $h * $ratio );
550
551         if ( is_wp_error( $img->resize( $w2, $h2 ) ) )
552                 return false;
553
554         return wp_stream_image( $img, $post->post_mime_type, $post_id );
555 }
556
557 function wp_restore_image($post_id) {
558         $meta = wp_get_attachment_metadata($post_id);
559         $file = get_attached_file($post_id);
560         $backup_sizes = get_post_meta( $post_id, '_wp_attachment_backup_sizes', true );
561         $restored = false;
562         $msg = new stdClass;
563
564         if ( !is_array($backup_sizes) ) {
565                 $msg->error = __('Cannot load image metadata.');
566                 return $msg;
567         }
568
569         $parts = pathinfo($file);
570         $suffix = time() . rand(100, 999);
571         $default_sizes = get_intermediate_image_sizes();
572
573         if ( isset($backup_sizes['full-orig']) && is_array($backup_sizes['full-orig']) ) {
574                 $data = $backup_sizes['full-orig'];
575
576                 if ( $parts['basename'] != $data['file'] ) {
577                         if ( defined('IMAGE_EDIT_OVERWRITE') && IMAGE_EDIT_OVERWRITE ) {
578
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
605                                         // Delete only if it's edited image
606                                         if ( preg_match('/-e[0-9]{13}-/', $meta['sizes'][$default_size]['file']) ) {
607                                                 /** This filter is documented in wp-admin/custom-header.php */
608                                                 $delpath = apply_filters( 'wp_delete_file', path_join($parts['dirname'], $meta['sizes'][$default_size]['file']) );
609                                                 @unlink($delpath);
610                                         }
611                                 } else {
612                                         $backup_sizes["$default_size-{$suffix}"] = $meta['sizes'][$default_size];
613                                 }
614                         }
615
616                         $meta['sizes'][$default_size] = $data;
617                 } else {
618                         unset($meta['sizes'][$default_size]);
619                 }
620         }
621
622         if ( !wp_update_attachment_metadata($post_id, $meta) || !update_post_meta( $post_id, '_wp_attachment_backup_sizes', $backup_sizes) ) {
623                 $msg->error = __('Cannot save image metadata.');
624                 return $msg;
625         }
626
627         if ( !$restored )
628                 $msg->error = __('Image metadata is inconsistent.');
629         else
630                 $msg->msg = __('Image restored successfully.');
631
632         return $msg;
633 }
634
635 /**
636  * Saves image to post along with enqueued changes
637  * in $_REQUEST['history']
638  *
639  * @param int $post_id
640  * @return \stdClass
641  */
642 function wp_save_image( $post_id ) {
643         global $_wp_additional_image_sizes;
644
645         $return = new stdClass;
646         $success = $delete = $scaled = $nocrop = false;
647         $post = get_post( $post_id );
648
649         $img = wp_get_image_editor( _load_image_to_edit_path( $post_id, 'full' ) );
650         if ( is_wp_error( $img ) ) {
651                 $return->error = esc_js( __('Unable to create new image.') );
652                 return $return;
653         }
654
655         $fwidth = !empty($_REQUEST['fwidth']) ? intval($_REQUEST['fwidth']) : 0;
656         $fheight = !empty($_REQUEST['fheight']) ? intval($_REQUEST['fheight']) : 0;
657         $target = !empty($_REQUEST['target']) ? preg_replace('/[^a-z0-9_-]+/i', '', $_REQUEST['target']) : '';
658         $scale = !empty($_REQUEST['do']) && 'scale' == $_REQUEST['do'];
659
660         if ( $scale && $fwidth > 0 && $fheight > 0 ) {
661                 $size = $img->get_size();
662                 $sX = $size['width'];
663                 $sY = $size['height'];
664
665                 // Check if it has roughly the same w / h ratio.
666                 $diff = round($sX / $sY, 2) - round($fwidth / $fheight, 2);
667                 if ( -0.1 < $diff && $diff < 0.1 ) {
668                         // Scale the full size image.
669                         if ( $img->resize( $fwidth, $fheight ) )
670                                 $scaled = true;
671                 }
672
673                 if ( !$scaled ) {
674                         $return->error = esc_js( __('Error while saving the scaled image. Please reload the page and try again.') );
675                         return $return;
676                 }
677         } elseif ( !empty($_REQUEST['history']) ) {
678                 $changes = json_decode( wp_unslash($_REQUEST['history']) );
679                 if ( $changes )
680                         $img = image_edit_apply_changes($img, $changes);
681         } else {
682                 $return->error = esc_js( __('Nothing to save, the image has not changed.') );
683                 return $return;
684         }
685
686         $meta = wp_get_attachment_metadata($post_id);
687         $backup_sizes = get_post_meta( $post->ID, '_wp_attachment_backup_sizes', true );
688
689         if ( !is_array($meta) ) {
690                 $return->error = esc_js( __('Image data does not exist. Please re-upload the image.') );
691                 return $return;
692         }
693
694         if ( !is_array($backup_sizes) )
695                 $backup_sizes = array();
696
697         // Generate new filename.
698         $path = get_attached_file($post_id);
699         $path_parts = pathinfo( $path );
700         $filename = $path_parts['filename'];
701         $suffix = time() . rand(100, 999);
702
703         if ( defined('IMAGE_EDIT_OVERWRITE') && IMAGE_EDIT_OVERWRITE &&
704                 isset($backup_sizes['full-orig']) && $backup_sizes['full-orig']['file'] != $path_parts['basename'] ) {
705
706                 if ( 'thumbnail' == $target )
707                         $new_path = "{$path_parts['dirname']}/{$filename}-temp.{$path_parts['extension']}";
708                 else
709                         $new_path = $path;
710         } else {
711                 while( true ) {
712                         $filename = preg_replace( '/-e([0-9]+)$/', '', $filename );
713                         $filename .= "-e{$suffix}";
714                         $new_filename = "{$filename}.{$path_parts['extension']}";
715                         $new_path = "{$path_parts['dirname']}/$new_filename";
716                         if ( file_exists($new_path) )
717                                 $suffix++;
718                         else
719                                 break;
720                 }
721         }
722
723         // Save the full-size file, also needed to create sub-sizes.
724         if ( !wp_save_image_file($new_path, $img, $post->post_mime_type, $post_id) ) {
725                 $return->error = esc_js( __('Unable to save the image.') );
726                 return $return;
727         }
728
729         if ( 'nothumb' == $target || 'all' == $target || 'full' == $target || $scaled ) {
730                 $tag = false;
731                 if ( isset($backup_sizes['full-orig']) ) {
732                         if ( ( !defined('IMAGE_EDIT_OVERWRITE') || !IMAGE_EDIT_OVERWRITE ) && $backup_sizes['full-orig']['file'] != $path_parts['basename'] )
733                                 $tag = "full-$suffix";
734                 } else {
735                         $tag = 'full-orig';
736                 }
737
738                 if ( $tag )
739                         $backup_sizes[$tag] = array('width' => $meta['width'], 'height' => $meta['height'], 'file' => $path_parts['basename']);
740
741                 $success = update_attached_file( $post_id, $new_path );
742
743                 $meta['file'] = _wp_relative_upload_path( $new_path );
744
745                 $size = $img->get_size();
746                 $meta['width'] = $size['width'];
747                 $meta['height'] = $size['height'];
748
749                 if ( $success && ('nothumb' == $target || 'all' == $target) ) {
750                         $sizes = get_intermediate_image_sizes();
751                         if ( 'nothumb' == $target )
752                                 $sizes = array_diff( $sizes, array('thumbnail') );
753                 }
754
755                 $return->fw = $meta['width'];
756                 $return->fh = $meta['height'];
757         } elseif ( 'thumbnail' == $target ) {
758                 $sizes = array( 'thumbnail' );
759                 $success = $delete = $nocrop = true;
760         }
761
762         if ( isset( $sizes ) ) {
763                 $_sizes = array();
764
765                 foreach ( $sizes as $size ) {
766                         $tag = false;
767                         if ( isset( $meta['sizes'][$size] ) ) {
768                                 if ( isset($backup_sizes["$size-orig"]) ) {
769                                         if ( ( !defined('IMAGE_EDIT_OVERWRITE') || !IMAGE_EDIT_OVERWRITE ) && $backup_sizes["$size-orig"]['file'] != $meta['sizes'][$size]['file'] )
770                                                 $tag = "$size-$suffix";
771                                 } else {
772                                         $tag = "$size-orig";
773                                 }
774
775                                 if ( $tag )
776                                         $backup_sizes[$tag] = $meta['sizes'][$size];
777                         }
778
779                         if ( isset( $_wp_additional_image_sizes[ $size ] ) ) {
780                                 $width  = intval( $_wp_additional_image_sizes[ $size ]['width'] );
781                                 $height = intval( $_wp_additional_image_sizes[ $size ]['height'] );
782                                 $crop   = ( $nocrop ) ? false : $_wp_additional_image_sizes[ $size ]['crop'];
783                         } else {
784                                 $height = get_option( "{$size}_size_h" );
785                                 $width  = get_option( "{$size}_size_w" );
786                                 $crop   = ( $nocrop ) ? false : get_option( "{$size}_crop" );
787                         }
788
789                         $_sizes[ $size ] = array( 'width' => $width, 'height' => $height, 'crop' => $crop );
790                 }
791
792                 $meta['sizes'] = array_merge( $meta['sizes'], $img->multi_resize( $_sizes ) );
793         }
794
795         unset( $img );
796
797         if ( $success ) {
798                 wp_update_attachment_metadata( $post_id, $meta );
799                 update_post_meta( $post_id, '_wp_attachment_backup_sizes', $backup_sizes);
800
801                 if ( $target == 'thumbnail' || $target == 'all' || $target == 'full' ) {
802                         // Check if it's an image edit from attachment edit screen
803                         if ( ! empty( $_REQUEST['context'] ) && 'edit-attachment' == $_REQUEST['context'] ) {
804                                 $thumb_url = wp_get_attachment_image_src( $post_id, array( 900, 600 ), true );
805                                 $return->thumbnail = $thumb_url[0];
806                         } else {
807                                 $file_url = wp_get_attachment_url($post_id);
808                                 if ( ! empty( $meta['sizes']['thumbnail'] ) && $thumb = $meta['sizes']['thumbnail'] ) {
809                                         $return->thumbnail = path_join( dirname($file_url), $thumb['file'] );
810                                 } else {
811                                         $return->thumbnail = "$file_url?w=128&h=128";
812                                 }
813                         }
814                 }
815         } else {
816                 $delete = true;
817         }
818
819         if ( $delete ) {
820
821                 /** This filter is documented in wp-admin/custom-header.php */
822                 $delpath = apply_filters( 'wp_delete_file', $new_path );
823                 @unlink( $delpath );
824         }
825
826         $return->msg = esc_js( __('Image saved') );
827         return $return;
828 }