[ Index ] |
PHP Cross Reference of WordPress |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * File contains all the administration image manipulation functions. 4 * 5 * @package WordPress 6 * @subpackage Administration 7 */ 8 9 /** 10 * Crops 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 bool|false $src_abs Optional. If the source crop points are absolute. 22 * @param string|false $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 44 $src = $editor->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h, $src_abs ); 45 if ( is_wp_error( $src ) ) { 46 return $src; 47 } 48 49 if ( ! $dst_file ) { 50 $dst_file = str_replace( wp_basename( $src_file ), 'cropped-' . wp_basename( $src_file ), $src_file ); 51 } 52 53 /* 54 * The directory containing the original file may no longer exist when 55 * using a replication plugin. 56 */ 57 wp_mkdir_p( dirname( $dst_file ) ); 58 59 $dst_file = dirname( $dst_file ) . '/' . wp_unique_filename( dirname( $dst_file ), wp_basename( $dst_file ) ); 60 61 $result = $editor->save( $dst_file ); 62 if ( is_wp_error( $result ) ) { 63 return $result; 64 } 65 66 if ( ! empty( $result['path'] ) ) { 67 return $result['path']; 68 } 69 70 return $dst_file; 71 } 72 73 /** 74 * Compare the existing image sub-sizes (as saved in the attachment meta) 75 * to the currently registered image sub-sizes, and return the difference. 76 * 77 * Registered sub-sizes that are larger than the image are skipped. 78 * 79 * @since 5.3.0 80 * 81 * @param int $attachment_id The image attachment post ID. 82 * @return array[] Associative array of arrays of image sub-size information for 83 * missing image sizes, keyed by image size name. 84 */ 85 function wp_get_missing_image_subsizes( $attachment_id ) { 86 if ( ! wp_attachment_is_image( $attachment_id ) ) { 87 return array(); 88 } 89 90 $registered_sizes = wp_get_registered_image_subsizes(); 91 $image_meta = wp_get_attachment_metadata( $attachment_id ); 92 93 // Meta error? 94 if ( empty( $image_meta ) ) { 95 return $registered_sizes; 96 } 97 98 // Use the originally uploaded image dimensions as full_width and full_height. 99 if ( ! empty( $image_meta['original_image'] ) ) { 100 $image_file = wp_get_original_image_path( $attachment_id ); 101 $imagesize = wp_getimagesize( $image_file ); 102 } 103 104 if ( ! empty( $imagesize ) ) { 105 $full_width = $imagesize[0]; 106 $full_height = $imagesize[1]; 107 } else { 108 $full_width = (int) $image_meta['width']; 109 $full_height = (int) $image_meta['height']; 110 } 111 112 $possible_sizes = array(); 113 114 // Skip registered sizes that are too large for the uploaded image. 115 foreach ( $registered_sizes as $size_name => $size_data ) { 116 if ( image_resize_dimensions( $full_width, $full_height, $size_data['width'], $size_data['height'], $size_data['crop'] ) ) { 117 $possible_sizes[ $size_name ] = $size_data; 118 } 119 } 120 121 if ( empty( $image_meta['sizes'] ) ) { 122 $image_meta['sizes'] = array(); 123 } 124 125 /* 126 * Remove sizes that already exist. Only checks for matching "size names". 127 * It is possible that the dimensions for a particular size name have changed. 128 * For example the user has changed the values on the Settings -> Media screen. 129 * However we keep the old sub-sizes with the previous dimensions 130 * as the image may have been used in an older post. 131 */ 132 $missing_sizes = array_diff_key( $possible_sizes, $image_meta['sizes'] ); 133 134 /** 135 * Filters the array of missing image sub-sizes for an uploaded image. 136 * 137 * @since 5.3.0 138 * 139 * @param array[] $missing_sizes Associative array of arrays of image sub-size information for 140 * missing image sizes, keyed by image size name. 141 * @param array $image_meta The image meta data. 142 * @param int $attachment_id The image attachment post ID. 143 */ 144 return apply_filters( 'wp_get_missing_image_subsizes', $missing_sizes, $image_meta, $attachment_id ); 145 } 146 147 /** 148 * If any of the currently registered image sub-sizes are missing, 149 * create them and update the image meta data. 150 * 151 * @since 5.3.0 152 * 153 * @param int $attachment_id The image attachment post ID. 154 * @return array|WP_Error The updated image meta data array or WP_Error object 155 * if both the image meta and the attached file are missing. 156 */ 157 function wp_update_image_subsizes( $attachment_id ) { 158 $image_meta = wp_get_attachment_metadata( $attachment_id ); 159 $image_file = wp_get_original_image_path( $attachment_id ); 160 161 if ( empty( $image_meta ) || ! is_array( $image_meta ) ) { 162 // Previously failed upload? 163 // If there is an uploaded file, make all sub-sizes and generate all of the attachment meta. 164 if ( ! empty( $image_file ) ) { 165 $image_meta = wp_create_image_subsizes( $image_file, $attachment_id ); 166 } else { 167 return new WP_Error( 'invalid_attachment', __( 'The attached file cannot be found.' ) ); 168 } 169 } else { 170 $missing_sizes = wp_get_missing_image_subsizes( $attachment_id ); 171 172 if ( empty( $missing_sizes ) ) { 173 return $image_meta; 174 } 175 176 // This also updates the image meta. 177 $image_meta = _wp_make_subsizes( $missing_sizes, $image_file, $image_meta, $attachment_id ); 178 } 179 180 /** This filter is documented in wp-admin/includes/image.php */ 181 $image_meta = apply_filters( 'wp_generate_attachment_metadata', $image_meta, $attachment_id, 'update' ); 182 183 // Save the updated metadata. 184 wp_update_attachment_metadata( $attachment_id, $image_meta ); 185 186 return $image_meta; 187 } 188 189 /** 190 * Updates the attached file and image meta data when the original image was edited. 191 * 192 * @since 5.3.0 193 * @access private 194 * 195 * @param array $saved_data The data returned from WP_Image_Editor after successfully saving an image. 196 * @param string $original_file Path to the original file. 197 * @param array $image_meta The image meta data. 198 * @param int $attachment_id The attachment post ID. 199 * @return array The updated image meta data. 200 */ 201 function _wp_image_meta_replace_original( $saved_data, $original_file, $image_meta, $attachment_id ) { 202 $new_file = $saved_data['path']; 203 204 // Update the attached file meta. 205 update_attached_file( $attachment_id, $new_file ); 206 207 // Width and height of the new image. 208 $image_meta['width'] = $saved_data['width']; 209 $image_meta['height'] = $saved_data['height']; 210 211 // Make the file path relative to the upload dir. 212 $image_meta['file'] = _wp_relative_upload_path( $new_file ); 213 214 // Store the original image file name in image_meta. 215 $image_meta['original_image'] = wp_basename( $original_file ); 216 217 // Add image file size. 218 $image_meta['filesize'] = wp_filesize( $new_file ); 219 220 return $image_meta; 221 } 222 223 /** 224 * Creates image sub-sizes, adds the new data to the image meta `sizes` array, and updates the image metadata. 225 * 226 * Intended for use after an image is uploaded. Saves/updates the image metadata after each 227 * sub-size is created. If there was an error, it is added to the returned image metadata array. 228 * 229 * @since 5.3.0 230 * 231 * @param string $file Full path to the image file. 232 * @param int $attachment_id Attachment ID to process. 233 * @return array The image attachment meta data. 234 */ 235 function wp_create_image_subsizes( $file, $attachment_id ) { 236 $imagesize = wp_getimagesize( $file ); 237 238 if ( empty( $imagesize ) ) { 239 // File is not an image. 240 return array(); 241 } 242 243 // Default image meta. 244 $image_meta = array( 245 'width' => $imagesize[0], 246 'height' => $imagesize[1], 247 'file' => _wp_relative_upload_path( $file ), 248 'filesize' => wp_filesize( $file ), 249 'sizes' => array(), 250 ); 251 252 // Fetch additional metadata from EXIF/IPTC. 253 $exif_meta = wp_read_image_metadata( $file ); 254 255 if ( $exif_meta ) { 256 $image_meta['image_meta'] = $exif_meta; 257 } 258 259 // Do not scale (large) PNG images. May result in sub-sizes that have greater file size than the original. See #48736. 260 if ( 'image/png' !== $imagesize['mime'] ) { 261 262 /** 263 * Filters the "BIG image" threshold value. 264 * 265 * If the original image width or height is above the threshold, it will be scaled down. The threshold is 266 * used as max width and max height. The scaled down image will be used as the largest available size, including 267 * the `_wp_attached_file` post meta value. 268 * 269 * Returning `false` from the filter callback will disable the scaling. 270 * 271 * @since 5.3.0 272 * 273 * @param int $threshold The threshold value in pixels. Default 2560. 274 * @param array $imagesize { 275 * Indexed array of the image width and height in pixels. 276 * 277 * @type int $0 The image width. 278 * @type int $1 The image height. 279 * } 280 * @param string $file Full path to the uploaded image file. 281 * @param int $attachment_id Attachment post ID. 282 */ 283 $threshold = (int) apply_filters( 'big_image_size_threshold', 2560, $imagesize, $file, $attachment_id ); 284 285 // If the original image's dimensions are over the threshold, 286 // scale the image and use it as the "full" size. 287 if ( $threshold && ( $image_meta['width'] > $threshold || $image_meta['height'] > $threshold ) ) { 288 $editor = wp_get_image_editor( $file ); 289 290 if ( is_wp_error( $editor ) ) { 291 // This image cannot be edited. 292 return $image_meta; 293 } 294 295 // Resize the image. 296 $resized = $editor->resize( $threshold, $threshold ); 297 $rotated = null; 298 299 // If there is EXIF data, rotate according to EXIF Orientation. 300 if ( ! is_wp_error( $resized ) && is_array( $exif_meta ) ) { 301 $resized = $editor->maybe_exif_rotate(); 302 $rotated = $resized; 303 } 304 305 if ( ! is_wp_error( $resized ) ) { 306 // Append "-scaled" to the image file name. It will look like "my_image-scaled.jpg". 307 // This doesn't affect the sub-sizes names as they are generated from the original image (for best quality). 308 $saved = $editor->save( $editor->generate_filename( 'scaled' ) ); 309 310 if ( ! is_wp_error( $saved ) ) { 311 $image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id ); 312 313 // If the image was rotated update the stored EXIF data. 314 if ( true === $rotated && ! empty( $image_meta['image_meta']['orientation'] ) ) { 315 $image_meta['image_meta']['orientation'] = 1; 316 } 317 } else { 318 // TODO: Log errors. 319 } 320 } else { 321 // TODO: Log errors. 322 } 323 } elseif ( ! empty( $exif_meta['orientation'] ) && 1 !== (int) $exif_meta['orientation'] ) { 324 // Rotate the whole original image if there is EXIF data and "orientation" is not 1. 325 326 $editor = wp_get_image_editor( $file ); 327 328 if ( is_wp_error( $editor ) ) { 329 // This image cannot be edited. 330 return $image_meta; 331 } 332 333 // Rotate the image. 334 $rotated = $editor->maybe_exif_rotate(); 335 336 if ( true === $rotated ) { 337 // Append `-rotated` to the image file name. 338 $saved = $editor->save( $editor->generate_filename( 'rotated' ) ); 339 340 if ( ! is_wp_error( $saved ) ) { 341 $image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id ); 342 343 // Update the stored EXIF data. 344 if ( ! empty( $image_meta['image_meta']['orientation'] ) ) { 345 $image_meta['image_meta']['orientation'] = 1; 346 } 347 } else { 348 // TODO: Log errors. 349 } 350 } 351 } 352 } 353 354 /* 355 * Initial save of the new metadata. 356 * At this point the file was uploaded and moved to the uploads directory 357 * but the image sub-sizes haven't been created yet and the `sizes` array is empty. 358 */ 359 wp_update_attachment_metadata( $attachment_id, $image_meta ); 360 361 $new_sizes = wp_get_registered_image_subsizes(); 362 363 /** 364 * Filters the image sizes automatically generated when uploading an image. 365 * 366 * @since 2.9.0 367 * @since 4.4.0 Added the `$image_meta` argument. 368 * @since 5.3.0 Added the `$attachment_id` argument. 369 * 370 * @param array $new_sizes Associative array of image sizes to be created. 371 * @param array $image_meta The image meta data: width, height, file, sizes, etc. 372 * @param int $attachment_id The attachment post ID for the image. 373 */ 374 $new_sizes = apply_filters( 'intermediate_image_sizes_advanced', $new_sizes, $image_meta, $attachment_id ); 375 376 return _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ); 377 } 378 379 /** 380 * Low-level function to create image sub-sizes. 381 * 382 * Updates the image meta after each sub-size is created. 383 * Errors are stored in the returned image metadata array. 384 * 385 * @since 5.3.0 386 * @access private 387 * 388 * @param array $new_sizes Array defining what sizes to create. 389 * @param string $file Full path to the image file. 390 * @param array $image_meta The attachment meta data array. 391 * @param int $attachment_id Attachment ID to process. 392 * @return array The attachment meta data with updated `sizes` array. Includes an array of errors encountered while resizing. 393 */ 394 function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) { 395 if ( empty( $image_meta ) || ! is_array( $image_meta ) ) { 396 // Not an image attachment. 397 return array(); 398 } 399 400 // Check if any of the new sizes already exist. 401 if ( isset( $image_meta['sizes'] ) && is_array( $image_meta['sizes'] ) ) { 402 foreach ( $image_meta['sizes'] as $size_name => $size_meta ) { 403 /* 404 * Only checks "size name" so we don't override existing images even if the dimensions 405 * don't match the currently defined size with the same name. 406 * To change the behavior, unset changed/mismatched sizes in the `sizes` array in image meta. 407 */ 408 if ( array_key_exists( $size_name, $new_sizes ) ) { 409 unset( $new_sizes[ $size_name ] ); 410 } 411 } 412 } else { 413 $image_meta['sizes'] = array(); 414 } 415 416 if ( empty( $new_sizes ) ) { 417 // Nothing to do... 418 return $image_meta; 419 } 420 421 /* 422 * Sort the image sub-sizes in order of priority when creating them. 423 * This ensures there is an appropriate sub-size the user can access immediately 424 * even when there was an error and not all sub-sizes were created. 425 */ 426 $priority = array( 427 'medium' => null, 428 'large' => null, 429 'thumbnail' => null, 430 'medium_large' => null, 431 ); 432 433 $new_sizes = array_filter( array_merge( $priority, $new_sizes ) ); 434 435 $editor = wp_get_image_editor( $file ); 436 437 if ( is_wp_error( $editor ) ) { 438 // The image cannot be edited. 439 return $image_meta; 440 } 441 442 // If stored EXIF data exists, rotate the source image before creating sub-sizes. 443 if ( ! empty( $image_meta['image_meta'] ) ) { 444 $rotated = $editor->maybe_exif_rotate(); 445 446 if ( is_wp_error( $rotated ) ) { 447 // TODO: Log errors. 448 } 449 } 450 451 if ( method_exists( $editor, 'make_subsize' ) ) { 452 foreach ( $new_sizes as $new_size_name => $new_size_data ) { 453 $new_size_meta = $editor->make_subsize( $new_size_data ); 454 455 if ( is_wp_error( $new_size_meta ) ) { 456 // TODO: Log errors. 457 } else { 458 // Save the size meta value. 459 $image_meta['sizes'][ $new_size_name ] = $new_size_meta; 460 wp_update_attachment_metadata( $attachment_id, $image_meta ); 461 } 462 } 463 } else { 464 // Fall back to `$editor->multi_resize()`. 465 $created_sizes = $editor->multi_resize( $new_sizes ); 466 467 if ( ! empty( $created_sizes ) ) { 468 $image_meta['sizes'] = array_merge( $image_meta['sizes'], $created_sizes ); 469 wp_update_attachment_metadata( $attachment_id, $image_meta ); 470 } 471 } 472 473 return $image_meta; 474 } 475 476 /** 477 * Generate attachment meta data and create image sub-sizes for images. 478 * 479 * @since 2.1.0 480 * 481 * @param int $attachment_id Attachment ID to process. 482 * @param string $file Filepath of the attached image. 483 * @return array Metadata for attachment. 484 */ 485 function wp_generate_attachment_metadata( $attachment_id, $file ) { 486 $attachment = get_post( $attachment_id ); 487 488 $metadata = array(); 489 $support = false; 490 $mime_type = get_post_mime_type( $attachment ); 491 492 if ( preg_match( '!^image/!', $mime_type ) && file_is_displayable_image( $file ) ) { 493 // Make thumbnails and other intermediate sizes. 494 $metadata = wp_create_image_subsizes( $file, $attachment_id ); 495 } elseif ( wp_attachment_is( 'video', $attachment ) ) { 496 $metadata = wp_read_video_metadata( $file ); 497 $support = current_theme_supports( 'post-thumbnails', 'attachment:video' ) || post_type_supports( 'attachment:video', 'thumbnail' ); 498 } elseif ( wp_attachment_is( 'audio', $attachment ) ) { 499 $metadata = wp_read_audio_metadata( $file ); 500 $support = current_theme_supports( 'post-thumbnails', 'attachment:audio' ) || post_type_supports( 'attachment:audio', 'thumbnail' ); 501 } 502 503 /* 504 * wp_read_video_metadata() and wp_read_audio_metadata() return `false` 505 * if the attachment does not exist in the local filesystem, 506 * so make sure to convert the value to an array. 507 */ 508 if ( ! is_array( $metadata ) ) { 509 $metadata = array(); 510 } 511 512 if ( $support && ! empty( $metadata['image']['data'] ) ) { 513 // Check for existing cover. 514 $hash = md5( $metadata['image']['data'] ); 515 $posts = get_posts( 516 array( 517 'fields' => 'ids', 518 'post_type' => 'attachment', 519 'post_mime_type' => $metadata['image']['mime'], 520 'post_status' => 'inherit', 521 'posts_per_page' => 1, 522 'meta_key' => '_cover_hash', 523 'meta_value' => $hash, 524 ) 525 ); 526 $exists = reset( $posts ); 527 528 if ( ! empty( $exists ) ) { 529 update_post_meta( $attachment_id, '_thumbnail_id', $exists ); 530 } else { 531 $ext = '.jpg'; 532 switch ( $metadata['image']['mime'] ) { 533 case 'image/gif': 534 $ext = '.gif'; 535 break; 536 case 'image/png': 537 $ext = '.png'; 538 break; 539 case 'image/webp': 540 $ext = '.webp'; 541 break; 542 } 543 $basename = str_replace( '.', '-', wp_basename( $file ) ) . '-image' . $ext; 544 $uploaded = wp_upload_bits( $basename, '', $metadata['image']['data'] ); 545 if ( false === $uploaded['error'] ) { 546 $image_attachment = array( 547 'post_mime_type' => $metadata['image']['mime'], 548 'post_type' => 'attachment', 549 'post_content' => '', 550 ); 551 /** 552 * Filters the parameters for the attachment thumbnail creation. 553 * 554 * @since 3.9.0 555 * 556 * @param array $image_attachment An array of parameters to create the thumbnail. 557 * @param array $metadata Current attachment metadata. 558 * @param array $uploaded { 559 * Information about the newly-uploaded file. 560 * 561 * @type string $file Filename of the newly-uploaded file. 562 * @type string $url URL of the uploaded file. 563 * @type string $type File type. 564 * } 565 */ 566 $image_attachment = apply_filters( 'attachment_thumbnail_args', $image_attachment, $metadata, $uploaded ); 567 568 $sub_attachment_id = wp_insert_attachment( $image_attachment, $uploaded['file'] ); 569 add_post_meta( $sub_attachment_id, '_cover_hash', $hash ); 570 $attach_data = wp_generate_attachment_metadata( $sub_attachment_id, $uploaded['file'] ); 571 wp_update_attachment_metadata( $sub_attachment_id, $attach_data ); 572 update_post_meta( $attachment_id, '_thumbnail_id', $sub_attachment_id ); 573 } 574 } 575 } elseif ( 'application/pdf' === $mime_type ) { 576 // Try to create image thumbnails for PDFs. 577 578 $fallback_sizes = array( 579 'thumbnail', 580 'medium', 581 'large', 582 ); 583 584 /** 585 * Filters the image sizes generated for non-image mime types. 586 * 587 * @since 4.7.0 588 * 589 * @param string[] $fallback_sizes An array of image size names. 590 * @param array $metadata Current attachment metadata. 591 */ 592 $fallback_sizes = apply_filters( 'fallback_intermediate_image_sizes', $fallback_sizes, $metadata ); 593 594 $registered_sizes = wp_get_registered_image_subsizes(); 595 $merged_sizes = array_intersect_key( $registered_sizes, array_flip( $fallback_sizes ) ); 596 597 // Force thumbnails to be soft crops. 598 if ( isset( $merged_sizes['thumbnail'] ) && is_array( $merged_sizes['thumbnail'] ) ) { 599 $merged_sizes['thumbnail']['crop'] = false; 600 } 601 602 // Only load PDFs in an image editor if we're processing sizes. 603 if ( ! empty( $merged_sizes ) ) { 604 $editor = wp_get_image_editor( $file ); 605 606 if ( ! is_wp_error( $editor ) ) { // No support for this type of file. 607 /* 608 * PDFs may have the same file filename as JPEGs. 609 * Ensure the PDF preview image does not overwrite any JPEG images that already exist. 610 */ 611 $dirname = dirname( $file ) . '/'; 612 $ext = '.' . pathinfo( $file, PATHINFO_EXTENSION ); 613 $preview_file = $dirname . wp_unique_filename( $dirname, wp_basename( $file, $ext ) . '-pdf.jpg' ); 614 615 $uploaded = $editor->save( $preview_file, 'image/jpeg' ); 616 unset( $editor ); 617 618 // Resize based on the full size image, rather than the source. 619 if ( ! is_wp_error( $uploaded ) ) { 620 $image_file = $uploaded['path']; 621 unset( $uploaded['path'] ); 622 623 $metadata['sizes'] = array( 624 'full' => $uploaded, 625 ); 626 627 // Save the meta data before any image post-processing errors could happen. 628 wp_update_attachment_metadata( $attachment_id, $metadata ); 629 630 // Create sub-sizes saving the image meta after each. 631 $metadata = _wp_make_subsizes( $merged_sizes, $image_file, $metadata, $attachment_id ); 632 } 633 } 634 } 635 } 636 637 // Remove the blob of binary data from the array. 638 unset( $metadata['image']['data'] ); 639 640 // Capture file size for cases where it has not been captured yet, such as PDFs. 641 if ( ! isset( $metadata['filesize'] ) && file_exists( $file ) ) { 642 $metadata['filesize'] = wp_filesize( $file ); 643 } 644 645 /** 646 * Filters the generated attachment meta data. 647 * 648 * @since 2.1.0 649 * @since 5.3.0 The `$context` parameter was added. 650 * 651 * @param array $metadata An array of attachment meta data. 652 * @param int $attachment_id Current attachment ID. 653 * @param string $context Additional context. Can be 'create' when metadata was initially created for new attachment 654 * or 'update' when the metadata was updated. 655 */ 656 return apply_filters( 'wp_generate_attachment_metadata', $metadata, $attachment_id, 'create' ); 657 } 658 659 /** 660 * Convert a fraction string to a decimal. 661 * 662 * @since 2.5.0 663 * 664 * @param string $str Fraction string. 665 * @return int|float Returns calculated fraction or integer 0 on invalid input. 666 */ 667 function wp_exif_frac2dec( $str ) { 668 if ( ! is_scalar( $str ) || is_bool( $str ) ) { 669 return 0; 670 } 671 672 if ( ! is_string( $str ) ) { 673 return $str; // This can only be an integer or float, so this is fine. 674 } 675 676 // Fractions passed as a string must contain a single `/`. 677 if ( substr_count( $str, '/' ) !== 1 ) { 678 if ( is_numeric( $str ) ) { 679 return (float) $str; 680 } 681 682 return 0; 683 } 684 685 list( $numerator, $denominator ) = explode( '/', $str ); 686 687 // Both the numerator and the denominator must be numbers. 688 if ( ! is_numeric( $numerator ) || ! is_numeric( $denominator ) ) { 689 return 0; 690 } 691 692 // The denominator must not be zero. 693 if ( 0 == $denominator ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison -- Deliberate loose comparison. 694 return 0; 695 } 696 697 return $numerator / $denominator; 698 } 699 700 /** 701 * Convert the exif date format to a unix timestamp. 702 * 703 * @since 2.5.0 704 * 705 * @param string $str A date string expected to be in Exif format (Y:m:d H:i:s). 706 * @return int|false The unix timestamp, or false on failure. 707 */ 708 function wp_exif_date2ts( $str ) { 709 list( $date, $time ) = explode( ' ', trim( $str ) ); 710 list( $y, $m, $d ) = explode( ':', $date ); 711 712 return strtotime( "{$y}-{$m}-{$d} {$time}" ); 713 } 714 715 /** 716 * Get extended image metadata, exif or iptc as available. 717 * 718 * Retrieves the EXIF metadata aperture, credit, camera, caption, copyright, iso 719 * created_timestamp, focal_length, shutter_speed, and title. 720 * 721 * The IPTC metadata that is retrieved is APP13, credit, byline, created date 722 * and time, caption, copyright, and title. Also includes FNumber, Model, 723 * DateTimeDigitized, FocalLength, ISOSpeedRatings, and ExposureTime. 724 * 725 * @todo Try other exif libraries if available. 726 * @since 2.5.0 727 * 728 * @param string $file 729 * @return array|false Image metadata array on success, false on failure. 730 */ 731 function wp_read_image_metadata( $file ) { 732 if ( ! file_exists( $file ) ) { 733 return false; 734 } 735 736 list( , , $image_type ) = wp_getimagesize( $file ); 737 738 /* 739 * EXIF contains a bunch of data we'll probably never need formatted in ways 740 * that are difficult to use. We'll normalize it and just extract the fields 741 * that are likely to be useful. Fractions and numbers are converted to 742 * floats, dates to unix timestamps, and everything else to strings. 743 */ 744 $meta = array( 745 'aperture' => 0, 746 'credit' => '', 747 'camera' => '', 748 'caption' => '', 749 'created_timestamp' => 0, 750 'copyright' => '', 751 'focal_length' => 0, 752 'iso' => 0, 753 'shutter_speed' => 0, 754 'title' => '', 755 'orientation' => 0, 756 'keywords' => array(), 757 ); 758 759 $iptc = array(); 760 $info = array(); 761 /* 762 * Read IPTC first, since it might contain data not available in exif such 763 * as caption, description etc. 764 */ 765 if ( is_callable( 'iptcparse' ) ) { 766 wp_getimagesize( $file, $info ); 767 768 if ( ! empty( $info['APP13'] ) ) { 769 // Don't silence errors when in debug mode, unless running unit tests. 770 if ( defined( 'WP_DEBUG' ) && WP_DEBUG 771 && ! defined( 'WP_RUN_CORE_TESTS' ) 772 ) { 773 $iptc = iptcparse( $info['APP13'] ); 774 } else { 775 // phpcs:ignore WordPress.PHP.NoSilencedErrors -- Silencing notice and warning is intentional. See https://core.trac.wordpress.org/ticket/42480 776 $iptc = @iptcparse( $info['APP13'] ); 777 } 778 779 if ( ! is_array( $iptc ) ) { 780 $iptc = array(); 781 } 782 783 // Headline, "A brief synopsis of the caption". 784 if ( ! empty( $iptc['2#105'][0] ) ) { 785 $meta['title'] = trim( $iptc['2#105'][0] ); 786 /* 787 * Title, "Many use the Title field to store the filename of the image, 788 * though the field may be used in many ways". 789 */ 790 } elseif ( ! empty( $iptc['2#005'][0] ) ) { 791 $meta['title'] = trim( $iptc['2#005'][0] ); 792 } 793 794 if ( ! empty( $iptc['2#120'][0] ) ) { // Description / legacy caption. 795 $caption = trim( $iptc['2#120'][0] ); 796 797 mbstring_binary_safe_encoding(); 798 $caption_length = strlen( $caption ); 799 reset_mbstring_encoding(); 800 801 if ( empty( $meta['title'] ) && $caption_length < 80 ) { 802 // Assume the title is stored in 2:120 if it's short. 803 $meta['title'] = $caption; 804 } 805 806 $meta['caption'] = $caption; 807 } 808 809 if ( ! empty( $iptc['2#110'][0] ) ) { // Credit. 810 $meta['credit'] = trim( $iptc['2#110'][0] ); 811 } elseif ( ! empty( $iptc['2#080'][0] ) ) { // Creator / legacy byline. 812 $meta['credit'] = trim( $iptc['2#080'][0] ); 813 } 814 815 if ( ! empty( $iptc['2#055'][0] ) && ! empty( $iptc['2#060'][0] ) ) { // Created date and time. 816 $meta['created_timestamp'] = strtotime( $iptc['2#055'][0] . ' ' . $iptc['2#060'][0] ); 817 } 818 819 if ( ! empty( $iptc['2#116'][0] ) ) { // Copyright. 820 $meta['copyright'] = trim( $iptc['2#116'][0] ); 821 } 822 823 if ( ! empty( $iptc['2#025'][0] ) ) { // Keywords array. 824 $meta['keywords'] = array_values( $iptc['2#025'] ); 825 } 826 } 827 } 828 829 $exif = array(); 830 831 /** 832 * Filters the image types to check for exif data. 833 * 834 * @since 2.5.0 835 * 836 * @param int[] $image_types Array of image types to check for exif data. Each value 837 * is usually one of the `IMAGETYPE_*` constants. 838 */ 839 $exif_image_types = apply_filters( 'wp_read_image_metadata_types', array( IMAGETYPE_JPEG, IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM ) ); 840 841 if ( is_callable( 'exif_read_data' ) && in_array( $image_type, $exif_image_types, true ) ) { 842 // Don't silence errors when in debug mode, unless running unit tests. 843 if ( defined( 'WP_DEBUG' ) && WP_DEBUG 844 && ! defined( 'WP_RUN_CORE_TESTS' ) 845 ) { 846 $exif = exif_read_data( $file ); 847 } else { 848 // phpcs:ignore WordPress.PHP.NoSilencedErrors -- Silencing notice and warning is intentional. See https://core.trac.wordpress.org/ticket/42480 849 $exif = @exif_read_data( $file ); 850 } 851 852 if ( ! is_array( $exif ) ) { 853 $exif = array(); 854 } 855 856 if ( ! empty( $exif['ImageDescription'] ) ) { 857 mbstring_binary_safe_encoding(); 858 $description_length = strlen( $exif['ImageDescription'] ); 859 reset_mbstring_encoding(); 860 861 if ( empty( $meta['title'] ) && $description_length < 80 ) { 862 // Assume the title is stored in ImageDescription. 863 $meta['title'] = trim( $exif['ImageDescription'] ); 864 } 865 866 if ( empty( $meta['caption'] ) && ! empty( $exif['COMPUTED']['UserComment'] ) ) { 867 $meta['caption'] = trim( $exif['COMPUTED']['UserComment'] ); 868 } 869 870 if ( empty( $meta['caption'] ) ) { 871 $meta['caption'] = trim( $exif['ImageDescription'] ); 872 } 873 } elseif ( empty( $meta['caption'] ) && ! empty( $exif['Comments'] ) ) { 874 $meta['caption'] = trim( $exif['Comments'] ); 875 } 876 877 if ( empty( $meta['credit'] ) ) { 878 if ( ! empty( $exif['Artist'] ) ) { 879 $meta['credit'] = trim( $exif['Artist'] ); 880 } elseif ( ! empty( $exif['Author'] ) ) { 881 $meta['credit'] = trim( $exif['Author'] ); 882 } 883 } 884 885 if ( empty( $meta['copyright'] ) && ! empty( $exif['Copyright'] ) ) { 886 $meta['copyright'] = trim( $exif['Copyright'] ); 887 } 888 if ( ! empty( $exif['FNumber'] ) && is_scalar( $exif['FNumber'] ) ) { 889 $meta['aperture'] = round( wp_exif_frac2dec( $exif['FNumber'] ), 2 ); 890 } 891 if ( ! empty( $exif['Model'] ) ) { 892 $meta['camera'] = trim( $exif['Model'] ); 893 } 894 if ( empty( $meta['created_timestamp'] ) && ! empty( $exif['DateTimeDigitized'] ) ) { 895 $meta['created_timestamp'] = wp_exif_date2ts( $exif['DateTimeDigitized'] ); 896 } 897 if ( ! empty( $exif['FocalLength'] ) ) { 898 $meta['focal_length'] = (string) $exif['FocalLength']; 899 if ( is_scalar( $exif['FocalLength'] ) ) { 900 $meta['focal_length'] = (string) wp_exif_frac2dec( $exif['FocalLength'] ); 901 } 902 } 903 if ( ! empty( $exif['ISOSpeedRatings'] ) ) { 904 $meta['iso'] = is_array( $exif['ISOSpeedRatings'] ) ? reset( $exif['ISOSpeedRatings'] ) : $exif['ISOSpeedRatings']; 905 $meta['iso'] = trim( $meta['iso'] ); 906 } 907 if ( ! empty( $exif['ExposureTime'] ) ) { 908 $meta['shutter_speed'] = (string) $exif['ExposureTime']; 909 if ( is_scalar( $exif['ExposureTime'] ) ) { 910 $meta['shutter_speed'] = (string) wp_exif_frac2dec( $exif['ExposureTime'] ); 911 } 912 } 913 if ( ! empty( $exif['Orientation'] ) ) { 914 $meta['orientation'] = $exif['Orientation']; 915 } 916 } 917 918 foreach ( array( 'title', 'caption', 'credit', 'copyright', 'camera', 'iso' ) as $key ) { 919 if ( $meta[ $key ] && ! seems_utf8( $meta[ $key ] ) ) { 920 $meta[ $key ] = utf8_encode( $meta[ $key ] ); 921 } 922 } 923 924 foreach ( $meta['keywords'] as $key => $keyword ) { 925 if ( ! seems_utf8( $keyword ) ) { 926 $meta['keywords'][ $key ] = utf8_encode( $keyword ); 927 } 928 } 929 930 $meta = wp_kses_post_deep( $meta ); 931 932 /** 933 * Filters the array of meta data read from an image's exif data. 934 * 935 * @since 2.5.0 936 * @since 4.4.0 The `$iptc` parameter was added. 937 * @since 5.0.0 The `$exif` parameter was added. 938 * 939 * @param array $meta Image meta data. 940 * @param string $file Path to image file. 941 * @param int $image_type Type of image, one of the `IMAGETYPE_XXX` constants. 942 * @param array $iptc IPTC data. 943 * @param array $exif EXIF data. 944 */ 945 return apply_filters( 'wp_read_image_metadata', $meta, $file, $image_type, $iptc, $exif ); 946 947 } 948 949 /** 950 * Validate that file is an image. 951 * 952 * @since 2.5.0 953 * 954 * @param string $path File path to test if valid image. 955 * @return bool True if valid image, false if not valid image. 956 */ 957 function file_is_valid_image( $path ) { 958 $size = wp_getimagesize( $path ); 959 return ! empty( $size ); 960 } 961 962 /** 963 * Validate that file is suitable for displaying within a web page. 964 * 965 * @since 2.5.0 966 * 967 * @param string $path File path to test. 968 * @return bool True if suitable, false if not suitable. 969 */ 970 function file_is_displayable_image( $path ) { 971 $displayable_image_types = array( IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_BMP, IMAGETYPE_ICO, IMAGETYPE_WEBP ); 972 973 $info = wp_getimagesize( $path ); 974 if ( empty( $info ) ) { 975 $result = false; 976 } elseif ( ! in_array( $info[2], $displayable_image_types, true ) ) { 977 $result = false; 978 } else { 979 $result = true; 980 } 981 982 /** 983 * Filters whether the current image is displayable in the browser. 984 * 985 * @since 2.5.0 986 * 987 * @param bool $result Whether the image can be displayed. Default true. 988 * @param string $path Path to the image. 989 */ 990 return apply_filters( 'file_is_displayable_image', $result, $path ); 991 } 992 993 /** 994 * Load an image resource for editing. 995 * 996 * @since 2.9.0 997 * 998 * @param int $attachment_id Attachment ID. 999 * @param string $mime_type Image mime type. 1000 * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array 1001 * of width and height values in pixels (in that order). Default 'full'. 1002 * @return resource|GdImage|false The resulting image resource or GdImage instance on success, 1003 * false on failure. 1004 */ 1005 function load_image_to_edit( $attachment_id, $mime_type, $size = 'full' ) { 1006 $filepath = _load_image_to_edit_path( $attachment_id, $size ); 1007 if ( empty( $filepath ) ) { 1008 return false; 1009 } 1010 1011 switch ( $mime_type ) { 1012 case 'image/jpeg': 1013 $image = imagecreatefromjpeg( $filepath ); 1014 break; 1015 case 'image/png': 1016 $image = imagecreatefrompng( $filepath ); 1017 break; 1018 case 'image/gif': 1019 $image = imagecreatefromgif( $filepath ); 1020 break; 1021 case 'image/webp': 1022 $image = false; 1023 if ( function_exists( 'imagecreatefromwebp' ) ) { 1024 $image = imagecreatefromwebp( $filepath ); 1025 } 1026 break; 1027 default: 1028 $image = false; 1029 break; 1030 } 1031 1032 if ( is_gd_image( $image ) ) { 1033 /** 1034 * Filters the current image being loaded for editing. 1035 * 1036 * @since 2.9.0 1037 * 1038 * @param resource|GdImage $image Current image. 1039 * @param int $attachment_id Attachment ID. 1040 * @param string|int[] $size Requested image size. Can be any registered image size name, or 1041 * an array of width and height values in pixels (in that order). 1042 */ 1043 $image = apply_filters( 'load_image_to_edit', $image, $attachment_id, $size ); 1044 1045 if ( function_exists( 'imagealphablending' ) && function_exists( 'imagesavealpha' ) ) { 1046 imagealphablending( $image, false ); 1047 imagesavealpha( $image, true ); 1048 } 1049 } 1050 1051 return $image; 1052 } 1053 1054 /** 1055 * Retrieve the path or URL of an attachment's attached file. 1056 * 1057 * If the attached file is not present on the local filesystem (usually due to replication plugins), 1058 * then the URL of the file is returned if `allow_url_fopen` is supported. 1059 * 1060 * @since 3.4.0 1061 * @access private 1062 * 1063 * @param int $attachment_id Attachment ID. 1064 * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array 1065 * of width and height values in pixels (in that order). Default 'full'. 1066 * @return string|false File path or URL on success, false on failure. 1067 */ 1068 function _load_image_to_edit_path( $attachment_id, $size = 'full' ) { 1069 $filepath = get_attached_file( $attachment_id ); 1070 1071 if ( $filepath && file_exists( $filepath ) ) { 1072 if ( 'full' !== $size ) { 1073 $data = image_get_intermediate_size( $attachment_id, $size ); 1074 1075 if ( $data ) { 1076 $filepath = path_join( dirname( $filepath ), $data['file'] ); 1077 1078 /** 1079 * Filters the path to an attachment's file when editing the image. 1080 * 1081 * The filter is evaluated for all image sizes except 'full'. 1082 * 1083 * @since 3.1.0 1084 * 1085 * @param string $path Path to the current image. 1086 * @param int $attachment_id Attachment ID. 1087 * @param string|int[] $size Requested image size. Can be any registered image size name, or 1088 * an array of width and height values in pixels (in that order). 1089 */ 1090 $filepath = apply_filters( 'load_image_to_edit_filesystempath', $filepath, $attachment_id, $size ); 1091 } 1092 } 1093 } elseif ( function_exists( 'fopen' ) && ini_get( 'allow_url_fopen' ) ) { 1094 /** 1095 * Filters the path to an attachment's URL when editing the image. 1096 * 1097 * The filter is only evaluated if the file isn't stored locally and `allow_url_fopen` is enabled on the server. 1098 * 1099 * @since 3.1.0 1100 * 1101 * @param string|false $image_url Current image URL. 1102 * @param int $attachment_id Attachment ID. 1103 * @param string|int[] $size Requested image size. Can be any registered image size name, or 1104 * an array of width and height values in pixels (in that order). 1105 */ 1106 $filepath = apply_filters( 'load_image_to_edit_attachmenturl', wp_get_attachment_url( $attachment_id ), $attachment_id, $size ); 1107 } 1108 1109 /** 1110 * Filters the returned path or URL of the current image. 1111 * 1112 * @since 2.9.0 1113 * 1114 * @param string|false $filepath File path or URL to current image, or false. 1115 * @param int $attachment_id Attachment ID. 1116 * @param string|int[] $size Requested image size. Can be any registered image size name, or 1117 * an array of width and height values in pixels (in that order). 1118 */ 1119 return apply_filters( 'load_image_to_edit_path', $filepath, $attachment_id, $size ); 1120 } 1121 1122 /** 1123 * Copy an existing image file. 1124 * 1125 * @since 3.4.0 1126 * @access private 1127 * 1128 * @param int $attachment_id Attachment ID. 1129 * @return string|false New file path on success, false on failure. 1130 */ 1131 function _copy_image_file( $attachment_id ) { 1132 $dst_file = get_attached_file( $attachment_id ); 1133 $src_file = $dst_file; 1134 1135 if ( ! file_exists( $src_file ) ) { 1136 $src_file = _load_image_to_edit_path( $attachment_id ); 1137 } 1138 1139 if ( $src_file ) { 1140 $dst_file = str_replace( wp_basename( $dst_file ), 'copy-' . wp_basename( $dst_file ), $dst_file ); 1141 $dst_file = dirname( $dst_file ) . '/' . wp_unique_filename( dirname( $dst_file ), wp_basename( $dst_file ) ); 1142 1143 /* 1144 * The directory containing the original file may no longer 1145 * exist when using a replication plugin. 1146 */ 1147 wp_mkdir_p( dirname( $dst_file ) ); 1148 1149 if ( ! copy( $src_file, $dst_file ) ) { 1150 $dst_file = false; 1151 } 1152 } else { 1153 $dst_file = false; 1154 } 1155 1156 return $dst_file; 1157 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Nov 21 01:00:03 2024 | Cross-referenced by PHPXref 0.7.1 |