Ticket #51228: 51228.diff
File 51228.diff, 58.0 KB (added by , 13 months ago) |
---|
-
phpcs.xml.dist
diff --git a/phpcs.xml.dist b/phpcs.xml.dist index ccb0430321..20f3a20539 100644
a b 59 59 <exclude-pattern>/src/wp-includes/class-requests\.php</exclude-pattern> 60 60 <exclude-pattern>/src/wp-includes/class-simplepie\.php</exclude-pattern> 61 61 <exclude-pattern>/src/wp-includes/class-snoopy\.php</exclude-pattern> 62 <exclude-pattern>/src/wp-includes/class-wp-block-parser\.php</exclude-pattern> 63 <exclude-pattern>/src/wp-includes/class-avif-info\.php</exclude-pattern> 62 64 <exclude-pattern>/src/wp-includes/deprecated\.php</exclude-pattern> 63 65 <exclude-pattern>/src/wp-includes/ms-deprecated\.php</exclude-pattern> 64 66 <exclude-pattern>/src/wp-includes/pluggable-deprecated\.php</exclude-pattern> -
src/js/_enqueues/vendor/plupload/handlers.js
diff --git a/src/js/_enqueues/vendor/plupload/handlers.js b/src/js/_enqueues/vendor/plupload/handlers.js index b82a6e8847..71e248fb5e 100644
a b jQuery( document ).ready( function( $ ) { 608 608 wpQueueError( pluploadL10n.noneditable_image ); 609 609 up.removeFile( file ); 610 610 return; 611 } else if ( file.type === 'image/avif' && up.settings.avif_upload_error ) { 612 // Disallow uploading of AVIF images if the server cannot edit them. 613 wpQueueError( pluploadL10n.noneditable_image ); 614 up.removeFile( file ); 615 return; 611 616 } 612 617 613 618 fileQueued( file ); -
src/js/_enqueues/vendor/plupload/wp-plupload.js
diff --git a/src/js/_enqueues/vendor/plupload/wp-plupload.js b/src/js/_enqueues/vendor/plupload/wp-plupload.js index 0fdebf77d1..c0eb570657 100644
a b window.wp = window.wp || {}; 363 363 error( pluploadL10n.noneditable_image, {}, file, 'no-retry' ); 364 364 up.removeFile( file ); 365 365 return; 366 } else if ( file.type === 'image/avif' && up.settings.avif_upload_error ) { 367 // Disallow uploading of AVIF images if the server cannot edit them. 368 error( pluploadL10n.noneditable_image, {}, file, 'no-retry' ); 369 up.removeFile( file ); 370 return; 366 371 } 367 372 368 373 // Generate attributes for a new `Attachment` model. -
src/js/_enqueues/vendor/thickbox/thickbox.js
diff --git a/src/js/_enqueues/vendor/thickbox/thickbox.js b/src/js/_enqueues/vendor/thickbox/thickbox.js index 5470467a1e..e8b95677c1 100644
a b function tb_show(caption, url, imageGroup) {//function called when the user clic 76 76 baseURL = url; 77 77 } 78 78 79 var urlString = /\.jpg$|\.jpeg$|\.png$|\.gif$|\.bmp$|\.webp$ /;79 var urlString = /\.jpg$|\.jpeg$|\.png$|\.gif$|\.bmp$|\.webp$|\.avif$/; 80 80 var urlType = baseURL.toLowerCase().match(urlString); 81 81 82 82 if(urlType == '.jpg' || … … function tb_show(caption, url, imageGroup) {//function called when the user clic 84 84 urlType == '.png' || 85 85 urlType == '.gif' || 86 86 urlType == '.bmp' || 87 urlType == '.webp' 87 urlType == '.webp' || 88 urlType == '.avif' 88 89 ){//code to show images 89 90 90 91 TB_PrevCaption = ""; -
src/js/media/controllers/library.js
diff --git a/src/js/media/controllers/library.js b/src/js/media/controllers/library.js index 2acc89a586..126ce8d783 100644
a b Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Librar 196 196 isImageAttachment: function( attachment ) { 197 197 // If uploading, we know the filename but not the mime type. 198 198 if ( attachment.get('uploading') ) { 199 return /\.(jpe?g|png|gif|webp )$/i.test( attachment.get('filename') );199 return /\.(jpe?g|png|gif|webp|avif)$/i.test( attachment.get('filename') ); 200 200 } 201 201 202 202 return attachment.get('type') === 'image'; -
src/wp-admin/includes/image-edit.php
diff --git a/src/wp-admin/includes/image-edit.php b/src/wp-admin/includes/image-edit.php index 739b09f9a1..2d150e691c 100644
a b function wp_stream_image( $image, $mime_type, $attachment_id ) { 390 390 return imagewebp( $image, null, 90 ); 391 391 } 392 392 return false; 393 case 'image/avif': 394 if ( function_exists( 'imageavif' ) ) { 395 header( 'Content-Type: image/avif' ); 396 return imageavif( $image, null, 90 ); 397 } 398 return false; 393 399 default: 394 400 return false; 395 401 } … … function wp_save_image_file( $filename, $image, $mime_type, $post_id ) { 494 500 return imagewebp( $image, $filename ); 495 501 } 496 502 return false; 503 case 'image/avif': 504 if ( function_exists( 'imageavif' ) ) { 505 return imageavif( $image, $filename ); 506 } 507 return false; 497 508 default: 498 509 return false; 499 510 } -
src/wp-admin/includes/image.php
diff --git a/src/wp-admin/includes/image.php b/src/wp-admin/includes/image.php index d60ec8508b..0f4ba818e6 100644
a b function file_is_valid_image( $path ) { 1006 1006 * @return bool True if suitable, false if not suitable. 1007 1007 */ 1008 1008 function file_is_displayable_image( $path ) { 1009 $displayable_image_types = array( IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_BMP, IMAGETYPE_ICO, IMAGETYPE_WEBP );1009 $displayable_image_types = array( IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_BMP, IMAGETYPE_ICO, IMAGETYPE_WEBP, IMAGETYPE_AVIF ); 1010 1010 1011 1011 $info = wp_getimagesize( $path ); 1012 1012 if ( empty( $info ) ) { -
src/wp-admin/includes/media.php
diff --git a/src/wp-admin/includes/media.php b/src/wp-admin/includes/media.php index 57df2bcbff..193e67f7dd 100644
a b function media_upload_form( $errors = null ) { 2198 2198 $plupload_init['webp_upload_error'] = true; 2199 2199 } 2200 2200 2201 // Check if AVIF images can be edited. 2202 if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/avif' ) ) ) { 2203 $plupload_init['avif_upload_error'] = true; 2204 } 2205 2201 2206 /** 2202 2207 * Filters the default Plupload settings. 2203 2208 * -
src/wp-admin/includes/schema.php
diff --git a/src/wp-admin/includes/schema.php b/src/wp-admin/includes/schema.php index d339af3b17..871caaa03b 100644
a b We hope you enjoy your new site. Thanks! 1252 1252 'png', 1253 1253 'gif', 1254 1254 'webp', 1255 'avif', 1255 1256 // Video. 1256 1257 'mov', 1257 1258 'avi', -
new file src/wp-includes/class-avif-info.php
diff --git a/src/wp-includes/class-avif-info.php b/src/wp-includes/class-avif-info.php new file mode 100644 index 0000000000..72405c326c
- + 1 <?php 2 /** 3 * Copyright (c) 2021, Alliance for Open Media. All rights reserved 4 * 5 * This source code is subject to the terms of the BSD 2 Clause License and 6 * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License 7 * was not distributed with this source code in the LICENSE file, you can 8 * obtain it at www.aomedia.org/license/software. If the Alliance for Open 9 * Media Patent License 1.0 was not distributed with this source code in the 10 * PATENTS file, you can obtain it at www.aomedia.org/license/patent. 11 * 12 * Note: this class is from libavifinfo - https://aomedia.googlesource.com/libavifinfo/+/refs/heads/main/avifinfo.php at b496868. 13 */ 14 15 namespace Avifinfo; 16 17 const FOUND = 0; // Input correctly parsed and information retrieved. 18 const NOT_FOUND = 1; // Input correctly parsed but information is missing or elsewhere. 19 const TRUNCATED = 2; // Input correctly parsed until missing bytes to continue. 20 const ABORTED = 3; // Input correctly parsed until stopped to avoid timeout or crash. 21 const INVALID = 4; // Input incorrectly parsed. 22 23 const MAX_SIZE = 4294967295; // Unlikely to be insufficient to parse AVIF headers. 24 const MAX_NUM_BOXES = 4096; // Be reasonable. Avoid timeouts and out-of-memory. 25 const MAX_VALUE = 255; 26 const MAX_TILES = 16; 27 const MAX_PROPS = 32; 28 const MAX_FEATURES = 8; 29 const UNDEFINED = 0; // Value was not yet parsed. 30 31 /** 32 * Reads an unsigned integer with most significant bits first. 33 * 34 * @param binary string $input Must be at least $num_bytes-long. 35 * @param int $num_bytes Number of parsed bytes. 36 * @return int Value. 37 */ 38 function read_big_endian( $input, $num_bytes ) { 39 if ( $num_bytes == 1 ) { 40 return unpack( 'C', $input ) [1]; 41 } else if ( $num_bytes == 2 ) { 42 return unpack( 'n', $input ) [1]; 43 } else if ( $num_bytes == 3 ) { 44 $bytes = unpack( 'C3', $input ); 45 return ( $bytes[1] << 16 ) | ( $bytes[2] << 8 ) | $bytes[3]; 46 } else { // $num_bytes is 4 47 // This might fail to read unsigned values >= 2^31 on 32-bit systems. 48 // See https://www.php.net/manual/en/function.unpack.php#106041 49 return unpack( 'N', $input ) [1]; 50 } 51 } 52 53 /** 54 * Reads bytes and advances the stream position by the same count. 55 * 56 * @param stream $handle Bytes will be read from this resource. 57 * @param int $num_bytes Number of bytes read. Must be greater than 0. 58 * @return binary string|false The raw bytes or false on failure. 59 */ 60 function read( $handle, $num_bytes ) { 61 $data = fread( $handle, $num_bytes ); 62 return ( $data !== false && strlen( $data ) >= $num_bytes ) ? $data : false; 63 } 64 65 /** 66 * Advances the stream position by the given offset. 67 * 68 * @param stream $handle Bytes will be skipped from this resource. 69 * @param int $num_bytes Number of skipped bytes. Can be 0. 70 * @return bool True on success or false on failure. 71 */ 72 // Skips 'num_bytes' from the 'stream'. 'num_bytes' can be zero. 73 function skip( $handle, $num_bytes ) { 74 return ( fseek( $handle, $num_bytes, SEEK_CUR ) == 0 ); 75 } 76 77 //------------------------------------------------------------------------------ 78 // Features are parsed into temporary property associations. 79 80 class Tile { // Tile item id <-> parent item id associations. 81 public $tile_item_id; 82 public $parent_item_id; 83 } 84 85 class Prop { // Property index <-> item id associations. 86 public $property_index; 87 public $item_id; 88 } 89 90 class Dim_Prop { // Property <-> features associations. 91 public $property_index; 92 public $width; 93 public $height; 94 } 95 96 class Chan_Prop { // Property <-> features associations. 97 public $property_index; 98 public $bit_depth; 99 public $num_channels; 100 } 101 102 class Features { 103 public $has_primary_item = false; // True if "pitm" was parsed. 104 public $has_alpha = false; // True if an alpha "auxC" was parsed. 105 public $primary_item_id; 106 public $primary_item_features = array( // Deduced from the data below. 107 'width' => UNDEFINED, // In number of pixels. 108 'height' => UNDEFINED, // Ignores mirror and rotation. 109 'bit_depth' => UNDEFINED, // Likely 8, 10 or 12 bits per channel per pixel. 110 'num_channels' => UNDEFINED // Likely 1, 2, 3 or 4 channels: 111 // (1 monochrome or 3 colors) + (0 or 1 alpha) 112 ); 113 114 public $tiles = array(); // Tile[] 115 public $props = array(); // Prop[] 116 public $dim_props = array(); // Dim_Prop[] 117 public $chan_props = array(); // Chan_Prop[] 118 119 /** 120 * Binds the width, height, bit depth and number of channels from stored internal features. 121 * 122 * @param int $target_item_id Id of the item whose features will be bound. 123 * @param int $tile_depth Maximum recursion to search within tile-parent relations. 124 * @return Status FOUND on success or NOT_FOUND on failure. 125 */ 126 private function get_item_features( $target_item_id, $tile_depth ) { 127 foreach ( $this->props as $prop ) { 128 if ( $prop->item_id != $target_item_id ) { 129 continue; 130 } 131 132 // Retrieve the width and height of the primary item if not already done. 133 if ( $target_item_id == $this->primary_item_id && 134 ( $this->primary_item_features['width'] == UNDEFINED || 135 $this->primary_item_features['height'] == UNDEFINED ) ) { 136 foreach ( $this->dim_props as $dim_prop ) { 137 if ( $dim_prop->property_index != $prop->property_index ) { 138 continue; 139 } 140 $this->primary_item_features['width'] = $dim_prop->width; 141 $this->primary_item_features['height'] = $dim_prop->height; 142 if ( $this->primary_item_features['bit_depth'] != UNDEFINED && 143 $this->primary_item_features['num_channels'] != UNDEFINED ) { 144 return FOUND; 145 } 146 break; 147 } 148 } 149 // Retrieve the bit depth and number of channels of the target item if not 150 // already done. 151 if ( $this->primary_item_features['bit_depth'] == UNDEFINED || 152 $this->primary_item_features['num_channels'] == UNDEFINED ) { 153 foreach ( $this->chan_props as $chan_prop ) { 154 if ( $chan_prop->property_index != $prop->property_index ) { 155 continue; 156 } 157 $this->primary_item_features['bit_depth'] = $chan_prop->bit_depth; 158 $this->primary_item_features['num_channels'] = $chan_prop->num_channels; 159 if ( $this->primary_item_features['width'] != UNDEFINED && 160 $this->primary_item_features['height'] != UNDEFINED ) { 161 return FOUND; 162 } 163 break; 164 } 165 } 166 } 167 168 // Check for the bit_depth and num_channels in a tile if not yet found. 169 if ( $tile_depth < 3 ) { 170 foreach ( $this->tiles as $tile ) { 171 if ( $tile->parent_item_id != $target_item_id ) { 172 continue; 173 } 174 $status = get_item_features( $tile->tile_item_id, $tile_depth + 1 ); 175 if ( $status != NOT_FOUND ) { 176 return $status; 177 } 178 } 179 } 180 return NOT_FOUND; 181 } 182 183 /** 184 * Finds the width, height, bit depth and number of channels of the primary item. 185 * 186 * @return Status FOUND on success or NOT_FOUND on failure. 187 */ 188 public function get_primary_item_features() { 189 // Nothing to do without the primary item ID. 190 if ( !$this->has_primary_item ) { 191 return NOT_FOUND; 192 } 193 // Early exit. 194 if ( empty( $this->dim_props ) || empty( $this->chan_props ) ) { 195 return NOT_FOUND; 196 } 197 $status = $this->get_item_features( $this->primary_item_id, /*tile_depth=*/ 0 ); 198 if ( $status != FOUND ) { 199 return $status; 200 } 201 202 // "auxC" is parsed before the "ipma" properties so it is known now, if any. 203 if ( $this->has_alpha ) { 204 ++$this->primary_item_features['num_channels']; 205 } 206 return FOUND; 207 } 208 } 209 210 //------------------------------------------------------------------------------ 211 212 class Box { 213 public $size; // In bytes. 214 public $type; // Four characters. 215 public $version; // 0 or actual version if this is a full box. 216 public $flags; // 0 or actual value if this is a full box. 217 public $content_size; // 'size' minus the header size. 218 219 /** 220 * Reads the box header. 221 * 222 * @param stream $handle The resource the header will be parsed from. 223 * @param int $num_parsed_boxes The total number of parsed boxes. Prevents timeouts. 224 * @param int $num_remaining_bytes The number of bytes that should be available from the resource. 225 * @return Status FOUND on success or an error on failure. 226 */ 227 public function parse( $handle, &$num_parsed_boxes, $num_remaining_bytes = MAX_SIZE ) { 228 // See ISO/IEC 14496-12:2012(E) 4.2 229 $header_size = 8; // box 32b size + 32b type (at least) 230 if ( $header_size > $num_remaining_bytes ) { 231 return INVALID; 232 } 233 if ( !( $data = read( $handle, 8 ) ) ) { 234 return TRUNCATED; 235 } 236 $this->size = read_big_endian( $data, 4 ); 237 $this->type = substr( $data, 4, 4 ); 238 // 'box->size==1' means 64-bit size should be read after the box type. 239 // 'box->size==0' means this box extends to all remaining bytes. 240 if ( $this->size == 1 ) { 241 $header_size += 8; 242 if ( $header_size > $num_remaining_bytes ) { 243 return INVALID; 244 } 245 if ( !( $data = read( $handle, 8 ) ) ) { 246 return TRUNCATED; 247 } 248 // Stop the parsing if any box has a size greater than 4GB. 249 if ( read_big_endian( $data, 4 ) != 0 ) { 250 return ABORTED; 251 } 252 // Read the 32 least-significant bits. 253 $this->size = read_big_endian( substr( $data, 4, 4 ), 4 ); 254 } else if ( $this->size == 0 ) { 255 $this->size = $num_remaining_bytes; 256 } 257 if ( $this->size < $header_size ) { 258 return INVALID; 259 } 260 if ( $this->size > $num_remaining_bytes ) { 261 return INVALID; 262 } 263 264 $has_fullbox_header = $this->type == 'meta' || $this->type == 'pitm' || 265 $this->type == 'ipma' || $this->type == 'ispe' || 266 $this->type == 'pixi' || $this->type == 'iref' || 267 $this->type == 'auxC'; 268 if ( $has_fullbox_header ) { 269 $header_size += 4; 270 } 271 if ( $this->size < $header_size ) { 272 return INVALID; 273 } 274 $this->content_size = $this->size - $header_size; 275 // Avoid timeouts. The maximum number of parsed boxes is arbitrary. 276 ++$num_parsed_boxes; 277 if ( $num_parsed_boxes >= MAX_NUM_BOXES ) { 278 return ABORTED; 279 } 280 281 $this->version = 0; 282 $this->flags = 0; 283 if ( $has_fullbox_header ) { 284 if ( !( $data = read( $handle, 4 ) ) ) { 285 return TRUNCATED; 286 } 287 $this->version = read_big_endian( $data, 1 ); 288 $this->flags = read_big_endian( substr( $data, 1, 3 ), 3 ); 289 // See AV1 Image File Format (AVIF) 8.1 290 // at https://aomediacodec.github.io/av1-avif/#avif-boxes (available when 291 // https://github.com/AOMediaCodec/av1-avif/pull/170 is merged). 292 $is_parsable = ( $this->type == 'meta' && $this->version <= 0 ) || 293 ( $this->type == 'pitm' && $this->version <= 1 ) || 294 ( $this->type == 'ipma' && $this->version <= 1 ) || 295 ( $this->type == 'ispe' && $this->version <= 0 ) || 296 ( $this->type == 'pixi' && $this->version <= 0 ) || 297 ( $this->type == 'iref' && $this->version <= 1 ) || 298 ( $this->type == 'auxC' && $this->version <= 0 ); 299 // Instead of considering this file as invalid, skip unparsable boxes. 300 if ( !$is_parsable ) { 301 $this->type = 'unknownversion'; 302 } 303 } 304 // print_r( $this ); // Uncomment to print all boxes. 305 return FOUND; 306 } 307 } 308 309 //------------------------------------------------------------------------------ 310 311 class Parser { 312 private $handle; // Input stream. 313 private $num_parsed_boxes = 0; 314 private $data_was_skipped = false; 315 public $features; 316 317 function __construct( $handle ) { 318 $this->handle = $handle; 319 $this->features = new Features(); 320 } 321 322 /** 323 * Parses an "ipco" box. 324 * 325 * "ispe" is used for width and height, "pixi" and "av1C" are used for bit depth 326 * and number of channels, and "auxC" is used for alpha. 327 * 328 * @param stream $handle The resource the box will be parsed from. 329 * @param int $num_remaining_bytes The number of bytes that should be available from the resource. 330 * @return Status FOUND on success or an error on failure. 331 */ 332 private function parse_ipco( $num_remaining_bytes ) { 333 $box_index = 1; // 1-based index. Used for iterating over properties. 334 do { 335 $box = new Box(); 336 $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); 337 if ( $status != FOUND ) { 338 return $status; 339 } 340 341 if ( $box->type == 'ispe' ) { 342 // See ISO/IEC 23008-12:2017(E) 6.5.3.2 343 if ( $box->content_size < 8 ) { 344 return INVALID; 345 } 346 if ( !( $data = read( $this->handle, 8 ) ) ) { 347 return TRUNCATED; 348 } 349 $width = read_big_endian( substr( $data, 0, 4 ), 4 ); 350 $height = read_big_endian( substr( $data, 4, 4 ), 4 ); 351 if ( $width == 0 || $height == 0 ) { 352 return INVALID; 353 } 354 if ( count( $this->features->dim_props ) <= MAX_FEATURES && 355 $box_index <= MAX_VALUE ) { 356 $dim_prop_count = count( $this->features->dim_props ); 357 $this->features->dim_props[$dim_prop_count] = new Dim_Prop(); 358 $this->features->dim_props[$dim_prop_count]->property_index = $box_index; 359 $this->features->dim_props[$dim_prop_count]->width = $width; 360 $this->features->dim_props[$dim_prop_count]->height = $height; 361 } else { 362 $this->data_was_skipped = true; 363 } 364 if ( !skip( $this->handle, $box->content_size - 8 ) ) { 365 return TRUNCATED; 366 } 367 } else if ( $box->type == 'pixi' ) { 368 // See ISO/IEC 23008-12:2017(E) 6.5.6.2 369 if ( $box->content_size < 1 ) { 370 return INVALID; 371 } 372 if ( !( $data = read( $this->handle, 1 ) ) ) { 373 return TRUNCATED; 374 } 375 $num_channels = read_big_endian( $data, 1 ); 376 if ( $num_channels < 1 ) { 377 return INVALID; 378 } 379 if ( $box->content_size < 1 + $num_channels ) { 380 return INVALID; 381 } 382 if ( !( $data = read( $this->handle, 1 ) ) ) { 383 return TRUNCATED; 384 } 385 $bit_depth = read_big_endian( $data, 1 ); 386 if ( $bit_depth < 1 ) { 387 return INVALID; 388 } 389 for ( $i = 1; $i < $num_channels; ++$i ) { 390 if ( !( $data = read( $this->handle, 1 ) ) ) { 391 return TRUNCATED; 392 } 393 // Bit depth should be the same for all channels. 394 if ( read_big_endian( $data, 1 ) != $bit_depth ) { 395 return INVALID; 396 } 397 if ( $i > 32 ) { 398 return ABORTED; // Be reasonable. 399 } 400 } 401 if ( count( $this->features->chan_props ) <= MAX_FEATURES && 402 $box_index <= MAX_VALUE && $bit_depth <= MAX_VALUE && 403 $num_channels <= MAX_VALUE ) { 404 $chan_prop_count = count( $this->features->chan_props ); 405 $this->features->chan_props[$chan_prop_count] = new Chan_Prop(); 406 $this->features->chan_props[$chan_prop_count]->property_index = $box_index; 407 $this->features->chan_props[$chan_prop_count]->bit_depth = $bit_depth; 408 $this->features->chan_props[$chan_prop_count]->num_channels = $num_channels; 409 } else { 410 $this->data_was_skipped = true; 411 } 412 if ( !skip( $this->handle, $box->content_size - ( 1 + $num_channels ) ) ) { 413 return TRUNCATED; 414 } 415 } else if ( $box->type == 'av1C' ) { 416 // See AV1 Codec ISO Media File Format Binding 2.3.1 417 // at https://aomediacodec.github.io/av1-isobmff/#av1c 418 // Only parse the necessary third byte. Assume that the others are valid. 419 if ( $box->content_size < 3 ) { 420 return INVALID; 421 } 422 if ( !( $data = read( $this->handle, 3 ) ) ) { 423 return TRUNCATED; 424 } 425 $byte = read_big_endian( substr( $data, 2, 1 ), 1 ); 426 $high_bitdepth = ( $byte & 0x40 ) != 0; 427 $twelve_bit = ( $byte & 0x20 ) != 0; 428 $monochrome = ( $byte & 0x10 ) != 0; 429 if ( $twelve_bit && !$high_bitdepth ) { 430 return INVALID; 431 } 432 if ( count( $this->features->chan_props ) <= MAX_FEATURES && 433 $box_index <= MAX_VALUE ) { 434 $chan_prop_count = count( $this->features->chan_props ); 435 $this->features->chan_props[$chan_prop_count] = new Chan_Prop(); 436 $this->features->chan_props[$chan_prop_count]->property_index = $box_index; 437 $this->features->chan_props[$chan_prop_count]->bit_depth = 438 $high_bitdepth ? $twelve_bit ? 12 : 10 : 8; 439 $this->features->chan_props[$chan_prop_count]->num_channels = $monochrome ? 1 : 3; 440 } else { 441 $this->data_was_skipped = true; 442 } 443 if ( !skip( $this->handle, $box->content_size - 3 ) ) { 444 return TRUNCATED; 445 } 446 } else if ( $box->type == 'auxC' ) { 447 // See AV1 Image File Format (AVIF) 4 448 // at https://aomediacodec.github.io/av1-avif/#auxiliary-images 449 $kAlphaStr = "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"; 450 $kAlphaStrLength = 44; // Includes terminating character. 451 if ( $box->content_size >= $kAlphaStrLength ) { 452 if ( !( $data = read( $this->handle, $kAlphaStrLength ) ) ) { 453 return TRUNCATED; 454 } 455 if ( substr( $data, 0, $kAlphaStrLength ) == $kAlphaStr ) { 456 // Note: It is unlikely but it is possible that this alpha plane does 457 // not belong to the primary item or a tile. Ignore this issue. 458 $this->features->has_alpha = true; 459 } 460 if ( !skip( $this->handle, $box->content_size - $kAlphaStrLength ) ) { 461 return TRUNCATED; 462 } 463 } else { 464 if ( !skip( $this->handle, $box->content_size ) ) { 465 return TRUNCATED; 466 } 467 } 468 } else { 469 if ( !skip( $this->handle, $box->content_size ) ) { 470 return TRUNCATED; 471 } 472 } 473 ++$box_index; 474 $num_remaining_bytes -= $box->size; 475 } while ( $num_remaining_bytes > 0 ); 476 return NOT_FOUND; 477 } 478 479 /** 480 * Parses an "iprp" box. 481 * 482 * The "ipco" box contain the properties which are linked to items by the "ipma" box. 483 * 484 * @param stream $handle The resource the box will be parsed from. 485 * @param int $num_remaining_bytes The number of bytes that should be available from the resource. 486 * @return Status FOUND on success or an error on failure. 487 */ 488 private function parse_iprp( $num_remaining_bytes ) { 489 do { 490 $box = new Box(); 491 $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); 492 if ( $status != FOUND ) { 493 return $status; 494 } 495 496 if ( $box->type == 'ipco' ) { 497 $status = $this->parse_ipco( $box->content_size ); 498 if ( $status != NOT_FOUND ) { 499 return $status; 500 } 501 } else if ( $box->type == 'ipma' ) { 502 // See ISO/IEC 23008-12:2017(E) 9.3.2 503 $num_read_bytes = 4; 504 if ( $box->content_size < $num_read_bytes ) { 505 return INVALID; 506 } 507 if ( !( $data = read( $this->handle, $num_read_bytes ) ) ) { 508 return TRUNCATED; 509 } 510 $entry_count = read_big_endian( $data, 4 ); 511 $id_num_bytes = ( $box->version < 1 ) ? 2 : 4; 512 $index_num_bytes = ( $box->flags & 1 ) ? 2 : 1; 513 $essential_bit_mask = ( $box->flags & 1 ) ? 0x8000 : 0x80; 514 515 for ( $entry = 0; $entry < $entry_count; ++$entry ) { 516 if ( $entry >= MAX_PROPS || 517 count( $this->features->props ) >= MAX_PROPS ) { 518 $this->data_was_skipped = true; 519 break; 520 } 521 $num_read_bytes += $id_num_bytes + 1; 522 if ( $box->content_size < $num_read_bytes ) { 523 return INVALID; 524 } 525 if ( !( $data = read( $this->handle, $id_num_bytes + 1 ) ) ) { 526 return TRUNCATED; 527 } 528 $item_id = read_big_endian( 529 substr( $data, 0, $id_num_bytes ), $id_num_bytes ); 530 $association_count = read_big_endian( 531 substr( $data, $id_num_bytes, 1 ), 1 ); 532 533 for ( $property = 0; $property < $association_count; ++$property ) { 534 if ( $property >= MAX_PROPS || 535 count( $this->features->props ) >= MAX_PROPS ) { 536 $this->data_was_skipped = true; 537 break; 538 } 539 $num_read_bytes += $index_num_bytes; 540 if ( $box->content_size < $num_read_bytes ) { 541 return INVALID; 542 } 543 if ( !( $data = read( $this->handle, $index_num_bytes ) ) ) { 544 return TRUNCATED; 545 } 546 $value = read_big_endian( $data, $index_num_bytes ); 547 // $essential = ($value & $essential_bit_mask); // Unused. 548 $property_index = ( $value & ~$essential_bit_mask ); 549 if ( $property_index <= MAX_VALUE && $item_id <= MAX_VALUE ) { 550 $prop_count = count( $this->features->props ); 551 $this->features->props[$prop_count] = new Prop(); 552 $this->features->props[$prop_count]->property_index = $property_index; 553 $this->features->props[$prop_count]->item_id = $item_id; 554 } else { 555 $this->data_was_skipped = true; 556 } 557 } 558 if ( $property < $association_count ) { 559 break; // Do not read garbage. 560 } 561 } 562 563 // If all features are available now, do not look further. 564 $status = $this->features->get_primary_item_features(); 565 if ( $status != NOT_FOUND ) { 566 return $status; 567 } 568 569 // Mostly if 'data_was_skipped'. 570 if ( !skip( $this->handle, $box->content_size - $num_read_bytes ) ) { 571 return TRUNCATED; 572 } 573 } else { 574 if ( !skip( $this->handle, $box->content_size ) ) { 575 return TRUNCATED; 576 } 577 } 578 $num_remaining_bytes -= $box->size; 579 } while ( $num_remaining_bytes > 0 ); 580 return NOT_FOUND; 581 } 582 583 /** 584 * Parses an "iref" box. 585 * 586 * The "dimg" boxes contain links between tiles and their parent items, which 587 * can be used to infer bit depth and number of channels for the primary item 588 * when the latter does not have these properties. 589 * 590 * @param stream $handle The resource the box will be parsed from. 591 * @param int $num_remaining_bytes The number of bytes that should be available from the resource. 592 * @return Status FOUND on success or an error on failure. 593 */ 594 private function parse_iref( $num_remaining_bytes ) { 595 do { 596 $box = new Box(); 597 $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); 598 if ( $status != FOUND ) { 599 return $status; 600 } 601 602 if ( $box->type == 'dimg' ) { 603 // See ISO/IEC 14496-12:2015(E) 8.11.12.2 604 $num_bytes_per_id = ( $box->version == 0 ) ? 2 : 4; 605 $num_read_bytes = $num_bytes_per_id + 2; 606 if ( $box->content_size < $num_read_bytes ) { 607 return INVALID; 608 } 609 if ( !( $data = read( $this->handle, $num_read_bytes ) ) ) { 610 return TRUNCATED; 611 } 612 $from_item_id = read_big_endian( $data, $num_bytes_per_id ); 613 $reference_count = read_big_endian( substr( $data, $num_bytes_per_id, 2 ), 2 ); 614 615 for ( $i = 0; $i < $reference_count; ++$i ) { 616 if ( $i >= MAX_TILES ) { 617 $this->data_was_skipped = true; 618 break; 619 } 620 $num_read_bytes += $num_bytes_per_id; 621 if ( $box->content_size < $num_read_bytes ) { 622 return INVALID; 623 } 624 if ( !( $data = read( $this->handle, $num_bytes_per_id ) ) ) { 625 return TRUNCATED; 626 } 627 $to_item_id = read_big_endian( $data, $num_bytes_per_id ); 628 $tile_count = count( $this->features->tiles ); 629 if ( $from_item_id <= MAX_VALUE && $to_item_id <= MAX_VALUE && 630 $tile_count < MAX_TILES ) { 631 $this->features->tiles[$tile_count] = new Tile(); 632 $this->features->tiles[$tile_count]->tile_item_id = $to_item_id; 633 $this->features->tiles[$tile_count]->parent_item_id = $from_item_id; 634 } else { 635 $this->data_was_skipped = true; 636 } 637 } 638 639 // If all features are available now, do not look further. 640 $status = $this->features->get_primary_item_features(); 641 if ( $status != NOT_FOUND ) { 642 return $status; 643 } 644 645 // Mostly if 'data_was_skipped'. 646 if ( !skip( $this->handle, $box->content_size - $num_read_bytes ) ) { 647 return TRUNCATED; 648 } 649 } else { 650 if ( !skip( $this->handle, $box->content_size ) ) { 651 return TRUNCATED; 652 } 653 } 654 $num_remaining_bytes -= $box->size; 655 } while ( $num_remaining_bytes > 0 ); 656 return NOT_FOUND; 657 } 658 659 /** 660 * Parses a "meta" box. 661 * 662 * It looks for the primary item ID in the "pitm" box and recurses into other boxes 663 * to find its features. 664 * 665 * @param stream $handle The resource the box will be parsed from. 666 * @param int $num_remaining_bytes The number of bytes that should be available from the resource. 667 * @return Status FOUND on success or an error on failure. 668 */ 669 private function parse_meta( $num_remaining_bytes ) { 670 do { 671 $box = new Box(); 672 $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); 673 if ( $status != FOUND ) { 674 return $status; 675 } 676 677 if ( $box->type == 'pitm' ) { 678 // See ISO/IEC 14496-12:2015(E) 8.11.4.2 679 $num_bytes_per_id = ( $box->version == 0 ) ? 2 : 4; 680 if ( $num_bytes_per_id > $num_remaining_bytes ) { 681 return INVALID; 682 } 683 if ( !( $data = read( $this->handle, $num_bytes_per_id ) ) ) { 684 return TRUNCATED; 685 } 686 $primary_item_id = read_big_endian( $data, $num_bytes_per_id ); 687 if ( $primary_item_id > MAX_VALUE ) { 688 return ABORTED; 689 } 690 $this->features->has_primary_item = true; 691 $this->features->primary_item_id = $primary_item_id; 692 if ( !skip( $this->handle, $box->content_size - $num_bytes_per_id ) ) { 693 return TRUNCATED; 694 } 695 } else if ( $box->type == 'iprp' ) { 696 $status = $this->parse_iprp( $box->content_size ); 697 if ( $status != NOT_FOUND ) { 698 return $status; 699 } 700 } else if ( $box->type == 'iref' ) { 701 $status = $this->parse_iref( $box->content_size ); 702 if ( $status != NOT_FOUND ) { 703 return $status; 704 } 705 } else { 706 if ( !skip( $this->handle, $box->content_size ) ) { 707 return TRUNCATED; 708 } 709 } 710 $num_remaining_bytes -= $box->size; 711 } while ( $num_remaining_bytes != 0 ); 712 // According to ISO/IEC 14496-12:2012(E) 8.11.1.1 there is at most one "meta". 713 return INVALID; 714 } 715 716 /** 717 * Parses a file stream. 718 * 719 * The file type is checked through the "ftyp" box. 720 * 721 * @return bool True if the input stream is an AVIF bitstream or false. 722 */ 723 public function parse_ftyp() { 724 $box = new Box(); 725 $status = $box->parse( $this->handle, $this->num_parsed_boxes ); 726 if ( $status != FOUND ) { 727 return false; 728 } 729 730 if ( $box->type != 'ftyp' ) { 731 return false; 732 } 733 // Iterate over brands. See ISO/IEC 14496-12:2012(E) 4.3.1 734 if ( $box->content_size < 8 ) { 735 return false; 736 } 737 for ( $i = 0; $i + 4 <= $box->content_size; $i += 4 ) { 738 if ( !( $data = read( $this->handle, 4 ) ) ) { 739 return false; 740 } 741 if ( $i == 4 ) { 742 continue; // Skip minor_version. 743 } 744 if ( substr( $data, 0, 4 ) == 'avif' || substr( $data, 0, 4 ) == 'avis' ) { 745 return skip( $this->handle, $box->content_size - ( $i + 4 ) ); 746 } 747 if ( $i > 32 * 4 ) { 748 return false; // Be reasonable. 749 } 750 751 } 752 return false; // No AVIF brand no good. 753 } 754 755 /** 756 * Parses a file stream. 757 * 758 * Features are extracted from the "meta" box. 759 * 760 * @return bool True if the main features of the primary item were parsed or false. 761 */ 762 public function parse_file() { 763 $box = new Box(); 764 while ( $box->parse( $this->handle, $this->num_parsed_boxes ) == FOUND ) { 765 if ( $box->type === 'meta' ) { 766 if ( $this->parse_meta( $box->content_size ) != FOUND ) { 767 return false; 768 } 769 return true; 770 } 771 if ( !skip( $this->handle, $box->content_size ) ) { 772 return false; 773 } 774 } 775 return false; // No "meta" no good. 776 } 777 } -
src/wp-includes/class-wp-image-editor-gd.php
diff --git a/src/wp-includes/class-wp-image-editor-gd.php b/src/wp-includes/class-wp-image-editor-gd.php index de079357fb..23331c7f19 100644
a b class WP_Image_Editor_GD extends WP_Image_Editor { 71 71 return ( $image_types & IMG_GIF ) != 0; 72 72 case 'image/webp': 73 73 return ( $image_types & IMG_WEBP ) != 0; 74 case 'image/avif': 75 return ( $image_types & IMG_AVIF ) != 0; 74 76 } 75 77 76 78 return false; … … class WP_Image_Editor_GD extends WP_Image_Editor { 111 113 $this->image = @imagecreatefromstring( $file_contents ); 112 114 } 113 115 116 // AVIF may not work with imagecreatefromstring(). 117 if ( 118 function_exists( 'imagecreatefromavif' ) && 119 ( 'image/avif' === wp_get_image_mime( $this->file ) ) 120 ) { 121 $this->image = @imagecreatefromavif( $this->file ); 122 } else { 123 $this->image = @imagecreatefromstring( $file_contents ); 124 } 125 114 126 if ( ! is_gd_image( $this->image ) ) { 115 127 return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file ); 116 128 } … … class WP_Image_Editor_GD extends WP_Image_Editor { 513 525 if ( ! function_exists( 'imagewebp' ) || ! $this->make_image( $filename, 'imagewebp', array( $image, $filename, $this->get_quality() ) ) ) { 514 526 return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); 515 527 } 528 } elseif ( 'image/avif' == $mime_type ) { 529 if ( ! function_exists( 'imageavif' ) || ! $this->make_image( $filename, 'imageavif', array( $image, $filename, $this->get_quality() ) ) ) { 530 return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); 531 } 516 532 } else { 517 533 return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); 518 534 } … … class WP_Image_Editor_GD extends WP_Image_Editor { 561 577 if ( function_exists( 'imagewebp' ) ) { 562 578 header( 'Content-Type: image/webp' ); 563 579 return imagewebp( $this->image, null, $this->get_quality() ); 580 } else { 581 // Fall back to JPEG. 582 header( 'Content-Type: image/jpeg' ); 583 return imagejpeg( $this->image, null, $this->get_quality() ); 584 } 585 case 'image/avif': 586 if ( function_exists( 'imageavif' ) ) { 587 header( 'Content-Type: image/avif' ); 588 return imageavif( $this->image, null, $this->get_quality() ); 564 589 } 565 // Fall back to the default if webp isn't supported.590 // Fall back to JPEG. 566 591 default: 567 592 header( 'Content-Type: image/jpeg' ); 568 593 return imagejpeg( $this->image, null, $this->get_quality() ); -
src/wp-includes/class-wp-image-editor-imagick.php
diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php index 03fe0bca69..02e3d204d7 100644
a b class WP_Image_Editor_Imagick extends WP_Image_Editor { 219 219 $this->image->setImageCompressionQuality( $quality ); 220 220 } 221 221 break; 222 case 'image/avif': 222 223 default: 223 224 $this->image->setImageCompressionQuality( $quality ); 224 225 } … … class WP_Image_Editor_Imagick extends WP_Image_Editor { 256 257 $height = $size['height']; 257 258 } 258 259 260 // If we still don't have the image size, fall back to `wp_getimagesize`. This ensures AVIF images 261 // are properly sized without affecting previous `getImageGeometry` behavior. 262 if ( ( ! $width || ! $height ) && 'image/avif' === $this->mime_type ) { 263 $size = wp_getimagesize( $this->file ); 264 $width = $size[0]; 265 $height = $size[1]; 266 } 267 259 268 return parent::update_size( $width, $height ); 260 269 } 261 270 -
src/wp-includes/class-wp-image-editor.php
diff --git a/src/wp-includes/class-wp-image-editor.php b/src/wp-includes/class-wp-image-editor.php index 3c636dc6ba..6604685a02 100644
a b abstract class WP_Image_Editor { 318 318 $quality = 86; 319 319 break; 320 320 case 'image/jpeg': 321 case 'image/avif': 321 322 default: 322 323 $quality = $this->default_quality; 323 324 } -
src/wp-includes/class-wp-theme.php
diff --git a/src/wp-includes/class-wp-theme.php b/src/wp-includes/class-wp-theme.php index 09905bee1b..2058a9e557 100644
a b final class WP_Theme implements ArrayAccess { 1263 1263 return false; 1264 1264 } 1265 1265 1266 foreach ( array( 'png', 'gif', 'jpg', 'jpeg', 'webp' ) as $ext ) {1266 foreach ( array( 'png', 'gif', 'jpg', 'jpeg', 'webp', 'avif' ) as $ext ) { 1267 1267 if ( file_exists( $this->get_stylesheet_directory() . "/screenshot.$ext" ) ) { 1268 1268 $this->cache_add( 'screenshot', 'screenshot.' . $ext ); 1269 1269 if ( 'relative' === $uri ) { -
src/wp-includes/compat.php
diff --git a/src/wp-includes/compat.php b/src/wp-includes/compat.php index 5bfdbc23d6..3c8e911a3d 100644
a b if ( ! defined( 'IMAGETYPE_WEBP' ) ) { 497 497 if ( ! defined( 'IMG_WEBP' ) ) { 498 498 define( 'IMG_WEBP', IMAGETYPE_WEBP ); 499 499 } 500 501 // IMAGETYPE_AVIF constant is only defined in PHP 8.x or later. 502 if ( ! defined( 'IMAGETYPE_AVIF' ) ) { 503 define( 'IMAGETYPE_AVIF', 19 ); 504 } 505 506 // IMG_AVIF constant is only defined in PHP 8.x or later. 507 if ( ! defined( 'IMG_AVIF' ) ) { 508 define( 'IMG_AVIF', IMAGETYPE_AVIF ); 509 } -
src/wp-includes/customize/class-wp-customize-media-control.php
diff --git a/src/wp-includes/customize/class-wp-customize-media-control.php b/src/wp-includes/customize/class-wp-customize-media-control.php index f636892925..3bdc4e2dc1 100644
a b class WP_Customize_Media_Control extends WP_Customize_Control { 93 93 * Note that the default value must be a URL, NOT an attachment ID. 94 94 */ 95 95 $ext = substr( $this->setting->default, -3 ); 96 $type = in_array( $ext, array( 'jpg', 'png', 'gif', 'bmp', 'webp' ), true ) ? 'image' : 'document';96 $type = in_array( $ext, array( 'jpg', 'png', 'gif', 'bmp', 'webp', 'avif' ), true ) ? 'image' : 'document'; 97 97 98 98 $default_attachment = array( 99 99 'id' => 1, -
src/wp-includes/deprecated.php
diff --git a/src/wp-includes/deprecated.php b/src/wp-includes/deprecated.php index e63708f91b..b003c161d6 100644
a b function gd_edit_image_support($mime_type) { 3336 3336 return (imagetypes() & IMG_GIF) != 0; 3337 3337 case 'image/webp': 3338 3338 return (imagetypes() & IMG_WEBP) != 0; 3339 } 3339 case 'image/avif': 3340 return (imagetypes() & IMG_AVIF) != 0; 3341 } 3340 3342 } else { 3341 3343 switch( $mime_type ) { 3342 3344 case 'image/jpeg': … … function gd_edit_image_support($mime_type) { 3347 3349 return function_exists('imagecreatefromgif'); 3348 3350 case 'image/webp': 3349 3351 return function_exists('imagecreatefromwebp'); 3352 case 'image/avif': 3353 return function_exists('imagecreatefromavif'); 3350 3354 } 3351 3355 } 3352 3356 return false; -
src/wp-includes/formatting.php
diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php index 05b103e6f7..ce8d035420 100644
a b function translate_smiley( $matches ) { 3464 3464 3465 3465 $matches = array(); 3466 3466 $ext = preg_match( '/\.([^.]+)$/', $img, $matches ) ? strtolower( $matches[1] ) : false; 3467 $image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp' );3467 $image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp', 'avif' ); 3468 3468 3469 3469 // Don't convert smilies that aren't images - they're probably emoji. 3470 3470 if ( ! in_array( $ext, $image_exts, true ) ) { -
src/wp-includes/functions.php
diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index a3a5e56bfa..e5a39f588b 100644
a b function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) { 3117 3117 'image/bmp' => 'bmp', 3118 3118 'image/tiff' => 'tif', 3119 3119 'image/webp' => 'webp', 3120 'image/avif' => 'avif', 3120 3121 ) 3121 3122 ); 3122 3123 … … function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) { 3295 3296 * 3296 3297 * @since 4.7.1 3297 3298 * @since 5.8.0 Added support for WebP images. 3299 * @since 6.5.0 Added support for AVIF images. 3298 3300 * 3299 3301 * @param string $file Full path to the file. 3300 3302 * @return string|false The actual mime type or false if the type cannot be determined. … … function wp_get_image_mime( $file ) { 3349 3351 ) { 3350 3352 $mime = 'image/webp'; 3351 3353 } 3354 3355 /** 3356 * Add AVIF fallback detection when image library doesn't support AVIF. 3357 * 3358 * @todo check the third 4-byte token "[major_brand]", for "avif" or "avis" . 3359 * 3360 */ 3361 3362 // Divide the header string into 4 byte groups. 3363 $magic = str_split( $magic, 8 ); 3364 3365 if ( 3366 isset( $magic[1] ) && 3367 isset( $magic[2] ) && 3368 'ftyp' === hex2bin( $magic[1] ) && 3369 ( 'avif' === hex2bin( $magic[2] ) || 'avis' === hex2bin( $magic[2] ) ) 3370 ) { 3371 $mime = 'image/avif'; 3372 } 3352 3373 } catch ( Exception $e ) { 3353 3374 $mime = false; 3354 3375 } … … function wp_get_mime_types() { 3388 3409 'bmp' => 'image/bmp', 3389 3410 'tiff|tif' => 'image/tiff', 3390 3411 'webp' => 'image/webp', 3412 'avif' => 'image/avif', 3391 3413 'ico' => 'image/x-icon', 3392 3414 'heic' => 'image/heic', 3393 3415 // Video formats. … … function wp_get_ext_types() { 3509 3531 return apply_filters( 3510 3532 'ext2type', 3511 3533 array( 3512 'image' => array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'bmp', 'tif', 'tiff', 'ico', 'heic', 'webp' ),3534 'image' => array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'bmp', 'tif', 'tiff', 'ico', 'heic', 'webp', 'avif' ), 3513 3535 'audio' => array( 'aac', 'ac3', 'aif', 'aiff', 'flac', 'm3a', 'm4a', 'm4b', 'mka', 'mp1', 'mp2', 'mp3', 'ogg', 'oga', 'ram', 'wav', 'wma' ), 3514 3536 'video' => array( '3g2', '3gp', '3gpp', 'asf', 'avi', 'divx', 'dv', 'flv', 'm4v', 'mkv', 'mov', 'mp4', 'mpeg', 'mpg', 'mpv', 'ogm', 'ogv', 'qt', 'rm', 'vob', 'wmv' ), 3515 3537 'document' => array( 'doc', 'docx', 'docm', 'dotm', 'odt', 'pages', 'pdf', 'xps', 'oxps', 'rtf', 'wp', 'wpd', 'psd', 'xcf' ), -
src/wp-includes/media.php
diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 38ec2213b7..a2cb6ead84 100644
a b function _wp_image_editor_choose( $args = array() ) { 4100 4100 require_once ABSPATH . WPINC . '/class-wp-image-editor.php'; 4101 4101 require_once ABSPATH . WPINC . '/class-wp-image-editor-gd.php'; 4102 4102 require_once ABSPATH . WPINC . '/class-wp-image-editor-imagick.php'; 4103 require_once ABSPATH . WPINC . '/class-avif-info.php'; 4103 4104 /** 4104 4105 * Filters the list of image editing library classes. 4105 4106 * … … function wp_plupload_default_settings() { 4204 4205 $defaults['webp_upload_error'] = true; 4205 4206 } 4206 4207 4208 // Check if AVIF images can be edited. 4209 if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/avif' ) ) ) { 4210 $defaults['avif_upload_error'] = true; 4211 } 4212 4207 4213 /** 4208 4214 * Filters the Plupload default settings. 4209 4215 * … … function wp_show_heic_upload_error( $plupload_settings ) { 5480 5486 * 5481 5487 * @since 5.7.0 5482 5488 * @since 5.8.0 Added support for WebP images. 5489 * @since 6.5.0 Added support for AVIF images. 5483 5490 * 5484 5491 * @param string $filename The file path. 5485 5492 * @param array $image_info Optional. Extended image information (passed by reference). … … function wp_getimagesize( $filename, array &$image_info = null ) { 5512 5519 } 5513 5520 } 5514 5521 5515 if ( false !== $info ) { 5522 if ( 5523 ! empty( $info ) && 5524 // Some PHP versions return 0x0 sizes from `getimagesize` for unrecognized image formats, including AVIFs 5525 ! ( empty( $info[0] ) && empty( $info[1] ) ) 5526 ) { 5516 5527 return $info; 5517 5528 } 5518 5529 … … function wp_getimagesize( $filename, array &$image_info = null ) { 5541 5552 } 5542 5553 } 5543 5554 5555 // For PHP versions that don't support AVIF images, 5556 // extract the image size info from the file headers. 5557 if ( 'image/avif' === wp_get_image_mime( $filename ) ) { 5558 $avif_info = wp_get_avif_info( $filename ); 5559 5560 $width = $avif_info['width']; 5561 $height = $avif_info['height']; 5562 5563 // Mimic the native return format. 5564 if ( $width && $height ) { 5565 return array( 5566 $width, 5567 $height, 5568 IMAGETYPE_AVIF, 5569 sprintf( 5570 'width="%d" height="%d"', 5571 $width, 5572 $height 5573 ), 5574 'mime' => 'image/avif', 5575 ); 5576 } 5577 } 5578 5544 5579 // The image could not be parsed. 5545 5580 return false; 5546 5581 } 5547 5582 5583 /** 5584 * Extracts meta information about an AVIF file: width, height, bit depth, and number of channels. 5585 * 5586 * @since 6.5.0 5587 * 5588 * @param string $filename Path to an AVIF file. 5589 * @return array { 5590 * An array of AVIF image information. 5591 * 5592 * @type int|false $width Image width on success, false on failure. 5593 * @type int|false $height Image height on success, false on failure. 5594 * @type int|false $bit_depth Image bit depth on success, false on failure. 5595 * @type int|false $num_channels Image number of channels on success, false on failure. 5596 * } 5597 */ 5598 function wp_get_avif_info( $filename ) { 5599 $width = false; 5600 $height = false; 5601 $type = false; 5602 5603 if ( 'image/avif' !== wp_get_image_mime( $filename ) ) { 5604 return compact( 'width', 'height', 'type' ); 5605 } 5606 5607 // Parse the file using libavifinfo's PHP implementation. 5608 require_once ABSPATH . WPINC . '/class-avif-info.php'; 5609 $results = array( 5610 'width' => false, 5611 'height' => false, 5612 'bit_depth' => false, 5613 'num_channels' => false, 5614 ); 5615 5616 $handle = fopen( $filename, 'rb' ); 5617 if ( $handle ) { 5618 $parser = new Avifinfo\Parser( $handle ); 5619 $success = $parser->parse_ftyp() && $parser->parse_file(); 5620 fclose( $handle ); 5621 if ( $success ) { 5622 $results = $parser->features->primary_item_features; 5623 } 5624 } 5625 return $results; 5626 } 5627 5548 5628 /** 5549 5629 * Extracts meta information about a WebP file: width, height, and type. 5550 5630 * -
src/wp-includes/post.php
diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 69a6362552..001ccaf7d0 100644
a b function wp_attachment_is( $type, $post = null ) { 6700 6700 6701 6701 switch ( $type ) { 6702 6702 case 'image': 6703 $image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp' );6703 $image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp', 'avif' ); 6704 6704 return in_array( $ext, $image_exts, true ); 6705 6705 6706 6706 case 'audio': -
src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php
diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php index 7367c0fc57..bccbfa559d 100644
a b class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller { 450 450 ); 451 451 } 452 452 453 $supported_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp' );453 $supported_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif' ); 454 454 $mime_type = get_post_mime_type( $attachment_id ); 455 455 if ( ! in_array( $mime_type, $supported_types, true ) ) { 456 456 return new WP_Error( -
tests/phpunit/tests/functions.php
diff --git a/tests/phpunit/data/images/avif-animated.avif b/tests/phpunit/data/images/avif-animated.avif new file mode 100644 index 0000000000..6d6a34a730 Binary files /dev/null and b/tests/phpunit/data/images/avif-animated.avif differ diff --git a/tests/phpunit/data/images/avif-lossless.avif b/tests/phpunit/data/images/avif-lossless.avif new file mode 100644 index 0000000000..7eb2d5ce68 Binary files /dev/null and b/tests/phpunit/data/images/avif-lossless.avif differ diff --git a/tests/phpunit/data/images/avif-lossy.avif b/tests/phpunit/data/images/avif-lossy.avif new file mode 100644 index 0000000000..0aba41c1bf Binary files /dev/null and b/tests/phpunit/data/images/avif-lossy.avif differ diff --git a/tests/phpunit/data/images/avif-rotated.avif b/tests/phpunit/data/images/avif-rotated.avif new file mode 100644 index 0000000000..ee7c5246e1 Binary files /dev/null and b/tests/phpunit/data/images/avif-rotated.avif differ diff --git a/tests/phpunit/data/images/avif-transparent.avif b/tests/phpunit/data/images/avif-transparent.avif new file mode 100644 index 0000000000..db34cd3f74 Binary files /dev/null and b/tests/phpunit/data/images/avif-transparent.avif differ diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php index 3c2e1101ad..abef68f75b 100644
a b class Tests_Functions extends WP_UnitTestCase { 1370 1370 DIR_TESTDATA . '/uploads/dashicons.woff', 1371 1371 false, 1372 1372 ), 1373 // Animated AVIF. 1374 array( 1375 DIR_TESTDATA . '/images/avif-animated.avif', 1376 'image/avif', 1377 ), 1378 // Lossless AVIF. 1379 array( 1380 DIR_TESTDATA . '/images/avif-lossless.avif', 1381 'image/avif', 1382 ), 1383 // Lossy AVIF. 1384 array( 1385 DIR_TESTDATA . '/images/avif-lossy.avif', 1386 'image/avif', 1387 ), 1388 // Transparent AVIF. 1389 array( 1390 DIR_TESTDATA . '/images/avif-transparent.avif', 1391 'image/avif', 1392 ), 1373 1393 ); 1374 1394 1375 1395 return $data; … … class Tests_Functions extends WP_UnitTestCase { 1496 1516 DIR_TESTDATA . '/uploads/dashicons.woff', 1497 1517 false, 1498 1518 ), 1519 // Animated AVIF. 1520 array( 1521 DIR_TESTDATA . '/images/avif-animated.avif', 1522 array( 1523 150, 1524 150, 1525 IMAGETYPE_AVIF, 1526 'width="150" height="150"', 1527 'mime' => 'image/avif', 1528 ), 1529 ), 1530 // Lossless AVIF. 1531 array( 1532 DIR_TESTDATA . '/images/avif-lossless.avif', 1533 array( 1534 400, 1535 400, 1536 IMAGETYPE_AVIF, 1537 'width="400" height="400"', 1538 'mime' => 'image/avif', 1539 ), 1540 ), 1541 // Lossy AVIF. 1542 array( 1543 DIR_TESTDATA . '/images/avif-lossy.avif', 1544 array( 1545 400, 1546 400, 1547 IMAGETYPE_AVIF, 1548 'width="400" height="400"', 1549 'mime' => 'image/avif', 1550 ), 1551 ), 1552 // Transparent AVIF. 1553 array( 1554 DIR_TESTDATA . '/images/avif-transparent.avif', 1555 array( 1556 128, 1557 128, 1558 IMAGETYPE_AVIF, 1559 'width="128" height="128"', 1560 'mime' => 'image/avif', 1561 ), 1562 ), 1499 1563 ); 1500 1564 1501 1565 return $data; -
tests/phpunit/tests/image/editor.php
diff --git a/tests/phpunit/tests/image/editor.php b/tests/phpunit/tests/image/editor.php index bd54b803e2..1244e761cf 100644
a b class Tests_Image_Editor extends WP_Image_UnitTestCase { 363 363 ), 364 364 ); 365 365 } 366 367 /** 368 * Test wp_get_avif_info. 369 * 370 * @ticket 51228 371 * @dataProvider data_wp_get_avif_info 372 * 373 */ 374 public function test_wp_get_avif_info( $file, $expected ) { 375 $editor = wp_get_image_editor( $file ); 376 377 if ( is_wp_error( $editor ) || ! $editor->supports_mime_type( 'image/avif' ) ) { 378 $this->markTestSkipped( sprintf( 'No AVIF support in the editor engine %s on this system.', $this->editor_engine ) ); 379 } 380 381 $file_data = wp_get_avif_info( $file ); 382 $this->assertSame( $expected, $file_data ); 383 } 384 385 /** 386 * Data provider for test_wp_get_avif_info(). 387 */ 388 public function data_wp_get_avif_info() { 389 return array( 390 // Standard JPEG. 391 array( 392 DIR_TESTDATA . '/images/test-image.jpg', 393 array( 394 'width' => false, 395 'height' => false, 396 'type' => false, 397 ), 398 ), 399 // Standard GIF. 400 array( 401 DIR_TESTDATA . '/images/test-image.gif', 402 array( 403 'width' => false, 404 'height' => false, 405 'type' => false, 406 ), 407 ), 408 // Animated AVIF. 409 array( 410 DIR_TESTDATA . '/images/avif-animated.avif', 411 array( 412 'width' => 150, 413 'height' => 150, 414 'bit_depth' => 8, 415 'num_channels' => 3, 416 ), 417 ), 418 // Lossless AVIF. 419 array( 420 DIR_TESTDATA . '/images/avif-lossless.avif', 421 array( 422 'width' => 400, 423 'height' => 400, 424 'bit_depth' => 8, 425 'num_channels' => 3, 426 ), 427 ), 428 // Lossy AVIF. 429 array( 430 DIR_TESTDATA . '/images/avif-lossy.avif', 431 array( 432 'width' => 400, 433 'height' => 400, 434 'bit_depth' => 8, 435 'num_channels' => 3, 436 ), 437 ), 438 // Transparent AVIF. 439 array( 440 DIR_TESTDATA . '/images/avif-transparent.avif', 441 array( 442 'width' => 128, 443 'height' => 128, 444 'bit_depth' => 12, 445 'num_channels' => 3, 446 ), 447 ), 448 ); 449 } 366 450 } -
tests/phpunit/tests/image/functions.php
diff --git a/tests/phpunit/tests/image/functions.php b/tests/phpunit/tests/image/functions.php index f55b164496..56c81a62f0 100644
a b class Tests_Image_Functions extends WP_UnitTestCase { 111 111 'webp-lossless.webp', 112 112 'webp-lossy.webp', 113 113 'webp-transparent.webp', 114 'avif-animated.avif', 115 'avif-lossless.avif', 116 'avif-lossy.avif', 117 'avif-transparent.avif', 114 118 ); 115 119 116 120 return $this->text_array_to_dataprovider( $files ); … … class Tests_Image_Functions extends WP_UnitTestCase { 186 190 $files[] = 'webp-transparent.webp'; 187 191 } 188 192 193 // Add AVIF images if the image editor supports them. 194 $file = DIR_TESTDATA . '/images/avif-lossless.avif'; 195 $editor = wp_get_image_editor( $file ); 196 197 if ( ! is_wp_error( $editor ) && $editor->supports_mime_type( 'image/avif' ) ) { 198 $files[] = 'avif-animated.avif'; 199 $files[] = 'avif-lossless.avif'; 200 $files[] = 'avif-lossy.avif'; 201 $files[] = 'avif-transparent.avif'; 202 } 203 189 204 return $this->text_array_to_dataprovider( $files ); 190 205 } 191 206 -
tests/phpunit/tests/image/resize.php
diff --git a/tests/phpunit/tests/image/resize.php b/tests/phpunit/tests/image/resize.php index 5b302ce295..094e1d341e 100644
a b abstract class WP_Tests_Image_Resize_UnitTestCase extends WP_Image_UnitTestCase 88 88 $this->assertSame( IMAGETYPE_WEBP, $type ); 89 89 } 90 90 91 /** 92 * Test resizing AVIF image. 93 * 94 * @ticket 51228 95 */ 96 public function test_resize_avif() { 97 $file = DIR_TESTDATA . '/images/avif-lossy.avif'; 98 $editor = wp_get_image_editor( $file ); 99 100 // Check if the editor supports the avif mime type. 101 if ( is_wp_error( $editor ) || ! $editor->supports_mime_type( 'image/avif' ) ) { 102 $this->markTestSkipped( sprintf( 'No AVIF support in the editor engine %s on this system.', $this->editor_engine ) ); 103 } 104 105 $image = $this->resize_helper( $file, 25, 25 ); 106 107 list( $w, $h, $type ) = wp_getimagesize( $image ); 108 109 unlink( $image ); 110 111 $this->assertSame( 'avif-lossy-25x25.avif', wp_basename( $image ) ); 112 $this->assertSame( 25, $w ); 113 $this->assertSame( 25, $h ); 114 $this->assertSame( IMAGETYPE_AVIF, $type ); 115 } 116 117 91 118 public function test_resize_larger() { 92 119 // image_resize() should refuse to make an image larger. 93 120 $image = $this->resize_helper( DIR_TESTDATA . '/images/test-image.jpg', 100, 100 );