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