WordPress 3.9
[autoinstalls/wordpress.git] / wp-admin / includes / image.php
1 <?php
2 /**
3  * File contains all the administration image manipulation functions.
4  *
5  * @package WordPress
6  * @subpackage Administration
7  */
8
9 /**
10  * Crop an Image to a given size.
11  *
12  * @since 2.1.0
13  *
14  * @param string|int $src The source file or Attachment ID.
15  * @param int $src_x The start x position to crop from.
16  * @param int $src_y The start y position to crop from.
17  * @param int $src_w The width to crop.
18  * @param int $src_h The height to crop.
19  * @param int $dst_w The destination width.
20  * @param int $dst_h The destination height.
21  * @param int $src_abs Optional. If the source crop points are absolute.
22  * @param string $dst_file Optional. The destination file to write to.
23  * @return string|WP_Error New filepath on success, WP_Error on failure.
24  */
25 function wp_crop_image( $src, $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h, $src_abs = false, $dst_file = false ) {
26         $src_file = $src;
27         if ( is_numeric( $src ) ) { // Handle int as attachment ID
28                 $src_file = get_attached_file( $src );
29
30                 if ( ! file_exists( $src_file ) ) {
31                         // If the file doesn't exist, attempt a url fopen on the src link.
32                         // This can occur with certain file replication plugins.
33                         $src = _load_image_to_edit_path( $src, 'full' );
34                 } else {
35                         $src = $src_file;
36                 }
37         }
38
39         $editor = wp_get_image_editor( $src );
40         if ( is_wp_error( $editor ) )
41                 return $editor;
42
43         $src = $editor->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h, $src_abs );
44         if ( is_wp_error( $src ) )
45                 return $src;
46
47         if ( ! $dst_file )
48                 $dst_file = str_replace( basename( $src_file ), 'cropped-' . basename( $src_file ), $src_file );
49
50         // The directory containing the original file may no longer exist when
51         // using a replication plugin.
52         wp_mkdir_p( dirname( $dst_file ) );
53
54         $dst_file = dirname( $dst_file ) . '/' . wp_unique_filename( dirname( $dst_file ), basename( $dst_file ) );
55
56         $result = $editor->save( $dst_file );
57         if ( is_wp_error( $result ) )
58                 return $result;
59
60         return $dst_file;
61 }
62
63 /**
64  * Generate post thumbnail attachment meta data.
65  *
66  * @since 2.1.0
67  *
68  * @param int $attachment_id Attachment Id to process.
69  * @param string $file Filepath of the Attached image.
70  * @return mixed Metadata for attachment.
71  */
72 function wp_generate_attachment_metadata( $attachment_id, $file ) {
73         $attachment = get_post( $attachment_id );
74
75         $metadata = array();
76         $support = false;
77         if ( preg_match('!^image/!', get_post_mime_type( $attachment )) && file_is_displayable_image($file) ) {
78                 $imagesize = getimagesize( $file );
79                 $metadata['width'] = $imagesize[0];
80                 $metadata['height'] = $imagesize[1];
81
82                 // Make the file path relative to the upload dir
83                 $metadata['file'] = _wp_relative_upload_path($file);
84
85                 // make thumbnails and other intermediate sizes
86                 global $_wp_additional_image_sizes;
87
88                 $sizes = array();
89                 foreach ( get_intermediate_image_sizes() as $s ) {
90                         $sizes[$s] = array( 'width' => '', 'height' => '', 'crop' => false );
91                         if ( isset( $_wp_additional_image_sizes[$s]['width'] ) )
92                                 $sizes[$s]['width'] = intval( $_wp_additional_image_sizes[$s]['width'] ); // For theme-added sizes
93                         else
94                                 $sizes[$s]['width'] = get_option( "{$s}_size_w" ); // For default sizes set in options
95                         if ( isset( $_wp_additional_image_sizes[$s]['height'] ) )
96                                 $sizes[$s]['height'] = intval( $_wp_additional_image_sizes[$s]['height'] ); // For theme-added sizes
97                         else
98                                 $sizes[$s]['height'] = get_option( "{$s}_size_h" ); // For default sizes set in options
99                         if ( isset( $_wp_additional_image_sizes[$s]['crop'] ) )
100                                 $sizes[$s]['crop'] = $_wp_additional_image_sizes[$s]['crop']; // For theme-added sizes
101                         else
102                                 $sizes[$s]['crop'] = get_option( "{$s}_crop" ); // For default sizes set in options
103                 }
104
105                 /**
106                  * Filter the image sizes automatically generated when uploading an image.
107                  *
108                  * @since 2.9.0
109                  *
110                  * @param array $sizes An associative array of image sizes.
111                  */
112                 $sizes = apply_filters( 'intermediate_image_sizes_advanced', $sizes );
113
114                 if ( $sizes ) {
115                         $editor = wp_get_image_editor( $file );
116
117                         if ( ! is_wp_error( $editor ) )
118                                 $metadata['sizes'] = $editor->multi_resize( $sizes );
119                 } else {
120                         $metadata['sizes'] = array();
121                 }
122
123                 // fetch additional metadata from exif/iptc
124                 $image_meta = wp_read_image_metadata( $file );
125                 if ( $image_meta )
126                         $metadata['image_meta'] = $image_meta;
127
128         } elseif ( preg_match( '#^video/#', get_post_mime_type( $attachment ) ) ) {
129                 $metadata = wp_read_video_metadata( $file );
130                 $support = current_theme_supports( 'post-thumbnails', 'attachment:video' ) || post_type_supports( 'attachment:video', 'thumbnail' );
131         } elseif ( preg_match( '#^audio/#', get_post_mime_type( $attachment ) ) ) {
132                 $metadata = wp_read_audio_metadata( $file );
133                 $support = current_theme_supports( 'post-thumbnails', 'attachment:audio' ) || post_type_supports( 'attachment:audio', 'thumbnail' );
134         }
135
136         if ( $support && ! empty( $metadata['image']['data'] ) ) {
137                 // check for existing cover
138                 $hash = md5( $metadata['image']['data'] );
139                 $posts = get_posts( array(
140                         'fields' => 'ids',
141                         'post_type' => 'attachment',
142                         'post_mime_type' => $metadata['image']['mime'],
143                         'post_status' => 'inherit',
144                         'posts_per_page' => 1,
145                         'meta_key' => '_cover_hash',
146                         'meta_value' => $hash
147                 ) );
148                 $exists = reset( $posts );
149
150                 if ( ! empty( $exists ) ) {
151                         update_post_meta( $attachment_id, '_thumbnail_id', $exists );
152                 } else {
153                         $ext = '.jpg';
154                         switch ( $metadata['image']['mime'] ) {
155                         case 'image/gif':
156                                 $ext = '.gif';
157                                 break;
158                         case 'image/png':
159                                 $ext = '.png';
160                                 break;
161                         }
162                         $basename = str_replace( '.', '-', basename( $file ) ) . '-image' . $ext;
163                         $uploaded = wp_upload_bits( $basename, '', $metadata['image']['data'] );
164                         if ( false === $uploaded['error'] ) {
165                                 $image_attachment = array(
166                                         'post_mime_type' => $metadata['image']['mime'],
167                                         'post_type' => 'attachment',
168                                         'post_content' => '',
169                                 );
170                                 /**
171                                  * Filter the parameters for the attachment thumbnail creation.
172                                  *
173                                  * @since 3.9.0
174                                  *
175                                  * @param array $image_attachment An array of parameters to create the thumbnail.
176                                  * @param array $metadata         Current attachment metadata.
177                                  * @param array $uploaded         An array containing the thumbnail path and url.
178                                  */
179                                 $image_attachment = apply_filters( 'attachment_thumbnail_args', $image_attachment, $metadata, $uploaded );
180
181                                 $sub_attachment_id = wp_insert_attachment( $image_attachment, $uploaded['file'] );
182                                 add_post_meta( $sub_attachment_id, '_cover_hash', $hash );
183                                 $attach_data = wp_generate_attachment_metadata( $sub_attachment_id, $uploaded['file'] );
184                                 wp_update_attachment_metadata( $sub_attachment_id, $attach_data );
185                                 update_post_meta( $attachment_id, '_thumbnail_id', $sub_attachment_id );
186                         }
187                 }
188         }
189
190         // remove the blob of binary data from the array
191         if ( isset( $metadata['image']['data'] ) )
192                 unset( $metadata['image']['data'] );
193
194         /**
195          * Filter the generated attachment meta data.
196          *
197          * @since 2.1.0
198          *
199          * @param array $metadata      An array of attachment meta data.
200          * @param int   $attachment_id Current attachment ID.
201          */
202         return apply_filters( 'wp_generate_attachment_metadata', $metadata, $attachment_id );
203 }
204
205 /**
206  * Convert a fraction string to a decimal.
207  *
208  * @since 2.5.0
209  *
210  * @param string $str
211  * @return int|float
212  */
213 function wp_exif_frac2dec($str) {
214         @list( $n, $d ) = explode( '/', $str );
215         if ( !empty($d) )
216                 return $n / $d;
217         return $str;
218 }
219
220 /**
221  * Convert the exif date format to a unix timestamp.
222  *
223  * @since 2.5.0
224  *
225  * @param string $str
226  * @return int
227  */
228 function wp_exif_date2ts($str) {
229         @list( $date, $time ) = explode( ' ', trim($str) );
230         @list( $y, $m, $d ) = explode( ':', $date );
231
232         return strtotime( "{$y}-{$m}-{$d} {$time}" );
233 }
234
235 /**
236  * Get extended image metadata, exif or iptc as available.
237  *
238  * Retrieves the EXIF metadata aperture, credit, camera, caption, copyright, iso
239  * created_timestamp, focal_length, shutter_speed, and title.
240  *
241  * The IPTC metadata that is retrieved is APP13, credit, byline, created date
242  * and time, caption, copyright, and title. Also includes FNumber, Model,
243  * DateTimeDigitized, FocalLength, ISOSpeedRatings, and ExposureTime.
244  *
245  * @todo Try other exif libraries if available.
246  * @since 2.5.0
247  *
248  * @param string $file
249  * @return bool|array False on failure. Image metadata array on success.
250  */
251 function wp_read_image_metadata( $file ) {
252         if ( ! file_exists( $file ) )
253                 return false;
254
255         list( , , $sourceImageType ) = getimagesize( $file );
256
257         // exif contains a bunch of data we'll probably never need formatted in ways
258         // that are difficult to use. We'll normalize it and just extract the fields
259         // that are likely to be useful. Fractions and numbers are converted to
260         // floats, dates to unix timestamps, and everything else to strings.
261         $meta = array(
262                 'aperture' => 0,
263                 'credit' => '',
264                 'camera' => '',
265                 'caption' => '',
266                 'created_timestamp' => 0,
267                 'copyright' => '',
268                 'focal_length' => 0,
269                 'iso' => 0,
270                 'shutter_speed' => 0,
271                 'title' => '',
272         );
273
274         // read iptc first, since it might contain data not available in exif such
275         // as caption, description etc
276         if ( is_callable( 'iptcparse' ) ) {
277                 getimagesize( $file, $info );
278
279                 if ( ! empty( $info['APP13'] ) ) {
280                         $iptc = iptcparse( $info['APP13'] );
281
282                         // headline, "A brief synopsis of the caption."
283                         if ( ! empty( $iptc['2#105'][0] ) )
284                                 $meta['title'] = trim( $iptc['2#105'][0] );
285                         // title, "Many use the Title field to store the filename of the image, though the field may be used in many ways."
286                         elseif ( ! empty( $iptc['2#005'][0] ) )
287                                 $meta['title'] = trim( $iptc['2#005'][0] );
288
289                         if ( ! empty( $iptc['2#120'][0] ) ) { // description / legacy caption
290                                 $caption = trim( $iptc['2#120'][0] );
291                                 if ( empty( $meta['title'] ) ) {
292                                         // Assume the title is stored in 2:120 if it's short.
293                                         if ( strlen( $caption ) < 80 )
294                                                 $meta['title'] = $caption;
295                                         else
296                                                 $meta['caption'] = $caption;
297                                 } elseif ( $caption != $meta['title'] ) {
298                                         $meta['caption'] = $caption;
299                                 }
300                         }
301
302                         if ( ! empty( $iptc['2#110'][0] ) ) // credit
303                                 $meta['credit'] = trim( $iptc['2#110'][0] );
304                         elseif ( ! empty( $iptc['2#080'][0] ) ) // creator / legacy byline
305                                 $meta['credit'] = trim( $iptc['2#080'][0] );
306
307                         if ( ! empty( $iptc['2#055'][0] ) and ! empty( $iptc['2#060'][0] ) ) // created date and time
308                                 $meta['created_timestamp'] = strtotime( $iptc['2#055'][0] . ' ' . $iptc['2#060'][0] );
309
310                         if ( ! empty( $iptc['2#116'][0] ) ) // copyright
311                                 $meta['copyright'] = trim( $iptc['2#116'][0] );
312                  }
313         }
314
315         /**
316          * Filter the image types to check for exif data.
317          *
318          * @since 2.5.0
319          *
320          * @param array $image_types Image types to check for exif data.
321          */
322         if ( is_callable( 'exif_read_data' ) && in_array( $sourceImageType, apply_filters( 'wp_read_image_metadata_types', array( IMAGETYPE_JPEG, IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM ) ) ) ) {
323                 $exif = @exif_read_data( $file );
324
325                 if ( !empty( $exif['Title'] ) )
326                         $meta['title'] = trim( $exif['Title'] );
327
328                 if ( ! empty( $exif['ImageDescription'] ) ) {
329                         if ( empty( $meta['title'] ) && strlen( $exif['ImageDescription'] ) < 80 ) {
330                                 // Assume the title is stored in ImageDescription
331                                 $meta['title'] = trim( $exif['ImageDescription'] );
332                                 if ( ! empty( $exif['COMPUTED']['UserComment'] ) && trim( $exif['COMPUTED']['UserComment'] ) != $meta['title'] )
333                                         $meta['caption'] = trim( $exif['COMPUTED']['UserComment'] );
334                         } elseif ( trim( $exif['ImageDescription'] ) != $meta['title'] ) {
335                                 $meta['caption'] = trim( $exif['ImageDescription'] );
336                         }
337                 } elseif ( ! empty( $exif['Comments'] ) && trim( $exif['Comments'] ) != $meta['title'] ) {
338                         $meta['caption'] = trim( $exif['Comments'] );
339                 }
340
341                 if ( ! empty( $exif['Artist'] ) )
342                         $meta['credit'] = trim( $exif['Artist'] );
343                 elseif ( ! empty($exif['Author'] ) )
344                         $meta['credit'] = trim( $exif['Author'] );
345
346                 if ( ! empty( $exif['Copyright'] ) )
347                         $meta['copyright'] = trim( $exif['Copyright'] );
348                 if ( ! empty($exif['FNumber'] ) )
349                         $meta['aperture'] = round( wp_exif_frac2dec( $exif['FNumber'] ), 2 );
350                 if ( ! empty($exif['Model'] ) )
351                         $meta['camera'] = trim( $exif['Model'] );
352                 if ( ! empty($exif['DateTimeDigitized'] ) )
353                         $meta['created_timestamp'] = wp_exif_date2ts($exif['DateTimeDigitized'] );
354                 if ( ! empty($exif['FocalLength'] ) )
355                         $meta['focal_length'] = (string) wp_exif_frac2dec( $exif['FocalLength'] );
356                 if ( ! empty($exif['ISOSpeedRatings'] ) ) {
357                         $meta['iso'] = is_array( $exif['ISOSpeedRatings'] ) ? reset( $exif['ISOSpeedRatings'] ) : $exif['ISOSpeedRatings'];
358                         $meta['iso'] = trim( $meta['iso'] );
359                 }
360                 if ( ! empty($exif['ExposureTime'] ) )
361                         $meta['shutter_speed'] = (string) wp_exif_frac2dec( $exif['ExposureTime'] );
362         }
363
364         foreach ( array( 'title', 'caption', 'credit', 'copyright', 'camera', 'iso' ) as $key ) {
365                 if ( $meta[ $key ] && ! seems_utf8( $meta[ $key ] ) )
366                         $meta[ $key ] = utf8_encode( $meta[ $key ] );
367         }
368
369         /**
370          * Filter the array of meta data read from an image's exif data.
371          *
372          * @since 2.5.0
373          *
374          * @param array  $meta            Image meta data.
375          * @param string $file            Path to image file.
376          * @param int    $sourceImageType Type of image.
377          */
378         return apply_filters( 'wp_read_image_metadata', $meta, $file, $sourceImageType );
379
380 }
381
382 /**
383  * Validate that file is an image.
384  *
385  * @since 2.5.0
386  *
387  * @param string $path File path to test if valid image.
388  * @return bool True if valid image, false if not valid image.
389  */
390 function file_is_valid_image($path) {
391         $size = @getimagesize($path);
392         return !empty($size);
393 }
394
395 /**
396  * Validate that file is suitable for displaying within a web page.
397  *
398  * @since 2.5.0
399  *
400  * @param string $path File path to test.
401  * @return bool True if suitable, false if not suitable.
402  */
403 function file_is_displayable_image($path) {
404         $info = @getimagesize($path);
405         if ( empty($info) )
406                 $result = false;
407         elseif ( !in_array($info[2], array(IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG)) )     // only gif, jpeg and png images can reliably be displayed
408                 $result = false;
409         else
410                 $result = true;
411
412         /**
413          * Filter whether the current image is displayable in the browser.
414          *
415          * @since 2.5.0
416          *
417          * @param bool   $result Whether the image can be displayed. Default true.
418          * @param string $path   Path to the image.
419          */
420         return apply_filters( 'file_is_displayable_image', $result, $path );
421 }
422
423 /**
424  * Load an image resource for editing.
425  *
426  * @since 2.9.0
427  *
428  * @param string $attachment_id Attachment ID.
429  * @param string $mime_type Image mime type.
430  * @param string $size Optional. Image size, defaults to 'full'.
431  * @return resource|false The resulting image resource on success, false on failure.
432  */
433 function load_image_to_edit( $attachment_id, $mime_type, $size = 'full' ) {
434         $filepath = _load_image_to_edit_path( $attachment_id, $size );
435         if ( empty( $filepath ) )
436                 return false;
437
438         switch ( $mime_type ) {
439                 case 'image/jpeg':
440                         $image = imagecreatefromjpeg($filepath);
441                         break;
442                 case 'image/png':
443                         $image = imagecreatefrompng($filepath);
444                         break;
445                 case 'image/gif':
446                         $image = imagecreatefromgif($filepath);
447                         break;
448                 default:
449                         $image = false;
450                         break;
451         }
452         if ( is_resource($image) ) {
453                 /**
454                  * Filter the current image being loaded for editing.
455                  *
456                  * @since 2.9.0
457                  *
458                  * @param resource $image         Current image.
459                  * @param string   $attachment_id Attachment ID.
460                  * @param string   $size          Image size.
461                  */
462                 $image = apply_filters( 'load_image_to_edit', $image, $attachment_id, $size );
463                 if ( function_exists('imagealphablending') && function_exists('imagesavealpha') ) {
464                         imagealphablending($image, false);
465                         imagesavealpha($image, true);
466                 }
467         }
468         return $image;
469 }
470
471 /**
472  * Retrieve the path or url of an attachment's attached file.
473  *
474  * If the attached file is not present on the local filesystem (usually due to replication plugins),
475  * then the url of the file is returned if url fopen is supported.
476  *
477  * @since 3.4.0
478  * @access private
479  *
480  * @param string $attachment_id Attachment ID.
481  * @param string $size Optional. Image size, defaults to 'full'.
482  * @return string|false File path or url on success, false on failure.
483  */
484 function _load_image_to_edit_path( $attachment_id, $size = 'full' ) {
485         $filepath = get_attached_file( $attachment_id );
486
487         if ( $filepath && file_exists( $filepath ) ) {
488                 if ( 'full' != $size && ( $data = image_get_intermediate_size( $attachment_id, $size ) ) ) {
489                         /**
490                          * Filter the path to the current image.
491                          *
492                          * The filter is evaluated for all image sizes except 'full'.
493                          *
494                          * @since 3.1.0
495                          *
496                          * @param string $path          Path to the current image.
497                          * @param string $attachment_id Attachment ID.
498                          * @param string $size          Size of the image.
499                          */
500                         $filepath = apply_filters( 'load_image_to_edit_filesystempath', path_join( dirname( $filepath ), $data['file'] ), $attachment_id, $size );
501                 }
502         } elseif ( function_exists( 'fopen' ) && function_exists( 'ini_get' ) && true == ini_get( 'allow_url_fopen' ) ) {
503                 /**
504                  * Filter the image URL if not in the local filesystem.
505                  *
506                  * The filter is only evaluated if fopen is enabled on the server.
507                  *
508                  * @since 3.1.0
509                  *
510                  * @param string $image_url     Current image URL.
511                  * @param string $attachment_id Attachment ID.
512                  * @param string $size          Size of the image.
513                  */
514                 $filepath = apply_filters( 'load_image_to_edit_attachmenturl', wp_get_attachment_url( $attachment_id ), $attachment_id, $size );
515         }
516
517         /**
518          * Filter the returned path or URL of the current image.
519          *
520          * @since 2.9.0
521          *
522          * @param string|bool $filepath      File path or URL to current image, or false.
523          * @param string      $attachment_id Attachment ID.
524          * @param string      $size          Size of the image.
525          */
526         return apply_filters( 'load_image_to_edit_path', $filepath, $attachment_id, $size );
527 }
528
529 /**
530  * Copy an existing image file.
531  *
532  * @since 3.4.0
533  * @access private
534  *
535  * @param string $attachment_id Attachment ID.
536  * @return string|false New file path on success, false on failure.
537  */
538 function _copy_image_file( $attachment_id ) {
539         $dst_file = $src_file = get_attached_file( $attachment_id );
540         if ( ! file_exists( $src_file ) )
541                 $src_file = _load_image_to_edit_path( $attachment_id );
542
543         if ( $src_file ) {
544                 $dst_file = str_replace( basename( $dst_file ), 'copy-' . basename( $dst_file ), $dst_file );
545                 $dst_file = dirname( $dst_file ) . '/' . wp_unique_filename( dirname( $dst_file ), basename( $dst_file ) );
546
547                 // The directory containing the original file may no longer exist when
548                 // using a replication plugin.
549                 wp_mkdir_p( dirname( $dst_file ) );
550
551                 if ( ! @copy( $src_file, $dst_file ) )
552                         $dst_file = false;
553         } else {
554                 $dst_file = false;
555         }
556
557         return $dst_file;
558 }