From b4d8b4db615a404478752fae86dd23eaec5002b9 Mon Sep 17 00:00:00 2001 From: Andrew Ozz Date: Fri, 19 May 2017 05:48:01 +0000 Subject: [PATCH] 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. git-svn-id: https://develop.svn.wordpress.org/trunk@40790 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-admin/includes/ajax-actions.php | 23 +++++- .../includes/class-wp-community-events.php | 74 +++++++++++++++---- src/wp-admin/includes/dashboard.php | 22 ++++-- src/wp-admin/js/dashboard.js | 23 +++++- src/wp-includes/script-loader.php | 22 +++++- 5 files changed, 137 insertions(+), 27 deletions(-) diff --git a/src/wp-admin/includes/ajax-actions.php b/src/wp-admin/includes/ajax-actions.php index a2e829bf65..e132ac8101 100644 --- a/src/wp-admin/includes/ajax-actions.php +++ b/src/wp-admin/includes/ajax-actions.php @@ -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 ); } diff --git a/src/wp-admin/includes/class-wp-community-events.php b/src/wp-admin/includes/class-wp-community-events.php index e27e731fff..9a41459740 100644 --- a/src/wp-admin/includes/class-wp-community-events.php +++ b/src/wp-admin/includes/class-wp-community-events.php @@ -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'] ); } diff --git a/src/wp-admin/includes/dashboard.php b/src/wp-admin/includes/dashboard.php index 274935105e..124dea2206 100644 --- a/src/wp-admin/includes/dashboard.php +++ b/src/wp-admin/includes/dashboard.php @@ -1234,15 +1234,23 @@ function wp_print_community_events_templates() { - 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