Plugin Directory

Changeset 3458273


Ignore:
Timestamp:
02/10/2026 05:08:59 PM (8 days ago)
Author:
nosilver4u
Message:

tagging and releasing 2.9.0

Location:
imsanity
Files:
2 added
14 edited
1 copied

Legend:

Unmodified
Added
Removed
  • imsanity/tags/2.9.0/changelog.txt

    r3340527 r3458273  
     1= 2.9.0 =
     2*Release Date - February 10, 2026*
     3
     4* added: support for resizing AVIF image uploads
     5* added: settings for WebP and AVIF quality
     6* added: support for Modern Image Formats plugin
     7* added: PHP 8.5 compatibility
     8* fixed: quality settings not applied
     9* fixed: PNG alpha detection may throw errors if PHP GD cannot obtain information from a PNG image
     10
    111= 2.8.7 =
    212*Release Date = August 6, 2024*
  • imsanity/tags/2.9.0/class-imsanity-cli.php

    r2969420 r3458273  
    7676            $path = get_attached_file( $id );
    7777            if ( $path ) {
    78                 list( $imagew, $imageh ) = getimagesize( $path );
     78                $dimensions = getimagesize( $path );
     79                if ( is_array( $dimensions ) && count( $dimensions ) >= 2 ) {
     80                    $imagew = $dimensions[0];
     81                    $imageh = $dimensions[1];
     82                }
    7983            }
    8084            if ( empty( $imagew ) || empty( $imageh ) ) {
  • imsanity/tags/2.9.0/imsanity.php

    r3340527 r3458273  
    1515Author: Exactly WWW
    1616Domain Path: /languages
    17 Version: 2.8.7
    18 Requires at least: 6.5
     17Version: 2.9.0
     18Requires at least: 6.6
    1919Requires PHP: 7.4
    2020Author URI: https://ewww.io/about/
     
    2626}
    2727
    28 define( 'IMSANITY_VERSION', '2.8.7' );
     28define( 'IMSANITY_VERSION', '2.9.0' );
    2929define( 'IMSANITY_SCHEMA_VERSION', '1.1' );
    3030
     
    3434define( 'IMSANITY_DEFAULT_PNG_TO_JPG', false );
    3535define( 'IMSANITY_DEFAULT_QUALITY', 82 );
     36define( 'IMSANITY_DEFAULT_AVIF_QUALITY', 86 );
     37define( 'IMSANITY_DEFAULT_WEBP_QUALITY', 86 );
    3638
    3739define( 'IMSANITY_SOURCE_POST', 1 );
     
    4547 */
    4648define( 'IMSANITY_PLUGIN_FILE', __FILE__ );
     49
     50/**
     51 * The directory path of the main plugin file.
     52 *
     53 * @var string IMSANITY_PLUGIN_DIR
     54 */
     55define( 'IMSANITY_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
     56
    4757/**
    4858 * The path of the main plugin file, relative to the plugins/ folder.
     
    6272 * Import supporting libraries.
    6373 */
     74require_once plugin_dir_path( __FILE__ ) . 'libs/debug.php';
    6475require_once plugin_dir_path( __FILE__ ) . 'libs/utils.php';
    6576require_once plugin_dir_path( __FILE__ ) . 'settings.php';
     
    6879if ( defined( 'WP_CLI' ) && WP_CLI ) {
    6980    require_once plugin_dir_path( __FILE__ ) . 'class-imsanity-cli.php';
    70 }
    71 
    72 /**
    73  * Use the EWWW IO debugging functions (if available).
    74  *
    75  * @param string $message A message to send to the debugger.
    76  */
    77 function imsanity_debug( $message ) {
    78     if ( function_exists( 'ewwwio_debug_message' ) ) {
    79         if ( ! is_string( $message ) ) {
    80             if ( function_exists( 'print_r' ) ) {
    81                 $message = print_r( $message, true );
    82             } else {
    83                 $message = 'not a string, print_r disabled';
    84             }
    85         }
    86         ewwwio_debug_message( $message );
    87         if ( function_exists( 'ewww_image_optimizer_debug_log' ) ) {
    88             ewww_image_optimizer_debug_log();
    89         }
    90     }
    9181}
    9282
     
    175165 */
    176166function imsanity_handle_upload( $params ) {
     167    imsanity_debug( __FUNCTION__ );
     168
     169    if ( empty( $params['file'] ) || empty( $params['type'] ) ) {
     170        imsanity_debug( 'missing file or type parameter, skipping' );
     171        return $params;
     172    }
    177173
    178174    // If "noresize" is included in the filename then we will bypass imsanity scaling.
    179175    if ( strpos( $params['file'], 'noresize' ) !== false ) {
     176        imsanity_debug( "skipping {$params['file']}" );
    180177        return $params;
    181178    }
    182179
    183180    if ( apply_filters( 'imsanity_skip_image', false, $params['file'] ) ) {
     181        imsanity_debug( "skipping {$params['file']} per filter" );
    184182        return $params;
    185183    }
     
    194192    }
    195193
    196     // Make sure this is a type of image that we want to convert and that it exists.
     194    // Store the path for reference in case $params is modified.
    197195    $oldpath = $params['file'];
    198196
    199197    // Let folks filter the allowed mime-types for resizing.
     198    // Also allows conditional support for WebP and AVIF if the server supports it.
    200199    $allowed_types = apply_filters( 'imsanity_allowed_mimes', array( 'image/png', 'image/gif', 'image/jpeg' ), $oldpath );
    201200    if ( is_string( $allowed_types ) ) {
     
    213212        in_array( $params['type'], $allowed_types, true )
    214213    ) {
     214        // If the Modern Image Formats plugin is active but fallback mode is disabled, permit conversion to AVIF/WebP during upload by defining IMSANITY_ALLOW_CONVERSION.
     215        // Otherwise, no conversion should be allowed at all. The upload handler will still check for conversion and work with it if it happens somehow.
     216        if ( ! defined( 'IMSANITY_ALLOW_CONVERSION' ) && function_exists( 'webp_uploads_is_fallback_enabled' ) && ! webp_uploads_is_fallback_enabled() ) {
     217            define( 'IMSANITY_ALLOW_CONVERSION', true );
     218        }
    215219
    216220        // figure out where the upload is coming from.
     
    226230        $maxh = (int) $maxh;
    227231
    228         list( $oldw, $oldh ) = getimagesize( $oldpath );
     232        $dimensions = getimagesize( $oldpath );
     233        if ( is_array( $dimensions ) && count( $dimensions ) >= 2 ) {
     234            $oldw = $dimensions[0];
     235            $oldh = $dimensions[1];
     236        } else {
     237            imsanity_debug( "could not get dimensions for $oldpath, skipping" );
     238            return $params;
     239        }
    229240
    230241        if ( ( $oldw > $maxw + 1 && $maxw > 0 ) || ( $oldh > $maxh + 1 && $maxh > 0 ) ) {
    231             $quality = imsanity_get_option( 'imsanity_quality', IMSANITY_DEFAULT_QUALITY );
    232242
    233243            $ftype       = imsanity_quick_mimetype( $oldpath );
     
    253263            $original_preempt    = $ewww_preempt_editor;
    254264            $ewww_preempt_editor = true;
    255             $resizeresult        = imsanity_image_resize( $oldpath, $neww, $newh, apply_filters( 'imsanity_crop_image', false ), null, null, $quality );
     265            $resizeresult        = imsanity_image_resize( $oldpath, $neww, $newh, apply_filters( 'imsanity_crop_image', false ) );
    256266            $ewww_preempt_editor = $original_preempt;
    257267
    258268            if ( $resizeresult && ! is_wp_error( $resizeresult ) ) {
    259                 $newpath = $resizeresult;
    260 
     269                $newpath  = $resizeresult;
     270                $new_type = $params['type'];
     271
     272                imsanity_debug( "checking $newpath to see if resize was successful" );
    261273                if ( is_file( $newpath ) && filesize( $newpath ) < filesize( $oldpath ) ) {
     274                    imsanity_debug( 'resized image is smaller, replacing original' );
    262275                    // We saved some file space. remove original and replace with resized image.
     276                    $new_type = imsanity_mimetype( $newpath );
    263277                    unlink( $oldpath );
    264278                    rename( $newpath, $oldpath );
     279                    if ( $new_type && $new_type !== $params['type'] ) {
     280                        imsanity_debug( "mimetype changed from {$params['type']} to $new_type" );
     281                        $params['type'] = $new_type;
     282                        $params['file'] = imsanity_update_extension( $oldpath, $new_type );
     283                        if ( $params['file'] !== $oldpath ) {
     284                            rename( $oldpath, $params['file'] );
     285                        }
     286                        $params['url'] = imsanity_update_extension( $params['url'], $new_type );
     287                        imsanity_debug( "renamed file to match new extension: {$params['file']} / {$params['url']}" );
     288                    }
    265289                } elseif ( is_file( $newpath ) ) {
     290                    imsanity_debug( 'resized image is bigger, discarding' );
    266291                    // The resized image is actually bigger in filesize (most likely due to jpg quality).
    267292                    // Keep the old one and just get rid of the resized image.
     
    269294                }
    270295            } elseif ( false === $resizeresult ) {
     296                imsanity_debug( 'resize returned false, unknown error' );
    271297                return $params;
    272298            } elseif ( is_wp_error( $resizeresult ) ) {
     
    284310                    )
    285311                );
     312                imsanity_debug( 'resize result is wp_error, should have already output error to log' );
    286313            } else {
     314                imsanity_debug( 'unknown resize result, inconceivable!' );
    287315                return $params;
    288316            }
     
    304332 */
    305333function imsanity_convert_to_jpg( $type, $params ) {
     334    imsanity_debug( __FUNCTION__ );
    306335
    307336    if ( apply_filters( 'imsanity_disable_convert', false, $type, $params ) ) {
     337        imsanity_debug( "skipping conversion for {$params['file']}" );
    308338        return $params;
    309339    }
     
    313343    if ( 'bmp' === $type ) {
    314344        if ( ! function_exists( 'imagecreatefrombmp' ) ) {
     345            imsanity_debug( 'imagecreatefrombmp does not exist' );
    315346            return $params;
    316347        }
     
    372403// Outputs the actual column information for each attachment.
    373404add_action( 'manage_media_custom_column', 'imsanity_custom_column', 10, 2 );
     405// Checks for AVIF support and adds it to the allowed mime types.
     406add_filter( 'imsanity_allowed_mimes', 'imsanity_add_avif_support' );
    374407// Checks for WebP support and adds it to the allowed mime types.
    375408add_filter( 'imsanity_allowed_mimes', 'imsanity_add_webp_support' );
  • imsanity/tags/2.9.0/libs/utils.php

    r2969420 r3458273  
    4949    }
    5050    return '';
     51}
     52
     53/**
     54 * Checks the filename for a protocal wrapper (like s3://).
     55 *
     56 * @param string $path The path of the file to check.
     57 * @return bool True if the file contains :// indicating a stream wrapper.
     58 */
     59function imsanity_file_is_stream_wrapped( $path ) {
     60    if ( false !== strpos( $path, '://' ) ) {
     61        return true;
     62    }
     63    return false;
    5164}
    5265
     
    7083        case 'pdf':
    7184            return 'application/pdf';
     85        case 'avif':
     86            return 'image/avif';
    7287        case 'webp':
    7388            return 'image/webp';
     
    7590            return false;
    7691    }
     92}
     93
     94/**
     95 * Check the mimetype of the given file with magic mime strings/patterns.
     96 *
     97 * @param string $path The absolute path to the file.
     98 * @return bool|string A valid mime-type or false.
     99 */
     100function imsanity_mimetype( $path ) {
     101    imsanity_debug( "testing mimetype: $path" );
     102    $type = false;
     103    // For S3 images/files, don't attempt to read the file, just use the quick (filename) mime check.
     104    if ( imsanity_file_is_stream_wrapped( $path ) ) {
     105        return imsanity_quick_mimetype( $path );
     106    }
     107    $path = \realpath( $path );
     108    if ( ! is_file( $path ) ) {
     109        imsanity_debug( "$path is not a file, or out of bounds" );
     110        return $type;
     111    }
     112    if ( ! is_readable( $path ) ) {
     113        imsanity_debug( "$path is not readable" );
     114        return $type;
     115    }
     116    $file_handle   = fopen( $path, 'rb' );
     117    $file_contents = fread( $file_handle, 4096 );
     118    if ( $file_contents ) {
     119        // Read first 12 bytes, which equates to 24 hex characters.
     120        $magic = bin2hex( substr( $file_contents, 0, 12 ) );
     121        imsanity_debug( $magic );
     122        if ( 8 === strpos( $magic, '6674797061766966' ) ) {
     123            $type = 'image/avif';
     124            imsanity_debug( "imsanity type: $type" );
     125            return $type;
     126        }
     127        if ( '424d' === substr( $magic, 0, 4 ) ) {
     128            $type = 'image/bmp';
     129            imsanity_debug( "imsanity type: $type" );
     130            return $type;
     131        }
     132        if ( 0 === strpos( $magic, '52494646' ) && 16 === strpos( $magic, '57454250' ) ) {
     133            $type = 'image/webp';
     134            imsanity_debug( "imsanity type: $type" );
     135            return $type;
     136        }
     137        if ( 'ffd8ff' === substr( $magic, 0, 6 ) ) {
     138            $type = 'image/jpeg';
     139            imsanity_debug( "imsanity type: $type" );
     140            return $type;
     141        }
     142        if ( '89504e470d0a1a0a' === substr( $magic, 0, 16 ) ) {
     143            $type = 'image/png';
     144            imsanity_debug( "imsanity type: $type" );
     145            return $type;
     146        }
     147        if ( '474946383761' === substr( $magic, 0, 12 ) || '474946383961' === substr( $magic, 0, 12 ) ) {
     148            $type = 'image/gif';
     149            imsanity_debug( "imsanity type: $type" );
     150            return $type;
     151        }
     152        if ( '25504446' === substr( $magic, 0, 8 ) ) {
     153            $type = 'application/pdf';
     154            imsanity_debug( "imsanity type: $type" );
     155            return $type;
     156        }
     157        if ( preg_match( '/<svg/', $file_contents ) ) {
     158            $type = 'image/svg+xml';
     159            imsanity_debug( "imsanity type: $type" );
     160            return $type;
     161        }
     162        imsanity_debug( "match not found for file: $magic" );
     163    } else {
     164        imsanity_debug( 'could not open for reading' );
     165    }
     166    return false;
     167}
     168
     169/**
     170 * Update the file extension based on the new mime type.
     171 *
     172 * @param string $path The path of the file to update.
     173 * @param string $new_mime The new mime type.
     174 * @return string The updated path with the new extension.
     175 */
     176function imsanity_update_extension( $path, $new_mime ) {
     177    $extension = '';
     178    switch ( $new_mime ) {
     179        case 'image/jpeg':
     180            $extension = 'jpg';
     181            break;
     182        case 'image/png':
     183            $extension = 'png';
     184            break;
     185        case 'image/gif':
     186            $extension = 'gif';
     187            break;
     188        case 'image/avif':
     189            $extension = 'avif';
     190            break;
     191        case 'image/webp':
     192            $extension = 'webp';
     193            break;
     194        default:
     195            return $path;
     196    }
     197    $pathinfo = pathinfo( $path );
     198    if ( empty( $pathinfo['dirname'] ) || empty( $pathinfo['filename'] ) ) {
     199        return $path;
     200    }
     201    $new_name = trailingslashit( $pathinfo['dirname'] ) . $pathinfo['filename'] . '.' . $extension;
     202    return $new_name;
     203}
     204
     205/**
     206 * Check for AVIF support in the image editor and add to the list of allowed mimes.
     207 *
     208 * @param array $mimes A list of allowed mime types.
     209 * @return array The updated list of mimes after checking AVIF support.
     210 */
     211function imsanity_add_avif_support( $mimes ) {
     212    if ( ! in_array( 'image/avif', $mimes, true ) ) {
     213        if ( class_exists( 'Imagick' ) ) {
     214            $imagick = new Imagick();
     215            $formats = $imagick->queryFormats();
     216            if ( in_array( 'AVIF', $formats, true ) ) {
     217                $mimes[] = 'image/avif';
     218            }
     219        }
     220    }
     221    return $mimes;
    77222}
    78223
     
    120265 */
    121266function imsanity_has_alpha( $filename ) {
     267    imsanity_debug( __FUNCTION__ );
    122268    if ( ! is_file( $filename ) ) {
    123269        return false;
     
    131277    // If we do not have GD and the PNG color type is RGB alpha or Grayscale alpha.
    132278    if ( ! imsanity_gd_support() && ( 4 === $color_type || 6 === $color_type ) ) {
     279        imsanity_debug( "color type $color_type indicates alpha channel in $filename" );
    133280        return true;
    134281    } elseif ( imsanity_gd_support() ) {
    135282        $image = imagecreatefrompng( $filename );
     283        if ( ! $image ) {
     284            imsanity_debug( "could not create GD image from $filename" );
     285            return false;
     286        }
    136287        if ( imagecolortransparent( $image ) >= 0 ) {
     288            imsanity_debug( "$filename has a transparent color" );
    137289            return true;
    138290        }
    139         list( $width, $height ) = getimagesize( $filename );
     291        $image_size = getimagesize( $filename );
     292        if ( empty( $image_size[0] ) || empty( $image_size[1] ) ) {
     293            imsanity_debug( "invalid dimensions for $filename" );
     294            return false;
     295        }
     296        $width  = (int) $image_size[0];
     297        $height = (int) $image_size[1];
    140298        for ( $y = 0; $y < $height; $y++ ) {
    141299            for ( $x = 0; $x < $width; $x++ ) {
     
    143301                $rgb   = imagecolorsforindex( $image, $color );
    144302                if ( $rgb['alpha'] > 0 ) {
     303                    imsanity_debug( "found alpha in $filename at pixel $x, $y" );
    145304                    return true;
    146305                }
     
    175334 */
    176335function imsanity_resize_from_id( $id = 0 ) {
     336    imsanity_debug( __FUNCTION__ );
    177337
    178338    $id = (int) $id;
     
    181341        return;
    182342    }
     343    imsanity_debug( "attempting to resize attachment $id" );
    183344
    184345    $meta = wp_get_attachment_metadata( $id );
     
    196357        }
    197358
    198         // $uploads = wp_upload_dir();
    199359        $oldpath = imsanity_attachment_path( $meta, $id, '', false );
    200360
     
    245405        $maxw = imsanity_get_option( 'imsanity_max_width', IMSANITY_DEFAULT_MAX_WIDTH );
    246406        $maxh = imsanity_get_option( 'imsanity_max_height', IMSANITY_DEFAULT_MAX_HEIGHT );
     407        $oldw = false;
     408        $oldh = false;
    247409
    248410        // method one - slow but accurate, get file size from file itself.
    249         list( $oldw, $oldh ) = getimagesize( $oldpath );
     411        $dimensions = getimagesize( $oldpath );
     412        if ( is_array( $dimensions ) && count( $dimensions ) >= 2 ) {
     413            $oldw = $dimensions[0];
     414            $oldh = $dimensions[1];
     415        }
    250416        // method two - get file size from meta, fast but resize will fail if meta is out of sync.
    251417        if ( ! $oldw || ! $oldh ) {
     
    255421
    256422        if ( ( $oldw > $maxw && $maxw > 0 ) || ( $oldh > $maxh && $maxh > 0 ) ) {
    257             $quality = imsanity_get_option( 'imsanity_quality', IMSANITY_DEFAULT_QUALITY );
    258423
    259424            if ( $maxw > 0 && $maxh > 0 && $oldw >= $maxw && $oldh >= $maxh && ( $oldh > $maxh || $oldw > $maxw ) && apply_filters( 'imsanity_crop_image', false ) ) {
     
    269434                imsanity_debug( "subbing in $source_image for resizing" );
    270435            }
    271             $resizeresult = imsanity_image_resize( $source_image, $neww, $newh, apply_filters( 'imsanity_crop_image', false ), null, null, $quality );
     436            remove_all_filters( 'image_editor_output_format' );
     437            $resizeresult = imsanity_image_resize( $source_image, $neww, $newh, apply_filters( 'imsanity_crop_image', false ) );
    272438
    273439            if ( $resizeresult && ! is_wp_error( $resizeresult ) ) {
    274440                $newpath = $resizeresult;
    275441
    276                 if ( $newpath !== $oldpath && is_file( $newpath ) && filesize( $newpath ) < filesize( $oldpath ) ) {
     442                $new_type = imsanity_mimetype( $newpath );
     443                if ( $new_type && $new_type !== $ftype ) {
     444                    // The resized image is a different format,
     445                    // keep the old one and just get rid of the resized image.
     446                    imsanity_debug( "mime type changed from $ftype to $new_type, not allowed for existing images" );
     447                    if ( is_file( $newpath ) ) {
     448                        unlink( $newpath );
     449                    }
     450                    $results = array(
     451                        'success' => false,
     452                        'id'      => $id,
     453                        /* translators: 1: File-name of the image 2: the error message, translated elsewhere */
     454                        'message' => sprintf( esc_html__( 'ERROR: %1$s (%2$s)', 'imsanity' ), $meta['file'], esc_html__( 'File format/mime type was changed', 'imsanity' ) ),
     455                    );
     456                } elseif ( $newpath !== $oldpath && is_file( $newpath ) && filesize( $newpath ) < filesize( $oldpath ) ) {
    277457                    // we saved some file space. remove original and replace with resized image.
     458                    imsanity_debug( "$newpath is smaller, hurrah!" );
    278459                    unlink( $oldpath );
    279460                    rename( $newpath, $oldpath );
     
    286467                        'success' => true,
    287468                        'id'      => $id,
    288                         /* translators: 1: File-name of the image */
     469                        /* translators: 1: File-name of the image 2: the image width in pixels 3: the image height in pixels */
    289470                        'message' => sprintf( esc_html__( 'OK: %1$s resized to %2$s x %3$s', 'imsanity' ), $meta['file'], $neww . 'w', $newh . 'h' ),
    290471                    );
     
    292473                    // the resized image is actually bigger in filesize (most likely due to jpg quality).
    293474                    // keep the old one and just get rid of the resized image.
     475                    imsanity_debug( "$newpath is larger than $oldpath, bummer..." );
    294476                    if ( is_file( $newpath ) ) {
    295477                        unlink( $newpath );
     
    302484                    );
    303485                } else {
     486                    imsanity_debug( "$newpath === $oldpath, strange?" );
    304487                    $results = array(
    305488                        'success' => false,
     
    310493                }
    311494            } elseif ( false === $resizeresult ) {
     495                imsanity_debug( 'wp_get_image_editor likely missing, no resize result, and no error' );
    312496                $results = array(
    313497                    'success' => false,
     
    317501                );
    318502            } else {
     503                imsanity_debug( 'image editor returned an error: ' . $resizeresult->get_error_message() );
    319504                $results = array(
    320505                    'success' => false,
     
    325510            }
    326511        } else {
     512            imsanity_debug( "$oldpath is already small enough: $oldw x $oldh" );
    327513            $results = array(
    328514                'success' => true,
     
    406592 */
    407593function imsanity_remove_original_image( $id, $meta = null ) {
     594    imsanity_debug( __FUNCTION__ );
    408595    $id = (int) $id;
    409596    if ( empty( $id ) ) {
     
    420607    ) {
    421608        $original_image = imsanity_get_original_image_path( $id, '', $meta );
     609        imsanity_debug( "attempting to remove original image at $original_image" );
    422610        if ( $original_image && is_file( $original_image ) && is_writable( $original_image ) ) {
     611            imsanity_debug( 'original is writable, unlinking!' );
    423612            unlink( $original_image );
    424613        }
    425614        clearstatcache();
    426615        if ( empty( $original_image ) || ! is_file( $original_image ) ) {
     616            imsanity_debug( 'removal successful, updating meta' );
    427617            unset( $meta['original_image'] );
    428618            return $meta;
    429619        }
     620    } elseif ( empty( $meta['original_image'] ) ) {
     621        imsanity_debug( 'no original_image meta found, nothing to remove' );
     622    } elseif ( ! imsanity_get_option( 'imsanity_delete_originals', false ) ) {
     623        imsanity_debug( 'delete_originals option not enabled, not removing' );
     624    } elseif ( ! function_exists( 'wp_get_original_image_path' ) ) {
     625        imsanity_debug( 'wp_get_original_image_path function does not exist, cannot remove' );
    430626    }
    431627    return false;
     
    441637 * @param string $suffix Optional. File suffix.
    442638 * @param string $dest_path Optional. New image file path.
    443  * @param int    $jpeg_quality Optional, default is 82. Image quality level (1-100).
    444639 * @return mixed WP_Error on failure. String with new destination path.
    445640 */
    446 function imsanity_image_resize( $file, $max_w, $max_h, $crop = false, $suffix = null, $dest_path = null, $jpeg_quality = 82 ) {
     641function imsanity_image_resize( $file, $max_w, $max_h, $crop = false, $suffix = null, $dest_path = null ) {
     642    imsanity_debug( __FUNCTION__ );
     643
    447644    if ( function_exists( 'wp_get_image_editor' ) ) {
    448         imsanity_debug( "resizing $file" );
     645        imsanity_debug( "resizing $file to $max_w x $max_h" );
     646        if ( $crop ) {
     647            imsanity_debug( ' cropping enabled' );
     648        }
     649
    449650        $editor = wp_get_image_editor( $file );
    450651        if ( is_wp_error( $editor ) ) {
     652            imsanity_debug( 'get editor error: ' . $editor->get_error_message() );
    451653            return $editor;
    452654        }
    453655
    454         $ftype = imsanity_quick_mimetype( $file );
     656        // Default is 82 for JPG, can be anything from 1-100, though the extremes are kind of, well, extreme...
     657        $quality = imsanity_jpg_quality();
     658        $ftype   = imsanity_quick_mimetype( $file );
    455659        if ( 'image/webp' === $ftype ) {
    456             $jpeg_quality = (int) round( $jpeg_quality * .91 );
    457         }
    458 
    459         $editor->set_quality( min( 92, $jpeg_quality ) );
     660            $quality = imsanity_webp_quality();
     661        } elseif ( 'image/avif' === $ftype ) {
     662            $quality = imsanity_avif_quality();
     663        }
    460664
    461665        // Return 1 to override auto-rotate.
     
    464668        switch ( $orientation ) {
    465669            case 3:
     670                imsanity_debug( 'rotating 180' );
    466671                $editor->rotate( 180 );
    467672                break;
    468673            case 6:
     674                imsanity_debug( 'rotating -90' );
    469675                $editor->rotate( -90 );
    470676                break;
    471677            case 8:
     678                imsanity_debug( 'rotating 90' );
    472679                $editor->rotate( 90 );
    473680                break;
     
    476683        $resized = $editor->resize( $max_w, $max_h, $crop );
    477684        if ( is_wp_error( $resized ) ) {
     685            imsanity_debug( 'resize error: ' . $resized->get_error_message() );
    478686            return $resized;
    479687        }
     
    485693            $dest_file = $editor->generate_filename( 'TMP', $dest_path );
    486694        }
    487 
    488         $saved = $editor->save( $dest_file );
     695        imsanity_debug( "saving resized image to $dest_file with quality $quality" );
     696
     697        $editor->set_quality( min( 92, $quality ) );
     698
     699        // If Modern Image Formats is active, but fallback option is disabled, IMSANITY_ALLOW_CONVERSION will be set to allow AVIF/WebP conversion.
     700        // Otherwise don't allow conversion by any plugin at this stage--MIF will do it later during thumbnail generation.
     701        if ( defined( 'IMSANITY_ALLOW_CONVERSION' ) && IMSANITY_ALLOW_CONVERSION ) {
     702            imsanity_debug( 'Modern Image Formats detected, but no fallback option, conversion allowed' );
     703            add_filter( 'wp_editor_set_quality', 'imsanity_editor_quality', 11, 2 );
     704            $saved = $editor->save( $dest_file );
     705            remove_filter( 'wp_editor_set_quality', 'imsanity_editor_quality', 11 );
     706        } else {
     707            imsanity_debug( "passing mime type $ftype to prevent conversion by Modern Image Formats (or any other plugin)" );
     708            remove_all_filters( 'image_editor_output_format' );
     709            $saved = $editor->save( $dest_file, $ftype );
     710        }
    489711
    490712        if ( is_wp_error( $saved ) ) {
     713            imsanity_debug( 'save error: ' . $saved->get_error_message() );
    491714            return $saved;
    492715        }
    493716
     717        if ( ! empty( $saved['path'] ) && $saved['path'] !== $dest_file && is_file( $saved['path'] ) ) {
     718            $dest_file = $saved['path'];
     719        }
     720        imsanity_debug( "resized image saved to $dest_file" );
    494721        return $dest_file;
    495722    }
  • imsanity/tags/2.9.0/media.php

    r3340527 r3458273  
    8585        }
    8686
    87         list( $imagew, $imageh ) = getimagesize( $file_path );
     87        $dimensions = getimagesize( $file_path );
     88        if ( is_array( $dimensions ) && count( $dimensions ) >= 2 ) {
     89            $imagew = $dimensions[0];
     90            $imageh = $dimensions[1];
     91        }
     92
    8893        if ( empty( $imagew ) || empty( $imageh ) ) {
    8994            $imagew = $meta['width'];
  • imsanity/tags/2.9.0/readme.txt

    r3340527 r3458273  
    33Donate link: https://ewww.io/donate/
    44Tags: image, scale, resize, space saver, quality
    5 Tested up to: 6.8
    6 Stable tag: 2.8.7
     5Tested up to: 6.9
     6Stable tag: 2.9.0
    77License: GPLv3
    88
     
    107107== Changelog ==
    108108
     109= 2.9.0 =
     110*Release Date - February 10, 2026*
     111
     112* added: support for resizing AVIF image uploads
     113* added: settings for WebP and AVIF quality
     114* added: support for Modern Image Formats plugin
     115* added: PHP 8.5 compatibility
     116* fixed: quality settings not applied
     117* fixed: PNG alpha detection may throw errors if PHP GD cannot obtain information from a PNG image
     118
    109119= 2.8.7 =
    110120*Release Date = August 6, 2024*
  • imsanity/tags/2.9.0/settings.php

    r3197618 r3458273  
    3737        esc_html__( 'Imsanity', 'imsanity' ),                 // Menu Title.
    3838        $permissions,                                         // Required permissions.
    39         IMSANITY_PLUGIN_FILE_REL,                             // Slug.
     39        'imsanity-options',                                   // Slug.
    4040        'imsanity_settings_page'                              // Function to call.
    4141    );
     
    5757            esc_html__( 'Imsanity', 'imsanity' ),
    5858            $permissions,
    59             IMSANITY_PLUGIN_FILE_REL,
     59            'imsanity-options',
    6060            'imsanity_network_settings'
    6161        );
    6262    }
     63}
     64
     65/**
     66 * Get the settings link, based on whether we are in a multi-site network admin or not.
     67 *
     68 * @return string The URL for the settings page.
     69 */
     70function imsanity_get_settings_link() {
     71    if ( is_multisite() && is_network_admin() ) {
     72        return network_admin_url( 'settings.php?page=imsanity-options' );
     73    }
     74    return admin_url( 'options-general.php?page=imsanity-options' );
    6375}
    6476
     
    7486    }
    7587    if ( is_multisite() && is_network_admin() ) {
    76         $settings_link = '<a href="' . network_admin_url( 'settings.php?page=' . IMSANITY_PLUGIN_FILE_REL ) . '">' . esc_html__( 'Settings', 'imsanity' ) . '</a>';
     88        $settings_link = '<a href="' . imsanity_get_settings_link() . '">' . esc_html__( 'Settings', 'imsanity' ) . '</a>';
    7789    } else {
    78         $settings_link = '<a href="' . admin_url( 'options-general.php?page=' . IMSANITY_PLUGIN_FILE_REL ) . '">' . esc_html__( 'Settings', 'imsanity' ) . '</a>';
     90        $settings_link = '<a href="' . imsanity_get_settings_link() . '">' . esc_html__( 'Settings', 'imsanity' ) . '</a>';
    7991    }
    8092    array_unshift( $links, $settings_link );
     
    172184    $data->imsanity_png_to_jpg         = IMSANITY_DEFAULT_PNG_TO_JPG;
    173185    $data->imsanity_quality            = IMSANITY_DEFAULT_QUALITY;
     186    $data->imsanity_avif_quality       = IMSANITY_DEFAULT_AVIF_QUALITY;
     187    $data->imsanity_webp_quality       = IMSANITY_DEFAULT_WEBP_QUALITY;
    174188    $data->imsanity_delete_originals   = false;
    175189    return $data;
     
    318332        <tr>
    319333            <th scope="row">
     334                <label for='imsanity_avif_quality'><?php esc_html_e( 'AVIF image quality', 'imsanity' ); ?>
     335            </th>
     336            <td>
     337                <input type='text' id='imsanity_avif_quality' name='imsanity_avif_quality' class='small-text' value='<?php echo (int) $settings->imsanity_avif_quality; ?>' />
     338                <?php esc_html_e( 'Usable values are 1-92.', 'imsanity' ); ?>
     339                <p class='description'><?php esc_html_e( 'Only used when resizing images, does not affect thumbnails.', 'imsanity' ); ?></p>
     340            </td>
     341        </tr>
     342        <tr>
     343            <th scope="row">
     344                <label for='imsanity_webp_quality'><?php esc_html_e( 'WebP image quality', 'imsanity' ); ?>
     345            </th>
     346            <td>
     347                <input type='text' id='imsanity_webp_quality' name='imsanity_webp_quality' class='small-text' value='<?php echo (int) $settings->imsanity_webp_quality; ?>' />
     348                <?php esc_html_e( 'Usable values are 1-92.', 'imsanity' ); ?>
     349                <p class='description'><?php esc_html_e( 'Only used when resizing images, does not affect thumbnails.', 'imsanity' ); ?></p>
     350            </td>
     351        </tr>
     352        <tr>
     353            <th scope="row">
    320354                <label for"imsanity_bmp_to_jpg"><?php esc_html_e( 'Convert BMP to JPG', 'imsanity' ); ?></label>
    321355            </th>
    322356            <td>
    323357                <input type="checkbox" id="imsanity_bmp_to_jpg" name="imsanity_bmp_to_jpg" value="true" <?php checked( $settings->imsanity_bmp_to_jpg ); ?> />
    324                 <?php esc_html_e( 'Only applies to new image uploads, existing BMP images cannot be converted or resized.', 'imsanity' ); ?>
     358                <?php
     359                printf(
     360                    /* translators: %s: link to install EWWW Image Optimizer plugin */
     361                    esc_html__( 'Only applies to new image uploads, existing images may be converted with %s.', 'imsanity' ),
     362                    '<a href="' . esc_url( admin_url( 'plugin-install.php?s=ewww+image+optimizer&tab=search&type=term' ) ) . '">EWWW Image Optimizer</a>'
     363                );
     364                ?>
    325365            </td>
    326366        </tr>
     
    349389            </td>
    350390        </tr>
     391    <?php if ( is_file( imsanity_debug_log_path() ) ) : ?>
     392        <tr>
     393            <th><?php esc_html_e( 'Debug Log', 'imsanity' ); ?></th>
     394            <td>
     395                <p>
     396                    <a target='_blank' href='<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?action=imsanity_view_debug_log' ), 'imsanity-options' ) ); ?>'><?php esc_html_e( 'View Log', 'imsanity' ); ?></a> -
     397                    <a href='<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?action=imsanity_delete_debug_log' ), 'imsanity-options' ) ); ?>'><?php esc_html_e( 'Clear Log', 'imsanity' ); ?></a>
     398                </p>
     399            </td>
     400        </tr>
     401    <?php endif; ?>
    351402    </table>
    352403
     
    387438    $data->imsanity_png_to_jpg         = ! empty( $_POST['imsanity_png_to_jpg'] );
    388439    $data->imsanity_quality            = isset( $_POST['imsanity_quality'] ) ? imsanity_jpg_quality( intval( $_POST['imsanity_quality'] ) ) : 82;
     440    $data->imsanity_avif_quality       = isset( $_POST['imsanity_avif_quality'] ) ? imsanity_avif_quality( intval( $_POST['imsanity_avif_quality'] ) ) : 86;
     441    $data->imsanity_webp_quality       = isset( $_POST['imsanity_webp_quality'] ) ? imsanity_webp_quality( intval( $_POST['imsanity_webp_quality'] ) ) : 86;
    389442    $data->imsanity_delete_originals   = ! empty( $_POST['imsanity_delete_originals'] );
    390443
    391     $success = $wpdb->update(
     444    $wpdb->update(
    392445        $wpdb->imsanity_ms,
    393446        array( 'data' => maybe_serialize( $data ) ),
     
    501554    add_option( 'imsanity_png_to_jpg', $settings->imsanity_png_to_jpg, '', false );
    502555    add_option( 'imsanity_quality', $settings->imsanity_quality, '', false );
     556    add_option( 'imsanity_avif_quality', $settings->imsanity_avif_quality, '', false );
     557    add_option( 'imsanity_webp_quality', $settings->imsanity_webp_quality, '', false );
    503558    add_option( 'imsanity_delete_originals', $settings->imsanity_delete_originals, '', false );
    504559    if ( ! get_option( 'imsanity_version' ) ) {
     
    528583    register_setting( 'imsanity-settings-group', 'imsanity_png_to_jpg', 'boolval' );
    529584    register_setting( 'imsanity-settings-group', 'imsanity_quality', 'imsanity_jpg_quality' );
     585    register_setting( 'imsanity-settings-group', 'imsanity_avif_quality', 'imsanity_avif_quality' );
     586    register_setting( 'imsanity-settings-group', 'imsanity_webp_quality', 'imsanity_webp_quality' );
    530587    register_setting( 'imsanity-settings-group', 'imsanity_delete_originals', 'boolval' );
     588}
     589
     590/**
     591 * Set the quality based on the mime type of the image being resized.
     592 *
     593 * @param int    $quality The quality currently set.
     594 * @param string $mime_type The mime type of the image being resized.
     595 * @return int The (potentially) adjusted quality level.
     596 */
     597function imsanity_editor_quality( $quality, $mime_type = '' ) {
     598    if ( 'image/avif' === $mime_type ) {
     599        $new_quality = imsanity_avif_quality();
     600    } elseif ( 'image/webp' === $mime_type ) {
     601        $new_quality = imsanity_webp_quality();
     602    } elseif ( 'image/jpeg' === $mime_type ) {
     603        $new_quality = imsanity_jpg_quality();
     604    }
     605    if ( ! empty( $new_quality ) && $new_quality > 0 && $new_quality <= 92 ) {
     606        return $new_quality;
     607    }
     608    return $quality;
    531609}
    532610
     
    545623    } else {
    546624        return IMSANITY_DEFAULT_QUALITY;
     625    }
     626}
     627
     628/**
     629 * Validate and return the AVIF quality setting.
     630 *
     631 * @param int $quality The AVIF quality currently set.
     632 * @return int The (potentially) adjusted quality level.
     633 */
     634function imsanity_avif_quality( $quality = null ) {
     635    if ( is_null( $quality ) ) {
     636        $quality = get_option( 'imsanity_avif_quality' );
     637    }
     638    if ( preg_match( '/^(100|[1-9][0-9]?)$/', $quality ) ) {
     639        return (int) $quality;
     640    } else {
     641        return IMSANITY_DEFAULT_AVIF_QUALITY;
     642    }
     643}
     644
     645/**
     646 * Validate and return the WebP quality setting.
     647 *
     648 * @param int $quality The WebP quality currently set.
     649 * @return int The (potentially) adjusted quality level.
     650 */
     651function imsanity_webp_quality( $quality = null ) {
     652    if ( is_null( $quality ) ) {
     653        $quality = get_option( 'imsanity_webp_quality' );
     654    }
     655    if ( preg_match( '/^(100|[1-9][0-9]?)$/', $quality ) ) {
     656        return (int) $quality;
     657    } else {
     658        return IMSANITY_DEFAULT_WEBP_QUALITY;
    547659    }
    548660}
     
    779891
    780892        <tr>
    781         <th scope="row"><?php esc_html_e( 'Images uploaded elsewhere (Theme headers, backgrounds, logos, etc)', 'imsanity' ); ?></th>
    782         <td>
    783             <label for="imsanity_max_width_other"><?php esc_html_e( 'Max Width', 'imsanity' ); ?></label> <input type="number" step="1" min="0" class="small-text" name="imsanity_max_width_other" value="<?php echo (int) get_option( 'imsanity_max_width_other', IMSANITY_DEFAULT_MAX_WIDTH ); ?>" />
    784             <label for="imsanity_max_height_other"><?php esc_html_e( 'Max Height', 'imsanity' ); ?></label> <input type="number" step="1" min="0" class="small-text" name="imsanity_max_height_other" value="<?php echo (int) get_option( 'imsanity_max_height_other', IMSANITY_DEFAULT_MAX_HEIGHT ); ?>" /> <?php esc_html_e( 'in pixels, enter 0 to disable', 'imsanity' ); ?>
    785         </td>
    786         </tr>
    787 
     893            <th scope="row"><?php esc_html_e( 'Images uploaded elsewhere (Theme headers, backgrounds, logos, etc)', 'imsanity' ); ?></th>
     894            <td>
     895                <label for="imsanity_max_width_other"><?php esc_html_e( 'Max Width', 'imsanity' ); ?></label> <input type="number" step="1" min="0" class="small-text" name="imsanity_max_width_other" value="<?php echo (int) get_option( 'imsanity_max_width_other', IMSANITY_DEFAULT_MAX_WIDTH ); ?>" />
     896                <label for="imsanity_max_height_other"><?php esc_html_e( 'Max Height', 'imsanity' ); ?></label> <input type="number" step="1" min="0" class="small-text" name="imsanity_max_height_other" value="<?php echo (int) get_option( 'imsanity_max_height_other', IMSANITY_DEFAULT_MAX_HEIGHT ); ?>" /> <?php esc_html_e( 'in pixels, enter 0 to disable', 'imsanity' ); ?>
     897            </td>
     898        </tr>
    788899
    789900        <tr>
     
    800911        <tr>
    801912            <th scope="row">
     913                <label for='imsanity_avif_quality' ><?php esc_html_e( 'AVIF image quality', 'imsanity' ); ?>
     914            </th>
     915            <td>
     916                <input type='text' id='imsanity_avif_quality' name='imsanity_avif_quality' class='small-text' value='<?php echo (int) imsanity_avif_quality(); ?>' />
     917                <?php esc_html_e( 'Usable values are 1-92.', 'imsanity' ); ?>
     918                <p class='description'><?php esc_html_e( 'Only used when resizing images, does not affect thumbnails.', 'imsanity' ); ?></p>
     919            </td>
     920        </tr>
     921
     922        <tr>
     923            <th scope="row">
     924                <label for='imsanity_webp_quality' ><?php esc_html_e( 'WebP image quality', 'imsanity' ); ?>
     925            </th>
     926            <td>
     927                <input type='text' id='imsanity_webp_quality' name='imsanity_webp_quality' class='small-text' value='<?php echo (int) imsanity_webp_quality(); ?>' />
     928                <?php esc_html_e( 'Usable values are 1-92.', 'imsanity' ); ?>
     929                <p class='description'><?php esc_html_e( 'Only used when resizing images, does not affect thumbnails.', 'imsanity' ); ?></p>
     930            </td>
     931        </tr>
     932
     933        <tr>
     934            <th scope="row">
    802935                <label for="imsanity_bmp_to_jpg"><?php esc_html_e( 'Convert BMP To JPG', 'imsanity' ); ?></label>
    803936            </th>
    804937            <td>
    805938                <input type="checkbox" id="imsanity_bmp_to_jpg" name="imsanity_bmp_to_jpg" value="true" <?php checked( (bool) get_option( 'imsanity_bmp_to_jpg', IMSANITY_DEFAULT_BMP_TO_JPG ) ); ?> />
    806                 <?php esc_html_e( 'Only applies to new image uploads, existing BMP images cannot be converted or resized.', 'imsanity' ); ?>
     939                <?php
     940                printf(
     941                    /* translators: %s: link to install EWWW Image Optimizer plugin */
     942                    esc_html__( 'Only applies to new image uploads, existing images may be converted with %s.', 'imsanity' ),
     943                    '<a href="' . esc_url( admin_url( 'plugin-install.php?s=ewww+image+optimizer&tab=search&type=term' ) ) . '">EWWW Image Optimizer</a>'
     944                );
     945                ?>
    807946            </td>
    808947        </tr>
     
    831970            </td>
    832971        </tr>
     972    <?php if ( is_file( imsanity_debug_log_path() ) ) : ?>
     973        <tr>
     974            <th><?php esc_html_e( 'Debug Log', 'imsanity' ); ?></th>
     975            <td>
     976                <p>
     977                    <a target='_blank' href='<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?action=imsanity_view_debug_log' ), 'imsanity-options' ) ); ?>'><?php esc_html_e( 'View Log', 'imsanity' ); ?></a> -
     978                    <a href='<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?action=imsanity_delete_debug_log' ), 'imsanity-options' ) ); ?>'><?php esc_html_e( 'Clear Log', 'imsanity' ); ?></a>
     979                </p>
     980            </td>
     981        </tr>
     982    <?php endif; ?>
    833983    </table>
    834984
  • imsanity/trunk/changelog.txt

    r3340527 r3458273  
     1= 2.9.0 =
     2*Release Date - February 10, 2026*
     3
     4* added: support for resizing AVIF image uploads
     5* added: settings for WebP and AVIF quality
     6* added: support for Modern Image Formats plugin
     7* added: PHP 8.5 compatibility
     8* fixed: quality settings not applied
     9* fixed: PNG alpha detection may throw errors if PHP GD cannot obtain information from a PNG image
     10
    111= 2.8.7 =
    212*Release Date = August 6, 2024*
  • imsanity/trunk/class-imsanity-cli.php

    r2969420 r3458273  
    7676            $path = get_attached_file( $id );
    7777            if ( $path ) {
    78                 list( $imagew, $imageh ) = getimagesize( $path );
     78                $dimensions = getimagesize( $path );
     79                if ( is_array( $dimensions ) && count( $dimensions ) >= 2 ) {
     80                    $imagew = $dimensions[0];
     81                    $imageh = $dimensions[1];
     82                }
    7983            }
    8084            if ( empty( $imagew ) || empty( $imageh ) ) {
  • imsanity/trunk/imsanity.php

    r3340527 r3458273  
    1515Author: Exactly WWW
    1616Domain Path: /languages
    17 Version: 2.8.7
    18 Requires at least: 6.5
     17Version: 2.9.0
     18Requires at least: 6.6
    1919Requires PHP: 7.4
    2020Author URI: https://ewww.io/about/
     
    2626}
    2727
    28 define( 'IMSANITY_VERSION', '2.8.7' );
     28define( 'IMSANITY_VERSION', '2.9.0' );
    2929define( 'IMSANITY_SCHEMA_VERSION', '1.1' );
    3030
     
    3434define( 'IMSANITY_DEFAULT_PNG_TO_JPG', false );
    3535define( 'IMSANITY_DEFAULT_QUALITY', 82 );
     36define( 'IMSANITY_DEFAULT_AVIF_QUALITY', 86 );
     37define( 'IMSANITY_DEFAULT_WEBP_QUALITY', 86 );
    3638
    3739define( 'IMSANITY_SOURCE_POST', 1 );
     
    4547 */
    4648define( 'IMSANITY_PLUGIN_FILE', __FILE__ );
     49
     50/**
     51 * The directory path of the main plugin file.
     52 *
     53 * @var string IMSANITY_PLUGIN_DIR
     54 */
     55define( 'IMSANITY_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
     56
    4757/**
    4858 * The path of the main plugin file, relative to the plugins/ folder.
     
    6272 * Import supporting libraries.
    6373 */
     74require_once plugin_dir_path( __FILE__ ) . 'libs/debug.php';
    6475require_once plugin_dir_path( __FILE__ ) . 'libs/utils.php';
    6576require_once plugin_dir_path( __FILE__ ) . 'settings.php';
     
    6879if ( defined( 'WP_CLI' ) && WP_CLI ) {
    6980    require_once plugin_dir_path( __FILE__ ) . 'class-imsanity-cli.php';
    70 }
    71 
    72 /**
    73  * Use the EWWW IO debugging functions (if available).
    74  *
    75  * @param string $message A message to send to the debugger.
    76  */
    77 function imsanity_debug( $message ) {
    78     if ( function_exists( 'ewwwio_debug_message' ) ) {
    79         if ( ! is_string( $message ) ) {
    80             if ( function_exists( 'print_r' ) ) {
    81                 $message = print_r( $message, true );
    82             } else {
    83                 $message = 'not a string, print_r disabled';
    84             }
    85         }
    86         ewwwio_debug_message( $message );
    87         if ( function_exists( 'ewww_image_optimizer_debug_log' ) ) {
    88             ewww_image_optimizer_debug_log();
    89         }
    90     }
    9181}
    9282
     
    175165 */
    176166function imsanity_handle_upload( $params ) {
     167    imsanity_debug( __FUNCTION__ );
     168
     169    if ( empty( $params['file'] ) || empty( $params['type'] ) ) {
     170        imsanity_debug( 'missing file or type parameter, skipping' );
     171        return $params;
     172    }
    177173
    178174    // If "noresize" is included in the filename then we will bypass imsanity scaling.
    179175    if ( strpos( $params['file'], 'noresize' ) !== false ) {
     176        imsanity_debug( "skipping {$params['file']}" );
    180177        return $params;
    181178    }
    182179
    183180    if ( apply_filters( 'imsanity_skip_image', false, $params['file'] ) ) {
     181        imsanity_debug( "skipping {$params['file']} per filter" );
    184182        return $params;
    185183    }
     
    194192    }
    195193
    196     // Make sure this is a type of image that we want to convert and that it exists.
     194    // Store the path for reference in case $params is modified.
    197195    $oldpath = $params['file'];
    198196
    199197    // Let folks filter the allowed mime-types for resizing.
     198    // Also allows conditional support for WebP and AVIF if the server supports it.
    200199    $allowed_types = apply_filters( 'imsanity_allowed_mimes', array( 'image/png', 'image/gif', 'image/jpeg' ), $oldpath );
    201200    if ( is_string( $allowed_types ) ) {
     
    213212        in_array( $params['type'], $allowed_types, true )
    214213    ) {
     214        // If the Modern Image Formats plugin is active but fallback mode is disabled, permit conversion to AVIF/WebP during upload by defining IMSANITY_ALLOW_CONVERSION.
     215        // Otherwise, no conversion should be allowed at all. The upload handler will still check for conversion and work with it if it happens somehow.
     216        if ( ! defined( 'IMSANITY_ALLOW_CONVERSION' ) && function_exists( 'webp_uploads_is_fallback_enabled' ) && ! webp_uploads_is_fallback_enabled() ) {
     217            define( 'IMSANITY_ALLOW_CONVERSION', true );
     218        }
    215219
    216220        // figure out where the upload is coming from.
     
    226230        $maxh = (int) $maxh;
    227231
    228         list( $oldw, $oldh ) = getimagesize( $oldpath );
     232        $dimensions = getimagesize( $oldpath );
     233        if ( is_array( $dimensions ) && count( $dimensions ) >= 2 ) {
     234            $oldw = $dimensions[0];
     235            $oldh = $dimensions[1];
     236        } else {
     237            imsanity_debug( "could not get dimensions for $oldpath, skipping" );
     238            return $params;
     239        }
    229240
    230241        if ( ( $oldw > $maxw + 1 && $maxw > 0 ) || ( $oldh > $maxh + 1 && $maxh > 0 ) ) {
    231             $quality = imsanity_get_option( 'imsanity_quality', IMSANITY_DEFAULT_QUALITY );
    232242
    233243            $ftype       = imsanity_quick_mimetype( $oldpath );
     
    253263            $original_preempt    = $ewww_preempt_editor;
    254264            $ewww_preempt_editor = true;
    255             $resizeresult        = imsanity_image_resize( $oldpath, $neww, $newh, apply_filters( 'imsanity_crop_image', false ), null, null, $quality );
     265            $resizeresult        = imsanity_image_resize( $oldpath, $neww, $newh, apply_filters( 'imsanity_crop_image', false ) );
    256266            $ewww_preempt_editor = $original_preempt;
    257267
    258268            if ( $resizeresult && ! is_wp_error( $resizeresult ) ) {
    259                 $newpath = $resizeresult;
    260 
     269                $newpath  = $resizeresult;
     270                $new_type = $params['type'];
     271
     272                imsanity_debug( "checking $newpath to see if resize was successful" );
    261273                if ( is_file( $newpath ) && filesize( $newpath ) < filesize( $oldpath ) ) {
     274                    imsanity_debug( 'resized image is smaller, replacing original' );
    262275                    // We saved some file space. remove original and replace with resized image.
     276                    $new_type = imsanity_mimetype( $newpath );
    263277                    unlink( $oldpath );
    264278                    rename( $newpath, $oldpath );
     279                    if ( $new_type && $new_type !== $params['type'] ) {
     280                        imsanity_debug( "mimetype changed from {$params['type']} to $new_type" );
     281                        $params['type'] = $new_type;
     282                        $params['file'] = imsanity_update_extension( $oldpath, $new_type );
     283                        if ( $params['file'] !== $oldpath ) {
     284                            rename( $oldpath, $params['file'] );
     285                        }
     286                        $params['url'] = imsanity_update_extension( $params['url'], $new_type );
     287                        imsanity_debug( "renamed file to match new extension: {$params['file']} / {$params['url']}" );
     288                    }
    265289                } elseif ( is_file( $newpath ) ) {
     290                    imsanity_debug( 'resized image is bigger, discarding' );
    266291                    // The resized image is actually bigger in filesize (most likely due to jpg quality).
    267292                    // Keep the old one and just get rid of the resized image.
     
    269294                }
    270295            } elseif ( false === $resizeresult ) {
     296                imsanity_debug( 'resize returned false, unknown error' );
    271297                return $params;
    272298            } elseif ( is_wp_error( $resizeresult ) ) {
     
    284310                    )
    285311                );
     312                imsanity_debug( 'resize result is wp_error, should have already output error to log' );
    286313            } else {
     314                imsanity_debug( 'unknown resize result, inconceivable!' );
    287315                return $params;
    288316            }
     
    304332 */
    305333function imsanity_convert_to_jpg( $type, $params ) {
     334    imsanity_debug( __FUNCTION__ );
    306335
    307336    if ( apply_filters( 'imsanity_disable_convert', false, $type, $params ) ) {
     337        imsanity_debug( "skipping conversion for {$params['file']}" );
    308338        return $params;
    309339    }
     
    313343    if ( 'bmp' === $type ) {
    314344        if ( ! function_exists( 'imagecreatefrombmp' ) ) {
     345            imsanity_debug( 'imagecreatefrombmp does not exist' );
    315346            return $params;
    316347        }
     
    372403// Outputs the actual column information for each attachment.
    373404add_action( 'manage_media_custom_column', 'imsanity_custom_column', 10, 2 );
     405// Checks for AVIF support and adds it to the allowed mime types.
     406add_filter( 'imsanity_allowed_mimes', 'imsanity_add_avif_support' );
    374407// Checks for WebP support and adds it to the allowed mime types.
    375408add_filter( 'imsanity_allowed_mimes', 'imsanity_add_webp_support' );
  • imsanity/trunk/libs/utils.php

    r2969420 r3458273  
    4949    }
    5050    return '';
     51}
     52
     53/**
     54 * Checks the filename for a protocal wrapper (like s3://).
     55 *
     56 * @param string $path The path of the file to check.
     57 * @return bool True if the file contains :// indicating a stream wrapper.
     58 */
     59function imsanity_file_is_stream_wrapped( $path ) {
     60    if ( false !== strpos( $path, '://' ) ) {
     61        return true;
     62    }
     63    return false;
    5164}
    5265
     
    7083        case 'pdf':
    7184            return 'application/pdf';
     85        case 'avif':
     86            return 'image/avif';
    7287        case 'webp':
    7388            return 'image/webp';
     
    7590            return false;
    7691    }
     92}
     93
     94/**
     95 * Check the mimetype of the given file with magic mime strings/patterns.
     96 *
     97 * @param string $path The absolute path to the file.
     98 * @return bool|string A valid mime-type or false.
     99 */
     100function imsanity_mimetype( $path ) {
     101    imsanity_debug( "testing mimetype: $path" );
     102    $type = false;
     103    // For S3 images/files, don't attempt to read the file, just use the quick (filename) mime check.
     104    if ( imsanity_file_is_stream_wrapped( $path ) ) {
     105        return imsanity_quick_mimetype( $path );
     106    }
     107    $path = \realpath( $path );
     108    if ( ! is_file( $path ) ) {
     109        imsanity_debug( "$path is not a file, or out of bounds" );
     110        return $type;
     111    }
     112    if ( ! is_readable( $path ) ) {
     113        imsanity_debug( "$path is not readable" );
     114        return $type;
     115    }
     116    $file_handle   = fopen( $path, 'rb' );
     117    $file_contents = fread( $file_handle, 4096 );
     118    if ( $file_contents ) {
     119        // Read first 12 bytes, which equates to 24 hex characters.
     120        $magic = bin2hex( substr( $file_contents, 0, 12 ) );
     121        imsanity_debug( $magic );
     122        if ( 8 === strpos( $magic, '6674797061766966' ) ) {
     123            $type = 'image/avif';
     124            imsanity_debug( "imsanity type: $type" );
     125            return $type;
     126        }
     127        if ( '424d' === substr( $magic, 0, 4 ) ) {
     128            $type = 'image/bmp';
     129            imsanity_debug( "imsanity type: $type" );
     130            return $type;
     131        }
     132        if ( 0 === strpos( $magic, '52494646' ) && 16 === strpos( $magic, '57454250' ) ) {
     133            $type = 'image/webp';
     134            imsanity_debug( "imsanity type: $type" );
     135            return $type;
     136        }
     137        if ( 'ffd8ff' === substr( $magic, 0, 6 ) ) {
     138            $type = 'image/jpeg';
     139            imsanity_debug( "imsanity type: $type" );
     140            return $type;
     141        }
     142        if ( '89504e470d0a1a0a' === substr( $magic, 0, 16 ) ) {
     143            $type = 'image/png';
     144            imsanity_debug( "imsanity type: $type" );
     145            return $type;
     146        }
     147        if ( '474946383761' === substr( $magic, 0, 12 ) || '474946383961' === substr( $magic, 0, 12 ) ) {
     148            $type = 'image/gif';
     149            imsanity_debug( "imsanity type: $type" );
     150            return $type;
     151        }
     152        if ( '25504446' === substr( $magic, 0, 8 ) ) {
     153            $type = 'application/pdf';
     154            imsanity_debug( "imsanity type: $type" );
     155            return $type;
     156        }
     157        if ( preg_match( '/<svg/', $file_contents ) ) {
     158            $type = 'image/svg+xml';
     159            imsanity_debug( "imsanity type: $type" );
     160            return $type;
     161        }
     162        imsanity_debug( "match not found for file: $magic" );
     163    } else {
     164        imsanity_debug( 'could not open for reading' );
     165    }
     166    return false;
     167}
     168
     169/**
     170 * Update the file extension based on the new mime type.
     171 *
     172 * @param string $path The path of the file to update.
     173 * @param string $new_mime The new mime type.
     174 * @return string The updated path with the new extension.
     175 */
     176function imsanity_update_extension( $path, $new_mime ) {
     177    $extension = '';
     178    switch ( $new_mime ) {
     179        case 'image/jpeg':
     180            $extension = 'jpg';
     181            break;
     182        case 'image/png':
     183            $extension = 'png';
     184            break;
     185        case 'image/gif':
     186            $extension = 'gif';
     187            break;
     188        case 'image/avif':
     189            $extension = 'avif';
     190            break;
     191        case 'image/webp':
     192            $extension = 'webp';
     193            break;
     194        default:
     195            return $path;
     196    }
     197    $pathinfo = pathinfo( $path );
     198    if ( empty( $pathinfo['dirname'] ) || empty( $pathinfo['filename'] ) ) {
     199        return $path;
     200    }
     201    $new_name = trailingslashit( $pathinfo['dirname'] ) . $pathinfo['filename'] . '.' . $extension;
     202    return $new_name;
     203}
     204
     205/**
     206 * Check for AVIF support in the image editor and add to the list of allowed mimes.
     207 *
     208 * @param array $mimes A list of allowed mime types.
     209 * @return array The updated list of mimes after checking AVIF support.
     210 */
     211function imsanity_add_avif_support( $mimes ) {
     212    if ( ! in_array( 'image/avif', $mimes, true ) ) {
     213        if ( class_exists( 'Imagick' ) ) {
     214            $imagick = new Imagick();
     215            $formats = $imagick->queryFormats();
     216            if ( in_array( 'AVIF', $formats, true ) ) {
     217                $mimes[] = 'image/avif';
     218            }
     219        }
     220    }
     221    return $mimes;
    77222}
    78223
     
    120265 */
    121266function imsanity_has_alpha( $filename ) {
     267    imsanity_debug( __FUNCTION__ );
    122268    if ( ! is_file( $filename ) ) {
    123269        return false;
     
    131277    // If we do not have GD and the PNG color type is RGB alpha or Grayscale alpha.
    132278    if ( ! imsanity_gd_support() && ( 4 === $color_type || 6 === $color_type ) ) {
     279        imsanity_debug( "color type $color_type indicates alpha channel in $filename" );
    133280        return true;
    134281    } elseif ( imsanity_gd_support() ) {
    135282        $image = imagecreatefrompng( $filename );
     283        if ( ! $image ) {
     284            imsanity_debug( "could not create GD image from $filename" );
     285            return false;
     286        }
    136287        if ( imagecolortransparent( $image ) >= 0 ) {
     288            imsanity_debug( "$filename has a transparent color" );
    137289            return true;
    138290        }
    139         list( $width, $height ) = getimagesize( $filename );
     291        $image_size = getimagesize( $filename );
     292        if ( empty( $image_size[0] ) || empty( $image_size[1] ) ) {
     293            imsanity_debug( "invalid dimensions for $filename" );
     294            return false;
     295        }
     296        $width  = (int) $image_size[0];
     297        $height = (int) $image_size[1];
    140298        for ( $y = 0; $y < $height; $y++ ) {
    141299            for ( $x = 0; $x < $width; $x++ ) {
     
    143301                $rgb   = imagecolorsforindex( $image, $color );
    144302                if ( $rgb['alpha'] > 0 ) {
     303                    imsanity_debug( "found alpha in $filename at pixel $x, $y" );
    145304                    return true;
    146305                }
     
    175334 */
    176335function imsanity_resize_from_id( $id = 0 ) {
     336    imsanity_debug( __FUNCTION__ );
    177337
    178338    $id = (int) $id;
     
    181341        return;
    182342    }
     343    imsanity_debug( "attempting to resize attachment $id" );
    183344
    184345    $meta = wp_get_attachment_metadata( $id );
     
    196357        }
    197358
    198         // $uploads = wp_upload_dir();
    199359        $oldpath = imsanity_attachment_path( $meta, $id, '', false );
    200360
     
    245405        $maxw = imsanity_get_option( 'imsanity_max_width', IMSANITY_DEFAULT_MAX_WIDTH );
    246406        $maxh = imsanity_get_option( 'imsanity_max_height', IMSANITY_DEFAULT_MAX_HEIGHT );
     407        $oldw = false;
     408        $oldh = false;
    247409
    248410        // method one - slow but accurate, get file size from file itself.
    249         list( $oldw, $oldh ) = getimagesize( $oldpath );
     411        $dimensions = getimagesize( $oldpath );
     412        if ( is_array( $dimensions ) && count( $dimensions ) >= 2 ) {
     413            $oldw = $dimensions[0];
     414            $oldh = $dimensions[1];
     415        }
    250416        // method two - get file size from meta, fast but resize will fail if meta is out of sync.
    251417        if ( ! $oldw || ! $oldh ) {
     
    255421
    256422        if ( ( $oldw > $maxw && $maxw > 0 ) || ( $oldh > $maxh && $maxh > 0 ) ) {
    257             $quality = imsanity_get_option( 'imsanity_quality', IMSANITY_DEFAULT_QUALITY );
    258423
    259424            if ( $maxw > 0 && $maxh > 0 && $oldw >= $maxw && $oldh >= $maxh && ( $oldh > $maxh || $oldw > $maxw ) && apply_filters( 'imsanity_crop_image', false ) ) {
     
    269434                imsanity_debug( "subbing in $source_image for resizing" );
    270435            }
    271             $resizeresult = imsanity_image_resize( $source_image, $neww, $newh, apply_filters( 'imsanity_crop_image', false ), null, null, $quality );
     436            remove_all_filters( 'image_editor_output_format' );
     437            $resizeresult = imsanity_image_resize( $source_image, $neww, $newh, apply_filters( 'imsanity_crop_image', false ) );
    272438
    273439            if ( $resizeresult && ! is_wp_error( $resizeresult ) ) {
    274440                $newpath = $resizeresult;
    275441
    276                 if ( $newpath !== $oldpath && is_file( $newpath ) && filesize( $newpath ) < filesize( $oldpath ) ) {
     442                $new_type = imsanity_mimetype( $newpath );
     443                if ( $new_type && $new_type !== $ftype ) {
     444                    // The resized image is a different format,
     445                    // keep the old one and just get rid of the resized image.
     446                    imsanity_debug( "mime type changed from $ftype to $new_type, not allowed for existing images" );
     447                    if ( is_file( $newpath ) ) {
     448                        unlink( $newpath );
     449                    }
     450                    $results = array(
     451                        'success' => false,
     452                        'id'      => $id,
     453                        /* translators: 1: File-name of the image 2: the error message, translated elsewhere */
     454                        'message' => sprintf( esc_html__( 'ERROR: %1$s (%2$s)', 'imsanity' ), $meta['file'], esc_html__( 'File format/mime type was changed', 'imsanity' ) ),
     455                    );
     456                } elseif ( $newpath !== $oldpath && is_file( $newpath ) && filesize( $newpath ) < filesize( $oldpath ) ) {
    277457                    // we saved some file space. remove original and replace with resized image.
     458                    imsanity_debug( "$newpath is smaller, hurrah!" );
    278459                    unlink( $oldpath );
    279460                    rename( $newpath, $oldpath );
     
    286467                        'success' => true,
    287468                        'id'      => $id,
    288                         /* translators: 1: File-name of the image */
     469                        /* translators: 1: File-name of the image 2: the image width in pixels 3: the image height in pixels */
    289470                        'message' => sprintf( esc_html__( 'OK: %1$s resized to %2$s x %3$s', 'imsanity' ), $meta['file'], $neww . 'w', $newh . 'h' ),
    290471                    );
     
    292473                    // the resized image is actually bigger in filesize (most likely due to jpg quality).
    293474                    // keep the old one and just get rid of the resized image.
     475                    imsanity_debug( "$newpath is larger than $oldpath, bummer..." );
    294476                    if ( is_file( $newpath ) ) {
    295477                        unlink( $newpath );
     
    302484                    );
    303485                } else {
     486                    imsanity_debug( "$newpath === $oldpath, strange?" );
    304487                    $results = array(
    305488                        'success' => false,
     
    310493                }
    311494            } elseif ( false === $resizeresult ) {
     495                imsanity_debug( 'wp_get_image_editor likely missing, no resize result, and no error' );
    312496                $results = array(
    313497                    'success' => false,
     
    317501                );
    318502            } else {
     503                imsanity_debug( 'image editor returned an error: ' . $resizeresult->get_error_message() );
    319504                $results = array(
    320505                    'success' => false,
     
    325510            }
    326511        } else {
     512            imsanity_debug( "$oldpath is already small enough: $oldw x $oldh" );
    327513            $results = array(
    328514                'success' => true,
     
    406592 */
    407593function imsanity_remove_original_image( $id, $meta = null ) {
     594    imsanity_debug( __FUNCTION__ );
    408595    $id = (int) $id;
    409596    if ( empty( $id ) ) {
     
    420607    ) {
    421608        $original_image = imsanity_get_original_image_path( $id, '', $meta );
     609        imsanity_debug( "attempting to remove original image at $original_image" );
    422610        if ( $original_image && is_file( $original_image ) && is_writable( $original_image ) ) {
     611            imsanity_debug( 'original is writable, unlinking!' );
    423612            unlink( $original_image );
    424613        }
    425614        clearstatcache();
    426615        if ( empty( $original_image ) || ! is_file( $original_image ) ) {
     616            imsanity_debug( 'removal successful, updating meta' );
    427617            unset( $meta['original_image'] );
    428618            return $meta;
    429619        }
     620    } elseif ( empty( $meta['original_image'] ) ) {
     621        imsanity_debug( 'no original_image meta found, nothing to remove' );
     622    } elseif ( ! imsanity_get_option( 'imsanity_delete_originals', false ) ) {
     623        imsanity_debug( 'delete_originals option not enabled, not removing' );
     624    } elseif ( ! function_exists( 'wp_get_original_image_path' ) ) {
     625        imsanity_debug( 'wp_get_original_image_path function does not exist, cannot remove' );
    430626    }
    431627    return false;
     
    441637 * @param string $suffix Optional. File suffix.
    442638 * @param string $dest_path Optional. New image file path.
    443  * @param int    $jpeg_quality Optional, default is 82. Image quality level (1-100).
    444639 * @return mixed WP_Error on failure. String with new destination path.
    445640 */
    446 function imsanity_image_resize( $file, $max_w, $max_h, $crop = false, $suffix = null, $dest_path = null, $jpeg_quality = 82 ) {
     641function imsanity_image_resize( $file, $max_w, $max_h, $crop = false, $suffix = null, $dest_path = null ) {
     642    imsanity_debug( __FUNCTION__ );
     643
    447644    if ( function_exists( 'wp_get_image_editor' ) ) {
    448         imsanity_debug( "resizing $file" );
     645        imsanity_debug( "resizing $file to $max_w x $max_h" );
     646        if ( $crop ) {
     647            imsanity_debug( ' cropping enabled' );
     648        }
     649
    449650        $editor = wp_get_image_editor( $file );
    450651        if ( is_wp_error( $editor ) ) {
     652            imsanity_debug( 'get editor error: ' . $editor->get_error_message() );
    451653            return $editor;
    452654        }
    453655
    454         $ftype = imsanity_quick_mimetype( $file );
     656        // Default is 82 for JPG, can be anything from 1-100, though the extremes are kind of, well, extreme...
     657        $quality = imsanity_jpg_quality();
     658        $ftype   = imsanity_quick_mimetype( $file );
    455659        if ( 'image/webp' === $ftype ) {
    456             $jpeg_quality = (int) round( $jpeg_quality * .91 );
    457         }
    458 
    459         $editor->set_quality( min( 92, $jpeg_quality ) );
     660            $quality = imsanity_webp_quality();
     661        } elseif ( 'image/avif' === $ftype ) {
     662            $quality = imsanity_avif_quality();
     663        }
    460664
    461665        // Return 1 to override auto-rotate.
     
    464668        switch ( $orientation ) {
    465669            case 3:
     670                imsanity_debug( 'rotating 180' );
    466671                $editor->rotate( 180 );
    467672                break;
    468673            case 6:
     674                imsanity_debug( 'rotating -90' );
    469675                $editor->rotate( -90 );
    470676                break;
    471677            case 8:
     678                imsanity_debug( 'rotating 90' );
    472679                $editor->rotate( 90 );
    473680                break;
     
    476683        $resized = $editor->resize( $max_w, $max_h, $crop );
    477684        if ( is_wp_error( $resized ) ) {
     685            imsanity_debug( 'resize error: ' . $resized->get_error_message() );
    478686            return $resized;
    479687        }
     
    485693            $dest_file = $editor->generate_filename( 'TMP', $dest_path );
    486694        }
    487 
    488         $saved = $editor->save( $dest_file );
     695        imsanity_debug( "saving resized image to $dest_file with quality $quality" );
     696
     697        $editor->set_quality( min( 92, $quality ) );
     698
     699        // If Modern Image Formats is active, but fallback option is disabled, IMSANITY_ALLOW_CONVERSION will be set to allow AVIF/WebP conversion.
     700        // Otherwise don't allow conversion by any plugin at this stage--MIF will do it later during thumbnail generation.
     701        if ( defined( 'IMSANITY_ALLOW_CONVERSION' ) && IMSANITY_ALLOW_CONVERSION ) {
     702            imsanity_debug( 'Modern Image Formats detected, but no fallback option, conversion allowed' );
     703            add_filter( 'wp_editor_set_quality', 'imsanity_editor_quality', 11, 2 );
     704            $saved = $editor->save( $dest_file );
     705            remove_filter( 'wp_editor_set_quality', 'imsanity_editor_quality', 11 );
     706        } else {
     707            imsanity_debug( "passing mime type $ftype to prevent conversion by Modern Image Formats (or any other plugin)" );
     708            remove_all_filters( 'image_editor_output_format' );
     709            $saved = $editor->save( $dest_file, $ftype );
     710        }
    489711
    490712        if ( is_wp_error( $saved ) ) {
     713            imsanity_debug( 'save error: ' . $saved->get_error_message() );
    491714            return $saved;
    492715        }
    493716
     717        if ( ! empty( $saved['path'] ) && $saved['path'] !== $dest_file && is_file( $saved['path'] ) ) {
     718            $dest_file = $saved['path'];
     719        }
     720        imsanity_debug( "resized image saved to $dest_file" );
    494721        return $dest_file;
    495722    }
  • imsanity/trunk/media.php

    r3340527 r3458273  
    8585        }
    8686
    87         list( $imagew, $imageh ) = getimagesize( $file_path );
     87        $dimensions = getimagesize( $file_path );
     88        if ( is_array( $dimensions ) && count( $dimensions ) >= 2 ) {
     89            $imagew = $dimensions[0];
     90            $imageh = $dimensions[1];
     91        }
     92
    8893        if ( empty( $imagew ) || empty( $imageh ) ) {
    8994            $imagew = $meta['width'];
  • imsanity/trunk/readme.txt

    r3340527 r3458273  
    33Donate link: https://ewww.io/donate/
    44Tags: image, scale, resize, space saver, quality
    5 Tested up to: 6.8
    6 Stable tag: 2.8.7
     5Tested up to: 6.9
     6Stable tag: 2.9.0
    77License: GPLv3
    88
     
    107107== Changelog ==
    108108
     109= 2.9.0 =
     110*Release Date - February 10, 2026*
     111
     112* added: support for resizing AVIF image uploads
     113* added: settings for WebP and AVIF quality
     114* added: support for Modern Image Formats plugin
     115* added: PHP 8.5 compatibility
     116* fixed: quality settings not applied
     117* fixed: PNG alpha detection may throw errors if PHP GD cannot obtain information from a PNG image
     118
    109119= 2.8.7 =
    110120*Release Date = August 6, 2024*
  • imsanity/trunk/settings.php

    r3197618 r3458273  
    3737        esc_html__( 'Imsanity', 'imsanity' ),                 // Menu Title.
    3838        $permissions,                                         // Required permissions.
    39         IMSANITY_PLUGIN_FILE_REL,                             // Slug.
     39        'imsanity-options',                                   // Slug.
    4040        'imsanity_settings_page'                              // Function to call.
    4141    );
     
    5757            esc_html__( 'Imsanity', 'imsanity' ),
    5858            $permissions,
    59             IMSANITY_PLUGIN_FILE_REL,
     59            'imsanity-options',
    6060            'imsanity_network_settings'
    6161        );
    6262    }
     63}
     64
     65/**
     66 * Get the settings link, based on whether we are in a multi-site network admin or not.
     67 *
     68 * @return string The URL for the settings page.
     69 */
     70function imsanity_get_settings_link() {
     71    if ( is_multisite() && is_network_admin() ) {
     72        return network_admin_url( 'settings.php?page=imsanity-options' );
     73    }
     74    return admin_url( 'options-general.php?page=imsanity-options' );
    6375}
    6476
     
    7486    }
    7587    if ( is_multisite() && is_network_admin() ) {
    76         $settings_link = '<a href="' . network_admin_url( 'settings.php?page=' . IMSANITY_PLUGIN_FILE_REL ) . '">' . esc_html__( 'Settings', 'imsanity' ) . '</a>';
     88        $settings_link = '<a href="' . imsanity_get_settings_link() . '">' . esc_html__( 'Settings', 'imsanity' ) . '</a>';
    7789    } else {
    78         $settings_link = '<a href="' . admin_url( 'options-general.php?page=' . IMSANITY_PLUGIN_FILE_REL ) . '">' . esc_html__( 'Settings', 'imsanity' ) . '</a>';
     90        $settings_link = '<a href="' . imsanity_get_settings_link() . '">' . esc_html__( 'Settings', 'imsanity' ) . '</a>';
    7991    }
    8092    array_unshift( $links, $settings_link );
     
    172184    $data->imsanity_png_to_jpg         = IMSANITY_DEFAULT_PNG_TO_JPG;
    173185    $data->imsanity_quality            = IMSANITY_DEFAULT_QUALITY;
     186    $data->imsanity_avif_quality       = IMSANITY_DEFAULT_AVIF_QUALITY;
     187    $data->imsanity_webp_quality       = IMSANITY_DEFAULT_WEBP_QUALITY;
    174188    $data->imsanity_delete_originals   = false;
    175189    return $data;
     
    318332        <tr>
    319333            <th scope="row">
     334                <label for='imsanity_avif_quality'><?php esc_html_e( 'AVIF image quality', 'imsanity' ); ?>
     335            </th>
     336            <td>
     337                <input type='text' id='imsanity_avif_quality' name='imsanity_avif_quality' class='small-text' value='<?php echo (int) $settings->imsanity_avif_quality; ?>' />
     338                <?php esc_html_e( 'Usable values are 1-92.', 'imsanity' ); ?>
     339                <p class='description'><?php esc_html_e( 'Only used when resizing images, does not affect thumbnails.', 'imsanity' ); ?></p>
     340            </td>
     341        </tr>
     342        <tr>
     343            <th scope="row">
     344                <label for='imsanity_webp_quality'><?php esc_html_e( 'WebP image quality', 'imsanity' ); ?>
     345            </th>
     346            <td>
     347                <input type='text' id='imsanity_webp_quality' name='imsanity_webp_quality' class='small-text' value='<?php echo (int) $settings->imsanity_webp_quality; ?>' />
     348                <?php esc_html_e( 'Usable values are 1-92.', 'imsanity' ); ?>
     349                <p class='description'><?php esc_html_e( 'Only used when resizing images, does not affect thumbnails.', 'imsanity' ); ?></p>
     350            </td>
     351        </tr>
     352        <tr>
     353            <th scope="row">
    320354                <label for"imsanity_bmp_to_jpg"><?php esc_html_e( 'Convert BMP to JPG', 'imsanity' ); ?></label>
    321355            </th>
    322356            <td>
    323357                <input type="checkbox" id="imsanity_bmp_to_jpg" name="imsanity_bmp_to_jpg" value="true" <?php checked( $settings->imsanity_bmp_to_jpg ); ?> />
    324                 <?php esc_html_e( 'Only applies to new image uploads, existing BMP images cannot be converted or resized.', 'imsanity' ); ?>
     358                <?php
     359                printf(
     360                    /* translators: %s: link to install EWWW Image Optimizer plugin */
     361                    esc_html__( 'Only applies to new image uploads, existing images may be converted with %s.', 'imsanity' ),
     362                    '<a href="' . esc_url( admin_url( 'plugin-install.php?s=ewww+image+optimizer&tab=search&type=term' ) ) . '">EWWW Image Optimizer</a>'
     363                );
     364                ?>
    325365            </td>
    326366        </tr>
     
    349389            </td>
    350390        </tr>
     391    <?php if ( is_file( imsanity_debug_log_path() ) ) : ?>
     392        <tr>
     393            <th><?php esc_html_e( 'Debug Log', 'imsanity' ); ?></th>
     394            <td>
     395                <p>
     396                    <a target='_blank' href='<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?action=imsanity_view_debug_log' ), 'imsanity-options' ) ); ?>'><?php esc_html_e( 'View Log', 'imsanity' ); ?></a> -
     397                    <a href='<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?action=imsanity_delete_debug_log' ), 'imsanity-options' ) ); ?>'><?php esc_html_e( 'Clear Log', 'imsanity' ); ?></a>
     398                </p>
     399            </td>
     400        </tr>
     401    <?php endif; ?>
    351402    </table>
    352403
     
    387438    $data->imsanity_png_to_jpg         = ! empty( $_POST['imsanity_png_to_jpg'] );
    388439    $data->imsanity_quality            = isset( $_POST['imsanity_quality'] ) ? imsanity_jpg_quality( intval( $_POST['imsanity_quality'] ) ) : 82;
     440    $data->imsanity_avif_quality       = isset( $_POST['imsanity_avif_quality'] ) ? imsanity_avif_quality( intval( $_POST['imsanity_avif_quality'] ) ) : 86;
     441    $data->imsanity_webp_quality       = isset( $_POST['imsanity_webp_quality'] ) ? imsanity_webp_quality( intval( $_POST['imsanity_webp_quality'] ) ) : 86;
    389442    $data->imsanity_delete_originals   = ! empty( $_POST['imsanity_delete_originals'] );
    390443
    391     $success = $wpdb->update(
     444    $wpdb->update(
    392445        $wpdb->imsanity_ms,
    393446        array( 'data' => maybe_serialize( $data ) ),
     
    501554    add_option( 'imsanity_png_to_jpg', $settings->imsanity_png_to_jpg, '', false );
    502555    add_option( 'imsanity_quality', $settings->imsanity_quality, '', false );
     556    add_option( 'imsanity_avif_quality', $settings->imsanity_avif_quality, '', false );
     557    add_option( 'imsanity_webp_quality', $settings->imsanity_webp_quality, '', false );
    503558    add_option( 'imsanity_delete_originals', $settings->imsanity_delete_originals, '', false );
    504559    if ( ! get_option( 'imsanity_version' ) ) {
     
    528583    register_setting( 'imsanity-settings-group', 'imsanity_png_to_jpg', 'boolval' );
    529584    register_setting( 'imsanity-settings-group', 'imsanity_quality', 'imsanity_jpg_quality' );
     585    register_setting( 'imsanity-settings-group', 'imsanity_avif_quality', 'imsanity_avif_quality' );
     586    register_setting( 'imsanity-settings-group', 'imsanity_webp_quality', 'imsanity_webp_quality' );
    530587    register_setting( 'imsanity-settings-group', 'imsanity_delete_originals', 'boolval' );
     588}
     589
     590/**
     591 * Set the quality based on the mime type of the image being resized.
     592 *
     593 * @param int    $quality The quality currently set.
     594 * @param string $mime_type The mime type of the image being resized.
     595 * @return int The (potentially) adjusted quality level.
     596 */
     597function imsanity_editor_quality( $quality, $mime_type = '' ) {
     598    if ( 'image/avif' === $mime_type ) {
     599        $new_quality = imsanity_avif_quality();
     600    } elseif ( 'image/webp' === $mime_type ) {
     601        $new_quality = imsanity_webp_quality();
     602    } elseif ( 'image/jpeg' === $mime_type ) {
     603        $new_quality = imsanity_jpg_quality();
     604    }
     605    if ( ! empty( $new_quality ) && $new_quality > 0 && $new_quality <= 92 ) {
     606        return $new_quality;
     607    }
     608    return $quality;
    531609}
    532610
     
    545623    } else {
    546624        return IMSANITY_DEFAULT_QUALITY;
     625    }
     626}
     627
     628/**
     629 * Validate and return the AVIF quality setting.
     630 *
     631 * @param int $quality The AVIF quality currently set.
     632 * @return int The (potentially) adjusted quality level.
     633 */
     634function imsanity_avif_quality( $quality = null ) {
     635    if ( is_null( $quality ) ) {
     636        $quality = get_option( 'imsanity_avif_quality' );
     637    }
     638    if ( preg_match( '/^(100|[1-9][0-9]?)$/', $quality ) ) {
     639        return (int) $quality;
     640    } else {
     641        return IMSANITY_DEFAULT_AVIF_QUALITY;
     642    }
     643}
     644
     645/**
     646 * Validate and return the WebP quality setting.
     647 *
     648 * @param int $quality The WebP quality currently set.
     649 * @return int The (potentially) adjusted quality level.
     650 */
     651function imsanity_webp_quality( $quality = null ) {
     652    if ( is_null( $quality ) ) {
     653        $quality = get_option( 'imsanity_webp_quality' );
     654    }
     655    if ( preg_match( '/^(100|[1-9][0-9]?)$/', $quality ) ) {
     656        return (int) $quality;
     657    } else {
     658        return IMSANITY_DEFAULT_WEBP_QUALITY;
    547659    }
    548660}
     
    779891
    780892        <tr>
    781         <th scope="row"><?php esc_html_e( 'Images uploaded elsewhere (Theme headers, backgrounds, logos, etc)', 'imsanity' ); ?></th>
    782         <td>
    783             <label for="imsanity_max_width_other"><?php esc_html_e( 'Max Width', 'imsanity' ); ?></label> <input type="number" step="1" min="0" class="small-text" name="imsanity_max_width_other" value="<?php echo (int) get_option( 'imsanity_max_width_other', IMSANITY_DEFAULT_MAX_WIDTH ); ?>" />
    784             <label for="imsanity_max_height_other"><?php esc_html_e( 'Max Height', 'imsanity' ); ?></label> <input type="number" step="1" min="0" class="small-text" name="imsanity_max_height_other" value="<?php echo (int) get_option( 'imsanity_max_height_other', IMSANITY_DEFAULT_MAX_HEIGHT ); ?>" /> <?php esc_html_e( 'in pixels, enter 0 to disable', 'imsanity' ); ?>
    785         </td>
    786         </tr>
    787 
     893            <th scope="row"><?php esc_html_e( 'Images uploaded elsewhere (Theme headers, backgrounds, logos, etc)', 'imsanity' ); ?></th>
     894            <td>
     895                <label for="imsanity_max_width_other"><?php esc_html_e( 'Max Width', 'imsanity' ); ?></label> <input type="number" step="1" min="0" class="small-text" name="imsanity_max_width_other" value="<?php echo (int) get_option( 'imsanity_max_width_other', IMSANITY_DEFAULT_MAX_WIDTH ); ?>" />
     896                <label for="imsanity_max_height_other"><?php esc_html_e( 'Max Height', 'imsanity' ); ?></label> <input type="number" step="1" min="0" class="small-text" name="imsanity_max_height_other" value="<?php echo (int) get_option( 'imsanity_max_height_other', IMSANITY_DEFAULT_MAX_HEIGHT ); ?>" /> <?php esc_html_e( 'in pixels, enter 0 to disable', 'imsanity' ); ?>
     897            </td>
     898        </tr>
    788899
    789900        <tr>
     
    800911        <tr>
    801912            <th scope="row">
     913                <label for='imsanity_avif_quality' ><?php esc_html_e( 'AVIF image quality', 'imsanity' ); ?>
     914            </th>
     915            <td>
     916                <input type='text' id='imsanity_avif_quality' name='imsanity_avif_quality' class='small-text' value='<?php echo (int) imsanity_avif_quality(); ?>' />
     917                <?php esc_html_e( 'Usable values are 1-92.', 'imsanity' ); ?>
     918                <p class='description'><?php esc_html_e( 'Only used when resizing images, does not affect thumbnails.', 'imsanity' ); ?></p>
     919            </td>
     920        </tr>
     921
     922        <tr>
     923            <th scope="row">
     924                <label for='imsanity_webp_quality' ><?php esc_html_e( 'WebP image quality', 'imsanity' ); ?>
     925            </th>
     926            <td>
     927                <input type='text' id='imsanity_webp_quality' name='imsanity_webp_quality' class='small-text' value='<?php echo (int) imsanity_webp_quality(); ?>' />
     928                <?php esc_html_e( 'Usable values are 1-92.', 'imsanity' ); ?>
     929                <p class='description'><?php esc_html_e( 'Only used when resizing images, does not affect thumbnails.', 'imsanity' ); ?></p>
     930            </td>
     931        </tr>
     932
     933        <tr>
     934            <th scope="row">
    802935                <label for="imsanity_bmp_to_jpg"><?php esc_html_e( 'Convert BMP To JPG', 'imsanity' ); ?></label>
    803936            </th>
    804937            <td>
    805938                <input type="checkbox" id="imsanity_bmp_to_jpg" name="imsanity_bmp_to_jpg" value="true" <?php checked( (bool) get_option( 'imsanity_bmp_to_jpg', IMSANITY_DEFAULT_BMP_TO_JPG ) ); ?> />
    806                 <?php esc_html_e( 'Only applies to new image uploads, existing BMP images cannot be converted or resized.', 'imsanity' ); ?>
     939                <?php
     940                printf(
     941                    /* translators: %s: link to install EWWW Image Optimizer plugin */
     942                    esc_html__( 'Only applies to new image uploads, existing images may be converted with %s.', 'imsanity' ),
     943                    '<a href="' . esc_url( admin_url( 'plugin-install.php?s=ewww+image+optimizer&tab=search&type=term' ) ) . '">EWWW Image Optimizer</a>'
     944                );
     945                ?>
    807946            </td>
    808947        </tr>
     
    831970            </td>
    832971        </tr>
     972    <?php if ( is_file( imsanity_debug_log_path() ) ) : ?>
     973        <tr>
     974            <th><?php esc_html_e( 'Debug Log', 'imsanity' ); ?></th>
     975            <td>
     976                <p>
     977                    <a target='_blank' href='<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?action=imsanity_view_debug_log' ), 'imsanity-options' ) ); ?>'><?php esc_html_e( 'View Log', 'imsanity' ); ?></a> -
     978                    <a href='<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?action=imsanity_delete_debug_log' ), 'imsanity-options' ) ); ?>'><?php esc_html_e( 'Clear Log', 'imsanity' ); ?></a>
     979                </p>
     980            </td>
     981        </tr>
     982    <?php endif; ?>
    833983    </table>
    834984
Note: See TracChangeset for help on using the changeset viewer.