Issues (2010)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

wp-admin/includes/image-edit.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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