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 );
$events_client = new WP_Community_Events( $user_id, $saved_location );
$events = $events_client->get_events( $search, $timezone );
$ip_changed = false;
if ( is_wp_error( $events ) ) {
wp_send_json_error( array(
'error' => $events->get_error_message(),
) );
} else {
if ( isset( $events['location'] ) ) {
// Store the location network-wide, so the user doesn't have to set it on each site.
if ( empty( $saved_location['ip'] ) && ! empty( $events['location']['ip'] ) ) {
$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 );
}

View File

@ -94,12 +94,13 @@ class WP_Community_Events {
return $cached_events;
}
$request_url = $this->get_request_url( $location_search, $timezone );
$response = wp_remote_get( $request_url );
$api_url = 'https://api.wordpress.org/events/1.0/';
$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_body = json_decode( wp_remote_retrieve_body( $response ), true );
$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 ) ) {
$response_error = $response;
@ -128,6 +129,31 @@ class WP_Community_Events {
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 );
$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
* @since 4.8.0
*
* @param string $search Optional. City search 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 = '' ) {
$api_url = 'https://api.wordpress.org/events/1.0/';
$args = array(
protected function get_request_args( $search = '', $timezone = '' ) {
$args = array(
'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.
*/
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
* or false on failure.
*/
protected function get_client_ip() {
public static function get_unsafe_client_ip() {
$client_ip = false;
// In order of preference, with the best ones for this purpose first.
@ -249,6 +277,24 @@ class WP_Community_Events {
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.
*
@ -266,7 +312,9 @@ class WP_Community_Events {
protected function get_events_transient_key( $location ) {
$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'] );
}

View File

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

View File

@ -385,7 +385,26 @@ jQuery( function( $ ) {
* Determine which templates should be rendered and which elements
* 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' );
$locationMessage.html( template( templateParams ) );
@ -435,7 +454,7 @@ jQuery( function( $ ) {
$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.
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' );
$user_id = get_current_user_id();
$user_location = get_user_option( 'community-events-location', $user_id );
$events_client = new WP_Community_Events( $user_id, $user_location );
$user_id = get_current_user_id();
$saved_location = get_user_option( 'community-events-location', $user_id );
$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(
'nonce' => wp_create_nonce( 'community_events' ),
@ -1023,6 +1038,7 @@ function wp_localize_community_events() {
'l10n' => array(
'enter_closest_city' => __( 'Enter your closest city to find nearby events.' ),
'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

View File

@ -4,7 +4,7 @@
*
* @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.