WordPress 4.5
[autoinstalls/wordpress.git] / wp-includes / class-wp-image-editor.php
1 <?php
2 /**
3  * Base WordPress Image Editor
4  *
5  * @package WordPress
6  * @subpackage Image_Editor
7  */
8
9 /**
10  * Base image editor class from which implementations extend
11  *
12  * @since 3.5.0
13  */
14 abstract class WP_Image_Editor {
15         protected $file = null;
16         protected $size = null;
17         protected $mime_type = null;
18         protected $default_mime_type = 'image/jpeg';
19         protected $quality = false;
20         protected $default_quality = 82;
21
22         /**
23          * Each instance handles a single file.
24          *
25          * @param string $file Path to the file to load.
26          */
27         public function __construct( $file ) {
28                 $this->file = $file;
29         }
30
31         /**
32          * Checks to see if current environment supports the editor chosen.
33          * Must be overridden in a sub-class.
34          *
35          * @since 3.5.0
36          *
37          * @static
38          * @access public
39          * @abstract
40          *
41          * @param array $args
42          * @return bool
43          */
44         public static function test( $args = array() ) {
45                 return false;
46         }
47
48         /**
49          * Checks to see if editor supports the mime-type specified.
50          * Must be overridden in a sub-class.
51          *
52          * @since 3.5.0
53          *
54          * @static
55          * @access public
56          * @abstract
57          *
58          * @param string $mime_type
59          * @return bool
60          */
61         public static function supports_mime_type( $mime_type ) {
62                 return false;
63         }
64
65         /**
66          * Loads image from $this->file into editor.
67          *
68          * @since 3.5.0
69          * @access protected
70          * @abstract
71          *
72          * @return bool|WP_Error True if loaded; WP_Error on failure.
73          */
74         abstract public function load();
75
76         /**
77          * Saves current image to file.
78          *
79          * @since 3.5.0
80          * @access public
81          * @abstract
82          *
83          * @param string $destfilename
84          * @param string $mime_type
85          * @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string}
86          */
87         abstract public function save( $destfilename = null, $mime_type = null );
88
89         /**
90          * Resizes current image.
91          *
92          * At minimum, either a height or width must be provided.
93          * If one of the two is set to null, the resize will
94          * maintain aspect ratio according to the provided dimension.
95          *
96          * @since 3.5.0
97          * @access public
98          * @abstract
99          *
100          * @param  int|null $max_w Image width.
101          * @param  int|null $max_h Image height.
102          * @param  bool     $crop
103          * @return bool|WP_Error
104          */
105         abstract public function resize( $max_w, $max_h, $crop = false );
106
107         /**
108          * Resize multiple images from a single source.
109          *
110          * @since 3.5.0
111          * @access public
112          * @abstract
113          *
114          * @param array $sizes {
115          *     An array of image size arrays. Default sizes are 'small', 'medium', 'large'.
116          *
117          *     @type array $size {
118          *         @type int  $width  Image width.
119          *         @type int  $height Image height.
120          *         @type bool $crop   Optional. Whether to crop the image. Default false.
121          *     }
122          * }
123          * @return array An array of resized images metadata by size.
124          */
125         abstract public function multi_resize( $sizes );
126
127         /**
128          * Crops Image.
129          *
130          * @since 3.5.0
131          * @access public
132          * @abstract
133          *
134          * @param int $src_x The start x position to crop from.
135          * @param int $src_y The start y position to crop from.
136          * @param int $src_w The width to crop.
137          * @param int $src_h The height to crop.
138          * @param int $dst_w Optional. The destination width.
139          * @param int $dst_h Optional. The destination height.
140          * @param bool $src_abs Optional. If the source crop points are absolute.
141          * @return bool|WP_Error
142          */
143         abstract public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false );
144
145         /**
146          * Rotates current image counter-clockwise by $angle.
147          *
148          * @since 3.5.0
149          * @access public
150          * @abstract
151          *
152          * @param float $angle
153          * @return bool|WP_Error
154          */
155         abstract public function rotate( $angle );
156
157         /**
158          * Flips current image.
159          *
160          * @since 3.5.0
161          * @access public
162          * @abstract
163          *
164          * @param bool $horz Flip along Horizontal Axis
165          * @param bool $vert Flip along Vertical Axis
166          * @return bool|WP_Error
167          */
168         abstract public function flip( $horz, $vert );
169
170         /**
171          * Streams current image to browser.
172          *
173          * @since 3.5.0
174          * @access public
175          * @abstract
176          *
177          * @param string $mime_type
178          * @return bool|WP_Error
179          */
180         abstract public function stream( $mime_type = null );
181
182         /**
183          * Gets dimensions of image.
184          *
185          * @since 3.5.0
186          * @access public
187          *
188          * @return array {'width'=>int, 'height'=>int}
189          */
190         public function get_size() {
191                 return $this->size;
192         }
193
194         /**
195          * Sets current image size.
196          *
197          * @since 3.5.0
198          * @access protected
199          *
200          * @param int $width
201          * @param int $height
202          * @return true
203          */
204         protected function update_size( $width = null, $height = null ) {
205                 $this->size = array(
206                         'width' => (int) $width,
207                         'height' => (int) $height
208                 );
209                 return true;
210         }
211
212         /**
213          * Gets the Image Compression quality on a 1-100% scale.
214          *
215          * @since 4.0.0
216          * @access public
217          *
218          * @return int $quality Compression Quality. Range: [1,100]
219          */
220         public function get_quality() {
221                 if ( ! $this->quality ) {
222                         $this->set_quality();
223                 }
224
225                 return $this->quality;
226         }
227
228         /**
229          * Sets Image Compression quality on a 1-100% scale.
230          *
231          * @since 3.5.0
232          * @access public
233          *
234          * @param int $quality Compression Quality. Range: [1,100]
235          * @return true|WP_Error True if set successfully; WP_Error on failure.
236          */
237         public function set_quality( $quality = null ) {
238                 if ( null === $quality ) {
239                         /**
240                          * Filter the default image compression quality setting.
241                          *
242                          * Applies only during initial editor instantiation, or when set_quality() is run
243                          * manually without the `$quality` argument.
244                          *
245                          * set_quality() has priority over the filter.
246                          *
247                          * @since 3.5.0
248                          *
249                          * @param int    $quality   Quality level between 1 (low) and 100 (high).
250                          * @param string $mime_type Image mime type.
251                          */
252                         $quality = apply_filters( 'wp_editor_set_quality', $this->default_quality, $this->mime_type );
253
254                         if ( 'image/jpeg' == $this->mime_type ) {
255                                 /**
256                                  * Filter the JPEG compression quality for backward-compatibility.
257                                  *
258                                  * Applies only during initial editor instantiation, or when set_quality() is run
259                                  * manually without the `$quality` argument.
260                                  *
261                                  * set_quality() has priority over the filter.
262                                  *
263                                  * The filter is evaluated under two contexts: 'image_resize', and 'edit_image',
264                                  * (when a JPEG image is saved to file).
265                                  *
266                                  * @since 2.5.0
267                                  *
268                                  * @param int    $quality Quality level between 0 (low) and 100 (high) of the JPEG.
269                                  * @param string $context Context of the filter.
270                                  */
271                                 $quality = apply_filters( 'jpeg_quality', $quality, 'image_resize' );
272                         }
273
274                         if ( $quality < 0 || $quality > 100 ) {
275                                 $quality = $this->default_quality;
276                         }
277                 }
278
279                 // Allow 0, but squash to 1 due to identical images in GD, and for backwards compatibility.
280                 if ( 0 === $quality ) {
281                         $quality = 1;
282                 }
283
284                 if ( ( $quality >= 1 ) && ( $quality <= 100 ) ) {
285                         $this->quality = $quality;
286                         return true;
287                 } else {
288                         return new WP_Error( 'invalid_image_quality', __('Attempted to set image quality outside of the range [1,100].') );
289                 }
290         }
291
292         /**
293          * Returns preferred mime-type and extension based on provided
294          * file's extension and mime, or current file's extension and mime.
295          *
296          * Will default to $this->default_mime_type if requested is not supported.
297          *
298          * Provides corrected filename only if filename is provided.
299          *
300          * @since 3.5.0
301          * @access protected
302          *
303          * @param string $filename
304          * @param string $mime_type
305          * @return array { filename|null, extension, mime-type }
306          */
307         protected function get_output_format( $filename = null, $mime_type = null ) {
308                 $new_ext = null;
309
310                 // By default, assume specified type takes priority
311                 if ( $mime_type ) {
312                         $new_ext = $this->get_extension( $mime_type );
313                 }
314
315                 if ( $filename ) {
316                         $file_ext = strtolower( pathinfo( $filename, PATHINFO_EXTENSION ) );
317                         $file_mime = $this->get_mime_type( $file_ext );
318                 }
319                 else {
320                         // If no file specified, grab editor's current extension and mime-type.
321                         $file_ext = strtolower( pathinfo( $this->file, PATHINFO_EXTENSION ) );
322                         $file_mime = $this->mime_type;
323                 }
324
325                 // Check to see if specified mime-type is the same as type implied by
326                 // file extension.  If so, prefer extension from file.
327                 if ( ! $mime_type || ( $file_mime == $mime_type ) ) {
328                         $mime_type = $file_mime;
329                         $new_ext = $file_ext;
330                 }
331
332                 // Double-check that the mime-type selected is supported by the editor.
333                 // If not, choose a default instead.
334                 if ( ! $this->supports_mime_type( $mime_type ) ) {
335                         /**
336                          * Filter default mime type prior to getting the file extension.
337                          *
338                          * @see wp_get_mime_types()
339                          *
340                          * @since 3.5.0
341                          *
342                          * @param string $mime_type Mime type string.
343                          */
344                         $mime_type = apply_filters( 'image_editor_default_mime_type', $this->default_mime_type );
345                         $new_ext = $this->get_extension( $mime_type );
346                 }
347
348                 if ( $filename ) {
349                         $ext = '';
350                         $info = pathinfo( $filename );
351                         $dir  = $info['dirname'];
352
353                         if ( isset( $info['extension'] ) )
354                                 $ext = $info['extension'];
355
356                         $filename = trailingslashit( $dir ) . wp_basename( $filename, ".$ext" ) . ".{$new_ext}";
357                 }
358
359                 return array( $filename, $new_ext, $mime_type );
360         }
361
362         /**
363          * Builds an output filename based on current file, and adding proper suffix
364          *
365          * @since 3.5.0
366          * @access public
367          *
368          * @param string $suffix
369          * @param string $dest_path
370          * @param string $extension
371          * @return string filename
372          */
373         public function generate_filename( $suffix = null, $dest_path = null, $extension = null ) {
374                 // $suffix will be appended to the destination filename, just before the extension
375                 if ( ! $suffix )
376                         $suffix = $this->get_suffix();
377
378                 $info = pathinfo( $this->file );
379                 $dir  = $info['dirname'];
380                 $ext  = $info['extension'];
381
382                 $name = wp_basename( $this->file, ".$ext" );
383                 $new_ext = strtolower( $extension ? $extension : $ext );
384
385                 if ( ! is_null( $dest_path ) && $_dest_path = realpath( $dest_path ) )
386                         $dir = $_dest_path;
387
388                 return trailingslashit( $dir ) . "{$name}-{$suffix}.{$new_ext}";
389         }
390
391         /**
392          * Builds and returns proper suffix for file based on height and width.
393          *
394          * @since 3.5.0
395          * @access public
396          *
397          * @return false|string suffix
398          */
399         public function get_suffix() {
400                 if ( ! $this->get_size() )
401                         return false;
402
403                 return "{$this->size['width']}x{$this->size['height']}";
404         }
405
406         /**
407          * Either calls editor's save function or handles file as a stream.
408          *
409          * @since 3.5.0
410          * @access protected
411          *
412          * @param string|stream $filename
413          * @param callable $function
414          * @param array $arguments
415          * @return bool
416          */
417         protected function make_image( $filename, $function, $arguments ) {
418                 if ( $stream = wp_is_stream( $filename ) ) {
419                         ob_start();
420                 } else {
421                         // The directory containing the original file may no longer exist when using a replication plugin.
422                         wp_mkdir_p( dirname( $filename ) );
423                 }
424
425                 $result = call_user_func_array( $function, $arguments );
426
427                 if ( $result && $stream ) {
428                         $contents = ob_get_contents();
429
430                         $fp = fopen( $filename, 'w' );
431
432                         if ( ! $fp )
433                                 return false;
434
435                         fwrite( $fp, $contents );
436                         fclose( $fp );
437                 }
438
439                 if ( $stream ) {
440                         ob_end_clean();
441                 }
442
443                 return $result;
444         }
445
446         /**
447          * Returns first matched mime-type from extension,
448          * as mapped from wp_get_mime_types()
449          *
450          * @since 3.5.0
451          *
452          * @static
453          * @access protected
454          *
455          * @param string $extension
456          * @return string|false
457          */
458         protected static function get_mime_type( $extension = null ) {
459                 if ( ! $extension )
460                         return false;
461
462                 $mime_types = wp_get_mime_types();
463                 $extensions = array_keys( $mime_types );
464
465                 foreach ( $extensions as $_extension ) {
466                         if ( preg_match( "/{$extension}/i", $_extension ) ) {
467                                 return $mime_types[$_extension];
468                         }
469                 }
470
471                 return false;
472         }
473
474         /**
475          * Returns first matched extension from Mime-type,
476          * as mapped from wp_get_mime_types()
477          *
478          * @since 3.5.0
479          *
480          * @static
481          * @access protected
482          *
483          * @param string $mime_type
484          * @return string|false
485          */
486         protected static function get_extension( $mime_type = null ) {
487                 $extensions = explode( '|', array_search( $mime_type, wp_get_mime_types() ) );
488
489                 if ( empty( $extensions[0] ) )
490                         return false;
491
492                 return $extensions[0];
493         }
494 }
495