From cbaa88cb5a8c16654a86c9273e6e1e3f0d4930ed Mon Sep 17 00:00:00 2001 From: Sergey Biryukov Date: Sun, 16 Aug 2020 13:31:57 +0000 Subject: [PATCH] Code Modernization: Introduce `is_gd_image()` to check for PHP 8 `GdImage` object instances. In PHP 8, the GD extension uses `GdImage` objects instead of resources for its underlying data structures. This updates the existing `is_resource()` calls for image resources in core to accomodate for `GdImage` instances as well. Props ayeshrajans, jrf. Fixes #50833. git-svn-id: https://develop.svn.wordpress.org/trunk@48798 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-admin/includes/image-edit.php | 52 +++++++++++-------- src/wp-admin/includes/image.php | 43 ++++++++++++--- src/wp-includes/class-wp-image-editor-gd.php | 32 ++++++------ .../class-wp-image-editor-imagick.php | 2 + src/wp-includes/deprecated.php | 5 +- src/wp-includes/media.php | 10 ++-- tests/phpunit/tests/image/functions.php | 22 ++++++++ 7 files changed, 116 insertions(+), 50 deletions(-) diff --git a/src/wp-admin/includes/image-edit.php b/src/wp-admin/includes/image-edit.php index 1363f00ae7..c3bd58613c 100644 --- a/src/wp-admin/includes/image-edit.php +++ b/src/wp-admin/includes/image-edit.php @@ -291,8 +291,8 @@ function wp_stream_image( $image, $mime_type, $attachment_id ) { * @since 2.9.0 * @deprecated 3.5.0 Use {@see 'image_editor_save_pre'} instead. * - * @param resource $image Image resource to be streamed. - * @param int $attachment_id The attachment post ID. + * @param resource|GdImage $image Image resource to be streamed. + * @param int $attachment_id The attachment post ID. */ $image = apply_filters_deprecated( 'image_save_pre', array( $image, $attachment_id ), '3.5.0', 'image_editor_save_pre' ); @@ -420,19 +420,22 @@ function _image_get_preview_ratio( $w, $h ) { * @see WP_Image_Editor::rotate() * * @ignore - * @param resource $img Image resource. - * @param float|int $angle Image rotation angle, in degrees. - * @return resource|false GD image resource, false otherwise. + * @param resource|GdImage $img Image resource. + * @param float|int $angle Image rotation angle, in degrees. + * @return resource|GdImage|false GD image resource or GdImage instance, false otherwise. */ function _rotate_image_resource( $img, $angle ) { _deprecated_function( __FUNCTION__, '3.5.0', 'WP_Image_Editor::rotate()' ); + if ( function_exists( 'imagerotate' ) ) { $rotated = imagerotate( $img, $angle, 0 ); - if ( is_resource( $rotated ) ) { + + if ( is_gd_image( $rotated ) ) { imagedestroy( $img ); $img = $rotated; } } + return $img; } @@ -444,17 +447,19 @@ function _rotate_image_resource( $img, $angle ) { * @see WP_Image_Editor::flip() * * @ignore - * @param resource $img Image resource. - * @param bool $horz Whether to flip horizontally. - * @param bool $vert Whether to flip vertically. - * @return resource (maybe) flipped image resource. + * @param resource|GdImage $img Image resource or GdImage instance. + * @param bool $horz Whether to flip horizontally. + * @param bool $vert Whether to flip vertically. + * @return resource|GdImage (maybe) flipped image resource or GdImage instance. */ function _flip_image_resource( $img, $horz, $vert ) { _deprecated_function( __FUNCTION__, '3.5.0', 'WP_Image_Editor::flip()' ); + $w = imagesx( $img ); $h = imagesy( $img ); $dst = wp_imagecreatetruecolor( $w, $h ); - if ( is_resource( $dst ) ) { + + if ( is_gd_image( $dst ) ) { $sx = $vert ? ( $w - 1 ) : 0; $sy = $horz ? ( $h - 1 ) : 0; $sw = $vert ? -$w : $w; @@ -465,6 +470,7 @@ function _flip_image_resource( $img, $horz, $vert ) { $img = $dst; } } + return $img; } @@ -474,21 +480,23 @@ function _flip_image_resource( $img, $horz, $vert ) { * @since 2.9.0 * * @ignore - * @param resource $img Image resource. - * @param float $x Source point x-coordinate. - * @param float $y Source point y-coordinate. - * @param float $w Source width. - * @param float $h Source height. - * @return resource (maybe) cropped image resource. + * @param resource|GdImage $img Image resource or GdImage instance. + * @param float $x Source point x-coordinate. + * @param float $y Source point y-coordinate. + * @param float $w Source width. + * @param float $h Source height. + * @return resource|GdImage (maybe) cropped image resource or GdImage instance. */ function _crop_image_resource( $img, $x, $y, $w, $h ) { $dst = wp_imagecreatetruecolor( $w, $h ); - if ( is_resource( $dst ) ) { + + if ( is_gd_image( $dst ) ) { if ( imagecopy( $dst, $img, 0, 0, $x, $y, $w, $h ) ) { imagedestroy( $img ); $img = $dst; } } + return $img; } @@ -502,7 +510,7 @@ function _crop_image_resource( $img, $x, $y, $w, $h ) { * @return WP_Image_Editor WP_Image_Editor instance with changes applied. */ function image_edit_apply_changes( $image, $changes ) { - if ( is_resource( $image ) ) { + if ( is_gd_image( $image ) ) { /* translators: 1: $image, 2: WP_Image_Editor */ _deprecated_argument( __FUNCTION__, '3.5.0', sprintf( __( '%1$s needs to be a %2$s object.' ), '$image', 'WP_Image_Editor' ) ); } @@ -566,7 +574,7 @@ function image_edit_apply_changes( $image, $changes ) { * @param array $changes Array of change operations. */ $image = apply_filters( 'wp_image_editor_before_change', $image, $changes ); - } elseif ( is_resource( $image ) ) { + } elseif ( is_gd_image( $image ) ) { /** * Filters the GD image resource before applying changes to the image. @@ -574,8 +582,8 @@ function image_edit_apply_changes( $image, $changes ) { * @since 2.9.0 * @deprecated 3.5.0 Use {@see 'wp_image_editor_before_change'} instead. * - * @param resource $image GD image resource. - * @param array $changes Array of change operations. + * @param resource|GdImage $image GD image resource or GdImage instance. + * @param array $changes Array of change operations. */ $image = apply_filters_deprecated( 'image_edit_before_change', array( $image, $changes ), '3.5.0', 'wp_image_editor_before_change' ); } diff --git a/src/wp-admin/includes/image.php b/src/wp-admin/includes/image.php index f0a3d38bb4..0245226c46 100644 --- a/src/wp-admin/includes/image.php +++ b/src/wp-admin/includes/image.php @@ -912,15 +912,39 @@ function file_is_displayable_image( $path ) { return apply_filters( 'file_is_displayable_image', $result, $path ); } +/** + * Determines whether the value is an acceptable type for GD image functions. + * + * In PHP 8.0, the GD extension uses GdImage objects for its data structures. + * This function checks if the passed value is either a resource of type `gd` + * or a GdImage object instance. Any other type will return false. + * + * @since 5.6.0 + * + * @param resource|GdImage|false $image A value to check for the type for. + * @return bool True if $image is either a GD image resource or GdImage instance, + * false otherwise. + */ +function is_gd_image( $image ) { + if ( is_resource( $image ) && 'gd' === get_resource_type( $image ) + || is_object( $image ) && $image instanceof GdImage + ) { + return true; + } + + return false; +} + /** * Load an image resource for editing. * * @since 2.9.0 * * @param string $attachment_id Attachment ID. - * @param string $mime_type Image mime type. - * @param string $size Optional. Image size, defaults to 'full'. - * @return resource|false The resulting image resource on success, false on failure. + * @param string $mime_type Image mime type. + * @param string $size Optional. Image size. Default 'full'. + * @return resource|GdImage|false The resulting image resource or GdImage instance on success, + * false on failure. */ function load_image_to_edit( $attachment_id, $mime_type, $size = 'full' ) { $filepath = _load_image_to_edit_path( $attachment_id, $size ); @@ -942,22 +966,25 @@ function load_image_to_edit( $attachment_id, $mime_type, $size = 'full' ) { $image = false; break; } - if ( is_resource( $image ) ) { + + if ( is_gd_image( $image ) ) { /** * Filters the current image being loaded for editing. * * @since 2.9.0 * - * @param resource $image Current image. - * @param string $attachment_id Attachment ID. - * @param string $size Image size. + * @param resource|GdImage $image Current image. + * @param string $attachment_id Attachment ID. + * @param string $size Image size. */ $image = apply_filters( 'load_image_to_edit', $image, $attachment_id, $size ); + if ( function_exists( 'imagealphablending' ) && function_exists( 'imagesavealpha' ) ) { imagealphablending( $image, false ); imagesavealpha( $image, true ); } } + return $image; } @@ -971,7 +998,7 @@ function load_image_to_edit( $attachment_id, $mime_type, $size = 'full' ) { * @access private * * @param string $attachment_id Attachment ID. - * @param string $size Optional. Image size, defaults to 'full'. + * @param string $size Optional. Image size. Default 'full'. * @return string|false File path or url on success, false on failure. */ function _load_image_to_edit_path( $attachment_id, $size = 'full' ) { diff --git a/src/wp-includes/class-wp-image-editor-gd.php b/src/wp-includes/class-wp-image-editor-gd.php index 3ead252dfe..4fa1fe24fa 100644 --- a/src/wp-includes/class-wp-image-editor-gd.php +++ b/src/wp-includes/class-wp-image-editor-gd.php @@ -17,7 +17,7 @@ class WP_Image_Editor_GD extends WP_Image_Editor { /** * GD Resource. * - * @var resource + * @var resource|GdImage */ protected $image; @@ -95,7 +95,7 @@ class WP_Image_Editor_GD extends WP_Image_Editor { $this->image = @imagecreatefromstring( file_get_contents( $this->file ) ); - if ( ! is_resource( $this->image ) ) { + if ( ! is_gd_image( $this->image ) ) { return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file ); } @@ -138,11 +138,11 @@ class WP_Image_Editor_GD extends WP_Image_Editor { /** * Resizes current image. - * Wraps _resize, since _resize returns a GD Resource. * - * At minimum, either a height or width must be provided. - * If one of the two is set to null, the resize will - * maintain aspect ratio according to the provided dimension. + * Wraps `::_resize()` which returns a GD resource or GdImage instance. + * + * At minimum, either a height or width must be provided. If one of the two is set + * to null, the resize will maintain aspect ratio according to the provided dimension. * * @since 3.5.0 * @@ -158,7 +158,7 @@ class WP_Image_Editor_GD extends WP_Image_Editor { $resized = $this->_resize( $max_w, $max_h, $crop ); - if ( is_resource( $resized ) ) { + if ( is_gd_image( $resized ) ) { imagedestroy( $this->image ); $this->image = $resized; return true; @@ -174,7 +174,7 @@ class WP_Image_Editor_GD extends WP_Image_Editor { * @param int $max_w * @param int $max_h * @param bool|array $crop - * @return resource|WP_Error + * @return resource|GdImage|WP_Error */ protected function _resize( $max_w, $max_h, $crop = false ) { $dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop ); @@ -188,7 +188,7 @@ class WP_Image_Editor_GD extends WP_Image_Editor { $resized = wp_imagecreatetruecolor( $dst_w, $dst_h ); imagecopyresampled( $resized, $this->image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ); - if ( is_resource( $resized ) ) { + if ( is_gd_image( $resized ) ) { $this->update_size( $dst_w, $dst_h ); return $resized; } @@ -329,7 +329,7 @@ class WP_Image_Editor_GD extends WP_Image_Editor { imagecopyresampled( $dst, $this->image, 0, 0, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ); - if ( is_resource( $dst ) ) { + if ( is_gd_image( $dst ) ) { imagedestroy( $this->image ); $this->image = $dst; $this->update_size(); @@ -353,7 +353,7 @@ class WP_Image_Editor_GD extends WP_Image_Editor { $transparency = imagecolorallocatealpha( $this->image, 255, 255, 255, 127 ); $rotated = imagerotate( $this->image, $angle, $transparency ); - if ( is_resource( $rotated ) ) { + if ( is_gd_image( $rotated ) ) { imagealphablending( $rotated, true ); imagesavealpha( $rotated, true ); imagedestroy( $this->image ); @@ -362,6 +362,7 @@ class WP_Image_Editor_GD extends WP_Image_Editor { return true; } } + return new WP_Error( 'image_rotate_error', __( 'Image rotate failed.' ), $this->file ); } @@ -379,7 +380,7 @@ class WP_Image_Editor_GD extends WP_Image_Editor { $h = $this->size['height']; $dst = wp_imagecreatetruecolor( $w, $h ); - if ( is_resource( $dst ) ) { + if ( is_gd_image( $dst ) ) { $sx = $vert ? ( $w - 1 ) : 0; $sy = $horz ? ( $h - 1 ) : 0; $sw = $vert ? -$w : $w; @@ -391,6 +392,7 @@ class WP_Image_Editor_GD extends WP_Image_Editor { return true; } } + return new WP_Error( 'image_flip_error', __( 'Image flip failed.' ), $this->file ); } @@ -415,9 +417,9 @@ class WP_Image_Editor_GD extends WP_Image_Editor { } /** - * @param resource $image - * @param string|null $filename - * @param string|null $mime_type + * @param resource|GdImage $image + * @param string|null $filename + * @param string|null $mime_type * @return array|WP_Error */ protected function _save( $image, $filename = null, $mime_type = null ) { diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php index 650aaa8ca5..dbdf1607cd 100644 --- a/src/wp-includes/class-wp-image-editor-imagick.php +++ b/src/wp-includes/class-wp-image-editor-imagick.php @@ -548,6 +548,7 @@ class WP_Image_Editor_Imagick extends WP_Image_Editor { } catch ( Exception $e ) { return new WP_Error( 'image_crop_error', $e->getMessage() ); } + return $this->update_size(); } @@ -582,6 +583,7 @@ class WP_Image_Editor_Imagick extends WP_Image_Editor { } catch ( Exception $e ) { return new WP_Error( 'image_rotate_error', $e->getMessage() ); } + return true; } diff --git a/src/wp-includes/deprecated.php b/src/wp-includes/deprecated.php index a586c336c7..bde6bdd430 100644 --- a/src/wp-includes/deprecated.php +++ b/src/wp-includes/deprecated.php @@ -3196,7 +3196,8 @@ function _get_post_ancestors( &$post ) { * @see wp_get_image_editor() * * @param string $file Filename of the image to load. - * @return resource The resulting image resource on success, Error string on failure. + * @return resource|GdImage|string The resulting image resource or GdImage instance on success, + * error string on failure. */ function wp_load_image( $file ) { _deprecated_function( __FUNCTION__, '3.5.0', 'wp_get_image_editor()' ); @@ -3217,7 +3218,7 @@ function wp_load_image( $file ) { $image = imagecreatefromstring( file_get_contents( $file ) ); - if ( ! is_resource( $image ) ) { + if ( ! is_gd_image( $image ) ) { /* translators: %s: File name. */ return sprintf( __( 'File “%s” is not an image.' ), $file ); } diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 3df41f58f7..1871678fad 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -3459,15 +3459,19 @@ function get_taxonomies_for_attachments( $output = 'names' ) { * @since 2.9.0 * * @param int $width Image width in pixels. - * @param int $height Image height in pixels.. - * @return resource The GD image resource. + * @param int $height Image height in pixels. + * @return resource|GdImage The GD image resource or GdImage instance. */ function wp_imagecreatetruecolor( $width, $height ) { $img = imagecreatetruecolor( $width, $height ); - if ( is_resource( $img ) && function_exists( 'imagealphablending' ) && function_exists( 'imagesavealpha' ) ) { + + if ( is_gd_image( $img ) + && function_exists( 'imagealphablending' ) && function_exists( 'imagesavealpha' ) + ) { imagealphablending( $img, false ); imagesavealpha( $img, true ); } + return $img; } diff --git a/tests/phpunit/tests/image/functions.php b/tests/phpunit/tests/image/functions.php index cfffd71922..d6ea84e598 100644 --- a/tests/phpunit/tests/image/functions.php +++ b/tests/phpunit/tests/image/functions.php @@ -123,6 +123,28 @@ class Tests_Image_Functions extends WP_UnitTestCase { } } + + /** + * @ticket 50833 + */ + function test_is_gd_image_invalid_types() { + $this->assertFalse( is_gd_image( new stdClass() ) ); + $this->assertFalse( is_gd_image( array() ) ); + $this->assertFalse( is_gd_image( null ) ); + + $handle = fopen( __FILE__, 'r' ); + $this->assertFalse( is_gd_image( $handle ) ); + fclose( $handle ); + } + + /** + * @ticket 50833 + * @requires extension gd + */ + function test_is_gd_image_valid_types() { + $this->assertTrue( is_gd_image( imagecreate( 5, 5 ) ) ); + } + /** * Test save image file and mime_types *