Make WordPress Core

Ticket #40702: 40702-geoip.4.diff

File 40702-geoip.4.diff, 12.7 KB (added by iandunn, 7 years ago)

Incorporates 40702-request-args.diff and fixes 2 bugs

  • src/wp-admin/includes/ajax-actions.php

    diff --git src/wp-admin/includes/ajax-actions.php src/wp-admin/includes/ajax-actions.php
    index a2e829bf65..e132ac8101 100644
    function wp_ajax_get_community_events() { 
    312312        $saved_location = get_user_option( 'community-events-location', $user_id );
    313313        $events_client  = new WP_Community_Events( $user_id, $saved_location );
    314314        $events         = $events_client->get_events( $search, $timezone );
     315        $ip_changed     = false;
    315316
    316317        if ( is_wp_error( $events ) ) {
    317318                wp_send_json_error( array(
    318319                        'error' => $events->get_error_message(),
    319320                ) );
    320321        } else {
    321                 if ( isset( $events['location'] ) ) {
    322                         // Store the location network-wide, so the user doesn't have to set it on each site.
     322                if ( empty( $saved_location['ip'] ) && ! empty( $events['location']['ip'] ) ) {
     323                        $ip_changed = true;
     324                } elseif ( isset( $saved_location['ip'] ) && ! empty( $events['location']['ip'] ) && $saved_location['ip'] !== $events['location']['ip'] ) {
     325                        $ip_changed = true;
     326                }
     327
     328                /*
     329                 * The location should only be updated when it changes. The API doesn't always return
     330                 * a full location; sometimes it's missing the description or country. The location
     331                 * that was saved during the initial request is known to be good and complete, though.
     332                 * It should be left in tact until the user explicitly changes it (either by manually
     333                 * searching for a new location, or by changing their IP address).
     334                 *
     335                 * If the location were updated with an incomplete response from the API, then it could
     336                 * break assumptions that the UI makes (e.g., that there will always be a description
     337                 * that corresponds to a latitude/longitude location).
     338                 *
     339                 * The location is stored network-wide, so that the user doesn't have to set it on each site.
     340                 */
     341                if ( $ip_changed || $search ) {
    323342                        update_user_option( $user_id, 'community-events-location', $events['location'], true );
    324343                }
    325344
  • src/wp-admin/includes/class-wp-community-events.php

    diff --git src/wp-admin/includes/class-wp-community-events.php src/wp-admin/includes/class-wp-community-events.php
    index e27e731fff..9a41459740 100644
    class WP_Community_Events { 
    9494                        return $cached_events;
    9595                }
    9696
    97                 $request_url    = $this->get_request_url( $location_search, $timezone );
    98                 $response       = wp_remote_get( $request_url );
     97                $api_url        = 'https://api.wordpress.org/events/1.0/';
     98                $request_args   = $this->get_request_args( $location_search, $timezone );
     99                $response       = wp_remote_get( $api_url, $request_args );
    99100                $response_code  = wp_remote_retrieve_response_code( $response );
    100101                $response_body  = json_decode( wp_remote_retrieve_body( $response ), true );
    101102                $response_error = null;
    102                 $debugging_info = compact( 'request_url', 'response_code', 'response_body' );
     103                $debugging_info = compact( 'api_url', 'request_args', 'response_code', 'response_body' );
    103104
    104105                if ( is_wp_error( $response ) ) {
    105106                        $response_error = $response;
    class WP_Community_Events { 
    128129                                unset( $response_body['ttl'] );
    129130                        }
    130131
     132                        /*
     133                         * The IP in the response is usually the same as the one that was sent
     134                         * in the request, but in some cases it is different. In those cases,
     135                         * it's important to reset it back to the IP from the request.
     136                         *
     137                         * For example, if the IP sent in the request is private (e.g., 192.168.1.100),
     138                         * then the API will ignore that and use the corresponding public IP instead,
     139                         * and the public IP will get returned. If the public IP were saved, though,
     140                         * then get_cached_events() would always return `false`, because the transient
     141                         * would be generated based on the public IP when saving the cache, but generated
     142                         * based on the private IP when retrieving the cache.
     143                         */
     144                        if ( ! empty( $response_body['location']['ip'] ) ) {
     145                                $response_body['location']['ip'] = $request_args['body']['ip'];
     146                        }
     147
     148                        /*
     149                         * The API doesn't return a description for latitude/longitude requests,
     150                         * but the description is already saved in the user location, so that
     151                         * one can be used instead.
     152                         */
     153                        if ( $this->coordinates_match( $request_args['body'], $response_body['location'] ) && empty( $response_body['location']['description'] ) ) {
     154                                $response_body['location']['description'] = $this->user_location['description'];
     155                        }
     156
    131157                        $this->cache_events( $response_body, $expiration );
    132158
    133159                        $response_body = $this->trim_events( $response_body );
    class WP_Community_Events { 
    143169        }
    144170
    145171        /**
    146          * Builds a URL for requests to the w.org Events API.
     172         * Builds an array of args to use in an HTTP request to the w.org Events API.
    147173         *
    148174         * @access protected
    149175         * @since 4.8.0
    150176         *
    151177         * @param  string $search   Optional. City search string. Default empty string.
    152178         * @param  string $timezone Optional. Timezone string. Default empty string.
    153          * @return string The request URL.
     179         * @return @return array The request args.
    154180         */
    155         protected function get_request_url( $search = '', $timezone = '' ) {
    156                 $api_url = 'https://api.wordpress.org/events/1.0/';
    157                 $args    = array(
     181        protected function get_request_args( $search = '', $timezone = '' ) {
     182                $args = array(
    158183                        'number' => 5, // Get more than three in case some get trimmed out.
    159                         'ip'     => $this->get_client_ip(),
     184                        'ip'     => self::get_unsafe_client_ip(),
    160185                );
    161186
    162187                /*
    163                  * Send the minimal set of necessary arguments, in order to increase the
     188                 * Include the minimal set of necessary arguments, in order to increase the
    164189                 * chances of a cache-hit on the API side.
    165190                 */
    166191                if ( empty( $search ) && isset( $this->user_location['latitude'], $this->user_location['longitude'] ) ) {
    class WP_Community_Events { 
    178203                        }
    179204                }
    180205
    181                 return add_query_arg( $args, $api_url );
     206                // Wrap the args in an array compatible with the second parameter of `wp_remote_get()`.
     207                return array(
     208                        'body' => $args
     209                );
    182210        }
    183211
    184212        /**
    class WP_Community_Events { 
    207235         * @return false|string The anonymized address on success; the given address
    208236         *                      or false on failure.
    209237         */
    210         protected function get_client_ip() {
     238        public static function get_unsafe_client_ip() {
    211239                $client_ip = false;
    212240
    213241                // In order of preference, with the best ones for this purpose first.
    class WP_Community_Events { 
    250278        }
    251279
    252280        /**
     281         * Test if two pairs of latitude/longitude coordinates match each other.
     282         *
     283         * @since 4.8.0
     284         * @access protected
     285         *
     286         * @param array $a The first pair, with indexes 'latitude' and 'longitude'.
     287         * @param array $b The second pair, with indexes 'latitude' and 'longitude'.
     288         * @return bool True if they match, false if they don't.
     289         */
     290        protected function coordinates_match( $a, $b ) {
     291                if ( ! isset( $a['latitude'], $a['longitude'], $b['latitude'], $b['longitude'] ) ) {
     292                        return false;
     293                }
     294
     295                return $a['latitude'] === $b['latitude'] && $a['longitude'] === $b['longitude'];
     296        }
     297
     298        /**
    253299         * Generates a transient key based on user location.
    254300         *
    255301         * This could be reduced to a one-liner in the calling functions, but it's
    class WP_Community_Events { 
    266312        protected function get_events_transient_key( $location ) {
    267313                $key = false;
    268314
    269                 if ( isset( $location['latitude'], $location['longitude'] ) ) {
     315                if ( isset( $location['ip'] ) ) {
     316                        $key = 'community-events-' . md5( $location['ip'] );
     317                } else if ( isset( $location['latitude'], $location['longitude'] ) ) {
    270318                        $key = 'community-events-' . md5( $location['latitude'] . $location['longitude'] );
    271319                }
    272320
  • src/wp-admin/includes/dashboard.php

    diff --git src/wp-admin/includes/dashboard.php src/wp-admin/includes/dashboard.php
    index 922d2760b4..8c5daef7dd 100644
    function wp_print_community_events_templates() { 
    12341234
    12351235        <script id="tmpl-community-events-no-upcoming-events" type="text/template">
    12361236                <li class="event-none">
    1237                         <?php printf(
    1238                                 /* translators: 1: the city the user searched for, 2: meetup organization documentation URL */
    1239                                 __( 'There aren&#8217;t any events scheduled near %1$s at the moment. Would you like to <a href="%2$s">organize one</a>?' ),
    1240                                 '{{ data.location.description }}',
    1241                                 __( 'https://make.wordpress.org/community/handbook/meetup-organizer/welcome/' )
    1242                         ); ?>
     1237                        <# if ( data.location.description ) { #>
     1238                                <?php printf(
     1239                                        /* translators: 1: the city the user searched for, 2: meetup organization documentation URL */
     1240                                        __( 'There aren&#8217;t any events scheduled near %1$s at the moment. Would you like to <a href="%2$s">organize one</a>?' ),
     1241                                        '{{ data.location.description }}',
     1242                                        __( 'https://make.wordpress.org/community/handbook/meetup-organizer/welcome/' )
     1243                                ); ?>
     1244
     1245                        <# } else { #>
     1246                                <?php printf(
     1247                                        /* translators: meetup organization documentation URL. */
     1248                                        __( 'There aren&#8217;t any events scheduled near you at the moment. Would you like to <a href="%s">organize one</a>?' ),
     1249                                        __( 'https://make.wordpress.org/community/handbook/meetup-organizer/welcome/' )
     1250                                ); ?>
     1251                        <# } #>
    12431252                </li>
    12441253        </script>
    1245 
    12461254        <?php
    12471255}
    12481256
  • src/wp-admin/js/dashboard.js

    diff --git src/wp-admin/js/dashboard.js src/wp-admin/js/dashboard.js
    index 6a9b5033d4..9a91265af6 100644
    jQuery( function( $ ) { 
    369369                         * Determine which templates should be rendered and which elements
    370370                         * should be displayed.
    371371                         */
    372                         if ( templateParams.location ) {
     372                        if ( templateParams.location.ip ) {
     373                                /*
     374                                 * If the API determined the location by geolocating an IP, it will
     375                                 * provide events, but not a specific location.
     376                                 */
     377                                $locationMessage.text( communityEventsData.l10n.attend_event_near_generic );
     378
     379                                if ( templateParams.events.length ) {
     380                                        template = wp.template( 'community-events-event-list' );
     381                                        $results.html( template( templateParams ) );
     382                                } else {
     383                                        template = wp.template( 'community-events-no-upcoming-events' );
     384                                        $results.html( template( templateParams ) );
     385                                }
     386
     387                                elementVisibility['#community-events-location-message'] = true;
     388                                elementVisibility['.community-events-toggle-location']  = true;
     389                                elementVisibility['.community-events-results']          = true;
     390
     391                        } else if ( templateParams.location.description ) {
    373392                                template = wp.template( 'community-events-attend-event-near' );
    374393                                $locationMessage.html( template( templateParams ) );
    375394
    jQuery( function( $ ) { 
    427446                         * location, and displaying the form would unnecessarily clutter the
    428447                         * widget.
    429448                         */
    430                         if ( 'app' === initiatedBy && templateParams.location ) {
     449                        if ( 'app' === initiatedBy && ( templateParams.location.ip || templateParams.location.latitude ) ) {
    431450                                app.toggleLocationForm( 'hide' );
    432451                        } else {
    433452                                app.toggleLocationForm( 'show' );
  • src/wp-includes/script-loader.php

    diff --git src/wp-includes/script-loader.php src/wp-includes/script-loader.php
    index 5dad398001..ff7d8bb855 100644
    function wp_localize_community_events() { 
    10121012
    10131013        require_once( ABSPATH . 'wp-admin/includes/class-wp-community-events.php' );
    10141014
    1015         $user_id       = get_current_user_id();
    1016         $user_location = get_user_option( 'community-events-location', $user_id );
    1017         $events_client = new WP_Community_Events( $user_id, $user_location );
     1015        $user_id            = get_current_user_id();
     1016        $saved_location     = get_user_option( 'community-events-location', $user_id );
     1017        $saved_ip_address   = isset( $saved_location['ip'] ) ? $saved_location['ip'] : false;
     1018        $current_ip_address = WP_Community_Events::get_unsafe_client_ip();
     1019
     1020        /*
     1021         * If the user's location is based on their IP address, then update their
     1022         * location when their IP address changes. This allows them to see events
     1023         * in their current city when travelling. Otherwise, they would always be
     1024         * shown events in the city where they were when they first loaded the
     1025         * Dashboard, which could have been months or years ago.
     1026         */
     1027        if ( $saved_ip_address && $current_ip_address && $current_ip_address !== $saved_ip_address ) {
     1028                $saved_location['ip'] = $current_ip_address;
     1029                update_user_option( $user_id, 'community-events-location', $saved_location, true );
     1030        }
     1031
     1032        $events_client = new WP_Community_Events( $user_id, $saved_location );
    10181033
    10191034        wp_localize_script( 'dashboard', 'communityEventsData', array(
    10201035                'nonce' => wp_create_nonce( 'community_events' ),
    function wp_localize_community_events() { 
    10231038                'l10n' => array(
    10241039                        'enter_closest_city' => __( 'Enter your closest city to find nearby events.' ),
    10251040                        'error_occurred_please_try_again' => __( 'An error occurred. Please try again.' ),
     1041                        'attend_event_near_generic' => __( 'Attend an upcoming event near you.' ),
    10261042
    10271043                        /*
    10281044                         * These specific examples were chosen to highlight the fact that a