Make WordPress Core

Ticket #33278: image-edit.php

File image-edit.php, 29.6 KB (added by acbaile, 9 years ago)

/wp-admin/includes/image-edit.php

Line 
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 */
13function 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 */
210function 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 */
266function 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
333function _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
339function _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 */
361function _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 */
392function _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 */
412function 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 */
533function 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 */
567function 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 */
652function 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        // First try suffix-sizes - acbaile
714        $suffix_sizes = $img->get_size();
715        $suffix_sizes = "-".$suffix_sizes["width"]."x".$suffix_sizes["height"];
716
717        if ( defined('IMAGE_EDIT_OVERWRITE') && IMAGE_EDIT_OVERWRITE &&
718                isset($backup_sizes['full-orig']) && $backup_sizes['full-orig']['file'] != $path_parts['basename'] ) {
719
720                if ( 'thumbnail' == $target )
721                        $new_path = "{$path_parts['dirname']}/{$filename}-temp.{$path_parts['extension']}";
722                else
723                        $new_path = $path;
724        } else {
725                // First try suffix-sizes - acbaile
726                $filename = preg_replace( "/-e([0-9]+)$/", "", $filename );
727                $filename = preg_replace( "/-([0-9]{1,5})x([0-9]{1,5})$/", "", $filename );
728                $filename .= $suffix_sizes;
729                $new_filename = $filename.".".$path_parts["extension"];
730                $new_path = $path_parts["dirname"]."/".$new_filename;
731                if ( file_exists($new_path) ){
732                        while( true ) {
733                                $filename = preg_replace( '/-e([0-9]+)$/', '', $filename );
734                                $filename .= "-e{$suffix}";
735                                $new_filename = "{$filename}.{$path_parts['extension']}";
736                                $new_path = "{$path_parts['dirname']}/$new_filename";
737                                if ( file_exists($new_path) )
738                                        $suffix++;
739                                else
740                                        break;
741                        }
742                }
743        }
744
745        // Save the full-size file, also needed to create sub-sizes.
746        if ( !wp_save_image_file($new_path, $img, $post->post_mime_type, $post_id) ) {
747                $return->error = esc_js( __('Unable to save the image.') );
748                return $return;
749        }
750
751        if ( 'nothumb' == $target || 'all' == $target || 'full' == $target || $scaled ) {
752                $tag = false;
753                if ( isset($backup_sizes['full-orig']) ) {
754                        if ( ( !defined('IMAGE_EDIT_OVERWRITE') || !IMAGE_EDIT_OVERWRITE ) && $backup_sizes['full-orig']['file'] != $path_parts['basename'] )
755                                $tag = "full-$suffix";
756                } else {
757                        $tag = 'full-orig';
758                }
759
760                if ( $tag )
761                        $backup_sizes[$tag] = array('width' => $meta['width'], 'height' => $meta['height'], 'file' => $path_parts['basename']);
762
763                $success = update_attached_file( $post_id, $new_path );
764
765                $meta['file'] = _wp_relative_upload_path( $new_path );
766
767                $size = $img->get_size();
768                $meta['width'] = $size['width'];
769                $meta['height'] = $size['height'];
770
771                if ( $success && ('nothumb' == $target || 'all' == $target) ) {
772                        $sizes = get_intermediate_image_sizes();
773                        if ( 'nothumb' == $target )
774                                $sizes = array_diff( $sizes, array('thumbnail') );
775                }
776
777                $return->fw = $meta['width'];
778                $return->fh = $meta['height'];
779        } elseif ( 'thumbnail' == $target ) {
780                $sizes = array( 'thumbnail' );
781                $success = $delete = $nocrop = true;
782        }
783
784        if ( isset( $sizes ) ) {
785                $_sizes = array();
786
787                foreach ( $sizes as $size ) {
788                        $tag = false;
789                        if ( isset( $meta['sizes'][$size] ) ) {
790                                if ( isset($backup_sizes["$size-orig"]) ) {
791                                        if ( ( !defined('IMAGE_EDIT_OVERWRITE') || !IMAGE_EDIT_OVERWRITE ) && $backup_sizes["$size-orig"]['file'] != $meta['sizes'][$size]['file'] )
792                                                $tag = "$size-$suffix";
793                                } else {
794                                        $tag = "$size-orig";
795                                }
796
797                                if ( $tag )
798                                        $backup_sizes[$tag] = $meta['sizes'][$size];
799                        }
800
801                        if ( isset( $_wp_additional_image_sizes[ $size ] ) ) {
802                                $width  = intval( $_wp_additional_image_sizes[ $size ]['width'] );
803                                $height = intval( $_wp_additional_image_sizes[ $size ]['height'] );
804                                $crop   = ( $nocrop ) ? false : $_wp_additional_image_sizes[ $size ]['crop'];
805                        } else {
806                                $height = get_option( "{$size}_size_h" );
807                                $width  = get_option( "{$size}_size_w" );
808                                $crop   = ( $nocrop ) ? false : get_option( "{$size}_crop" );
809                        }
810
811                        $_sizes[ $size ] = array( 'width' => $width, 'height' => $height, 'crop' => $crop );
812                }
813
814                $meta['sizes'] = array_merge( $meta['sizes'], $img->multi_resize( $_sizes ) );
815        }
816
817        unset( $img );
818
819        if ( $success ) {
820                wp_update_attachment_metadata( $post_id, $meta );
821                update_post_meta( $post_id, '_wp_attachment_backup_sizes', $backup_sizes);
822
823                if ( $target == 'thumbnail' || $target == 'all' || $target == 'full' ) {
824                        // Check if it's an image edit from attachment edit screen
825                        if ( ! empty( $_REQUEST['context'] ) && 'edit-attachment' == $_REQUEST['context'] ) {
826                                $thumb_url = wp_get_attachment_image_src( $post_id, array( 900, 600 ), true );
827                                $return->thumbnail = $thumb_url[0];
828                        } else {
829                                $file_url = wp_get_attachment_url($post_id);
830                                if ( ! empty( $meta['sizes']['thumbnail'] ) && $thumb = $meta['sizes']['thumbnail'] ) {
831                                        $return->thumbnail = path_join( dirname($file_url), $thumb['file'] );
832                                } else {
833                                        $return->thumbnail = "$file_url?w=128&h=128";
834                                }
835                        }
836                }
837        } else {
838                $delete = true;
839        }
840
841        if ( $delete ) {
842
843                /** This filter is documented in wp-admin/custom-header.php */
844                $delpath = apply_filters( 'wp_delete_file', $new_path );
845                @unlink( $delpath );
846        }
847
848        $return->msg = esc_js( __('Image saved') );
849        return $return;
850}