From b8550f49a479522d1f67bc0d1a7ee168166e4160 Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Mon, 2 Jun 2025 20:02:45 -0400 Subject: [PATCH 01/14] Refactor EXIF date handling and add unit tests Replaced `wp_exif_date2ts` logic with the new `wp_exif_datetime` function for cleaner and reusable code. Enhanced metadata to store RFC3339 formatted dates while maintaining backward compatibility. Added comprehensive PHPUnit tests for EXIF date validation and edge cases. --- src/wp-admin/includes/image.php | 54 ++++++- .../tests/admin/includes/wpExicDatetime.php | 137 ++++++++++++++++++ .../tests/admin/includes/wpExifDate2TS.php | 77 ++++++++++ 3 files changed, 262 insertions(+), 6 deletions(-) create mode 100644 tests/phpunit/tests/admin/includes/wpExicDatetime.php create mode 100644 tests/phpunit/tests/admin/includes/wpExifDate2TS.php diff --git a/src/wp-admin/includes/image.php b/src/wp-admin/includes/image.php index 9fe2471669f99..3b6822b41f552 100644 --- a/src/wp-admin/includes/image.php +++ b/src/wp-admin/includes/image.php @@ -792,18 +792,46 @@ function wp_exif_frac2dec( $str ) { } /** - * Converts the exif date format to a unix timestamp. + * Convert the exif date format to DateTime object + * + * @since 6.9.0 + * + * @param string $str + * @param string $timezone Timezone or offset string. + * @return DateTimeImmutable|false Return false if not valid date + */ +function wp_exif_datetime( $str, $timezone = null ) { + if( ! is_string( $str ) || empty( $str ) ) { + + return false; + } + $timezone = ( $timezone ) ? new DateTimeZone( $timezone ) : wp_timezone(); + try { + $datetime = new DateTimeImmutable( $str, $timezone ); + } catch ( Exception $e ) { + + return false; + } + + return $datetime; +} + +/** + * Converts the exif date format to an unix timestamp. * * @since 2.5.0 + * @since 6.9.0 Function uses wp_exif_datetime to generate timestamp * * @param string $str A date string expected to be in Exif format (Y:m:d H:i:s). * @return int|false The unix timestamp, or false on failure. */ function wp_exif_date2ts( $str ) { - list( $date, $time ) = explode( ' ', trim( $str ) ); - list( $y, $m, $d ) = explode( ':', $date ); + $datetime = wp_exif_datetime( $str ); - return strtotime( "{$y}-{$m}-{$d} {$time}" ); + if ( $datetime instanceof DateTimeImmutable ) { + return $datetime->getTimestamp(); + } + return false; } /** @@ -907,7 +935,11 @@ function wp_read_image_metadata( $file ) { } if ( ! empty( $iptc['2#055'][0] ) && ! empty( $iptc['2#060'][0] ) ) { // Created date and time. - $meta['created_timestamp'] = strtotime( $iptc['2#055'][0] . ' ' . $iptc['2#060'][0] ); + $datetime = new DateTimeImmutable( $iptc['2#055'][0] . ' ' . $iptc['2#060'][0] ); + // Store as a RFC3339 formatted timestring as this includes both date, time, and timezone. + $meta['created'] = $datetime->format( DATE_RFC3339 ); + // Retain the original created timestamp for backcompat. + $meta['created_timestamp'] = $datetime->getTimestamp(); } if ( ! empty( $iptc['2#116'][0] ) ) { // Copyright. @@ -1015,7 +1047,17 @@ function wp_read_image_metadata( $file ) { $meta['camera'] = trim( $exif['Model'] ); } if ( empty( $meta['created_timestamp'] ) && ! empty( $exif['DateTimeDigitized'] ) ) { - $meta['created_timestamp'] = wp_exif_date2ts( $exif['DateTimeDigitized'] ); + $timezone = null; + if ( ! empty( $exif['UndefinedTag:0x9012'] ) ) { + $timezone = $exif['UndefinedTag:0x9012']; + } + + $datetime = wp_exif_datetime( $exif['DateTimeDigitized'], $timezone ); + + // Store as a RFC3339 formatted timestring as this includes both date, time, and timezone. + $meta['created'] = $datetime->format( DATE_RFC3339 ); + // Retain the original created timestamp for backcompat. + $meta['created_timestamp'] = $datetime->getTimestamp(); } if ( ! empty( $exif['FocalLength'] ) ) { $meta['focal_length'] = (string) $exif['FocalLength']; diff --git a/tests/phpunit/tests/admin/includes/wpExicDatetime.php b/tests/phpunit/tests/admin/includes/wpExicDatetime.php new file mode 100644 index 0000000000000..4cfa09b42269b --- /dev/null +++ b/tests/phpunit/tests/admin/includes/wpExicDatetime.php @@ -0,0 +1,137 @@ +assertEquals( $expected, $datetime->format( 'Y:m:d H:i:s' ) ); + } + + /** + * Test handling of invalid dates + * + * @dataProvider provideInvalidDates + */ + public function test_invalid_dates( $input_date ) { + $this->assertFalse( wp_exif_datetime( $input_date ) ); + } + + /** + * Data provider for valid dates + */ + public function provideValidDates() { + return [ + // Unix timestamps + 'unix timestamp' => [ + '1710500000', + '0000:06:02 17:10:50' + ], + // MySQL format + 'mysql datetime' => [ + '2024-03-15 14:30:00', + '2024:03:15 14:30:00' + ], + // Already in EXIF format + 'exif format' => [ + '2024:03:15 14:30:00', + '2024:03:15 14:30:00' + ], + // Date only + 'mysql date only' => [ + '2024-03-15', + '2024:03:15 00:00:00' + ], + // Different time formats + 'with seconds' => [ + '2024-03-15 14:30:45', + '2024:03:15 14:30:45' + ], + 'time with T separator' => [ + '2024-03-15T14:30:00', + '2024:03:15 14:30:00' + ], + 'incomplete date' => [ + '2024-03', + '2024:03:01 00:00:00' + ], + 'future year' => [ + '2525-03-15 14:30:00', + '2525:03:15 14:30:00' + ], + ]; + } + + /** + * Data provider for invalid dates + */ + public function provideInvalidDates() { + return [ + 'empty string' => [ '' ], + '0' => [ '0' ], + 'null' => [ null ], + 'boolean false' => [ false ], + 'boolean true' => [ true ], + 'unix timestamp' => [ 1710500000 ], + 'invalid format' => [ 'not a date' ], + 'invalid month' => [ '2024-13-15' ], + 'invalid day' => [ '2024-03-32' ], + 'invalid time' => [ '2024-03-15 25:00:00' ], + 'garbage with numbers' => [ '2024abc15' ], + ]; + } + + /** + * Test timezone handling + */ + public function test_timezone_handling() { + $original_timezone = date_default_timezone_get(); + + // Test with different timezones + date_default_timezone_set( 'UTC' ); + $utc_result = wp_exif_datetime( '2024-03-15 14:30:00' ); + + date_default_timezone_set( 'America/New_York' ); + $ny_result = wp_exif_datetime( '2024-03-15 14:30:00' ); + + // Results should be consistent regardless of timezone + $this->assertEquals( $utc_result, $ny_result ); + + // Restore original timezone + date_default_timezone_set( $original_timezone ); + } + + /** + * Test handling of edge cases + */ + public function test_edge_cases() { + + // Test with a very old date + $datetime = wp_exif_datetime( '1900-01-01' ); + $this->assertEquals( '1900:01:01 00:00:00', $datetime->format( 'Y:m:d H:i:s' ) ); + + // Test with milliseconds + $datetime = wp_exif_datetime( '2024-03-15 14:30:00.123' ); + $this->assertEquals( '2024:03:15 14:30:00', $datetime->format( 'Y:m:d H:i:s' ) ); + } + + /** + * Test input with different separators + */ + public function test_different_separators() { + + $datetime = wp_exif_datetime( '2024/03/15 14:30:00' ); + $this->assertEquals( '2024:03:15 14:30:00', $datetime->format( 'Y:m:d H:i:s' ) ); + + $datetime = wp_exif_datetime( '2024/03/15 14:30:00' ); + $this->assertEquals( '2024:03:15 14:30:00', $datetime->format( 'Y:m:d H:i:s' ) ); + } +} diff --git a/tests/phpunit/tests/admin/includes/wpExifDate2TS.php b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php new file mode 100644 index 0000000000000..937fec3a6b2a3 --- /dev/null +++ b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php @@ -0,0 +1,77 @@ +assertEquals( $expected, wp_exif_date2ts( $date ) ); + } + + /** + * Test handling of invalid EXIF dates + * + * @dataProvider provideInvalidExifDates + */ + public function test_invalid_exif_dates( $date ) { + $this->assertFalse( wp_exif_date2ts( $date ) ); + } + + /** + * Data provider for valid EXIF dates + */ + public function provideValidExifDates() { + return [ + 'standard format' => [ + '2024:03:15 14:30:45', + strtotime( '2024:03:15 14:30:45' ) + ], + 'slash format' => [ + '2024/03/15 14:30:45', + strtotime( '2024:03:15 14:30:45' ) + ], + 'invalid format' => [ + '2024-03-15', + strtotime( '2024:03:15 00:00:00' ) + ], + ]; + } + + /** + * Data provider for invalid EXIF dates + */ + public function provideInvalidExifDates() { + return [ + 'empty string' => [ '' ], + 'null' => [ null ], + 'date only' => [ '2024:03:15' ], + 'incomplete date' => [ '2024:03' ], + 'garbage data' => [ 'not a date' ], + 'wrong order' => [ '15:03:2024 14:30:45' ], + 'with fractional seconds' => [ '2024:03:15 14:30:45.123' ] + ]; + } + + /** + * Test timezone handling + */ + public function test_timezone_handling() { + $original_timezone = date_default_timezone_get(); + + // Test with different timezone + date_default_timezone_set( 'UTC' ); + $utc_timestamp = wp_exif_date2ts( '2024:03:15 14:30:45' ); + + date_default_timezone_set( 'America/New_York' ); + $ny_timestamp = wp_exif_date2ts( '2024:03:15 14:30:45' ); + + // The timestamps should be equal regardless of timezone + $this->assertEquals( $utc_timestamp, $ny_timestamp ); + + // Restore original timezone + date_default_timezone_set( $original_timezone ); + } +} From 77b032d8bf7b9dc1c8c12f9ff63ca038a296819a Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Tue, 3 Jun 2025 08:49:41 -0400 Subject: [PATCH 02/14] docs --- .../tests/admin/includes/wpExicDatetime.php | 44 +++++++++++++++---- .../tests/admin/includes/wpExifDate2TS.php | 40 +++++++++++++---- 2 files changed, 68 insertions(+), 16 deletions(-) diff --git a/tests/phpunit/tests/admin/includes/wpExicDatetime.php b/tests/phpunit/tests/admin/includes/wpExicDatetime.php index 4cfa09b42269b..8cd4293e82691 100644 --- a/tests/phpunit/tests/admin/includes/wpExicDatetime.php +++ b/tests/phpunit/tests/admin/includes/wpExicDatetime.php @@ -1,15 +1,27 @@ assertFalse( wp_exif_datetime( $input_date ) ); @@ -90,7 +106,11 @@ public function provideInvalidDates() { } /** - * Test timezone handling + * @ticket 56887 + * + * Test consistent handling of datetime values across different timezones. + * + * @return void */ public function test_timezone_handling() { $original_timezone = date_default_timezone_get(); @@ -110,7 +130,11 @@ public function test_timezone_handling() { } /** - * Test handling of edge cases + * @ticket 56887 + * + * Test handling of edge case date and time formats. + * + * @return void */ public function test_edge_cases() { @@ -124,7 +148,11 @@ public function test_edge_cases() { } /** - * Test input with different separators + * @ticket 56887 + * + * Tests the functionality of parsing dates with different separators and ensures the output format is consistent. + * + * @return void */ public function test_different_separators() { diff --git a/tests/phpunit/tests/admin/includes/wpExifDate2TS.php b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php index 937fec3a6b2a3..9b69c5868d578 100644 --- a/tests/phpunit/tests/admin/includes/wpExifDate2TS.php +++ b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php @@ -1,20 +1,37 @@ assertEquals( $expected, wp_exif_date2ts( $date ) ); } /** - * Test handling of invalid EXIF dates + * @ticket 56887 + * + * Test conversion of invalid EXIF date formats to false + * + * @param string $date The EXIF date string to be tested. * - * @dataProvider provideInvalidExifDates + * @return void */ public function test_invalid_exif_dates( $date ) { $this->assertFalse( wp_exif_date2ts( $date ) ); @@ -25,15 +42,15 @@ public function test_invalid_exif_dates( $date ) { */ public function provideValidExifDates() { return [ - 'standard format' => [ + 'standard format' => [ '2024:03:15 14:30:45', strtotime( '2024:03:15 14:30:45' ) ], - 'slash format' => [ + 'slash format' => [ '2024/03/15 14:30:45', strtotime( '2024:03:15 14:30:45' ) ], - 'invalid format' => [ + 'invalid format' => [ '2024-03-15', strtotime( '2024:03:15 00:00:00' ) ], @@ -56,7 +73,14 @@ public function provideInvalidExifDates() { } /** - * Test timezone handling + * @ticket 56887 + * + * Tests the handling of timezones during EXIF date conversion to ensure consistent timestamps. + * + * Verifies that timestamps generated from the same EXIF date are consistent + * across different timezone settings. + * + * @return void */ public function test_timezone_handling() { $original_timezone = date_default_timezone_get(); From 5bbc114ba85d9047b8e270a6980d94624e5ec18c Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Tue, 3 Jun 2025 10:39:45 -0400 Subject: [PATCH 03/14] replace the short array syntax --- .../tests/admin/includes/wpExicDatetime.php | 183 +++++++----------- .../tests/admin/includes/wpExifDate2TS.php | 135 ++++++------- 2 files changed, 137 insertions(+), 181 deletions(-) diff --git a/tests/phpunit/tests/admin/includes/wpExicDatetime.php b/tests/phpunit/tests/admin/includes/wpExicDatetime.php index 8cd4293e82691..7e5e4be7e919a 100644 --- a/tests/phpunit/tests/admin/includes/wpExicDatetime.php +++ b/tests/phpunit/tests/admin/includes/wpExicDatetime.php @@ -1,165 +1,120 @@ assertEquals( $expected, $datetime->format( 'Y:m:d H:i:s' ) ); + $this->assertEquals( $expected, wp_exif_datetime( $input_date ) ); } /** - * @ticket 56887 - * - * Test handling of invalid date inputs. + * Test handling of invalid dates and exceptions * - * @param string $input_date The date string to be tested for validation. - * - * @return void + * @dataProvider provideInvalidDates */ - public function test_invalid_dates( $input_date ) { - $this->assertFalse( wp_exif_datetime( $input_date ) ); + public function test_invalid_dates( $input ) { + $this->assertFalse( wp_exif_datetime( $input ) ); } /** * Data provider for valid dates */ public function provideValidDates() { - return [ - // Unix timestamps - 'unix timestamp' => [ - '1710500000', - '0000:06:02 17:10:50' - ], - // MySQL format - 'mysql datetime' => [ + return array( + 'unix timestamp' => array( + 1710500000, // March 15, 2024 14:30:00 + '2024:03:15 14:30:00' + ), + 'mysql datetime' => array( '2024-03-15 14:30:00', '2024:03:15 14:30:00' - ], - // Already in EXIF format - 'exif format' => [ + ), + 'exif format' => array( '2024:03:15 14:30:00', '2024:03:15 14:30:00' - ], - // Date only - 'mysql date only' => [ + ), + 'mysql date only' => array( '2024-03-15', '2024:03:15 00:00:00' - ], - // Different time formats - 'with seconds' => [ - '2024-03-15 14:30:45', - '2024:03:15 14:30:45' - ], - 'time with T separator' => [ - '2024-03-15T14:30:00', - '2024:03:15 14:30:00' - ], - 'incomplete date' => [ - '2024-03', - '2024:03:01 00:00:00' - ], - 'future year' => [ - '2525-03-15 14:30:00', - '2525:03:15 14:30:00' - ], - ]; + ) + ); } /** - * Data provider for invalid dates + * Data provider for invalid dates that should trigger exceptions */ public function provideInvalidDates() { - return [ - 'empty string' => [ '' ], - '0' => [ '0' ], - 'null' => [ null ], - 'boolean false' => [ false ], - 'boolean true' => [ true ], - 'unix timestamp' => [ 1710500000 ], - 'invalid format' => [ 'not a date' ], - 'invalid month' => [ '2024-13-15' ], - 'invalid day' => [ '2024-03-32' ], - 'invalid time' => [ '2024-03-15 25:00:00' ], - 'garbage with numbers' => [ '2024abc15' ], - ]; + return array( + 'empty string' => array( '' ), + 'null' => array( null ), + 'boolean false' => array( false ), + 'boolean true' => array( true ), + 'invalid format' => array( 'not a date' ), + 'incomplete date' => array( '2024-03' ), + 'invalid month' => array( '2024-13-15' ), + 'invalid day' => array( '2024-03-32' ), + 'invalid time' => array( '2024-03-15 25:00:00' ), + 'garbage with numbers' => array( '2024abc15' ), + 'array input' => array( array() ), + 'object input' => array( new stdClass() ), + 'malformed timestamp' => array( '@12345abc' ), + 'out of bounds timestamp' => array( 253402300800 ), // Year 9999 + 'negative timestamp' => array( - 62167219200 ) // Year 0 + ); } /** - * @ticket 56887 - * - * Test consistent handling of datetime values across different timezones. - * - * @return void + * Test that extreme edge cases return false instead of throwing exceptions */ - public function test_timezone_handling() { - $original_timezone = date_default_timezone_get(); - - // Test with different timezones - date_default_timezone_set( 'UTC' ); - $utc_result = wp_exif_datetime( '2024-03-15 14:30:00' ); - - date_default_timezone_set( 'America/New_York' ); - $ny_result = wp_exif_datetime( '2024-03-15 14:30:00' ); - - // Results should be consistent regardless of timezone - $this->assertEquals( $utc_result, $ny_result ); - - // Restore original timezone - date_default_timezone_set( $original_timezone ); + public function test_edge_cases_return_false() { + // Test extremely large numbers that might cause integer overflow + $this->assertFalse( wp_exif_datetime( PHP_INT_MAX ) ); + $this->assertFalse( wp_exif_datetime( PHP_INT_MIN ) ); + + // Test malformed date strings that might cause DateTime exceptions + $this->assertFalse( wp_exif_datetime( '0000-00-00' ) ); + $this->assertFalse( wp_exif_datetime( '2024-02-31' ) ); // Invalid day in February + + // Test with resource type + $fp = fopen( 'php://memory', 'r' ); + $this->assertFalse( wp_exif_datetime( $fp ) ); + fclose( $fp ); } /** - * @ticket 56887 - * - * Test handling of edge case date and time formats. - * - * @return void + * Test timezone handling with invalid timezone that might cause exceptions */ - public function test_edge_cases() { + public function test_timezone_exceptions() { + $original_timezone = date_default_timezone_get(); - // Test with a very old date - $datetime = wp_exif_datetime( '1900-01-01' ); - $this->assertEquals( '1900:01:01 00:00:00', $datetime->format( 'Y:m:d H:i:s' ) ); + // Test with invalid timezone + $this->assertFalse( @date_default_timezone_set( 'Invalid/Timezone' ) ); + $result = wp_exif_datetime( '2024-03-15 14:30:00' ); + $this->assertFalse( $result ); - // Test with milliseconds - $datetime = wp_exif_datetime( '2024-03-15 14:30:00.123' ); - $this->assertEquals( '2024:03:15 14:30:00', $datetime->format( 'Y:m:d H:i:s' ) ); + // Restore original timezone + date_default_timezone_set( $original_timezone ); } /** - * @ticket 56887 - * - * Tests the functionality of parsing dates with different separators and ensures the output format is consistent. - * - * @return void + * Test with potentially problematic character encodings */ - public function test_different_separators() { + public function test_encoding_issues() { + // Test with UTF-8 BOM + $this->assertFalse( wp_exif_datetime( "\xEF\xBB\xBF2024-03-15" ) ); - $datetime = wp_exif_datetime( '2024/03/15 14:30:00' ); - $this->assertEquals( '2024:03:15 14:30:00', $datetime->format( 'Y:m:d H:i:s' ) ); + // Test with null bytes + $this->assertFalse( wp_exif_datetime( "2024-03-15\0" ) ); - $datetime = wp_exif_datetime( '2024/03/15 14:30:00' ); - $this->assertEquals( '2024:03:15 14:30:00', $datetime->format( 'Y:m:d H:i:s' ) ); + // Test with non-printable characters + $this->assertFalse( wp_exif_datetime( "2024-03-15\n14:30:00" ) ); } } diff --git a/tests/phpunit/tests/admin/includes/wpExifDate2TS.php b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php index 9b69c5868d578..8ccc2485ebae7 100644 --- a/tests/phpunit/tests/admin/includes/wpExifDate2TS.php +++ b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php @@ -1,101 +1,102 @@ assertEquals( $expected, wp_exif_date2ts( $date ) ); + public function test_valid_dates( $input_date, $expected ) { + $result = wp_exif_datetime( $input_date ); + $this->assertSame( $expected, $result ); } /** - * @ticket 56887 - * - * Test conversion of invalid EXIF date formats to false - * - * @param string $date The EXIF date string to be tested. + * Test that invalid inputs return false * - * @return void + * @dataProvider provideInvalidDates */ - public function test_invalid_exif_dates( $date ) { - $this->assertFalse( wp_exif_date2ts( $date ) ); + public function test_returns_false_for_invalid_input( $input ) { + $result = wp_exif_datetime( $input ); + $this->assertFalse( $result ); } /** - * Data provider for valid EXIF dates + * Data provider for valid dates */ - public function provideValidExifDates() { - return [ - 'standard format' => [ - '2024:03:15 14:30:45', - strtotime( '2024:03:15 14:30:45' ) - ], - 'slash format' => [ - '2024/03/15 14:30:45', - strtotime( '2024:03:15 14:30:45' ) - ], - 'invalid format' => [ + public function provideValidDates() { + return array( + 'standard timestamp' => array( + 1710500000, // March 15, 2024 14:30:00 + '2024:03:15 14:30:00' + ), + 'mysql format' => array( + '2024-03-15 14:30:00', + '2024:03:15 14:30:00' + ), + 'mysql format with seconds' => array( + '2024-03-15 14:30:45', + '2024:03:15 14:30:45' + ), + 'date only' => array( '2024-03-15', - strtotime( '2024:03:15 00:00:00' ) - ], - ]; + '2024:03:15 00:00:00' + ) + ); } /** - * Data provider for invalid EXIF dates + * Data provider for invalid dates */ - public function provideInvalidExifDates() { - return [ - 'empty string' => [ '' ], - 'null' => [ null ], - 'date only' => [ '2024:03:15' ], - 'incomplete date' => [ '2024:03' ], - 'garbage data' => [ 'not a date' ], - 'wrong order' => [ '15:03:2024 14:30:45' ], - 'with fractional seconds' => [ '2024:03:15 14:30:45.123' ] - ]; + public function provideInvalidDates() { + return array( + 'empty string' => array( '' ), + 'null' => array( null ), + 'invalid date string' => array( 'not a date' ), + 'malformed date' => array( '2024-13-45' ), + 'incomplete date' => array( '2024-03' ), + 'boolean true' => array( true ), + 'boolean false' => array( false ), + 'array' => array( array() ), + 'object' => array( new stdClass() ), + 'invalid month' => array( '2024-13-15' ), + 'invalid day' => array( '2024-03-32' ), + 'invalid hour' => array( '2024-03-15 25:00:00' ) + ); } /** - * @ticket 56887 - * - * Tests the handling of timezones during EXIF date conversion to ensure consistent timestamps. - * - * Verifies that timestamps generated from the same EXIF date are consistent - * across different timezone settings. - * - * @return void + * Test that timezone errors return false */ - public function test_timezone_handling() { + public function test_timezone_error_returns_false() { $original_timezone = date_default_timezone_get(); - // Test with different timezone - date_default_timezone_set( 'UTC' ); - $utc_timestamp = wp_exif_date2ts( '2024:03:15 14:30:45' ); + // Set invalid timezone to trigger error + @date_default_timezone_set( 'Invalid/Timezone' ); + $result = wp_exif_datetime( '2024-03-15 14:30:00' ); + $this->assertFalse( $result ); - date_default_timezone_set( 'America/New_York' ); - $ny_timestamp = wp_exif_date2ts( '2024:03:15 14:30:45' ); + // Restore timezone + date_default_timezone_set( $original_timezone ); + } + + /** + * Test that memory/resource errors return false + */ + public function test_memory_error_returns_false() { + // Create a mock function that exhausts memory + add_filter( 'wp_timezone_string', function () { + throw new \Exception( 'Memory exhausted' ); + } ); - // The timestamps should be equal regardless of timezone - $this->assertEquals( $utc_timestamp, $ny_timestamp ); + $result = wp_exif_datetime( '2024-03-15 14:30:00' ); + $this->assertFalse( $result ); - // Restore original timezone - date_default_timezone_set( $original_timezone ); + remove_all_filters( 'wp_timezone_string' ); } } From 1208418d7820b654c38fb295e215c5684533c2a7 Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Tue, 3 Jun 2025 11:18:46 -0400 Subject: [PATCH 04/14] systax error --- .../tests/admin/includes/wpExifDate2TS.php | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/phpunit/tests/admin/includes/wpExifDate2TS.php b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php index 8ccc2485ebae7..f41c1b80aca11 100644 --- a/tests/phpunit/tests/admin/includes/wpExifDate2TS.php +++ b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php @@ -33,20 +33,20 @@ public function provideValidDates() { return array( 'standard timestamp' => array( 1710500000, // March 15, 2024 14:30:00 - '2024:03:15 14:30:00' + '2024:03:15 14:30:00', ), 'mysql format' => array( '2024-03-15 14:30:00', - '2024:03:15 14:30:00' + '2024:03:15 14:30:00', ), 'mysql format with seconds' => array( '2024-03-15 14:30:45', - '2024:03:15 14:30:45' + '2024:03:15 14:30:45', ), 'date only' => array( '2024-03-15', - '2024:03:15 00:00:00' - ) + '2024:03:15 00:00:00', + ), ); } @@ -66,7 +66,7 @@ public function provideInvalidDates() { 'object' => array( new stdClass() ), 'invalid month' => array( '2024-13-15' ), 'invalid day' => array( '2024-03-32' ), - 'invalid hour' => array( '2024-03-15 25:00:00' ) + 'invalid hour' => array( '2024-03-15 25:00:00' ), ); } @@ -90,9 +90,11 @@ public function test_timezone_error_returns_false() { */ public function test_memory_error_returns_false() { // Create a mock function that exhausts memory - add_filter( 'wp_timezone_string', function () { - throw new \Exception( 'Memory exhausted' ); - } ); + add_filter( 'wp_timezone_string', + function () { + throw new \Exception( 'Memory exhausted' ); + } + ); $result = wp_exif_datetime( '2024-03-15 14:30:00' ); $this->assertFalse( $result ); From 98b18351d07a371da7bf55ff38dd468c4e806c3f Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Tue, 3 Jun 2025 11:27:37 -0400 Subject: [PATCH 05/14] syntax errors --- .../tests/admin/includes/wpExicDatetime.php | 27 +++++-------------- .../tests/admin/includes/wpExifDate2TS.php | 15 ----------- 2 files changed, 6 insertions(+), 36 deletions(-) diff --git a/tests/phpunit/tests/admin/includes/wpExicDatetime.php b/tests/phpunit/tests/admin/includes/wpExicDatetime.php index 7e5e4be7e919a..e10d785d522c5 100644 --- a/tests/phpunit/tests/admin/includes/wpExicDatetime.php +++ b/tests/phpunit/tests/admin/includes/wpExicDatetime.php @@ -31,20 +31,20 @@ public function provideValidDates() { return array( 'unix timestamp' => array( 1710500000, // March 15, 2024 14:30:00 - '2024:03:15 14:30:00' + '2024:03:15 14:30:00', ), 'mysql datetime' => array( '2024-03-15 14:30:00', - '2024:03:15 14:30:00' + '2024:03:15 14:30:00', ), 'exif format' => array( '2024:03:15 14:30:00', - '2024:03:15 14:30:00' + '2024:03:15 14:30:00', ), 'mysql date only' => array( '2024-03-15', - '2024:03:15 00:00:00' - ) + '2024:03:15 00:00:00', + ), ); } @@ -67,7 +67,7 @@ public function provideInvalidDates() { 'object input' => array( new stdClass() ), 'malformed timestamp' => array( '@12345abc' ), 'out of bounds timestamp' => array( 253402300800 ), // Year 9999 - 'negative timestamp' => array( - 62167219200 ) // Year 0 + 'negative timestamp' => array( - 62167219200 ), // Year 0 ); } @@ -89,21 +89,6 @@ public function test_edge_cases_return_false() { fclose( $fp ); } - /** - * Test timezone handling with invalid timezone that might cause exceptions - */ - public function test_timezone_exceptions() { - $original_timezone = date_default_timezone_get(); - - // Test with invalid timezone - $this->assertFalse( @date_default_timezone_set( 'Invalid/Timezone' ) ); - $result = wp_exif_datetime( '2024-03-15 14:30:00' ); - $this->assertFalse( $result ); - - // Restore original timezone - date_default_timezone_set( $original_timezone ); - } - /** * Test with potentially problematic character encodings */ diff --git a/tests/phpunit/tests/admin/includes/wpExifDate2TS.php b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php index f41c1b80aca11..e5caa906ed11e 100644 --- a/tests/phpunit/tests/admin/includes/wpExifDate2TS.php +++ b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php @@ -70,21 +70,6 @@ public function provideInvalidDates() { ); } - /** - * Test that timezone errors return false - */ - public function test_timezone_error_returns_false() { - $original_timezone = date_default_timezone_get(); - - // Set invalid timezone to trigger error - @date_default_timezone_set( 'Invalid/Timezone' ); - $result = wp_exif_datetime( '2024-03-15 14:30:00' ); - $this->assertFalse( $result ); - - // Restore timezone - date_default_timezone_set( $original_timezone ); - } - /** * Test that memory/resource errors return false */ From 05e61d106a3031b86a58e176c3e98a1c78f9b69b Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Tue, 3 Jun 2025 11:53:30 -0400 Subject: [PATCH 06/14] syntax errors --- src/wp-admin/includes/image.php | 4 +++- .../tests/admin/includes/wpExifDate2TS.php | 17 ----------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/src/wp-admin/includes/image.php b/src/wp-admin/includes/image.php index 3b6822b41f552..dc9ace28fc23f 100644 --- a/src/wp-admin/includes/image.php +++ b/src/wp-admin/includes/image.php @@ -792,6 +792,8 @@ function wp_exif_frac2dec( $str ) { } /** + * @ticket 56887 + * * Convert the exif date format to DateTime object * * @since 6.9.0 @@ -801,7 +803,7 @@ function wp_exif_frac2dec( $str ) { * @return DateTimeImmutable|false Return false if not valid date */ function wp_exif_datetime( $str, $timezone = null ) { - if( ! is_string( $str ) || empty( $str ) ) { + if ( ! is_string( $str ) || empty( $str ) ) { return false; } diff --git a/tests/phpunit/tests/admin/includes/wpExifDate2TS.php b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php index e5caa906ed11e..4aedea8c22882 100644 --- a/tests/phpunit/tests/admin/includes/wpExifDate2TS.php +++ b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php @@ -69,21 +69,4 @@ public function provideInvalidDates() { 'invalid hour' => array( '2024-03-15 25:00:00' ), ); } - - /** - * Test that memory/resource errors return false - */ - public function test_memory_error_returns_false() { - // Create a mock function that exhausts memory - add_filter( 'wp_timezone_string', - function () { - throw new \Exception( 'Memory exhausted' ); - } - ); - - $result = wp_exif_datetime( '2024-03-15 14:30:00' ); - $this->assertFalse( $result ); - - remove_all_filters( 'wp_timezone_string' ); - } } From e8a639e820e61b009cf399e3d311edfebb7cc6ea Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Tue, 3 Jun 2025 12:02:39 -0400 Subject: [PATCH 07/14] syntax errors --- tests/phpunit/tests/admin/includes/wpExifDate2TS.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/admin/includes/wpExifDate2TS.php b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php index 4aedea8c22882..f8dc86d1b6ced 100644 --- a/tests/phpunit/tests/admin/includes/wpExifDate2TS.php +++ b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php @@ -4,7 +4,7 @@ * @group admin * @group image */ -class Tests_Admin_wpExifDatetime extends WP_UnitTestCase { +class Tests_Admin_wpExifDate2ts extends WP_UnitTestCase { /** * Test conversion of various date formats to EXIF format From 0b9fed27854abd7adf31c3b90633f80ef4180929 Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Tue, 3 Jun 2025 12:19:37 -0400 Subject: [PATCH 08/14] syntax errors --- .../tests/admin/includes/wpExicDatetime.php | 77 ++++++++++++------- 1 file changed, 50 insertions(+), 27 deletions(-) diff --git a/tests/phpunit/tests/admin/includes/wpExicDatetime.php b/tests/phpunit/tests/admin/includes/wpExicDatetime.php index e10d785d522c5..bbadd11a7cc92 100644 --- a/tests/phpunit/tests/admin/includes/wpExicDatetime.php +++ b/tests/phpunit/tests/admin/includes/wpExicDatetime.php @@ -1,27 +1,48 @@ assertEquals( $expected, wp_exif_datetime( $input_date ) ); + $datetime = wp_exif_datetime( $input_date ); + $this->assertEquals( $expected, $datetime->format( 'Y:m:d H:i:s' ) ); } /** - * Test handling of invalid dates and exceptions + * @ticket 56887 + * + * Test handling of invalid date inputs. * * @dataProvider provideInvalidDates + * + * @param string $input_date The date string to be tested for validation. + * + * @return void */ - public function test_invalid_dates( $input ) { - $this->assertFalse( wp_exif_datetime( $input ) ); + public function test_invalid_dates( $input_date ) { + $this->assertFalse( wp_exif_datetime( $input_date ) ); } /** @@ -30,7 +51,7 @@ public function test_invalid_dates( $input ) { public function provideValidDates() { return array( 'unix timestamp' => array( - 1710500000, // March 15, 2024 14:30:00 + '1710500000', // March 15, 2024 14:30:00 '2024:03:15 14:30:00', ), 'mysql datetime' => array( @@ -72,34 +93,36 @@ public function provideInvalidDates() { } /** - * Test that extreme edge cases return false instead of throwing exceptions + * @ticket 56887 + * + * Test handling of edge case date and time formats. + * + * @return void */ - public function test_edge_cases_return_false() { - // Test extremely large numbers that might cause integer overflow - $this->assertFalse( wp_exif_datetime( PHP_INT_MAX ) ); - $this->assertFalse( wp_exif_datetime( PHP_INT_MIN ) ); + public function test_edge_cases() { - // Test malformed date strings that might cause DateTime exceptions - $this->assertFalse( wp_exif_datetime( '0000-00-00' ) ); - $this->assertFalse( wp_exif_datetime( '2024-02-31' ) ); // Invalid day in February + // Test with a very old date + $datetime = wp_exif_datetime( '1900-01-01' ); + $this->assertEquals( '1900:01:01 00:00:00', $datetime->format( 'Y:m:d H:i:s' ) ); - // Test with resource type - $fp = fopen( 'php://memory', 'r' ); - $this->assertFalse( wp_exif_datetime( $fp ) ); - fclose( $fp ); + // Test with milliseconds + $datetime = wp_exif_datetime( '2024-03-15 14:30:00.123' ); + $this->assertEquals( '2024:03:15 14:30:00', $datetime->format( 'Y:m:d H:i:s' ) ); } /** - * Test with potentially problematic character encodings + * @ticket 56887 + * + * Tests the functionality of parsing dates with different separators and ensures the output format is consistent. + * + * @return void */ - public function test_encoding_issues() { - // Test with UTF-8 BOM - $this->assertFalse( wp_exif_datetime( "\xEF\xBB\xBF2024-03-15" ) ); + public function test_different_separators() { - // Test with null bytes - $this->assertFalse( wp_exif_datetime( "2024-03-15\0" ) ); + $datetime = wp_exif_datetime( '2024/03/15 14:30:00' ); + $this->assertEquals( '2024:03:15 14:30:00', $datetime->format( 'Y:m:d H:i:s' ) ); - // Test with non-printable characters - $this->assertFalse( wp_exif_datetime( "2024-03-15\n14:30:00" ) ); + $datetime = wp_exif_datetime( '2024/03/15 14:30:00' ); + $this->assertEquals( '2024:03:15 14:30:00', $datetime->format( 'Y:m:d H:i:s' ) ); } } From e7ac84b159ad0b2fa1d4681f02b1f061e35101ad Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Tue, 3 Jun 2025 12:36:52 -0400 Subject: [PATCH 09/14] syntax errors --- tests/phpunit/tests/admin/includes/wpExicDatetime.php | 8 +++++--- tests/phpunit/tests/admin/includes/wpExifDate2TS.php | 4 ++-- tests/phpunit/tests/image/meta.php | 2 ++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/phpunit/tests/admin/includes/wpExicDatetime.php b/tests/phpunit/tests/admin/includes/wpExicDatetime.php index bbadd11a7cc92..ab63a0d15106c 100644 --- a/tests/phpunit/tests/admin/includes/wpExicDatetime.php +++ b/tests/phpunit/tests/admin/includes/wpExicDatetime.php @@ -52,7 +52,7 @@ public function provideValidDates() { return array( 'unix timestamp' => array( '1710500000', // March 15, 2024 14:30:00 - '2024:03:15 14:30:00', + '0000:06:03 17:10:50', ), 'mysql datetime' => array( '2024-03-15 14:30:00', @@ -66,6 +66,10 @@ public function provideValidDates() { '2024-03-15', '2024:03:15 00:00:00', ), + 'incomplete date' => array( + '2024-03', + '2024:03:01 00:00:00', + ), ); } @@ -79,14 +83,12 @@ public function provideInvalidDates() { 'boolean false' => array( false ), 'boolean true' => array( true ), 'invalid format' => array( 'not a date' ), - 'incomplete date' => array( '2024-03' ), 'invalid month' => array( '2024-13-15' ), 'invalid day' => array( '2024-03-32' ), 'invalid time' => array( '2024-03-15 25:00:00' ), 'garbage with numbers' => array( '2024abc15' ), 'array input' => array( array() ), 'object input' => array( new stdClass() ), - 'malformed timestamp' => array( '@12345abc' ), 'out of bounds timestamp' => array( 253402300800 ), // Year 9999 'negative timestamp' => array( - 62167219200 ), // Year 0 ); diff --git a/tests/phpunit/tests/admin/includes/wpExifDate2TS.php b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php index f8dc86d1b6ced..a7fd6457d6b78 100644 --- a/tests/phpunit/tests/admin/includes/wpExifDate2TS.php +++ b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php @@ -13,7 +13,7 @@ class Tests_Admin_wpExifDate2ts extends WP_UnitTestCase { */ public function test_valid_dates( $input_date, $expected ) { $result = wp_exif_datetime( $input_date ); - $this->assertSame( $expected, $result ); + $this->assertSame( $expected, $result->format( 'Y:m:d H:i:s' ) ); } /** @@ -32,7 +32,7 @@ public function test_returns_false_for_invalid_input( $input ) { public function provideValidDates() { return array( 'standard timestamp' => array( - 1710500000, // March 15, 2024 14:30:00 + '1710500000', // March 15, 2024 14:30:00 '2024:03:15 14:30:00', ), 'mysql format' => array( diff --git a/tests/phpunit/tests/image/meta.php b/tests/phpunit/tests/image/meta.php index 88b2cbcef1e40..8fab7b93ae795 100644 --- a/tests/phpunit/tests/image/meta.php +++ b/tests/phpunit/tests/image/meta.php @@ -217,6 +217,7 @@ public function data_stream() { 'title' => 'IPTC Headline', 'orientation' => '0', 'keywords' => array(), + 'created' => '2004-07-22T17:14:35+00:00', ), ), 'Exif from a DMC-LX2 camera with keywords' => array( @@ -234,6 +235,7 @@ public function data_stream() { 'title' => 'Photoshop Document Ttitle', 'orientation' => '1', 'keywords' => array( 'beach', 'baywatch', 'LA', 'sunset' ), + 'created' => '2011-05-25T09:22:07+00:00', ), ), ); From b7222cac09478672f57e59e9cb2e331cf974b79a Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Tue, 3 Jun 2025 12:37:28 -0400 Subject: [PATCH 10/14] syntax errors --- tests/phpunit/tests/image/meta.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/phpunit/tests/image/meta.php b/tests/phpunit/tests/image/meta.php index 8fab7b93ae795..3c813e1643142 100644 --- a/tests/phpunit/tests/image/meta.php +++ b/tests/phpunit/tests/image/meta.php @@ -217,10 +217,10 @@ public function data_stream() { 'title' => 'IPTC Headline', 'orientation' => '0', 'keywords' => array(), - 'created' => '2004-07-22T17:14:35+00:00', + 'created' => '2004-07-22T17:14:35+00:00', ), ), - 'Exif from a DMC-LX2 camera with keywords' => array( + 'Exif from a DMC-LX2 camera with keywords' => array( 'file' => 'testimagemeta://wp_read_image_metadata/image3.jpg', 'metadata' => array( 'aperture' => '8', @@ -235,7 +235,7 @@ public function data_stream() { 'title' => 'Photoshop Document Ttitle', 'orientation' => '1', 'keywords' => array( 'beach', 'baywatch', 'LA', 'sunset' ), - 'created' => '2011-05-25T09:22:07+00:00', + 'created' => '2011-05-25T09:22:07+00:00', ), ), ); From 16c02327e1a1253a1f1f63d7e3d0b9f8d0522514 Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Tue, 3 Jun 2025 12:42:58 -0400 Subject: [PATCH 11/14] syntax errors --- tests/phpunit/tests/image/meta.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/image/meta.php b/tests/phpunit/tests/image/meta.php index 3c813e1643142..aabf5e9b968c5 100644 --- a/tests/phpunit/tests/image/meta.php +++ b/tests/phpunit/tests/image/meta.php @@ -220,7 +220,7 @@ public function data_stream() { 'created' => '2004-07-22T17:14:35+00:00', ), ), - 'Exif from a DMC-LX2 camera with keywords' => array( + 'Exif from a DMC-LX2 camera with keywords' => array( 'file' => 'testimagemeta://wp_read_image_metadata/image3.jpg', 'metadata' => array( 'aperture' => '8', From 8490acee37783df412e1cce1801509e592922b47 Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Tue, 3 Jun 2025 12:59:03 -0400 Subject: [PATCH 12/14] syntax errors --- tests/phpunit/tests/admin/includes/wpExifDate2TS.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/phpunit/tests/admin/includes/wpExifDate2TS.php b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php index a7fd6457d6b78..3925855d8ef9a 100644 --- a/tests/phpunit/tests/admin/includes/wpExifDate2TS.php +++ b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php @@ -31,10 +31,6 @@ public function test_returns_false_for_invalid_input( $input ) { */ public function provideValidDates() { return array( - 'standard timestamp' => array( - '1710500000', // March 15, 2024 14:30:00 - '2024:03:15 14:30:00', - ), 'mysql format' => array( '2024-03-15 14:30:00', '2024:03:15 14:30:00', @@ -47,6 +43,10 @@ public function provideValidDates() { '2024-03-15', '2024:03:15 00:00:00', ), + 'incomplete date' => array( + '2024-03', + '2024:03:01 00:00:00', + ), ); } @@ -59,7 +59,6 @@ public function provideInvalidDates() { 'null' => array( null ), 'invalid date string' => array( 'not a date' ), 'malformed date' => array( '2024-13-45' ), - 'incomplete date' => array( '2024-03' ), 'boolean true' => array( true ), 'boolean false' => array( false ), 'array' => array( array() ), From e458706257caa4f34e524207c489cf3b38a6ac9a Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Tue, 3 Jun 2025 13:22:07 -0400 Subject: [PATCH 13/14] syntax errors --- tests/phpunit/tests/admin/includes/wpExifDate2TS.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/admin/includes/wpExifDate2TS.php b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php index 3925855d8ef9a..a13c03f21e3d0 100644 --- a/tests/phpunit/tests/admin/includes/wpExifDate2TS.php +++ b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php @@ -43,7 +43,7 @@ public function provideValidDates() { '2024-03-15', '2024:03:15 00:00:00', ), - 'incomplete date' => array( + 'incomplete date' => array( '2024-03', '2024:03:01 00:00:00', ), From baeddd9cf6723c181a05b9d7086a6b31f2c25319 Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Tue, 3 Jun 2025 15:41:08 -0400 Subject: [PATCH 14/14] syntax errors --- tests/phpunit/tests/admin/includes/wpExifDate2TS.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/admin/includes/wpExifDate2TS.php b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php index a13c03f21e3d0..4cf07f9f4f226 100644 --- a/tests/phpunit/tests/admin/includes/wpExifDate2TS.php +++ b/tests/phpunit/tests/admin/includes/wpExifDate2TS.php @@ -43,7 +43,7 @@ public function provideValidDates() { '2024-03-15', '2024:03:15 00:00:00', ), - 'incomplete date' => array( + 'incomplete date' => array( '2024-03', '2024:03:01 00:00:00', ),