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