class-geocode.php
13.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
<?php
/**
* Geocode store locations
*
* @author Tijmen Smit
* @since 2.0.0
*/
if ( !defined( 'ABSPATH' ) ) exit;
if ( !class_exists( 'WPSL_Geocode' ) ) {
class WPSL_Geocode {
/**
* Check if we need to run a geocode request or use the current location data.
*
* The latlng value is only present if the user provided it himself, or used the preview
* on the map. Otherwise the latlng will be missing and we need to geocode the supplied address.
*
* @since 2.0.0
* @param integer $post_id Store post ID
* @param array $store_data The store data
* @return void
*/
public function check_geocode_data( $post_id, $store_data ) {
$location_data = array();
// Check if the latlng data is valid.
$latlng = $this->validate_latlng( $store_data['lat'], $store_data['lng'] );
// If we don't have a valid latlng value, we geocode the supplied address to get one.
if ( !$latlng ) {
$response = $this->geocode_location( $post_id, $store_data );
if ( empty( $response ) ) {
return;
}
$location_data['country_iso'] = $response['country_iso'];
$location_data['latlng'] = $response['latlng'];
} else {
$location_data['latlng'] = $latlng;
}
// Restrict the latlng to a max of 6 decimals.
$location_data['latlng'] = $this->format_latlng( $location_data['latlng'] );
$location_data['lat'] = $location_data['latlng']['lat'];
$location_data['lng'] = $location_data['latlng']['lng'];
$this->save_store_location( $post_id, $location_data );
}
/**
* Geocode the store location.
*
* @since 1.0.0
* @param integer $post_id Store post ID
* @param array $store_data The submitted store data ( address, city, country etc )
* @return void
*/
public function geocode_location( $post_id, $store_data ) {
$geocode_response = $this->get_latlng( $store_data );
if ( isset( $geocode_response['status'] ) ) {
switch ( $geocode_response['status'] ) {
case 'OK':
$country = $this->filter_country_name( $geocode_response );
$location_data = array(
'country_iso' => $country['short_name'],
'latlng' => $this->format_latlng( $geocode_response['results'][0]['geometry']['location'] )
);
return $location_data;
case 'ZERO_RESULTS':
$msg = __( 'The Google Geocoding API returned no results for the supplied address. Please change the address and try again.', 'wpsl' );
break;
case 'OVER_QUERY_LIMIT':
$msg = sprintf( __( 'You have reached the daily allowed geocoding limit, you can read more %shere%s.', 'wpsl' ), '<a target="_blank" href="https://developers.google.com/maps/documentation/geocoding/#Limits">', '</a>' );
break;
case 'REQUEST_DENIED':
$msg = sprintf( __( 'The Google Geocoding API returned REQUEST_DENIED. %s', 'wpsl' ), $this->check_geocode_error_msg( $geocode_response ) );
break;
default:
$msg = __( 'The Google Geocoding API failed to return valid data, please try again later.', 'wpsl' );
break;
}
} else {
$msg = $geocode_response;
}
// Handle the geocode code errors messages.
if ( !empty( $msg ) ) {
$this->geocode_failed( $msg, $post_id );
}
}
/**
* Check if the response from the Geocode API contains an error message.
*
* @since 2.1.0
* @param array $geocode_response The response from the Geocode API.
* @return string $error_msg The error message, or empty if none exists.
*/
public function check_geocode_error_msg( $geocode_response, $inc_breaks = true ) {
$breaks = ( $inc_breaks ) ? '<br><br>' : '';
if ( isset( $geocode_response['error_message'] ) && $geocode_response['error_message'] ) {
// If the problem is IP based, then show a different error msg.
if ( strpos( $geocode_response['error_message'], 'IP' ) !== false ) {
$error_msg = sprintf( __( '%sError message: %s. %s Make sure the IP address mentioned in the error matches with the IP set as the %sreferrer%s for the server API key in the %sGoogle API Console%s.', 'wpsl' ), $breaks, $this->clickable_error_links( $geocode_response['error_message'] ), $breaks, '<a href="https://wpstorelocator.co/document/create-google-api-keys/#server-key-referrer">', '</a>', '<a href="https://console.developers.google.com">', '</a>' );
} else {
$error_msg = sprintf( __( '%sError message: %s %s Check if your issue is covered in the %stroubleshooting%s section, if not, then please open a %ssupport ticket%s.', 'wpsl' ), $breaks, $this->clickable_error_links( $geocode_response['error_message'] ), $breaks, '<a href="https://wpstorelocator.co/document/create-google-api-keys/#troubleshooting">', '</a>', '<a href="https://wpstorelocator.co/support/">', '</a>' );
}
} else {
$error_msg = '';
}
return $error_msg;
}
/**
* Make the API call to Google to geocode the address.
*
* @since 1.0.0
* @param array $store_data The store data
* @return array|string $geo_response The response from the Google Geocode API, or the wp_remote_get error message.
*/
public function get_latlng( $store_data ) {
$address = $this->create_geocode_address( $store_data );
$response = wpsl_call_geocode_api( $address );
if ( is_wp_error( $response ) ) {
$geo_response = sprintf( __( 'Something went wrong connecting to the Google Geocode API: %s %s Please try again later.', 'wpsl' ), $response->get_error_message(), '<br><br>' );
} else if ( $response['response']['code'] == 500 ) {
$geo_response = sprintf( __( 'The Google Geocode API reported the following problem: error %s %s %s Please try again later.', 'wpsl' ), $response['response']['code'], $response['response']['message'], '<br><br>' );
} else if ( $response['response']['code'] == 400 ) {
// Check on which page the 400 error was triggered, and based on that adjust the msg.
if ( isset( $_GET['page'] ) && $_GET['page'] == 'wpsl_csv' ) {
$data_issue = sprintf( __( 'You can fix this by making sure the CSV file uses %sUTF-8 encoding%s.', 'wpsl' ), '<a href="https://wpstorelocator.co/document/csv-manager/#utf8">', '</a>' );
} else if ( !$address ) {
$data_issue = __( 'You need to provide the details for either the address, city, state or country before the API can return coordinates.', 'wpsl' ); // this is only possible if the required fields are disabled with custom code.
}
$geo_response = sprintf( __( 'The Google Geocode API reported the following problem: error %s %s %s %s', 'wpsl' ), $response['response']['code'], $response['response']['message'], '<br><br>', $data_issue );
} else if ( $response['response']['code'] != 200 ) {
$geo_response = sprintf( __( 'The Google Geocode API reported the following problem: error %s %s %s Please contact %ssupport%s if the problem persists.', 'wpsl' ), $response['response']['code'], $response['response']['message'], '<br><br>', '<a href="https://wpstorelocator.co/support/">', '</a>' );
} else {
$geo_response = json_decode( $response['body'], true );
}
return $geo_response;
}
/**
* Create the address we need to Geocode.
*
* @since 2.1.0
* @param array $store_data The provided store data
* @return string $geocode_address The address we are sending to the Geocode API separated by ,
*/
public function create_geocode_address( $store_data ) {
$address = array();
$address_parts = array( 'address', 'city', 'state', 'zip', 'country' );
foreach ( $address_parts as $address_part ) {
if ( isset( $store_data[$address_part] ) && $store_data[$address_part] ) {
$address[] = trim( $store_data[$address_part] );
}
}
$geocode_address = implode( ',', $address );
return $geocode_address;
}
/**
* If there is a problem with the geocoding then we save the notice and change the post status to pending.
*
* @since 2.0.0
* @param string $msg The geocode error message
* @param integer $post_id Store post ID
* @return void
*/
public function geocode_failed( $msg, $post_id ) {
global $wpsl_admin;
$wpsl_admin->notices->save( 'error', $msg );
$wpsl_admin->metaboxes->set_post_pending( $post_id );
}
/**
* Save the store location data.
*
* @since 2.0.0
* @param integer $post_id Store post ID
* @param array $location_data The country code and latlng
* @return void
*/
public function save_store_location( $post_id, $location_data ) {
if ( isset( $location_data['country_iso'] ) && ( !empty( $location_data['country_iso'] ) ) ) {
update_post_meta( $post_id, 'wpsl_country_iso', $location_data['country_iso'] );
}
update_post_meta( $post_id, 'wpsl_lat', $location_data['latlng']['lat'] );
update_post_meta( $post_id, 'wpsl_lng', $location_data['latlng']['lng'] );
}
/**
* Make sure the latlng value has a max of 6 decimals.
*
* @since 2.0.0
* @param array $latlng The latlng data
* @return array $latlng The formatted latlng
*/
public function format_latlng( $latlng ) {
foreach ( $latlng as $key => $value ) {
if ( strlen( substr( strrchr( $value, '.' ), 1 ) ) > 6 ) {
$latlng[$key] = round( $value, 6 );
}
}
return $latlng;
}
/**
* Filter out the two letter country code from the Geocode API response.
*
* @since 1.0.0
* @param array $response The full API geocode response
* @return array $country The country ISO code and full name
*/
public function filter_country_name( $response ) {
$length = count( $response['results'][0]['address_components'] );
$country = array();
// Loop over the address components until we find the country, political part.
for ( $i = 0; $i < $length; $i++ ) {
$address_component = $response['results'][0]['address_components'][$i]['types'];
if ( $address_component[0] == 'country' && $address_component[1] == 'political' ) {
$country = $response['results'][0]['address_components'][$i];
break;
}
}
return $country;
}
/**
* Validate the latlng values.
*
* @since 1.0.0
* @param string $lat The latitude value
* @param string $lng The longitude value
* @return boolean|array $latlng The validated latlng values or false if it fails
*/
public function validate_latlng( $lat, $lng ) {
if ( !is_numeric( $lat ) || ( $lat > 90 ) || ( $lat < -90 ) ) {
return false;
}
if ( !is_numeric( $lng ) || ( $lng > 180 ) || ( $lng < -180 ) ) {
return false;
}
$latlng = array(
'lat' => $lat,
'lng' => $lng
);
return $latlng;
}
/**
* Error messages returned by the Google Maps API
* don't always contain clickable links.
*
* They now just look like this http://g.co/dev/maps-no-account
* and are not clickable. To change this we wrap an href around it.
*
* @since 2.2.22
* @return void
*/
public function clickable_error_links( $msg ) {
// Make sure the URLS aren't clickable yet. They aren't at the moment, but maybe Google changes this in the future.
if ( strpos( $msg,'href' ) === false ) {
preg_match_all( '#\bhttp(s?)?://[^,\s()<>]+(?:\([\w\d]+\)|([^,[:punct:]\s]|/))#', $msg, $match );
foreach ( $match[0] as $k => $url ) {
$msg = str_replace( $url, '<a href="' . esc_url( $url ) . '">' . esc_html( $url ) . '</a>', $msg );
}
}
return $msg;
}
}
}