Dashboard: Improve the handling of locations determined by geolocating the IP address and by entering a city name. Fix couple of edge cases, and some names.

Props iandunn coreymckrill.
Fixes #40702.
Built from https://develop.svn.wordpress.org/trunk@40790


git-svn-id: http://core.svn.wordpress.org/trunk@40648 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Andrew Ozz 2017-05-19 05:48:42 +00:00
parent daab579a88
commit 7a9ce6d03f
7 changed files with 139 additions and 29 deletions

View File

@ -312,14 +312,33 @@ function wp_ajax_get_community_events() {
$saved_location = get_user_option( 'community-events-location', $user_id ); $saved_location = get_user_option( 'community-events-location', $user_id );
$events_client = new WP_Community_Events( $user_id, $saved_location ); $events_client = new WP_Community_Events( $user_id, $saved_location );
$events = $events_client->get_events( $search, $timezone ); $events = $events_client->get_events( $search, $timezone );
$ip_changed = false;
if ( is_wp_error( $events ) ) { if ( is_wp_error( $events ) ) {
wp_send_json_error( array( wp_send_json_error( array(
'error' => $events->get_error_message(), 'error' => $events->get_error_message(),
) ); ) );
} else { } else {
if ( isset( $events['location'] ) ) { if ( empty( $saved_location['ip'] ) && ! empty( $events['location']['ip'] ) ) {
// Store the location network-wide, so the user doesn't have to set it on each site. $ip_changed = true;
} elseif ( isset( $saved_location['ip'] ) && ! empty( $events['location']['ip'] ) && $saved_location['ip'] !== $events['location']['ip'] ) {
$ip_changed = true;
}
/*
* The location should only be updated when it changes. The API doesn't always return
* a full location; sometimes it's missing the description or country. The location
* that was saved during the initial request is known to be good and complete, though.
* It should be left in tact until the user explicitly changes it (either by manually
* searching for a new location, or by changing their IP address).
*
* If the location were updated with an incomplete response from the API, then it could
* break assumptions that the UI makes (e.g., that there will always be a description
* that corresponds to a latitude/longitude location).
*
* The location is stored network-wide, so that the user doesn't have to set it on each site.
*/
if ( $ip_changed || $search ) {
update_user_option( $user_id, 'community-events-location', $events['location'], true ); update_user_option( $user_id, 'community-events-location', $events['location'], true );
} }

View File

@ -94,12 +94,13 @@ class WP_Community_Events {
return $cached_events; return $cached_events;
} }
$request_url = $this->get_request_url( $location_search, $timezone ); $api_url = 'https://api.wordpress.org/events/1.0/';
$response = wp_remote_get( $request_url ); $request_args = $this->get_request_args( $location_search, $timezone );
$response = wp_remote_get( $api_url, $request_args );
$response_code = wp_remote_retrieve_response_code( $response ); $response_code = wp_remote_retrieve_response_code( $response );
$response_body = json_decode( wp_remote_retrieve_body( $response ), true ); $response_body = json_decode( wp_remote_retrieve_body( $response ), true );
$response_error = null; $response_error = null;
$debugging_info = compact( 'request_url', 'response_code', 'response_body' ); $debugging_info = compact( 'api_url', 'request_args', 'response_code', 'response_body' );
if ( is_wp_error( $response ) ) { if ( is_wp_error( $response ) ) {
$response_error = $response; $response_error = $response;
@ -128,6 +129,31 @@ class WP_Community_Events {
unset( $response_body['ttl'] ); unset( $response_body['ttl'] );
} }
/*
* The IP in the response is usually the same as the one that was sent
* in the request, but in some cases it is different. In those cases,
* it's important to reset it back to the IP from the request.
*
* For example, if the IP sent in the request is private (e.g., 192.168.1.100),
* then the API will ignore that and use the corresponding public IP instead,
* and the public IP will get returned. If the public IP were saved, though,
* then get_cached_events() would always return `false`, because the transient
* would be generated based on the public IP when saving the cache, but generated
* based on the private IP when retrieving the cache.
*/
if ( ! empty( $response_body['location']['ip'] ) ) {
$response_body['location']['ip'] = $request_args['body']['ip'];
}
/*
* The API doesn't return a description for latitude/longitude requests,
* but the description is already saved in the user location, so that
* one can be used instead.
*/
if ( $this->coordinates_match( $request_args['body'], $response_body['location'] ) && empty( $response_body['location']['description'] ) ) {
$response_body['location']['description'] = $this->user_location['description'];
}
$this->cache_events( $response_body, $expiration ); $this->cache_events( $response_body, $expiration );
$response_body = $this->trim_events( $response_body ); $response_body = $this->trim_events( $response_body );
@ -143,24 +169,23 @@ class WP_Community_Events {
} }
/** /**
* Builds a URL for requests to the w.org Events API. * Builds an array of args to use in an HTTP request to the w.org Events API.
* *
* @access protected * @access protected
* @since 4.8.0 * @since 4.8.0
* *
* @param string $search Optional. City search string. Default empty string. * @param string $search Optional. City search string. Default empty string.
* @param string $timezone Optional. Timezone string. Default empty string. * @param string $timezone Optional. Timezone string. Default empty string.
* @return string The request URL. * @return @return array The request args.
*/ */
protected function get_request_url( $search = '', $timezone = '' ) { protected function get_request_args( $search = '', $timezone = '' ) {
$api_url = 'https://api.wordpress.org/events/1.0/'; $args = array(
$args = array(
'number' => 5, // Get more than three in case some get trimmed out. 'number' => 5, // Get more than three in case some get trimmed out.
'ip' => $this->get_client_ip(), 'ip' => self::get_unsafe_client_ip(),
); );
/* /*
* Send the minimal set of necessary arguments, in order to increase the * Include the minimal set of necessary arguments, in order to increase the
* chances of a cache-hit on the API side. * chances of a cache-hit on the API side.
*/ */
if ( empty( $search ) && isset( $this->user_location['latitude'], $this->user_location['longitude'] ) ) { if ( empty( $search ) && isset( $this->user_location['latitude'], $this->user_location['longitude'] ) ) {
@ -178,7 +203,10 @@ class WP_Community_Events {
} }
} }
return add_query_arg( $args, $api_url ); // Wrap the args in an array compatible with the second parameter of `wp_remote_get()`.
return array(
'body' => $args
);
} }
/** /**
@ -207,7 +235,7 @@ class WP_Community_Events {
* @return false|string The anonymized address on success; the given address * @return false|string The anonymized address on success; the given address
* or false on failure. * or false on failure.
*/ */
protected function get_client_ip() { public static function get_unsafe_client_ip() {
$client_ip = false; $client_ip = false;
// In order of preference, with the best ones for this purpose first. // In order of preference, with the best ones for this purpose first.
@ -249,6 +277,24 @@ class WP_Community_Events {
return $client_ip; return $client_ip;
} }
/**
* Test if two pairs of latitude/longitude coordinates match each other.
*
* @since 4.8.0
* @access protected
*
* @param array $a The first pair, with indexes 'latitude' and 'longitude'.
* @param array $b The second pair, with indexes 'latitude' and 'longitude'.
* @return bool True if they match, false if they don't.
*/
protected function coordinates_match( $a, $b ) {
if ( ! isset( $a['latitude'], $a['longitude'], $b['latitude'], $b['longitude'] ) ) {
return false;
}
return $a['latitude'] === $b['latitude'] && $a['longitude'] === $b['longitude'];
}
/** /**
* Generates a transient key based on user location. * Generates a transient key based on user location.
* *
@ -266,7 +312,9 @@ class WP_Community_Events {
protected function get_events_transient_key( $location ) { protected function get_events_transient_key( $location ) {
$key = false; $key = false;
if ( isset( $location['latitude'], $location['longitude'] ) ) { if ( isset( $location['ip'] ) ) {
$key = 'community-events-' . md5( $location['ip'] );
} else if ( isset( $location['latitude'], $location['longitude'] ) ) {
$key = 'community-events-' . md5( $location['latitude'] . $location['longitude'] ); $key = 'community-events-' . md5( $location['latitude'] . $location['longitude'] );
} }

View File

@ -1234,15 +1234,23 @@ function wp_print_community_events_templates() {
<script id="tmpl-community-events-no-upcoming-events" type="text/template"> <script id="tmpl-community-events-no-upcoming-events" type="text/template">
<li class="event-none"> <li class="event-none">
<?php printf( <# if ( data.location.description ) { #>
/* translators: 1: the city the user searched for, 2: meetup organization documentation URL */ <?php printf(
__( 'There aren&#8217;t any events scheduled near %1$s at the moment. Would you like to <a href="%2$s">organize one</a>?' ), /* translators: 1: the city the user searched for, 2: meetup organization documentation URL */
'{{ data.location.description }}', __( 'There aren&#8217;t any events scheduled near %1$s at the moment. Would you like to <a href="%2$s">organize one</a>?' ),
__( 'https://make.wordpress.org/community/handbook/meetup-organizer/welcome/' ) '{{ data.location.description }}',
); ?> __( 'https://make.wordpress.org/community/handbook/meetup-organizer/welcome/' )
); ?>
<# } else { #>
<?php printf(
/* translators: meetup organization documentation URL. */
__( 'There aren&#8217;t any events scheduled near you at the moment. Would you like to <a href="%s">organize one</a>?' ),
__( 'https://make.wordpress.org/community/handbook/meetup-organizer/welcome/' )
); ?>
<# } #>
</li> </li>
</script> </script>
<?php <?php
} }

View File

@ -385,7 +385,26 @@ jQuery( function( $ ) {
* Determine which templates should be rendered and which elements * Determine which templates should be rendered and which elements
* should be displayed. * should be displayed.
*/ */
if ( templateParams.location ) { if ( templateParams.location.ip ) {
/*
* If the API determined the location by geolocating an IP, it will
* provide events, but not a specific location.
*/
$locationMessage.text( communityEventsData.l10n.attend_event_near_generic );
if ( templateParams.events.length ) {
template = wp.template( 'community-events-event-list' );
$results.html( template( templateParams ) );
} else {
template = wp.template( 'community-events-no-upcoming-events' );
$results.html( template( templateParams ) );
}
elementVisibility['#community-events-location-message'] = true;
elementVisibility['.community-events-toggle-location'] = true;
elementVisibility['.community-events-results'] = true;
} else if ( templateParams.location.description ) {
template = wp.template( 'community-events-attend-event-near' ); template = wp.template( 'community-events-attend-event-near' );
$locationMessage.html( template( templateParams ) ); $locationMessage.html( template( templateParams ) );
@ -435,7 +454,7 @@ jQuery( function( $ ) {
$toggleButton.attr( 'aria-expanded', elementVisibility['.community-events-toggle-location'] ); $toggleButton.attr( 'aria-expanded', elementVisibility['.community-events-toggle-location'] );
if ( templateParams.location ) { if ( templateParams.location && ( templateParams.location.ip || templateParams.location.latitude ) ) {
// Hide the form when there's a valid location. // Hide the form when there's a valid location.
app.toggleLocationForm( 'hide' ); app.toggleLocationForm( 'hide' );

File diff suppressed because one or more lines are too long

View File

@ -1012,9 +1012,24 @@ function wp_localize_community_events() {
require_once( ABSPATH . 'wp-admin/includes/class-wp-community-events.php' ); require_once( ABSPATH . 'wp-admin/includes/class-wp-community-events.php' );
$user_id = get_current_user_id(); $user_id = get_current_user_id();
$user_location = get_user_option( 'community-events-location', $user_id ); $saved_location = get_user_option( 'community-events-location', $user_id );
$events_client = new WP_Community_Events( $user_id, $user_location ); $saved_ip_address = isset( $saved_location['ip'] ) ? $saved_location['ip'] : false;
$current_ip_address = WP_Community_Events::get_unsafe_client_ip();
/*
* If the user's location is based on their IP address, then update their
* location when their IP address changes. This allows them to see events
* in their current city when travelling. Otherwise, they would always be
* shown events in the city where they were when they first loaded the
* Dashboard, which could have been months or years ago.
*/
if ( $saved_ip_address && $current_ip_address && $current_ip_address !== $saved_ip_address ) {
$saved_location['ip'] = $current_ip_address;
update_user_option( $user_id, 'community-events-location', $saved_location, true );
}
$events_client = new WP_Community_Events( $user_id, $saved_location );
wp_localize_script( 'dashboard', 'communityEventsData', array( wp_localize_script( 'dashboard', 'communityEventsData', array(
'nonce' => wp_create_nonce( 'community_events' ), 'nonce' => wp_create_nonce( 'community_events' ),
@ -1023,6 +1038,7 @@ function wp_localize_community_events() {
'l10n' => array( 'l10n' => array(
'enter_closest_city' => __( 'Enter your closest city to find nearby events.' ), 'enter_closest_city' => __( 'Enter your closest city to find nearby events.' ),
'error_occurred_please_try_again' => __( 'An error occurred. Please try again.' ), 'error_occurred_please_try_again' => __( 'An error occurred. Please try again.' ),
'attend_event_near_generic' => __( 'Attend an upcoming event near you.' ),
/* /*
* These specific examples were chosen to highlight the fact that a * These specific examples were chosen to highlight the fact that a

View File

@ -4,7 +4,7 @@
* *
* @global string $wp_version * @global string $wp_version
*/ */
$wp_version = '4.8-beta1-40789'; $wp_version = '4.8-beta1-40790';
/** /**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema. * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.