Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 50 additions & 6 deletions src/wp-admin/includes/image.php
Original file line number Diff line number Diff line change
Expand Up @@ -792,18 +792,48 @@ function wp_exif_frac2dec( $str ) {
}

/**
* Converts the exif date format to a unix timestamp.
* @ticket 56887
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually don't mention tickets.
Note: this can be removed during commit by the committer.

*
* Convert the exif date format to DateTime object
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Converts"

*
* @since 6.9.0
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will Need to be updated to the correct version

*
* @param string $str
* @param string $timezone Timezone or offset string.
* @return DateTimeImmutable|false Return false if not valid date
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing period at the end of the comment

*/
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;
}

/**
Expand Down Expand Up @@ -907,7 +937,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.
Expand Down Expand Up @@ -1015,7 +1049,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'];
Expand Down
130 changes: 130 additions & 0 deletions tests/phpunit/tests/admin/includes/wpExicDatetime.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php

/**
* Class Tests_Admin_wpExifDatetime
*
* Contains unit tests for validating the functionality of the wp_exif_datetime function,
* which is responsible for formatting datetime strings to the EXIF-compliant format.
*
* @group datetime
* @group image
*
* @covers ::wp_exif_datetime
*/
class Tests_Admin_wpExifDatetime extends WP_UnitTestCase {

/**
* @ticket 56887
*
* Test valid date inputs and their expected formatted outputs.
*
* @dataProvider provideValidDates
*
* @param string $input_date The input date string to be formatted.
* @param string $expected The expected formatted date string.
*
* @return void
*/
public function test_valid_dates( $input_date, $expected ) {
$datetime = wp_exif_datetime( $input_date );
$this->assertEquals( $expected, $datetime->format( 'Y:m:d H:i:s' ) );
}

/**
* @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_date ) {
$this->assertFalse( wp_exif_datetime( $input_date ) );
}

/**
* Data provider for valid dates
*/
public function provideValidDates() {
return array(
'unix timestamp' => array(
'1710500000', // March 15, 2024 14:30:00
'0000:06:03 17:10:50',
),
'mysql datetime' => array(
'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',
),
'mysql date only' => array(
'2024-03-15',
'2024:03:15 00:00:00',
),
'incomplete date' => array(
'2024-03',
'2024:03:01 00:00:00',
),
);
}

/**
* Data provider for invalid dates that should trigger exceptions
*/
public function provideInvalidDates() {
return array(
'empty string' => array( '' ),
'null' => array( null ),
'boolean false' => array( false ),
'boolean true' => array( true ),
'invalid format' => array( 'not a date' ),
'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() ),
'out of bounds timestamp' => array( 253402300800 ), // Year 9999
'negative timestamp' => array( - 62167219200 ), // Year 0
);
}

/**
* @ticket 56887
*
* Test handling of edge case date and time formats.
*
* @return void
*/
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' ) );
}

/**
* @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() {

$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' ) );
}
}
71 changes: 71 additions & 0 deletions tests/phpunit/tests/admin/includes/wpExifDate2TS.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

/**
* @group admin
* @group image
*/
class Tests_Admin_wpExifDate2ts extends WP_UnitTestCase {

/**
* Test conversion of various date formats to EXIF format
*
* @dataProvider provideValidDates
*/
public function test_valid_dates( $input_date, $expected ) {
$result = wp_exif_datetime( $input_date );
$this->assertSame( $expected, $result->format( 'Y:m:d H:i:s' ) );
}

/**
* Test that invalid inputs return false
*
* @dataProvider provideInvalidDates
*/
public function test_returns_false_for_invalid_input( $input ) {
$result = wp_exif_datetime( $input );
$this->assertFalse( $result );
}

/**
* Data provider for valid dates
*/
public function provideValidDates() {
return array(
'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',
'2024:03:15 00:00:00',
),
'incomplete date' => array(
'2024-03',
'2024:03:01 00:00:00',
),
);
}

/**
* Data provider for invalid dates
*/
public function provideInvalidDates() {
return array(
'empty string' => array( '' ),
'null' => array( null ),
'invalid date string' => array( 'not a date' ),
'malformed date' => array( '2024-13-45' ),
'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' ),
);
}
}
2 changes: 2 additions & 0 deletions tests/phpunit/tests/image/meta.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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',
),
),
);
Expand Down
Loading