Plugin Directory

Changeset 3483997 for leaflet-map


Ignore:
Timestamp:
03/16/2026 03:30:46 PM (12 days ago)
Author:
bozdoz
Message:

Update gsm/nominatim geocoder implementation with user agent support and fix cache bug (#287)

  • feat(geocoder): rewrite osm geocoder with new endpoint, user agent, and wp_remote_get
  • feat(geocoder): add accept-language based on gotcha tip in nominatim docs
  • fix(geocoder): throw exception on fail instead of polluting cache with false as an object
  • refactor(geocoder): don't reuse var in endpoint url
  • feat(geocoder): osm lazy cache self-healing
  • feat(plugin-option): add placeholder support to input text
  • feat(plugin-settings): add nominatim_contact_email support
  • feat(geocode): add leaflet_map_nominatim_contact_email filter
  • docs(readme): document setting and filter for the nominatim contact email sent in the geocoder user agent
  • fix(geocoder): only use site language otherwise the global cache can end up multilingual depending on the users language
Location:
leaflet-map
Files:
10 edited
1 copied

Legend:

Unmodified
Added
Removed
  • leaflet-map/tags/3.4.3/README.md

    r3440762 r3483997  
    111111```
    112112
     113When using the OpenStreetMap Nominatim geocoder, the plugin sends a contact email in the request user agent. By default this uses the site admin email, but you can override it in the plugin settings with `Nominatim Contact Email (optional)` or in code with the `leaflet_map_nominatim_contact_email` filter.
     114
     115```php
     116add_filter('leaflet_map_nominatim_contact_email', function ($email) {
     117    return 'maps@example.com';
     118});
     119```
     120
    113121#### [leaflet-map] Options:
    114122
  • leaflet-map/tags/3.4.3/class.geocoder.php

    r2863840 r3483997  
    4545        $found_cache = get_option( $cached_address );
    4646
    47         if ( $found_cache ) {
     47        if ( $this->is_valid_cached_location( $found_cache ) ) {
    4848            $location = $found_cache;
    4949        } else {
     50            if ( false !== $found_cache ) {
     51                $this->remove_cache_key( $cached_address );
     52            }
     53
    5054            // try geocoding
    5155            $geocoding_method = $geocoder . '_geocode';
     
    5458                $location = (Object) $this->$geocoding_method( $address );
    5559
     60                if ( ! $this->is_valid_cached_location( $location ) ) {
     61                    throw new Exception('Invalid geocoder response');
     62                }
     63
    5664                /* add location */
    5765                add_option($cached_address, $location);
     
    5967                /* add option key to locations for clean up purposes */
    6068                $locations = get_option('leaflet_geocoded_locations', array());
    61                 array_push($locations, $cached_address);
    62                 update_option('leaflet_geocoded_locations', $locations);
     69                if ( ! in_array( $cached_address, $locations, true ) ) {
     70                    array_push($locations, $cached_address);
     71                    update_option('leaflet_geocoded_locations', $locations);
     72                }
    6373            } catch (Exception $e) {
    6474                // failed
     
    8292        }
    8393        delete_option('leaflet_geocoded_locations');
     94    }
     95
     96    /**
     97    * Determines whether a cached geocode entry is valid.
     98    *
     99    * @param mixed $location Cached location value from WordPress options.
     100    * @return bool
     101    */
     102    private function is_valid_cached_location( $location ) {
     103        if ( ! is_object( $location ) ) {
     104            return false;
     105        }
     106
     107        if ( ! isset( $location->lat ) || ! isset( $location->lng ) ) {
     108            return false;
     109        }
     110
     111        return filter_var( $location->lat, FILTER_VALIDATE_FLOAT ) !== false
     112            && filter_var( $location->lng, FILTER_VALIDATE_FLOAT ) !== false;
     113    }
     114
     115    /**
     116    * Removes a single geocode cache entry and its registry reference.
     117    *
     118    * @param string $cached_address Option key for the cached geocode.
     119    * @return void
     120    */
     121    private function remove_cache_key( $cached_address ) {
     122        delete_option( $cached_address );
     123
     124        $addresses = get_option( 'leaflet_geocoded_locations', array() );
     125        $addresses = array_values(
     126            array_filter(
     127                $addresses,
     128                function ( $address ) use ( $cached_address ) {
     129                    return $address !== $cached_address;
     130                }
     131            )
     132        );
     133
     134        if ( empty( $addresses ) ) {
     135            delete_option( 'leaflet_geocoded_locations' );
     136        } else {
     137            update_option( 'leaflet_geocoded_locations', $addresses );
     138        }
    84139    }
    85140
     
    159214    * OpenStreetMap geocoder Nominatim (https://nominatim.openstreetmap.org/)
    160215    *
    161     * @param string $address    the urlencoded address to look up
    162     * @return varies object from API or null (failed)
    163     */
    164 
    165     private function osm_geocode ( $address ) {
    166         $geocode_url = 'https://nominatim.openstreetmap.org/?format=json&limit=1&q=';
    167         $geocode_url .= $address;
    168         $json = $this->get_url($geocode_url);
    169         $json = json_decode($json);
    170 
    171         if (isset($json[0]->lat) && isset($json[0]->lon)) {
    172             return (Object) array(
    173                 'lat' => $json[0]->lat,
    174                 'lng' => $json[0]->lon,
    175             );
    176         } else {
    177             return false;
    178         }
     216    * @param string $address The URL-encoded address to look up.
     217    * @return object Object containing lat and lng properties.
     218    * @throws Exception When the request fails or no valid coordinates are returned.
     219    */
     220    private function osm_geocode( $address ) {
     221        $request_url = sprintf(
     222            'https://nominatim.openstreetmap.org/search?format=jsonv2&limit=1&q=%s',
     223            $address
     224        );
     225
     226        $accept_language = str_replace( '_', '-', get_locale() );
     227       
     228        $settings = Leaflet_Map_Plugin_Settings::init();
     229        $contact_email = $settings->get( 'nominatim_contact_email' );
     230
     231        if ( empty( $contact_email ) ) {
     232            $contact_email = get_bloginfo( 'admin_email' );
     233        }
     234
     235        $contact_email = apply_filters( 'leaflet_map_nominatim_contact_email', $contact_email );
     236
     237        $agent = 'Nominatim query for ' . get_bloginfo( 'url' ) . '; contact ' . $contact_email;
     238
     239        $response = wp_remote_get(
     240            $request_url,
     241            array(
     242                'user-agent' => $agent,
     243                'headers' => array(
     244                    'Accept-Language' => $accept_language,
     245                ),
     246            )
     247        );
     248
     249        if ( ! is_wp_error( $response ) && isset( $response['body'] ) ) {
     250            $json = json_decode( $response['body'] );
     251
     252            if ( isset( $json[0]->lat ) && isset( $json[0]->lon ) ) {
     253                return (object) array(
     254                    'lat' => $json[0]->lat,
     255                    'lng' => $json[0]->lon,
     256                );
     257            }
     258        }
     259
     260        throw new Exception('No Address Found');
    179261    }
    180262
  • leaflet-map/tags/3.4.3/class.plugin-option.php

    r3440762 r3483997  
    5656    public $max = 0;
    5757    public $step = 0;
     58    public $placeholder = '';
    5859
    5960    /**
     
    7778            'max'              =>     FILTER_DEFAULT,
    7879            'step'             =>     FILTER_DEFAULT,
     80            'placeholder'      =>     FILTER_SANITIZE_FULL_SPECIAL_CHARS,
    7981            'options'          =>     array(
    8082                'filter' => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
     
    113115            type="<?php echo $this->type; ?>"
    114116            id="<?php echo $name; ?>"
     117            placeholder="<?php echo htmlspecialchars($this->placeholder); ?>"
    115118            value="<?php echo htmlspecialchars($value); ?>"
    116119            />
  • leaflet-map/tags/3.4.3/class.plugin-settings.php

    r3272877 r3483997  
    346346                ),
    347347                'helptext' => __('Select the Geocoding provider to use to retrieve addresses defined in shortcode.', 'leaflet-map')
     348            ),
     349            'nominatim_contact_email' => array(
     350                'display_name'=>__('Nominatim Contact Email (optional)', 'leaflet-map'),
     351                'default' => '',
     352                'type' => 'text',
     353                'placeholder' => sprintf(
     354                    __('defaults to admin email (%s)', 'leaflet-map'),
     355                    get_bloginfo('admin_email')
     356                ),
     357                'helptext' => __(
     358                    'Optional contact email to send with OpenStreetMap Nominatim geocoding requests. If blank, the site admin email will be used.',
     359                    'leaflet-map'
     360                ),
    348361            ),
    349362            'google_appkey' => array(
  • leaflet-map/tags/3.4.3/readme.txt

    r3440762 r3483997  
    2929`[leaflet-map address="chicago"]`
    3030
     31When using the OpenStreetMap Nominatim geocoder, the plugin sends a contact email in the request user agent. By default this uses the site admin email, but you can override it in the plugin settings or with the `leaflet_map_nominatim_contact_email` filter.
     32
     33`add_filter('leaflet_map_nominatim_contact_email', function ($email) { return 'maps@example.com'; });`
     34
    3135Know the latitude and longitude of a location? Use them (and a zoom level) with:
    3236
  • leaflet-map/trunk/README.md

    r3440762 r3483997  
    111111```
    112112
     113When using the OpenStreetMap Nominatim geocoder, the plugin sends a contact email in the request user agent. By default this uses the site admin email, but you can override it in the plugin settings with `Nominatim Contact Email (optional)` or in code with the `leaflet_map_nominatim_contact_email` filter.
     114
     115```php
     116add_filter('leaflet_map_nominatim_contact_email', function ($email) {
     117    return 'maps@example.com';
     118});
     119```
     120
    113121#### [leaflet-map] Options:
    114122
  • leaflet-map/trunk/class.geocoder.php

    r2863840 r3483997  
    4545        $found_cache = get_option( $cached_address );
    4646
    47         if ( $found_cache ) {
     47        if ( $this->is_valid_cached_location( $found_cache ) ) {
    4848            $location = $found_cache;
    4949        } else {
     50            if ( false !== $found_cache ) {
     51                $this->remove_cache_key( $cached_address );
     52            }
     53
    5054            // try geocoding
    5155            $geocoding_method = $geocoder . '_geocode';
     
    5458                $location = (Object) $this->$geocoding_method( $address );
    5559
     60                if ( ! $this->is_valid_cached_location( $location ) ) {
     61                    throw new Exception('Invalid geocoder response');
     62                }
     63
    5664                /* add location */
    5765                add_option($cached_address, $location);
     
    5967                /* add option key to locations for clean up purposes */
    6068                $locations = get_option('leaflet_geocoded_locations', array());
    61                 array_push($locations, $cached_address);
    62                 update_option('leaflet_geocoded_locations', $locations);
     69                if ( ! in_array( $cached_address, $locations, true ) ) {
     70                    array_push($locations, $cached_address);
     71                    update_option('leaflet_geocoded_locations', $locations);
     72                }
    6373            } catch (Exception $e) {
    6474                // failed
     
    8292        }
    8393        delete_option('leaflet_geocoded_locations');
     94    }
     95
     96    /**
     97    * Determines whether a cached geocode entry is valid.
     98    *
     99    * @param mixed $location Cached location value from WordPress options.
     100    * @return bool
     101    */
     102    private function is_valid_cached_location( $location ) {
     103        if ( ! is_object( $location ) ) {
     104            return false;
     105        }
     106
     107        if ( ! isset( $location->lat ) || ! isset( $location->lng ) ) {
     108            return false;
     109        }
     110
     111        return filter_var( $location->lat, FILTER_VALIDATE_FLOAT ) !== false
     112            && filter_var( $location->lng, FILTER_VALIDATE_FLOAT ) !== false;
     113    }
     114
     115    /**
     116    * Removes a single geocode cache entry and its registry reference.
     117    *
     118    * @param string $cached_address Option key for the cached geocode.
     119    * @return void
     120    */
     121    private function remove_cache_key( $cached_address ) {
     122        delete_option( $cached_address );
     123
     124        $addresses = get_option( 'leaflet_geocoded_locations', array() );
     125        $addresses = array_values(
     126            array_filter(
     127                $addresses,
     128                function ( $address ) use ( $cached_address ) {
     129                    return $address !== $cached_address;
     130                }
     131            )
     132        );
     133
     134        if ( empty( $addresses ) ) {
     135            delete_option( 'leaflet_geocoded_locations' );
     136        } else {
     137            update_option( 'leaflet_geocoded_locations', $addresses );
     138        }
    84139    }
    85140
     
    159214    * OpenStreetMap geocoder Nominatim (https://nominatim.openstreetmap.org/)
    160215    *
    161     * @param string $address    the urlencoded address to look up
    162     * @return varies object from API or null (failed)
    163     */
    164 
    165     private function osm_geocode ( $address ) {
    166         $geocode_url = 'https://nominatim.openstreetmap.org/?format=json&limit=1&q=';
    167         $geocode_url .= $address;
    168         $json = $this->get_url($geocode_url);
    169         $json = json_decode($json);
    170 
    171         if (isset($json[0]->lat) && isset($json[0]->lon)) {
    172             return (Object) array(
    173                 'lat' => $json[0]->lat,
    174                 'lng' => $json[0]->lon,
    175             );
    176         } else {
    177             return false;
    178         }
     216    * @param string $address The URL-encoded address to look up.
     217    * @return object Object containing lat and lng properties.
     218    * @throws Exception When the request fails or no valid coordinates are returned.
     219    */
     220    private function osm_geocode( $address ) {
     221        $request_url = sprintf(
     222            'https://nominatim.openstreetmap.org/search?format=jsonv2&limit=1&q=%s',
     223            $address
     224        );
     225
     226        $accept_language = str_replace( '_', '-', get_locale() );
     227       
     228        $settings = Leaflet_Map_Plugin_Settings::init();
     229        $contact_email = $settings->get( 'nominatim_contact_email' );
     230
     231        if ( empty( $contact_email ) ) {
     232            $contact_email = get_bloginfo( 'admin_email' );
     233        }
     234
     235        $contact_email = apply_filters( 'leaflet_map_nominatim_contact_email', $contact_email );
     236
     237        $agent = 'Nominatim query for ' . get_bloginfo( 'url' ) . '; contact ' . $contact_email;
     238
     239        $response = wp_remote_get(
     240            $request_url,
     241            array(
     242                'user-agent' => $agent,
     243                'headers' => array(
     244                    'Accept-Language' => $accept_language,
     245                ),
     246            )
     247        );
     248
     249        if ( ! is_wp_error( $response ) && isset( $response['body'] ) ) {
     250            $json = json_decode( $response['body'] );
     251
     252            if ( isset( $json[0]->lat ) && isset( $json[0]->lon ) ) {
     253                return (object) array(
     254                    'lat' => $json[0]->lat,
     255                    'lng' => $json[0]->lon,
     256                );
     257            }
     258        }
     259
     260        throw new Exception('No Address Found');
    179261    }
    180262
  • leaflet-map/trunk/class.plugin-option.php

    r3440762 r3483997  
    5656    public $max = 0;
    5757    public $step = 0;
     58    public $placeholder = '';
    5859
    5960    /**
     
    7778            'max'              =>     FILTER_DEFAULT,
    7879            'step'             =>     FILTER_DEFAULT,
     80            'placeholder'      =>     FILTER_SANITIZE_FULL_SPECIAL_CHARS,
    7981            'options'          =>     array(
    8082                'filter' => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
     
    113115            type="<?php echo $this->type; ?>"
    114116            id="<?php echo $name; ?>"
     117            placeholder="<?php echo htmlspecialchars($this->placeholder); ?>"
    115118            value="<?php echo htmlspecialchars($value); ?>"
    116119            />
  • leaflet-map/trunk/class.plugin-settings.php

    r3272877 r3483997  
    346346                ),
    347347                'helptext' => __('Select the Geocoding provider to use to retrieve addresses defined in shortcode.', 'leaflet-map')
     348            ),
     349            'nominatim_contact_email' => array(
     350                'display_name'=>__('Nominatim Contact Email (optional)', 'leaflet-map'),
     351                'default' => '',
     352                'type' => 'text',
     353                'placeholder' => sprintf(
     354                    __('defaults to admin email (%s)', 'leaflet-map'),
     355                    get_bloginfo('admin_email')
     356                ),
     357                'helptext' => __(
     358                    'Optional contact email to send with OpenStreetMap Nominatim geocoding requests. If blank, the site admin email will be used.',
     359                    'leaflet-map'
     360                ),
    348361            ),
    349362            'google_appkey' => array(
  • leaflet-map/trunk/readme.txt

    r3440762 r3483997  
    2929`[leaflet-map address="chicago"]`
    3030
     31When using the OpenStreetMap Nominatim geocoder, the plugin sends a contact email in the request user agent. By default this uses the site admin email, but you can override it in the plugin settings or with the `leaflet_map_nominatim_contact_email` filter.
     32
     33`add_filter('leaflet_map_nominatim_contact_email', function ($email) { return 'maps@example.com'; });`
     34
    3135Know the latitude and longitude of a location? Use them (and a zoom level) with:
    3236
Note: See TracChangeset for help on using the changeset viewer.