~canonical-sysadmins/wordpress/4.6

« back to all changes in this revision

Viewing changes to wp-admin/includes/image-edit.php

  • Committer: Jacek Nykis
  • Date: 2015-01-05 16:17:05 UTC
  • Revision ID: jacek.nykis@canonical.com-20150105161705-w544l1h5mcg7u4w9
Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
}