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