Completed
Push — master ( a7cd2a...eabd6c )
by Stephen
38:42
created

WP_Image_Editor_Imagick::stream()   A

Complexity

Conditions 2
Paths 5

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 20
rs 9.4285
cc 2
eloc 10
nc 5
nop 1
1
<?php
2
/**
3
 * WordPress Imagick Image Editor
4
 *
5
 * @package WordPress
6
 * @subpackage Image_Editor
7
 */
8
9
/**
10
 * WordPress Image Editor Class for Image Manipulation through Imagick PHP Module
11
 *
12
 * @since 3.5.0
13
 * @package WordPress
14
 * @subpackage Image_Editor
15
 * @uses WP_Image_Editor Extends class
16
 */
17
class WP_Image_Editor_Imagick extends WP_Image_Editor {
18
	/**
19
	 * Imagick object.
20
	 *
21
	 * @access protected
22
	 * @var Imagick
23
	 */
24
	protected $image;
25
26
	public function __destruct() {
27
		if ( $this->image instanceof Imagick ) {
28
			// we don't need the original in memory anymore
29
			$this->image->clear();
30
			$this->image->destroy();
31
		}
32
	}
33
34
	/**
35
	 * Checks to see if current environment supports Imagick.
36
	 *
37
	 * We require Imagick 2.2.0 or greater, based on whether the queryFormats()
38
	 * method can be called statically.
39
	 *
40
	 * @since 3.5.0
41
	 *
42
	 * @static
43
	 * @access public
44
	 *
45
	 * @param array $args
46
	 * @return bool
47
	 */
48
	public static function test( $args = array() ) {
49
50
		// First, test Imagick's extension and classes.
51
		if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick', false ) || ! class_exists( 'ImagickPixel', false ) )
52
			return false;
53
54
		if ( version_compare( phpversion( 'imagick' ), '2.2.0', '<' ) )
55
			return false;
56
57
		$required_methods = array(
58
			'clear',
59
			'destroy',
60
			'valid',
61
			'getimage',
62
			'writeimage',
63
			'getimageblob',
64
			'getimagegeometry',
65
			'getimageformat',
66
			'setimageformat',
67
			'setimagecompression',
68
			'setimagecompressionquality',
69
			'setimagepage',
70
			'setoption',
71
			'scaleimage',
72
			'cropimage',
73
			'rotateimage',
74
			'flipimage',
75
			'flopimage',
76
		);
77
78
		// Now, test for deep requirements within Imagick.
79
		if ( ! defined( 'imagick::COMPRESSION_JPEG' ) )
80
			return false;
81
82
		$class_methods = array_map( 'strtolower', get_class_methods( 'Imagick' ) );
83
		if ( array_diff( $required_methods, $class_methods ) ) {
84
			return false;
85
		}
86
87
		// HHVM Imagick does not support loading from URL, so fail to allow fallback to GD.
88
		if ( defined( 'HHVM_VERSION' ) && isset( $args['path'] ) && preg_match( '|^https?://|', $args['path'] ) ) {
89
			return false;
90
		}
91
92
		return true;
93
	}
94
95
	/**
96
	 * Checks to see if editor supports the mime-type specified.
97
	 *
98
	 * @since 3.5.0
99
	 *
100
	 * @static
101
	 * @access public
102
	 *
103
	 * @param string $mime_type
104
	 * @return bool
105
	 */
106
	public static function supports_mime_type( $mime_type ) {
107
		$imagick_extension = strtoupper( self::get_extension( $mime_type ) );
108
109
		if ( ! $imagick_extension )
110
			return false;
111
112
		// setIteratorIndex is optional unless mime is an animated format.
113
		// Here, we just say no if you are missing it and aren't loading a jpeg.
114
		if ( ! method_exists( 'Imagick', 'setIteratorIndex' ) && $mime_type != 'image/jpeg' )
115
				return false;
116
117
		try {
118
			return ( (bool) @Imagick::queryFormats( $imagick_extension ) );
119
		}
120
		catch ( Exception $e ) {
121
			return false;
122
		}
123
	}
124
125
	/**
126
	 * Loads image from $this->file into new Imagick Object.
127
	 *
128
	 * @since 3.5.0
129
	 * @access protected
130
	 *
131
	 * @return true|WP_Error True if loaded; WP_Error on failure.
132
	 */
133
	public function load() {
134
		if ( $this->image instanceof Imagick )
135
			return true;
136
137 View Code Duplication
		if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) )
138
			return new WP_Error( 'error_loading_image', __('File doesn&#8217;t exist?'), $this->file );
139
140
		/** This filter is documented in wp-includes/class-wp-image-editor-imagick.php */
141
		// Even though Imagick uses less PHP memory than GD, set higher limit for users that have low PHP.ini limits
142
		@ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
143
144
		try {
145
			$this->image = new Imagick( $this->file );
146
147
			if ( ! $this->image->valid() )
148
				return new WP_Error( 'invalid_image', __('File is not an image.'), $this->file);
149
150
			// Select the first frame to handle animated images properly
151
			if ( is_callable( array( $this->image, 'setIteratorIndex' ) ) )
152
				$this->image->setIteratorIndex(0);
153
154
			$this->mime_type = $this->get_mime_type( $this->image->getImageFormat() );
155
		}
156
		catch ( Exception $e ) {
157
			return new WP_Error( 'invalid_image', $e->getMessage(), $this->file );
158
		}
159
160
		$updated_size = $this->update_size();
161
		if ( is_wp_error( $updated_size ) ) {
162
			return $updated_size;
163
		}
164
165
		return $this->set_quality();
166
	}
167
168
	/**
169
	 * Sets Image Compression quality on a 1-100% scale.
170
	 *
171
	 * @since 3.5.0
172
	 * @access public
173
	 *
174
	 * @param int $quality Compression Quality. Range: [1,100]
175
	 * @return true|WP_Error True if set successfully; WP_Error on failure.
176
	 */
177
	public function set_quality( $quality = null ) {
178
		$quality_result = parent::set_quality( $quality );
179
		if ( is_wp_error( $quality_result ) ) {
180
			return $quality_result;
181
		} else {
182
			$quality = $this->get_quality();
183
		}
184
185
		try {
186
			if ( 'image/jpeg' == $this->mime_type ) {
187
				$this->image->setImageCompressionQuality( $quality );
188
				$this->image->setImageCompression( imagick::COMPRESSION_JPEG );
189
			}
190
			else {
191
				$this->image->setImageCompressionQuality( $quality );
192
			}
193
		}
194
		catch ( Exception $e ) {
195
			return new WP_Error( 'image_quality_error', $e->getMessage() );
196
		}
197
198
		return true;
199
	}
200
201
	/**
202
	 * Sets or updates current image size.
203
	 *
204
	 * @since 3.5.0
205
	 * @access protected
206
	 *
207
	 * @param int $width
208
	 * @param int $height
209
	 *
210
	 * @return true|WP_Error
211
	 */
212
	protected function update_size( $width = null, $height = null ) {
213
		$size = null;
214
		if ( !$width || !$height ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $width of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
Bug Best Practice introduced by
The expression $height of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
215
			try {
216
				$size = $this->image->getImageGeometry();
217
			}
218
			catch ( Exception $e ) {
219
				return new WP_Error( 'invalid_image', __( 'Could not read image size.' ), $this->file );
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \WP_Error('in... size.'), $this->file); (WP_Error) is incompatible with the return type of the parent method WP_Image_Editor::update_size of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
220
			}
221
		}
222
223
		if ( ! $width )
0 ignored issues
show
Bug Best Practice introduced by
The expression $width of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
224
			$width = $size['width'];
225
226
		if ( ! $height )
0 ignored issues
show
Bug Best Practice introduced by
The expression $height of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
227
			$height = $size['height'];
228
229
		return parent::update_size( $width, $height );
230
	}
231
232
	/**
233
	 * Resizes current image.
234
	 *
235
	 * At minimum, either a height or width must be provided.
236
	 * If one of the two is set to null, the resize will
237
	 * maintain aspect ratio according to the provided dimension.
238
	 *
239
	 * @since 3.5.0
240
	 * @access public
241
	 *
242
	 * @param  int|null $max_w Image width.
243
	 * @param  int|null $max_h Image height.
244
	 * @param  bool     $crop
245
	 * @return bool|WP_Error
246
	 */
247
	public function resize( $max_w, $max_h, $crop = false ) {
248 View Code Duplication
		if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) )
249
			return true;
250
251
		$dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop );
252
		if ( ! $dims )
253
			return new WP_Error( 'error_getting_dimensions', __('Could not calculate resized image dimensions') );
254
		list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims;
0 ignored issues
show
Unused Code introduced by
The assignment to $dst_x is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $dst_y is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
255
256
		if ( $crop ) {
257
			return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h );
258
		}
259
260
		// Execute the resize
261
		$thumb_result = $this->thumbnail_image( $dst_w, $dst_h );
262
		if ( is_wp_error( $thumb_result ) ) {
263
			return $thumb_result;
264
		}
265
266
		return $this->update_size( $dst_w, $dst_h );
267
	}
268
269
	/**
270
	 * Efficiently resize the current image
271
	 *
272
	 * This is a WordPress specific implementation of Imagick::thumbnailImage(),
273
	 * which resizes an image to given dimensions and removes any associated profiles.
274
	 *
275
	 * @since 4.5.0
276
	 * @access protected
277
	 *
278
	 * @param int    $dst_w       The destination width.
279
	 * @param int    $dst_h       The destination height.
280
	 * @param string $filter_name Optional. The Imagick filter to use when resizing. Default 'FILTER_TRIANGLE'.
281
	 * @param bool   $strip_meta  Optional. Strip all profiles, excluding color profiles, from the image. Default true.
282
	 * @return bool|WP_Error
283
	 */
284
	protected function thumbnail_image( $dst_w, $dst_h, $filter_name = 'FILTER_TRIANGLE', $strip_meta = true ) {
285
		$allowed_filters = array(
286
			'FILTER_POINT',
287
			'FILTER_BOX',
288
			'FILTER_TRIANGLE',
289
			'FILTER_HERMITE',
290
			'FILTER_HANNING',
291
			'FILTER_HAMMING',
292
			'FILTER_BLACKMAN',
293
			'FILTER_GAUSSIAN',
294
			'FILTER_QUADRATIC',
295
			'FILTER_CUBIC',
296
			'FILTER_CATROM',
297
			'FILTER_MITCHELL',
298
			'FILTER_LANCZOS',
299
			'FILTER_BESSEL',
300
			'FILTER_SINC',
301
		);
302
303
		/**
304
		 * Set the filter value if '$filter_name' name is in our whitelist and the related
305
		 * Imagick constant is defined or fall back to our default filter.
306
		 */
307
		if ( in_array( $filter_name, $allowed_filters ) && defined( 'Imagick::' . $filter_name ) ) {
308
			$filter = constant( 'Imagick::' . $filter_name );
309
		} else {
310
			$filter = defined( 'Imagick::FILTER_TRIANGLE' ) ? Imagick::FILTER_TRIANGLE : false;
311
		}
312
313
		/**
314
		 * Filter whether to strip metadata from images when they're resized.
315
		 *
316
		 * This filter only applies when resizing using the Imagick editor since GD
317
		 * always strips profiles by default.
318
		 *
319
		 * @since 4.5.0
320
		 *
321
		 * @param bool $strip_meta Whether to strip image metadata during resizing. Default true.
322
		 */
323
		if ( apply_filters( 'image_strip_meta', $strip_meta ) ) {
324
			$this->strip_meta(); // Fail silently if not supported.
325
		}
326
327
		try {
328
			/*
329
			 * To be more efficient, resample large images to 5x the destination size before resizing
330
			 * whenever the output size is less that 1/3 of the original image size (1/3^2 ~= .111),
331
			 * unless we would be resampling to a scale smaller than 128x128.
332
			 */
333
			if ( is_callable( array( $this->image, 'sampleImage' ) ) ) {
334
				$resize_ratio = ( $dst_w / $this->size['width'] ) * ( $dst_h / $this->size['height'] );
335
				$sample_factor = 5;
336
337
				if ( $resize_ratio < .111 && ( $dst_w * $sample_factor > 128 && $dst_h * $sample_factor > 128 ) ) {
338
					$this->image->sampleImage( $dst_w * $sample_factor, $dst_h * $sample_factor );
339
				}
340
			}
341
342
			/*
343
			 * Use resizeImage() when it's available and a valid filter value is set.
344
			 * Otherwise, fall back to the scaleImage() method for resizing, which
345
			 * results in better image quality over resizeImage() with default filter
346
			 * settings and retains backwards compatibility with pre 4.5 functionality.
347
			 */
348
			if ( is_callable( array( $this->image, 'resizeImage' ) ) && $filter ) {
349
				$this->image->setOption( 'filter:support', '2.0' );
350
				$this->image->resizeImage( $dst_w, $dst_h, $filter, 1 );
351
			} else {
352
				$this->image->scaleImage( $dst_w, $dst_h );
353
			}
354
355
			// Set appropriate quality settings after resizing.
356
			if ( 'image/jpeg' == $this->mime_type ) {
357
				if ( is_callable( array( $this->image, 'unsharpMaskImage' ) ) ) {
358
					$this->image->unsharpMaskImage( 0.25, 0.25, 8, 0.065 );
359
				}
360
361
				$this->image->setOption( 'jpeg:fancy-upsampling', 'off' );
362
			}
363
364
			if ( 'image/png' === $this->mime_type ) {
365
				$this->image->setOption( 'png:compression-filter', '5' );
366
				$this->image->setOption( 'png:compression-level', '9' );
367
				$this->image->setOption( 'png:compression-strategy', '1' );
368
				$this->image->setOption( 'png:exclude-chunk', 'all' );
369
			}
370
371
			/*
372
			 * If alpha channel is not defined, set it opaque.
373
			 *
374
			 * Note that Imagick::getImageAlphaChannel() is only available if Imagick
375
			 * has been compiled against ImageMagick version 6.4.0 or newer.
376
			 */
377
			if ( is_callable( array( $this->image, 'getImageAlphaChannel' ) )
378
				&& is_callable( array( $this->image, 'setImageAlphaChannel' ) )
379
				&& defined( Imagick::ALPHACHANNEL_UNDEFINED )
380
				&& defined( Imagick::ALPHACHANNEL_OPAQUE )
381
			) {
382
				if ( $this->image->getImageAlphaChannel() === Imagick::ALPHACHANNEL_UNDEFINED ) {
383
					$this->image->setImageAlphaChannel( Imagick::ALPHACHANNEL_OPAQUE );
384
				}
385
			}
386
387
			// Limit the bit depth of resized images to 8 bits per channel.
388
			if ( is_callable( array( $this->image, 'getImageDepth' ) ) && is_callable( array( $this->image, 'setImageDepth' ) ) ) {
389
				if ( 8 < $this->image->getImageDepth() ) {
390
					$this->image->setImageDepth( 8 );
391
				}
392
			}
393
394
			if ( is_callable( array( $this->image, 'setInterlaceScheme' ) ) && defined( 'Imagick::INTERLACE_NO' ) ) {
395
				$this->image->setInterlaceScheme( Imagick::INTERLACE_NO );
396
			}
397
398
		}
399
		catch ( Exception $e ) {
400
			return new WP_Error( 'image_resize_error', $e->getMessage() );
401
		}
402
	}
403
404
	/**
405
	 * Resize multiple images from a single source.
406
	 *
407
	 * @since 3.5.0
408
	 * @access public
409
	 *
410
	 * @param array $sizes {
411
	 *     An array of image size arrays. Default sizes are 'small', 'medium', 'medium_large', 'large'.
412
	 *
413
	 *     Either a height or width must be provided.
414
	 *     If one of the two is set to null, the resize will
415
	 *     maintain aspect ratio according to the provided dimension.
416
	 *
417
	 *     @type array $size {
418
	 *         Array of height, width values, and whether to crop.
419
	 *
420
	 *         @type int  $width  Image width. Optional if `$height` is specified.
421
	 *         @type int  $height Image height. Optional if `$width` is specified.
422
	 *         @type bool $crop   Optional. Whether to crop the image. Default false.
423
	 *     }
424
	 * }
425
	 * @return array An array of resized images' metadata by size.
426
	 */
427
	public function multi_resize( $sizes ) {
428
		$metadata = array();
429
		$orig_size = $this->size;
430
		$orig_image = $this->image->getImage();
431
432
		foreach ( $sizes as $size => $size_data ) {
433
			if ( ! $this->image )
434
				$this->image = $orig_image->getImage();
435
436
			if ( ! isset( $size_data['width'] ) && ! isset( $size_data['height'] ) ) {
437
				continue;
438
			}
439
440
			if ( ! isset( $size_data['width'] ) ) {
441
				$size_data['width'] = null;
442
			}
443
			if ( ! isset( $size_data['height'] ) ) {
444
				$size_data['height'] = null;
445
			}
446
447
			if ( ! isset( $size_data['crop'] ) ) {
448
				$size_data['crop'] = false;
449
			}
450
451
			$resize_result = $this->resize( $size_data['width'], $size_data['height'], $size_data['crop'] );
452
			$duplicate = ( ( $orig_size['width'] == $size_data['width'] ) && ( $orig_size['height'] == $size_data['height'] ) );
453
454
			if ( ! is_wp_error( $resize_result ) && ! $duplicate ) {
455
				$resized = $this->_save( $this->image );
456
457
				$this->image->clear();
458
				$this->image->destroy();
459
				$this->image = null;
460
461 View Code Duplication
				if ( ! is_wp_error( $resized ) && $resized ) {
462
					unset( $resized['path'] );
463
					$metadata[$size] = $resized;
464
				}
465
			}
466
467
			$this->size = $orig_size;
468
		}
469
470
		$this->image = $orig_image;
471
472
		return $metadata;
473
	}
474
475
	/**
476
	 * Crops Image.
477
	 *
478
	 * @since 3.5.0
479
	 * @access public
480
	 *
481
	 * @param int  $src_x The start x position to crop from.
482
	 * @param int  $src_y The start y position to crop from.
483
	 * @param int  $src_w The width to crop.
484
	 * @param int  $src_h The height to crop.
485
	 * @param int  $dst_w Optional. The destination width.
486
	 * @param int  $dst_h Optional. The destination height.
487
	 * @param bool $src_abs Optional. If the source crop points are absolute.
488
	 * @return bool|WP_Error
489
	 */
490
	public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) {
491
		if ( $src_abs ) {
492
			$src_w -= $src_x;
493
			$src_h -= $src_y;
494
		}
495
496
		try {
497
			$this->image->cropImage( $src_w, $src_h, $src_x, $src_y );
498
			$this->image->setImagePage( $src_w, $src_h, 0, 0);
499
500
			if ( $dst_w || $dst_h ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $dst_w of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
Bug Best Practice introduced by
The expression $dst_h of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
501
				// If destination width/height isn't specified, use same as
502
				// width/height from source.
503
				if ( ! $dst_w )
0 ignored issues
show
Bug Best Practice introduced by
The expression $dst_w of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
504
					$dst_w = $src_w;
505
				if ( ! $dst_h )
0 ignored issues
show
Bug Best Practice introduced by
The expression $dst_h of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
506
					$dst_h = $src_h;
507
508
				$thumb_result = $this->thumbnail_image( $dst_w, $dst_h );
509
				if ( is_wp_error( $thumb_result ) ) {
510
					return $thumb_result;
511
				}
512
513
				return $this->update_size();
514
			}
515
		}
516
		catch ( Exception $e ) {
517
			return new WP_Error( 'image_crop_error', $e->getMessage() );
518
		}
519
		return $this->update_size();
520
	}
521
522
	/**
523
	 * Rotates current image counter-clockwise by $angle.
524
	 *
525
	 * @since 3.5.0
526
	 * @access public
527
	 *
528
	 * @param float $angle
529
	 * @return true|WP_Error
530
	 */
531
	public function rotate( $angle ) {
532
		/**
533
		 * $angle is 360-$angle because Imagick rotates clockwise
534
		 * (GD rotates counter-clockwise)
535
		 */
536
		try {
537
			$this->image->rotateImage( new ImagickPixel('none'), 360-$angle );
538
539
			// Since this changes the dimensions of the image, update the size.
540
			$result = $this->update_size();
541
			if ( is_wp_error( $result ) )
542
				return $result;
543
544
			$this->image->setImagePage( $this->size['width'], $this->size['height'], 0, 0 );
545
		}
546
		catch ( Exception $e ) {
547
			return new WP_Error( 'image_rotate_error', $e->getMessage() );
548
		}
549
		return true;
550
	}
551
552
	/**
553
	 * Flips current image.
554
	 *
555
	 * @since 3.5.0
556
	 * @access public
557
	 *
558
	 * @param bool $horz Flip along Horizontal Axis
559
	 * @param bool $vert Flip along Vertical Axis
560
	 * @return true|WP_Error
561
	 */
562
	public function flip( $horz, $vert ) {
563
		try {
564
			if ( $horz )
565
				$this->image->flipImage();
566
567
			if ( $vert )
568
				$this->image->flopImage();
569
		}
570
		catch ( Exception $e ) {
571
			return new WP_Error( 'image_flip_error', $e->getMessage() );
572
		}
573
		return true;
574
	}
575
576
	/**
577
	 * Saves current image to file.
578
	 *
579
	 * @since 3.5.0
580
	 * @access public
581
	 *
582
	 * @param string $destfilename
583
	 * @param string $mime_type
584
	 * @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string}
585
	 */
586
	public function save( $destfilename = null, $mime_type = null ) {
587
		$saved = $this->_save( $this->image, $destfilename, $mime_type );
588
589
		if ( ! is_wp_error( $saved ) ) {
590
			$this->file = $saved['path'];
591
			$this->mime_type = $saved['mime-type'];
592
593
			try {
594
				$this->image->setImageFormat( strtoupper( $this->get_extension( $this->mime_type ) ) );
595
			}
596
			catch ( Exception $e ) {
597
				return new WP_Error( 'image_save_error', $e->getMessage(), $this->file );
598
			}
599
		}
600
601
		return $saved;
602
	}
603
604
	/**
605
	 *
606
	 * @param Imagick $image
607
	 * @param string $filename
608
	 * @param string $mime_type
609
	 * @return array|WP_Error
610
	 */
611
	protected function _save( $image, $filename = null, $mime_type = null ) {
612
		list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type );
613
614
		if ( ! $filename )
615
			$filename = $this->generate_filename( null, null, $extension );
616
617
		try {
618
			// Store initial Format
619
			$orig_format = $this->image->getImageFormat();
620
621
			$this->image->setImageFormat( strtoupper( $this->get_extension( $mime_type ) ) );
622
			$this->make_image( $filename, array( $image, 'writeImage' ), array( $filename ) );
623
624
			// Reset original Format
625
			$this->image->setImageFormat( $orig_format );
626
		}
627
		catch ( Exception $e ) {
628
			return new WP_Error( 'image_save_error', $e->getMessage(), $filename );
629
		}
630
631
		// Set correct file permissions
632
		$stat = stat( dirname( $filename ) );
633
		$perms = $stat['mode'] & 0000666; //same permissions as parent folder, strip off the executable bits
634
		@ chmod( $filename, $perms );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
635
636
		/** This filter is documented in wp-includes/class-wp-image-editor-gd.php */
637
		return array(
638
			'path'      => $filename,
639
			'file'      => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ),
640
			'width'     => $this->size['width'],
641
			'height'    => $this->size['height'],
642
			'mime-type' => $mime_type,
643
		);
644
	}
645
646
	/**
647
	 * Streams current image to browser.
648
	 *
649
	 * @since 3.5.0
650
	 * @access public
651
	 *
652
	 * @param string $mime_type
653
	 * @return true|WP_Error
654
	 */
655
	public function stream( $mime_type = null ) {
656
		list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type );
0 ignored issues
show
Unused Code introduced by
The assignment to $filename is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
657
658
		try {
659
			// Temporarily change format for stream
660
			$this->image->setImageFormat( strtoupper( $extension ) );
661
662
			// Output stream of image content
663
			header( "Content-Type: $mime_type" );
664
			print $this->image->getImageBlob();
665
666
			// Reset Image to original Format
667
			$this->image->setImageFormat( $this->get_extension( $this->mime_type ) );
0 ignored issues
show
Security Bug introduced by
It seems like $this->mime_type can also be of type false; however, WP_Image_Editor::get_extension() does only seem to accept string|null, did you maybe forget to handle an error condition?
Loading history...
668
		}
669
		catch ( Exception $e ) {
670
			return new WP_Error( 'image_stream_error', $e->getMessage() );
671
		}
672
673
		return true;
674
	}
675
676
	/**
677
	 * Strips all image meta except color profiles from an image.
678
	 *
679
	 * @since 4.5.0
680
	 * @access protected
681
	 *
682
	 * @return true|WP_Error True if stripping metadata was successful. WP_Error object on error.
683
	 */
684
	protected function strip_meta() {
685
686 View Code Duplication
		if ( ! is_callable( array( $this->image, 'getImageProfiles' ) ) ) {
687
			/* translators: %s: ImageMagick method name */
688
			return new WP_Error( 'image_strip_meta_error', sprintf( __( '%s is required to strip image meta.' ), '<code>Imagick::getImageProfiles()</code>' ) );
689
		}
690
691 View Code Duplication
		if ( ! is_callable( array( $this->image, 'removeImageProfile' ) ) ) {
692
			/* translators: %s: ImageMagick method name */
693
			return new WP_Error( 'image_strip_meta_error', sprintf( __( '%s is required to strip image meta.' ), '<code>Imagick::removeImageProfile()</code>' ) );
694
		}
695
696
		/*
697
		 * Protect a few profiles from being stripped for the following reasons:
698
		 *
699
		 * - icc:  Color profile information
700
		 * - icm:  Color profile information
701
		 * - iptc: Copyright data
702
		 * - exif: Orientation data
703
		 * - xmp:  Rights usage data
704
		 */
705
		$protected_profiles = array(
706
			'icc',
707
			'icm',
708
			'iptc',
709
			'exif',
710
			'xmp',
711
		);
712
713
		try {
714
			// Strip profiles.
715
			foreach ( $this->image->getImageProfiles( '*', true ) as $key => $value ) {
716
				if ( ! in_array( $key, $protected_profiles ) ) {
717
					$this->image->removeImageProfile( $key );
718
				}
719
			}
720
721
		} catch ( Exception $e ) {
722
			return new WP_Error( 'image_strip_meta_error', $e->getMessage() );
723
		}
724
725
		return true;
726
	}
727
728
}
729