Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate that a meta_value is a recognizable date value before storing #1703

Merged
merged 11 commits into from
Apr 23, 2021
56 changes: 36 additions & 20 deletions includes/classes/Indexable.php
Original file line number Diff line number Diff line change
Expand Up @@ -441,29 +441,45 @@ public function prepare_meta_value_types( $meta_value ) {

$meta_types['boolean'] = filter_var( $meta_value, FILTER_VALIDATE_BOOLEAN );

if ( is_string( $meta_value ) ) {
$timestamp = strtotime( $meta_value );
$meta_types = $this->prepare_date_meta_values( $meta_types, $meta_value );

$date = '1971-01-01';
$datetime = '1971-01-01 00:00:01';
$time = '00:00:01';
return $meta_types;
}

/**
* Workaround for `strtotime` potentially producing valid timestamps that would result in 5 digit years
* which DateTime::__construct() can't handle,
* resulting in an 'Uncaught Error: Call to a member function getTimestamp() on bool' in date_i18n.
*
* This better be fixed by 9999-12-31 23:59:59
*/
if ( false !== $timestamp && 253402300799 > $timestamp ) {
$date = date_i18n( 'Y-m-d', $timestamp );
$datetime = date_i18n( 'Y-m-d H:i:s', $timestamp );
$time = date_i18n( 'H:i:s', $timestamp );
}
/**
* Checks if a meta_value is a valid date and prepare extra meta-data.
*
* @param array $meta_types Array of currently prepared data
* @param string $meta_value Meta value to prepare.
*
* @return array
*/
public function prepare_date_meta_values( $meta_types, $meta_value ) {

$meta_types['date'] = $date;
$meta_types['datetime'] = $datetime;
$meta_types['time'] = $time;
if ( empty( $meta_value ) || ! is_string( $meta_value ) ) {
return $meta_types;
}

$meta_types['date'] = '1970-01-01';
$meta_types['datetime'] = '1970-01-01 00:00:01';
$meta_types['time'] = '00:00:01';

try {
// is this is a recognizable date format?
$new_date = new \DateTime( $meta_value, \wp_timezone() );
$timestamp = $new_date->getTimestamp();

// PHP allows DateTime to build dates with the non-existing year 0000, and this causes
// issues when integrating into stricter systems. This is by design:
// https://bugs.php.net/bug.php?id=60288
if ( false !== $timestamp && '0000' !== $new_date->format( 'Y' ) ) {
$meta_types['date'] = $new_date->format( 'Y-m-d' );
$meta_types['datetime'] = $new_date->format( 'Y-m-d H:i:s' );
$meta_types['time'] = $new_date->format( 'H:i:s' );
}
} catch ( \Exception $e ) {
// if $meta_value is not a recognizable date format, DateTime will throw an exception,
// just catch it and move on.
}

return $meta_types;
Expand Down
59 changes: 52 additions & 7 deletions tests/php/indexables/TestPost.php
Original file line number Diff line number Diff line change
Expand Up @@ -3200,26 +3200,71 @@ public function filter_ep_prepare_meta_excluded_public_keys( $meta_keys ) {
*/
public function testMetaValueTypes() {

$intval = ElasticPress\Indexables::factory()->get( 'post' )->prepare_meta_value_types( 13 );
$floatval = ElasticPress\Indexables::factory()->get( 'post' )->prepare_meta_value_types( 13.43 );
$textval = ElasticPress\Indexables::factory()->get( 'post' )->prepare_meta_value_types( 'some text' );
$bool_false_val = ElasticPress\Indexables::factory()->get( 'post' )->prepare_meta_value_types( false );
$bool_true_val = ElasticPress\Indexables::factory()->get( 'post' )->prepare_meta_value_types( true );
$dateval = ElasticPress\Indexables::factory()->get( 'post' )->prepare_meta_value_types( '2015-01-01' );
$intval = ElasticPress\Indexables::factory()->get( 'post' )->prepare_meta_value_types( 13 );
$floatval = ElasticPress\Indexables::factory()->get( 'post' )->prepare_meta_value_types( 13.43 );
$textval = ElasticPress\Indexables::factory()->get( 'post' )->prepare_meta_value_types( 'some text' );
$float_string = ElasticPress\Indexables::factory()->get( 'post' )->prepare_meta_value_types( '20.000000' );
$bool_false_val = ElasticPress\Indexables::factory()->get( 'post' )->prepare_meta_value_types( false );
$bool_true_val = ElasticPress\Indexables::factory()->get( 'post' )->prepare_meta_value_types( true );
$dateval = ElasticPress\Indexables::factory()->get( 'post' )->prepare_meta_value_types( '2015-01-01' );
$recognizable_time = ElasticPress\Indexables::factory()->get( 'post' )->prepare_meta_value_types( 'third monday of January 2020' );
$relative_format = ElasticPress\Indexables::factory()->get( 'post' )->prepare_meta_value_types( '+1 year' );

$this->assertTrue( is_array( $intval ) && 5 === count( $intval ) );
$this->assertTrue( is_array( $intval ) && array_key_exists( 'long', $intval ) && 13 === $intval['long'] );
$this->assertTrue( is_array( $floatval ) && 5 === count( $floatval ) );
$this->assertTrue( is_array( $floatval ) && array_key_exists( 'double', $floatval ) && 13.43 === $floatval['double'] );
$this->assertTrue( is_array( $textval ) && 6 === count( $textval ) );
$this->assertTrue( is_array( $textval ) && array_key_exists( 'raw', $textval ) && 'some text' === $textval['raw'] );
$this->assertTrue( is_array( $float_string ) && 8 === count( $float_string ) );
$this->assertTrue( is_array( $float_string ) && array_key_exists( 'raw', $float_string ) && '20.000000' === $float_string['raw'] );
$this->assertTrue( is_array( $bool_false_val ) && 3 === count( $bool_false_val ) );
$this->assertTrue( is_array( $bool_false_val ) && array_key_exists( 'boolean', $bool_false_val ) && false === $bool_false_val['boolean'] );
$this->assertTrue( is_array( $bool_true_val ) && 3 === count( $bool_true_val ) );
$this->assertTrue( is_array( $bool_true_val ) && array_key_exists( 'boolean', $bool_true_val ) && true === $bool_true_val['boolean'] );
$this->assertTrue( is_array( $dateval ) && 6 === count( $dateval ) );
$this->assertTrue( is_array( $dateval ) && array_key_exists( 'datetime', $dateval ) && '2015-01-01 00:00:00' === $dateval['datetime'] );

$this->assertTrue( is_array( $recognizable_time ) && 6 === count( $recognizable_time ) );
$this->assertTrue( is_array( $recognizable_time ) && array_key_exists( 'datetime', $recognizable_time ) && '2020-01-20 00:00:00' === $recognizable_time['datetime'] );
$this->assertTrue( is_array( $relative_format ) && 6 === count( $relative_format ) );
$this->assertTrue( is_array( $relative_format ) && array_key_exists( 'datetime', $relative_format ) && date( 'Y-m-d H:i:s', strtotime( '+1 year' ) ) === $relative_format['datetime'] );

}

public function testMetaValueTypeDate() {
$meta_types = array();

$default_date_time = array(
'date' => '1970-01-01',
'datetime' => '1970-01-01 00:00:01',
'time' => '00:00:01'
);

// Invalid dates
$textval = ElasticPress\Indexables::factory()->get( 'post' )->prepare_date_meta_values( $meta_types, 'some text' );
$k20_string = ElasticPress\Indexables::factory()->get( 'post' )->prepare_date_meta_values( $meta_types, '20.000000' );
$bool_false_val = ElasticPress\Indexables::factory()->get( 'post' )->prepare_date_meta_values( $meta_types, false );
$bool_true_val = ElasticPress\Indexables::factory()->get( 'post' )->prepare_date_meta_values( $meta_types, true );

$this->assertEquals( $default_date_time, $textval );
$this->assertEquals( $default_date_time, $k20_string );
$this->assertEmpty( $bool_false_val );
$this->assertEmpty( $bool_true_val );

// Valid dates
$intval = ElasticPress\Indexables::factory()->get( 'post' )->prepare_date_meta_values( $meta_types, time() );
$floatval = ElasticPress\Indexables::factory()->get( 'post' )->prepare_date_meta_values( $meta_types, 13.43 );
$float_string = ElasticPress\Indexables::factory()->get( 'post' )->prepare_date_meta_values( $meta_types, '20.000001' );
$dateval = ElasticPress\Indexables::factory()->get( 'post' )->prepare_date_meta_values( $meta_types, '2015-01-01' );
$recognizable_time = ElasticPress\Indexables::factory()->get( 'post' )->prepare_date_meta_values( $meta_types, 'third day of January 2020' );
$relative_format = ElasticPress\Indexables::factory()->get( 'post' )->prepare_date_meta_values( $meta_types, '+1 year' );

$this->assertFalse( isset( $intval['date'] ) || isset( $intval['datetime'] ) || isset( $intval['time'] ) );
$this->assertFalse( isset( $floatval['date'] ) || isset( $floatval['datetime'] ) || isset( $floatval['time'] ) );
$this->assertTrue( isset( $float_string['date'] ) && isset( $float_string['datetime'] ) && isset( $float_string['time'] ) );
$this->assertTrue( isset( $dateval['date'] ) && isset( $dateval['datetime'] ) && isset( $dateval['time'] ) );
$this->assertTrue( isset( $recognizable_time['date'] ) && isset( $recognizable_time['datetime'] ) && isset( $recognizable_time['time'] ) );
$this->assertTrue( isset( $relative_format['date'] ) && isset( $relative_format['datetime'] ) && isset( $relative_format['time'] ) );
}

/**
Expand Down