Community Events: Trim events by Unix timestamp for accuracy.

The `date` and `end_date` fields are ''WP'' timestamps representing the venue's local time. As of meta:changeset:10270 (#meta4480), new `start_unix_timestamp` and `end_unix_timestamp` values are available, providing a proper ''Unix'' timestamp in the  UTC timezone. Using those is more precise, and removes the time window where the event has expired but still appears in the Events Widget.

To simplify the function, it now only accepts and returns the events themselves, rather than the entire response body.

See #51130
See #meta4480
Related: https://make.wordpress.org/core/2019/09/23/date-time-improvements-wp-5-3/


git-svn-id: https://develop.svn.wordpress.org/trunk@49145 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Ian Dunn 2020-10-14 18:17:55 +00:00
parent 3a19ff15f9
commit 92eba9720e
2 changed files with 200 additions and 142 deletions

View File

@ -158,9 +158,13 @@ class WP_Community_Events {
$response_body['location']['description'] = $this->user_location['description'];
}
/*
* Store the raw response, because events will expire before the cache does.
* The response will need to be processed every page load.
*/
$this->cache_events( $response_body, $expiration );
$response_body = $this->trim_events( $response_body );
$response_body['events'] = $this->trim_events( $response_body['events'] );
$response_body = $this->format_event_data_time( $response_body );
return $response_body;
@ -346,7 +350,10 @@ class WP_Community_Events {
*/
public function get_cached_events() {
$cached_response = get_site_transient( $this->get_events_transient_key( $this->user_location ) );
$cached_response = $this->trim_events( $cached_response );
if ( isset( $cached_response['events'] ) ) {
$cached_response['events'] = $this->trim_events( $cached_response['events'] );
}
return $this->format_event_data_time( $cached_response );
}
@ -435,44 +442,44 @@ class WP_Community_Events {
*
* @since 4.8.0
* @since 4.9.7 Stick a WordCamp to the final list.
* @since 5.6.0 Accepts and returns only the events, rather than an entire HTTP response.
*
* @param array $response_body The response body which contains the events.
* @param array $events The events that will be prepared.
* @return array The response body with events trimmed.
*/
protected function trim_events( $response_body ) {
if ( isset( $response_body['events'] ) ) {
$wordcamps = array();
$today = current_time( 'Y-m-d' );
protected function trim_events( array $events ) {
$future_events = array();
foreach ( $response_body['events'] as $key => $event ) {
/*
* Skip WordCamps, because they might be multi-day events.
* Save a copy so they can be pinned later.
*/
if ( 'wordcamp' === $event['type'] ) {
$wordcamps[] = $event;
continue;
}
foreach ( $events as $event ) {
/*
* The API's `date` and `end_date` fields are in the _event's_ local timezone, but UTC is needed so
* it can be converted to the _user's_ local time.
*/
$end_time = (int) $event['end_unix_timestamp'];
// We don't get accurate time with timezone from API, so we only take the date part (Y-m-d).
$event_date = substr( $event['date'], 0, 10 );
if ( $today > $event_date ) {
unset( $response_body['events'][ $key ] );
}
}
$response_body['events'] = array_slice( $response_body['events'], 0, 3 );
$trimmed_event_types = wp_list_pluck( $response_body['events'], 'type' );
// Make sure the soonest upcoming WordCamp is pinned in the list.
if ( ! in_array( 'wordcamp', $trimmed_event_types, true ) && $wordcamps ) {
array_pop( $response_body['events'] );
array_push( $response_body['events'], $wordcamps[0] );
if ( time() < $end_time ) {
array_push( $future_events, $event );
}
}
return $response_body;
$future_wordcamps = array_filter(
$future_events,
function( $wordcamp ) {
return 'wordcamp' === $wordcamp['type'];
}
);
$future_wordcamps = array_values( $future_wordcamps ); // Remove gaps in indices.
$trimmed_events = array_slice( $future_events, 0, 3 );
$trimmed_event_types = wp_list_pluck( $trimmed_events, 'type' );
// Make sure the soonest upcoming WordCamp is pinned in the list.
if ( $future_wordcamps && ! in_array( 'wordcamp', $trimmed_event_types, true ) ) {
array_pop( $trimmed_events );
array_push( $trimmed_events, $future_wordcamps[0] );
}
return $trimmed_events;
}
/**

View File

@ -153,7 +153,7 @@ class Test_WP_Community_Events extends WP_UnitTestCase {
/**
* Test: With a valid response, get_events() should return an associative array containing a location array and
* an events array with individual events that have formatted time and date.
* an events array with individual events that have Unix start/end timestamps.
*
* @since 4.8.0
*/
@ -164,15 +164,15 @@ class Test_WP_Community_Events extends WP_UnitTestCase {
$this->assertNotWPError( $response );
$this->assertSameSetsWithIndex( $this->get_user_location(), $response['location'] );
$this->assertSame( gmdate( 'l, M j, Y', strtotime( 'next Sunday 1pm' ) ), $response['events'][0]['formatted_date'] );
$this->assertSame( '1:00 pm', $response['events'][0]['formatted_time'] );
$this->assertSame( strtotime( 'next Sunday 1pm' ), $response['events'][0]['start_unix_timestamp'] );
$this->assertSame( strtotime( 'next Sunday 2pm' ), $response['events'][0]['end_unix_timestamp'] );
remove_filter( 'pre_http_request', array( $this, '_http_request_valid_response' ) );
}
/**
* Test: get_cached_events() should return the same data as get_events(), including formatted time
* and date values for each event.
* Test: `get_cached_events()` should return the same data as get_events(), including Unix start/end
* timestamps for each event.
*
* @since 4.8.0
*/
@ -185,8 +185,8 @@ class Test_WP_Community_Events extends WP_UnitTestCase {
$this->assertNotWPError( $cached_events );
$this->assertSameSetsWithIndex( $this->get_user_location(), $cached_events['location'] );
$this->assertSame( gmdate( 'l, M j, Y', strtotime( 'next Sunday 1pm' ) ), $cached_events['events'][0]['formatted_date'] );
$this->assertSame( '1:00 pm', $cached_events['events'][0]['formatted_time'] );
$this->assertSame( strtotime( 'next Sunday 1pm' ), $cached_events['events'][0]['start_unix_timestamp'] );
$this->assertSame( strtotime( 'next Sunday 2pm' ), $cached_events['events'][0]['end_unix_timestamp'] );
remove_filter( 'pre_http_request', array( $this, '_http_request_valid_response' ) );
}
@ -204,50 +204,7 @@ class Test_WP_Community_Events extends WP_UnitTestCase {
'body' => wp_json_encode(
array(
'location' => $this->get_user_location(),
'events' => array(
array(
'type' => 'meetup',
'title' => 'Flexbox + CSS Grid: Magic for Responsive Layouts',
'url' => 'https://www.meetup.com/Eastbay-WordPress-Meetup/events/236031233/',
'meetup' => 'The East Bay WordPress Meetup Group',
'meetup_url' => 'https://www.meetup.com/Eastbay-WordPress-Meetup/',
'date' => gmdate( 'Y-m-d H:i:s', strtotime( 'next Sunday 1pm' ) ),
'location' => array(
'location' => 'Oakland, CA, USA',
'country' => 'us',
'latitude' => 37.808453,
'longitude' => -122.26593,
),
),
array(
'type' => 'meetup',
'title' => 'Part 3- Site Maintenance - Tools to Make It Easy',
'url' => 'https://www.meetup.com/Wordpress-Bay-Area-CA-Foothills/events/237706839/',
'meetup' => 'WordPress Bay Area Foothills Group',
'meetup_url' => 'https://www.meetup.com/Wordpress-Bay-Area-CA-Foothills/',
'date' => gmdate( 'Y-m-d H:i:s', strtotime( 'next Wednesday 1:30pm' ) ),
'location' => array(
'location' => 'Milpitas, CA, USA',
'country' => 'us',
'latitude' => 37.432813,
'longitude' => -121.907095,
),
),
array(
'type' => 'wordcamp',
'title' => 'WordCamp Kansas City',
'url' => 'https://2017.kansascity.wordcamp.org',
'meetup' => null,
'meetup_url' => null,
'date' => gmdate( 'Y-m-d H:i:s', strtotime( 'next Saturday' ) ),
'location' => array(
'location' => 'Kansas City, MO',
'country' => 'US',
'latitude' => 39.0392325,
'longitude' => -94.577076,
),
),
),
'events' => $this->get_valid_events(),
)
),
'response' => array(
@ -259,51 +216,136 @@ class Test_WP_Community_Events extends WP_UnitTestCase {
}
/**
* Test: get_events() should return the events with the WordCamp pinned in the prepared list.
* Get a sample of valid events.
*
* @since 4.9.7
* @return array[]
*/
public function test_get_events_pin_wordcamp() {
add_filter( 'pre_http_request', array( $this, '_http_request_valid_response_unpinned_wordcamp' ) );
protected function get_valid_events() {
return array(
array(
'type' => 'meetup',
'title' => 'Flexbox + CSS Grid: Magic for Responsive Layouts',
'url' => 'https://www.meetup.com/Eastbay-WordPress-Meetup/events/236031233/',
'meetup' => 'The East Bay WordPress Meetup Group',
'meetup_url' => 'https://www.meetup.com/Eastbay-WordPress-Meetup/',
'start_unix_timestamp' => strtotime( 'next Sunday 1pm' ),
'end_unix_timestamp' => strtotime( 'next Sunday 2pm' ),
$response_body = $this->instance->get_events();
'location' => array(
'location' => 'Oakland, CA, USA',
'country' => 'us',
'latitude' => 37.808453,
'longitude' => -122.26593,
),
),
/*
* San Diego was at position 3 in the mock API response, but pinning puts it at position 2,
* so that it remains in the list. The other events should remain unchanged.
*/
$this->assertCount( 3, $response_body['events'] );
$this->assertSame( $response_body['events'][0]['title'], 'Flexbox + CSS Grid: Magic for Responsive Layouts' );
$this->assertSame( $response_body['events'][1]['title'], 'Part 3- Site Maintenance - Tools to Make It Easy' );
$this->assertSame( $response_body['events'][2]['title'], 'WordCamp San Diego' );
array(
'type' => 'meetup',
'title' => 'Part 3- Site Maintenance - Tools to Make It Easy',
'url' => 'https://www.meetup.com/Wordpress-Bay-Area-CA-Foothills/events/237706839/',
'meetup' => 'WordPress Bay Area Foothills Group',
'meetup_url' => 'https://www.meetup.com/Wordpress-Bay-Area-CA-Foothills/',
'start_unix_timestamp' => strtotime( 'next Wednesday 1:30pm' ),
'end_unix_timestamp' => strtotime( 'next Wednesday 2:30pm' ),
remove_filter( 'pre_http_request', array( $this, '_http_request_valid_response_unpinned_wordcamp' ) );
'location' => array(
'location' => 'Milpitas, CA, USA',
'country' => 'us',
'latitude' => 37.432813,
'longitude' => -121.907095,
),
),
array(
'type' => 'wordcamp',
'title' => 'WordCamp San Francisco',
'url' => 'https://sf.wordcamp.org/2020/',
'meetup' => null,
'meetup_url' => null,
'start_unix_timestamp' => strtotime( 'next Saturday' ),
'end_unix_timestamp' => strtotime( 'next Saturday 8pm' ),
'location' => array(
'location' => 'San Francisco, CA',
'country' => 'US',
'latitude' => 37.432813,
'longitude' => -121.907095,
),
),
);
}
/**
* Simulates a valid HTTP response where a WordCamp needs to be pinned higher than it's default position.
* Test: `trim_events()` should immediately remove expired events.
*
* @covers WP_Community_Events::trim_events
*
* @since 5.6.0
*/
public function test_trim_expired_events() {
$trim_events = new ReflectionMethod( $this->instance, 'trim_events' );
$trim_events->setAccessible( true );
$events = $this->get_valid_events();
// This should be removed because it's already ended.
$events[0]['start_unix_timestamp'] = strtotime( '1 hour ago' );
$events[0]['end_unix_timestamp'] = strtotime( '2 seconds ago' );
// This should remain because it hasn't ended yet.
$events[1]['start_unix_timestamp'] = strtotime( '2 seconds ago' );
$events[1]['end_unix_timestamp'] = strtotime( '+1 hour' );
$actual = $trim_events->invoke( $this->instance, $events );
$this->assertCount( 2, $actual );
$this->assertSame( $actual[0]['title'], 'Part 3- Site Maintenance - Tools to Make It Easy' );
$this->assertSame( $actual[1]['title'], 'WordCamp San Francisco' );
}
/**
* Test: get_events() should return the events with the WordCamp pinned in the prepared list.
*
* @covers WP_Community_Events::trim_events
*
* @since 4.9.7
*
* @return array A mock HTTP response.
* @since 5.6.0 Tests `trim_events()` directly instead of indirectly via `get_events()`.
*/
public function _http_request_valid_response_unpinned_wordcamp() {
public function test_trim_events_pin_wordcamp() {
$trim_events = new ReflectionMethod( $this->instance, 'trim_events' );
$trim_events->setAccessible( true );
$actual = $trim_events->invoke( $this->instance, $this->_events_with_unpinned_wordcamp() );
/*
* San Diego was at index 3 in the mock API response, but pinning puts it at index 2,
* so that it remains in the list. The other events should remain unchanged.
*/
$this->assertCount( 3, $actual );
$this->assertSame( $actual[0]['title'], 'Flexbox + CSS Grid: Magic for Responsive Layouts' );
$this->assertSame( $actual[1]['title'], 'Part 3- Site Maintenance - Tools to Make It Easy' );
$this->assertSame( $actual[2]['title'], 'WordCamp San Diego' );
}
/**
* Simulates a scenario where a WordCamp needs to be pinned higher than it's default position.
*
* @since 4.9.7
* @since 5.6.0 Accepts and returns only the events, rather than an entire HTTP response.
*
* @return array A list of mock events.
*/
public function _events_with_unpinned_wordcamp() {
return array(
'headers' => '',
'response' => array( 'code' => 200 ),
'cookies' => '',
'filename' => '',
'body' => wp_json_encode(
array(
'location' => $this->get_user_location(),
'events' => array(
array(
'type' => 'meetup',
'title' => 'Flexbox + CSS Grid: Magic for Responsive Layouts',
'url' => 'https://www.meetup.com/Eastbay-WordPress-Meetup/events/236031233/',
'meetup' => 'The East Bay WordPress Meetup Group',
'meetup_url' => 'https://www.meetup.com/Eastbay-WordPress-Meetup/',
'date' => gmdate( 'Y-m-d H:i:s', strtotime( 'next Monday 1pm' ) ),
'start_unix_timestamp' => strtotime( 'next Monday 1pm' ),
'end_unix_timestamp' => strtotime( 'next Monday 2pm' ),
'location' => array(
'location' => 'Oakland, CA, USA',
'country' => 'us',
@ -317,7 +359,9 @@ class Test_WP_Community_Events extends WP_UnitTestCase {
'url' => 'https://www.meetup.com/Wordpress-Bay-Area-CA-Foothills/events/237706839/',
'meetup' => 'WordPress Bay Area Foothills Group',
'meetup_url' => 'https://www.meetup.com/Wordpress-Bay-Area-CA-Foothills/',
'date' => gmdate( 'Y-m-d H:i:s', strtotime( 'next Tuesday 1:30pm' ) ),
'start_unix_timestamp' => strtotime( 'next Tuesday 1:30pm' ),
'end_unix_timestamp' => strtotime( 'next Tuesday 2:30pm' ),
'location' => array(
'location' => 'Milpitas, CA, USA',
'country' => 'us',
@ -331,7 +375,9 @@ class Test_WP_Community_Events extends WP_UnitTestCase {
'url' => 'https://www.meetup.com/sanjosewp/events/245419844/',
'meetup' => 'The San Jose WordPress Meetup',
'meetup_url' => 'https://www.meetup.com/sanjosewp/',
'date' => gmdate( 'Y-m-d H:i:s', strtotime( 'next Wednesday 5:30pm' ) ),
'start_unix_timestamp' => strtotime( 'next Wednesday 5:30pm' ),
'end_unix_timestamp' => strtotime( 'next Wednesday 6:30pm' ),
'location' => array(
'location' => 'Milpitas, CA, USA',
'country' => 'us',
@ -345,7 +391,9 @@ class Test_WP_Community_Events extends WP_UnitTestCase {
'url' => 'https://2018.sandiego.wordcamp.org',
'meetup' => null,
'meetup_url' => null,
'date' => gmdate( 'Y-m-d H:i:s', strtotime( 'next Thursday 9am' ) ),
'start_unix_timestamp' => strtotime( 'next Thursday 9am' ),
'end_unix_timestamp' => strtotime( 'next Thursday 10am' ),
'location' => array(
'location' => 'San Diego, CA',
'country' => 'US',
@ -353,9 +401,6 @@ class Test_WP_Community_Events extends WP_UnitTestCase {
'longitude' => -117.1534513,
),
),
),
)
),
);
}
@ -363,23 +408,25 @@ class Test_WP_Community_Events extends WP_UnitTestCase {
* Test: get_events() shouldn't stick an extra WordCamp when there's already one that naturally
* falls into the list.
*
* @covers WP_Community_Events::trim_events
*
* @since 4.9.7
* @since 5.6.0 Tests `trim_events()` directly instead of indirectly via `get_events()`.
*/
public function test_get_events_dont_pin_multiple_wordcamps() {
add_filter( 'pre_http_request', array( $this, '_http_request_valid_response_multiple_wordcamps' ) );
public function test_trim_events_dont_pin_multiple_wordcamps() {
$trim_events = new ReflectionMethod( $this->instance, 'trim_events' );
$trim_events->setAccessible( true );
$response_body = $this->instance->get_events();
$actual = $trim_events->invoke( $this->instance, $this->_events_with_multiple_wordcamps() );
/*
* The first meetup should be removed because it's expired, while the next 3 events are selected.
* WordCamp LA should not be stuck to the list, because San Diego already appears naturally.
*/
$this->assertCount( 3, $response_body['events'] );
$this->assertSame( $response_body['events'][0]['title'], 'WordCamp San Diego' );
$this->assertSame( $response_body['events'][1]['title'], 'Part 3- Site Maintenance - Tools to Make It Easy' );
$this->assertSame( $response_body['events'][2]['title'], 'WordPress Q&A' );
remove_filter( 'pre_http_request', array( $this, '_http_request_valid_response_multiple_wordcamps' ) );
$this->assertCount( 3, $actual );
$this->assertSame( $actual[0]['title'], 'WordCamp San Diego' );
$this->assertSame( $actual[1]['title'], 'Part 3- Site Maintenance - Tools to Make It Easy' );
$this->assertSame( $actual[2]['title'], 'WordPress Q&A' );
}
/**
@ -387,26 +434,21 @@ class Test_WP_Community_Events extends WP_UnitTestCase {
* no need to pin extra camp b/c one already exists in response
*
* @since 4.9.7
* @since 5.6.0 Tests `trim_events()` directly instead of indirectly via `get_events()`.
*
* @return array A mock HTTP response.
*/
public function _http_request_valid_response_multiple_wordcamps() {
public function _events_with_multiple_wordcamps() {
return array(
'headers' => '',
'response' => array( 'code' => 200 ),
'cookies' => '',
'filename' => '',
'body' => wp_json_encode(
array(
'location' => $this->get_user_location(),
'events' => array(
array(
'type' => 'meetup',
'title' => 'Flexbox + CSS Grid: Magic for Responsive Layouts',
'url' => 'https://www.meetup.com/Eastbay-WordPress-Meetup/events/236031233/',
'meetup' => 'The East Bay WordPress Meetup Group',
'meetup_url' => 'https://www.meetup.com/Eastbay-WordPress-Meetup/',
'date' => gmdate( 'Y-m-d H:i:s', strtotime( '2 days ago' ) ),
'start_unix_timestamp' => strtotime( '2 days ago' ) - HOUR_IN_SECONDS,
'end_unix_timestamp' => strtotime( '2 days ago' ),
'location' => array(
'location' => 'Oakland, CA, USA',
'country' => 'us',
@ -414,13 +456,16 @@ class Test_WP_Community_Events extends WP_UnitTestCase {
'longitude' => -122.26593,
),
),
array(
'type' => 'wordcamp',
'title' => 'WordCamp San Diego',
'url' => 'https://2018.sandiego.wordcamp.org',
'meetup' => null,
'meetup_url' => null,
'date' => gmdate( 'Y-m-d H:i:s', strtotime( 'next Tuesday 9am' ) ),
'start_unix_timestamp' => strtotime( 'next Tuesday 9am' ),
'end_unix_timestamp' => strtotime( 'next Tuesday 10am' ),
'location' => array(
'location' => 'San Diego, CA',
'country' => 'US',
@ -428,13 +473,16 @@ class Test_WP_Community_Events extends WP_UnitTestCase {
'longitude' => -117.1534513,
),
),
array(
'type' => 'meetup',
'title' => 'Part 3- Site Maintenance - Tools to Make It Easy',
'url' => 'https://www.meetup.com/Wordpress-Bay-Area-CA-Foothills/events/237706839/',
'meetup' => 'WordPress Bay Area Foothills Group',
'meetup_url' => 'https://www.meetup.com/Wordpress-Bay-Area-CA-Foothills/',
'date' => gmdate( 'Y-m-d H:i:s', strtotime( 'next Wednesday 1:30pm' ) ),
'start_unix_timestamp' => strtotime( 'next Wednesday 1:30pm' ),
'end_unix_timestamp' => strtotime( 'next Wednesday 2:30pm' ),
'location' => array(
'location' => 'Milpitas, CA, USA',
'country' => 'us',
@ -442,13 +490,16 @@ class Test_WP_Community_Events extends WP_UnitTestCase {
'longitude' => -121.907095,
),
),
array(
'type' => 'meetup',
'title' => 'WordPress Q&A',
'url' => 'https://www.meetup.com/sanjosewp/events/245419844/',
'meetup' => 'The San Jose WordPress Meetup',
'meetup_url' => 'https://www.meetup.com/sanjosewp/',
'date' => gmdate( 'Y-m-d H:i:s', strtotime( 'next Thursday 5:30pm' ) ),
'start_unix_timestamp' => strtotime( 'next Thursday 5:30pm' ),
'end_unix_timestamp' => strtotime( 'next Thursday 6:30pm' ),
'location' => array(
'location' => 'Milpitas, CA, USA',
'country' => 'us',
@ -456,13 +507,16 @@ class Test_WP_Community_Events extends WP_UnitTestCase {
'longitude' => -121.889313,
),
),
array(
'type' => 'wordcamp',
'title' => 'WordCamp Los Angeles',
'url' => 'https://2018.la.wordcamp.org',
'meetup' => null,
'meetup_url' => null,
'date' => gmdate( 'Y-m-d H:i:s', strtotime( 'next Friday 9am' ) ),
'start_unix_timestamp' => strtotime( 'next Friday 9am' ),
'end_unix_timestamp' => strtotime( 'next Friday 10am' ),
'location' => array(
'location' => 'Los Angeles, CA',
'country' => 'US',
@ -470,9 +524,6 @@ class Test_WP_Community_Events extends WP_UnitTestCase {
'longitude' => -118.285426,
),
),
),
)
),
);
}