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