WP Store Locator - Version 2.2.22

Version Description

Download this release

Release Info

Developer tijmensmit
Plugin Icon 128x128 WP Store Locator
Version 2.2.22
Comparing to
See all releases

Code changes from version 2.2.21 to 2.2.22

admin/EDD_SL_Plugin_Updater.php CHANGED
@@ -1,491 +1,491 @@
1
- <?php
2
-
3
- // Exit if accessed directly
4
- if ( ! defined( 'ABSPATH' ) ) exit;
5
-
6
- /**
7
- * Allows plugins to use their own update API.
8
- *
9
- * @author Easy Digital Downloads
10
- * @version 1.6.14
11
- */
12
- class EDD_SL_Plugin_Updater {
13
-
14
- private $api_url = '';
15
- private $api_data = array();
16
- private $name = '';
17
- private $slug = '';
18
- private $version = '';
19
- private $wp_override = false;
20
- private $cache_key = '';
21
-
22
- /**
23
- * Class constructor.
24
- *
25
- * @uses plugin_basename()
26
- * @uses hook()
27
- *
28
- * @param string $_api_url The URL pointing to the custom API endpoint.
29
- * @param string $_plugin_file Path to the plugin file.
30
- * @param array $_api_data Optional data to send with API calls.
31
- */
32
- public function __construct( $_api_url, $_plugin_file, $_api_data = null ) {
33
-
34
- global $edd_plugin_data;
35
-
36
- $this->api_url = trailingslashit( $_api_url );
37
- $this->api_data = $_api_data;
38
- $this->name = plugin_basename( $_plugin_file );
39
- $this->slug = basename( $_plugin_file, '.php' );
40
- $this->version = $_api_data['version'];
41
- $this->wp_override = isset( $_api_data['wp_override'] ) ? (bool) $_api_data['wp_override'] : false;
42
- $this->beta = ! empty( $this->api_data['beta'] ) ? true : false;
43
- $this->cache_key = md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) );
44
-
45
- $edd_plugin_data[ $this->slug ] = $this->api_data;
46
-
47
- // Set up hooks.
48
- $this->init();
49
-
50
- }
51
-
52
- /**
53
- * Set up WordPress filters to hook into WP's update process.
54
- *
55
- * @uses add_filter()
56
- *
57
- * @return void
58
- */
59
- public function init() {
60
-
61
- add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
62
- add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
63
- remove_action( 'after_plugin_row_' . $this->name, 'wp_plugin_update_row', 10 );
64
- add_action( 'after_plugin_row_' . $this->name, array( $this, 'show_update_notification' ), 10, 2 );
65
- add_action( 'admin_init', array( $this, 'show_changelog' ) );
66
-
67
- }
68
-
69
- /**
70
- * Check for Updates at the defined API endpoint and modify the update array.
71
- *
72
- * This function dives into the update API just when WordPress creates its update array,
73
- * then adds a custom API call and injects the custom plugin data retrieved from the API.
74
- * It is reassembled from parts of the native WordPress plugin update code.
75
- * See wp-includes/update.php line 121 for the original wp_update_plugins() function.
76
- *
77
- * @uses api_request()
78
- *
79
- * @param array $_transient_data Update array build by WordPress.
80
- * @return array Modified update array with custom plugin data.
81
- */
82
- public function check_update( $_transient_data ) {
83
-
84
- global $pagenow;
85
-
86
- if ( ! is_object( $_transient_data ) ) {
87
- $_transient_data = new stdClass;
88
- }
89
-
90
- if ( 'plugins.php' == $pagenow && is_multisite() ) {
91
- return $_transient_data;
92
- }
93
-
94
- if ( ! empty( $_transient_data->response ) && ! empty( $_transient_data->response[ $this->name ] ) && false === $this->wp_override ) {
95
- return $_transient_data;
96
- }
97
-
98
- $version_info = $this->get_cached_version_info();
99
-
100
- if ( false === $version_info ) {
101
- $version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
102
-
103
- $this->set_version_info_cache( $version_info );
104
-
105
- }
106
-
107
- if ( false !== $version_info && is_object( $version_info ) && isset( $version_info->new_version ) ) {
108
-
109
- if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
110
-
111
- $_transient_data->response[ $this->name ] = $version_info;
112
-
113
- }
114
-
115
- $_transient_data->last_checked = current_time( 'timestamp' );
116
- $_transient_data->checked[ $this->name ] = $this->version;
117
-
118
- }
119
-
120
- return $_transient_data;
121
- }
122
-
123
- /**
124
- * show update nofication row -- needed for multisite subsites, because WP won't tell you otherwise!
125
- *
126
- * @param string $file
127
- * @param array $plugin
128
- */
129
- public function show_update_notification( $file, $plugin ) {
130
-
131
- if ( is_network_admin() ) {
132
- return;
133
- }
134
-
135
- if( ! current_user_can( 'update_plugins' ) ) {
136
- return;
137
- }
138
-
139
- if( ! is_multisite() ) {
140
- return;
141
- }
142
-
143
- if ( $this->name != $file ) {
144
- return;
145
- }
146
-
147
- // Remove our filter on the site transient
148
- remove_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ), 10 );
149
-
150
- $update_cache = get_site_transient( 'update_plugins' );
151
-
152
- $update_cache = is_object( $update_cache ) ? $update_cache : new stdClass();
153
-
154
- if ( empty( $update_cache->response ) || empty( $update_cache->response[ $this->name ] ) ) {
155
-
156
- $version_info = $this->get_cached_version_info();
157
-
158
- if ( false === $version_info ) {
159
- $version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
160
-
161
- $this->set_version_info_cache( $version_info );
162
- }
163
-
164
- if ( ! is_object( $version_info ) ) {
165
- return;
166
- }
167
-
168
- if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
169
-
170
- $update_cache->response[ $this->name ] = $version_info;
171
-
172
- }
173
-
174
- $update_cache->last_checked = current_time( 'timestamp' );
175
- $update_cache->checked[ $this->name ] = $this->version;
176
-
177
- set_site_transient( 'update_plugins', $update_cache );
178
-
179
- } else {
180
-
181
- $version_info = $update_cache->response[ $this->name ];
182
-
183
- }
184
-
185
- // Restore our filter
186
- add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
187
-
188
- if ( ! empty( $update_cache->response[ $this->name ] ) && version_compare( $this->version, $version_info->new_version, '<' ) ) {
189
-
190
- // build a plugin list row, with update notification
191
- $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
192
- # <tr class="plugin-update-tr"><td colspan="' . $wp_list_table->get_column_count() . '" class="plugin-update colspanchange">
193
- echo '<tr class="plugin-update-tr" id="' . $this->slug . '-update" data-slug="' . $this->slug . '" data-plugin="' . $this->slug . '/' . $file . '">';
194
- echo '<td colspan="3" class="plugin-update colspanchange">';
195
- echo '<div class="update-message notice inline notice-warning notice-alt">';
196
-
197
- $changelog_link = self_admin_url( 'index.php?edd_sl_action=view_plugin_changelog&plugin=' . $this->name . '&slug=' . $this->slug . '&TB_iframe=true&width=772&height=911' );
198
-
199
- if ( empty( $version_info->download_link ) ) {
200
- printf(
201
- __( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s.', 'easy-digital-downloads' ),
202
- esc_html( $version_info->name ),
203
- '<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
204
- esc_html( $version_info->new_version ),
205
- '</a>'
206
- );
207
- } else {
208
- printf(
209
- __( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s or %5$supdate now%6$s.', 'easy-digital-downloads' ),
210
- esc_html( $version_info->name ),
211
- '<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
212
- esc_html( $version_info->new_version ),
213
- '</a>',
214
- '<a href="' . esc_url( wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' ) . $this->name, 'upgrade-plugin_' . $this->name ) ) .'">',
215
- '</a>'
216
- );
217
- }
218
-
219
- do_action( "in_plugin_update_message-{$file}", $plugin, $version_info );
220
-
221
- echo '</div></td></tr>';
222
- }
223
- }
224
-
225
- /**
226
- * Updates information on the "View version x.x details" page with custom data.
227
- *
228
- * @uses api_request()
229
- *
230
- * @param mixed $_data
231
- * @param string $_action
232
- * @param object $_args
233
- * @return object $_data
234
- */
235
- public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
236
-
237
- if ( $_action != 'plugin_information' ) {
238
-
239
- return $_data;
240
-
241
- }
242
-
243
- if ( ! isset( $_args->slug ) || ( $_args->slug != $this->slug ) ) {
244
-
245
- return $_data;
246
-
247
- }
248
-
249
- $to_send = array(
250
- 'slug' => $this->slug,
251
- 'is_ssl' => is_ssl(),
252
- 'fields' => array(
253
- 'banners' => array(),
254
- 'reviews' => false
255
- )
256
- );
257
-
258
- $cache_key = 'edd_api_request_' . md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) );
259
-
260
- // Get the transient where we store the api request for this plugin for 24 hours
261
- $edd_api_request_transient = $this->get_cached_version_info( $cache_key );
262
-
263
- //If we have no transient-saved value, run the API, set a fresh transient with the API value, and return that value too right now.
264
- if ( empty( $edd_api_request_transient ) ) {
265
-
266
- $api_response = $this->api_request( 'plugin_information', $to_send );
267
-
268
- // Expires in 3 hours
269
- $this->set_version_info_cache( $api_response, $cache_key );
270
-
271
- if ( false !== $api_response ) {
272
- $_data = $api_response;
273
- }
274
-
275
- } else {
276
- $_data = $edd_api_request_transient;
277
- }
278
-
279
- // Convert sections into an associative array, since we're getting an object, but Core expects an array.
280
- if ( isset( $_data->sections ) && ! is_array( $_data->sections ) ) {
281
- $new_sections = array();
282
- foreach ( $_data->sections as $key => $value ) {
283
- $new_sections[ $key ] = $value;
284
- }
285
-
286
- $_data->sections = $new_sections;
287
- }
288
-
289
- // Convert banners into an associative array, since we're getting an object, but Core expects an array.
290
- if ( isset( $_data->banners ) && ! is_array( $_data->banners ) ) {
291
- $new_banners = array();
292
- foreach ( $_data->banners as $key => $value ) {
293
- $new_banners[ $key ] = $value;
294
- }
295
-
296
- $_data->banners = $new_banners;
297
- }
298
-
299
- return $_data;
300
- }
301
-
302
- /**
303
- * Disable SSL verification in order to prevent download update failures
304
- *
305
- * @param array $args
306
- * @param string $url
307
- * @return object $array
308
- */
309
- public function http_request_args( $args, $url ) {
310
-
311
- $verify_ssl = $this->verify_ssl();
312
- if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) {
313
- $args['sslverify'] = $verify_ssl;
314
- }
315
- return $args;
316
-
317
- }
318
-
319
- /**
320
- * Calls the API and, if successfull, returns the object delivered by the API.
321
- *
322
- * @uses get_bloginfo()
323
- * @uses wp_remote_post()
324
- * @uses is_wp_error()
325
- *
326
- * @param string $_action The requested action.
327
- * @param array $_data Parameters for the API action.
328
- * @return false|object
329
- */
330
- private function api_request( $_action, $_data ) {
331
-
332
- global $wp_version;
333
-
334
- $data = array_merge( $this->api_data, $_data );
335
-
336
- if ( $data['slug'] != $this->slug ) {
337
- return;
338
- }
339
-
340
- if( $this->api_url == trailingslashit (home_url() ) ) {
341
- return false; // Don't allow a plugin to ping itself
342
- }
343
-
344
- $api_params = array(
345
- 'edd_action' => 'get_version',
346
- 'license' => ! empty( $data['license'] ) ? $data['license'] : '',
347
- 'item_name' => isset( $data['item_name'] ) ? $data['item_name'] : false,
348
- 'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false,
349
- 'version' => isset( $data['version'] ) ? $data['version'] : false,
350
- 'slug' => $data['slug'],
351
- 'author' => $data['author'],
352
- 'url' => home_url(),
353
- 'beta' => ! empty( $data['beta'] ),
354
- );
355
-
356
- $verify_ssl = $this->verify_ssl();
357
- $request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params ) );
358
-
359
- if ( ! is_wp_error( $request ) ) {
360
- $request = json_decode( wp_remote_retrieve_body( $request ) );
361
- }
362
-
363
- if ( $request && isset( $request->sections ) ) {
364
- $request->sections = maybe_unserialize( $request->sections );
365
- } else {
366
- $request = false;
367
- }
368
-
369
- if ( $request && isset( $request->banners ) ) {
370
- $request->banners = maybe_unserialize( $request->banners );
371
- }
372
-
373
- if( ! empty( $request->sections ) ) {
374
- foreach( $request->sections as $key => $section ) {
375
- $request->$key = (array) $section;
376
- }
377
- }
378
-
379
- return $request;
380
- }
381
-
382
- public function show_changelog() {
383
-
384
- global $edd_plugin_data;
385
-
386
- if( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' != $_REQUEST['edd_sl_action'] ) {
387
- return;
388
- }
389
-
390
- if( empty( $_REQUEST['plugin'] ) ) {
391
- return;
392
- }
393
-
394
- if( empty( $_REQUEST['slug'] ) ) {
395
- return;
396
- }
397
-
398
- if( ! current_user_can( 'update_plugins' ) ) {
399
- wp_die( __( 'You do not have permission to install plugin updates', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) );
400
- }
401
-
402
- $data = $edd_plugin_data[ $_REQUEST['slug'] ];
403
- $beta = ! empty( $data['beta'] ) ? true : false;
404
- $cache_key = md5( 'edd_plugin_' . sanitize_key( $_REQUEST['plugin'] ) . '_' . $beta . '_version_info' );
405
- $version_info = $this->get_cached_version_info( $cache_key );
406
-
407
- if( false === $version_info ) {
408
-
409
- $api_params = array(
410
- 'edd_action' => 'get_version',
411
- 'item_name' => isset( $data['item_name'] ) ? $data['item_name'] : false,
412
- 'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false,
413
- 'slug' => $_REQUEST['slug'],
414
- 'author' => $data['author'],
415
- 'url' => home_url(),
416
- 'beta' => ! empty( $data['beta'] )
417
- );
418
-
419
- $verify_ssl = $this->verify_ssl();
420
- $request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params ) );
421
-
422
- if ( ! is_wp_error( $request ) ) {
423
- $version_info = json_decode( wp_remote_retrieve_body( $request ) );
424
- }
425
-
426
-
427
- if ( ! empty( $version_info ) && isset( $version_info->sections ) ) {
428
- $version_info->sections = maybe_unserialize( $version_info->sections );
429
- } else {
430
- $version_info = false;
431
- }
432
-
433
- if( ! empty( $version_info ) ) {
434
- foreach( $version_info->sections as $key => $section ) {
435
- $version_info->$key = (array) $section;
436
- }
437
- }
438
-
439
- $this->set_version_info_cache( $version_info, $cache_key );
440
-
441
- }
442
-
443
- if( ! empty( $version_info ) && isset( $version_info->sections['changelog'] ) ) {
444
- echo '<div style="background:#fff;padding:10px;">' . $version_info->sections['changelog'] . '</div>';
445
- }
446
-
447
- exit;
448
- }
449
-
450
- public function get_cached_version_info( $cache_key = '' ) {
451
-
452
- if( empty( $cache_key ) ) {
453
- $cache_key = $this->cache_key;
454
- }
455
-
456
- $cache = get_option( $cache_key );
457
-
458
- if( empty( $cache['timeout'] ) || current_time( 'timestamp' ) > $cache['timeout'] ) {
459
- return false; // Cache is expired
460
- }
461
-
462
- return json_decode( $cache['value'] );
463
-
464
- }
465
-
466
- public function set_version_info_cache( $value = '', $cache_key = '' ) {
467
-
468
- if( empty( $cache_key ) ) {
469
- $cache_key = $this->cache_key;
470
- }
471
-
472
- $data = array(
473
- 'timeout' => strtotime( '+3 hours', current_time( 'timestamp' ) ),
474
- 'value' => json_encode( $value )
475
- );
476
-
477
- update_option( $cache_key, $data, 'no' );
478
-
479
- }
480
-
481
- /**
482
- * Returns if the SSL of the store should be verified.
483
- *
484
- * @since 1.6.13
485
- * @return bool
486
- */
487
- private function verify_ssl() {
488
- return (bool) apply_filters( 'edd_sl_api_request_verify_ssl', true, $this );
489
- }
490
-
491
- }
1
+ <?php
2
+
3
+ // Exit if accessed directly
4
+ if ( ! defined( 'ABSPATH' ) ) exit;
5
+
6
+ /**
7
+ * Allows plugins to use their own update API.
8
+ *
9
+ * @author Easy Digital Downloads
10
+ * @version 1.6.14
11
+ */
12
+ class EDD_SL_Plugin_Updater {
13
+
14
+ private $api_url = '';
15
+ private $api_data = array();
16
+ private $name = '';
17
+ private $slug = '';
18
+ private $version = '';
19
+ private $wp_override = false;
20
+ private $cache_key = '';
21
+
22
+ /**
23
+ * Class constructor.
24
+ *
25
+ * @uses plugin_basename()
26
+ * @uses hook()
27
+ *
28
+ * @param string $_api_url The URL pointing to the custom API endpoint.
29
+ * @param string $_plugin_file Path to the plugin file.
30
+ * @param array $_api_data Optional data to send with API calls.
31
+ */
32
+ public function __construct( $_api_url, $_plugin_file, $_api_data = null ) {
33
+
34
+ global $edd_plugin_data;
35
+
36
+ $this->api_url = trailingslashit( $_api_url );
37
+ $this->api_data = $_api_data;
38
+ $this->name = plugin_basename( $_plugin_file );
39
+ $this->slug = basename( $_plugin_file, '.php' );
40
+ $this->version = $_api_data['version'];
41
+ $this->wp_override = isset( $_api_data['wp_override'] ) ? (bool) $_api_data['wp_override'] : false;
42
+ $this->beta = ! empty( $this->api_data['beta'] ) ? true : false;
43
+ $this->cache_key = md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) );
44
+
45
+ $edd_plugin_data[ $this->slug ] = $this->api_data;
46
+
47
+ // Set up hooks.
48
+ $this->init();
49
+
50
+ }
51
+
52
+ /**
53
+ * Set up WordPress filters to hook into WP's update process.
54
+ *
55
+ * @uses add_filter()
56
+ *
57
+ * @return void
58
+ */
59
+ public function init() {
60
+
61
+ add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
62
+ add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
63
+ remove_action( 'after_plugin_row_' . $this->name, 'wp_plugin_update_row', 10 );
64
+ add_action( 'after_plugin_row_' . $this->name, array( $this, 'show_update_notification' ), 10, 2 );
65
+ add_action( 'admin_init', array( $this, 'show_changelog' ) );
66
+
67
+ }
68
+
69
+ /**
70
+ * Check for Updates at the defined API endpoint and modify the update array.
71
+ *
72
+ * This function dives into the update API just when WordPress creates its update array,
73
+ * then adds a custom API call and injects the custom plugin data retrieved from the API.
74
+ * It is reassembled from parts of the native WordPress plugin update code.
75
+ * See wp-includes/update.php line 121 for the original wp_update_plugins() function.
76
+ *
77
+ * @uses api_request()
78
+ *
79
+ * @param array $_transient_data Update array build by WordPress.
80
+ * @return array Modified update array with custom plugin data.
81
+ */
82
+ public function check_update( $_transient_data ) {
83
+
84
+ global $pagenow;
85
+
86
+ if ( ! is_object( $_transient_data ) ) {
87
+ $_transient_data = new stdClass;
88
+ }
89
+
90
+ if ( 'plugins.php' == $pagenow && is_multisite() ) {
91
+ return $_transient_data;
92
+ }
93
+
94
+ if ( ! empty( $_transient_data->response ) && ! empty( $_transient_data->response[ $this->name ] ) && false === $this->wp_override ) {
95
+ return $_transient_data;
96
+ }
97
+
98
+ $version_info = $this->get_cached_version_info();
99
+
100
+ if ( false === $version_info ) {
101
+ $version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
102
+
103
+ $this->set_version_info_cache( $version_info );
104
+
105
+ }
106
+
107
+ if ( false !== $version_info && is_object( $version_info ) && isset( $version_info->new_version ) ) {
108
+
109
+ if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
110
+
111
+ $_transient_data->response[ $this->name ] = $version_info;
112
+
113
+ }
114
+
115
+ $_transient_data->last_checked = current_time( 'timestamp' );
116
+ $_transient_data->checked[ $this->name ] = $this->version;
117
+
118
+ }
119
+
120
+ return $_transient_data;
121
+ }
122
+
123
+ /**
124
+ * show update nofication row -- needed for multisite subsites, because WP won't tell you otherwise!
125
+ *
126
+ * @param string $file
127
+ * @param array $plugin
128
+ */
129
+ public function show_update_notification( $file, $plugin ) {
130
+
131
+ if ( is_network_admin() ) {
132
+ return;
133
+ }
134
+
135
+ if( ! current_user_can( 'update_plugins' ) ) {
136
+ return;
137
+ }
138
+
139
+ if( ! is_multisite() ) {
140
+ return;
141
+ }
142
+
143
+ if ( $this->name != $file ) {
144
+ return;
145
+ }
146
+
147
+ // Remove our filter on the site transient
148
+ remove_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ), 10 );
149
+
150
+ $update_cache = get_site_transient( 'update_plugins' );
151
+
152
+ $update_cache = is_object( $update_cache ) ? $update_cache : new stdClass();
153
+
154
+ if ( empty( $update_cache->response ) || empty( $update_cache->response[ $this->name ] ) ) {
155
+
156
+ $version_info = $this->get_cached_version_info();
157
+
158
+ if ( false === $version_info ) {
159
+ $version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
160
+
161
+ $this->set_version_info_cache( $version_info );
162
+ }
163
+
164
+ if ( ! is_object( $version_info ) ) {
165
+ return;
166
+ }
167
+
168
+ if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
169
+
170
+ $update_cache->response[ $this->name ] = $version_info;
171
+
172
+ }
173
+
174
+ $update_cache->last_checked = current_time( 'timestamp' );
175
+ $update_cache->checked[ $this->name ] = $this->version;
176
+
177
+ set_site_transient( 'update_plugins', $update_cache );
178
+
179
+ } else {
180
+
181
+ $version_info = $update_cache->response[ $this->name ];
182
+
183
+ }
184
+
185
+ // Restore our filter
186
+ add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
187
+
188
+ if ( ! empty( $update_cache->response[ $this->name ] ) && version_compare( $this->version, $version_info->new_version, '<' ) ) {
189
+
190
+ // build a plugin list row, with update notification
191
+ $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
192
+ # <tr class="plugin-update-tr"><td colspan="' . $wp_list_table->get_column_count() . '" class="plugin-update colspanchange">
193
+ echo '<tr class="plugin-update-tr" id="' . $this->slug . '-update" data-slug="' . $this->slug . '" data-plugin="' . $this->slug . '/' . $file . '">';
194
+ echo '<td colspan="3" class="plugin-update colspanchange">';
195
+ echo '<div class="update-message notice inline notice-warning notice-alt">';
196
+
197
+ $changelog_link = self_admin_url( 'index.php?edd_sl_action=view_plugin_changelog&plugin=' . $this->name . '&slug=' . $this->slug . '&TB_iframe=true&width=772&height=911' );
198
+
199
+ if ( empty( $version_info->download_link ) ) {
200
+ printf(
201
+ __( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s.', 'easy-digital-downloads' ),
202
+ esc_html( $version_info->name ),
203
+ '<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
204
+ esc_html( $version_info->new_version ),
205
+ '</a>'
206
+ );
207
+ } else {
208
+ printf(
209
+ __( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s or %5$supdate now%6$s.', 'easy-digital-downloads' ),
210
+ esc_html( $version_info->name ),
211
+ '<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
212
+ esc_html( $version_info->new_version ),
213
+ '</a>',
214
+ '<a href="' . esc_url( wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' ) . $this->name, 'upgrade-plugin_' . $this->name ) ) .'">',
215
+ '</a>'
216
+ );
217
+ }
218
+
219
+ do_action( "in_plugin_update_message-{$file}", $plugin, $version_info );
220
+
221
+ echo '</div></td></tr>';
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Updates information on the "View version x.x details" page with custom data.
227
+ *
228
+ * @uses api_request()
229
+ *
230
+ * @param mixed $_data
231
+ * @param string $_action
232
+ * @param object $_args
233
+ * @return object $_data
234
+ */
235
+ public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
236
+
237
+ if ( $_action != 'plugin_information' ) {
238
+
239
+ return $_data;
240
+
241
+ }
242
+
243
+ if ( ! isset( $_args->slug ) || ( $_args->slug != $this->slug ) ) {
244
+
245
+ return $_data;
246
+
247
+ }
248
+
249
+ $to_send = array(
250
+ 'slug' => $this->slug,
251
+ 'is_ssl' => is_ssl(),
252
+ 'fields' => array(
253
+ 'banners' => array(),
254
+ 'reviews' => false
255
+ )
256
+ );
257
+
258
+ $cache_key = 'edd_api_request_' . md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) );
259
+
260
+ // Get the transient where we store the api request for this plugin for 24 hours
261
+ $edd_api_request_transient = $this->get_cached_version_info( $cache_key );
262
+
263
+ //If we have no transient-saved value, run the API, set a fresh transient with the API value, and return that value too right now.
264
+ if ( empty( $edd_api_request_transient ) ) {
265
+
266
+ $api_response = $this->api_request( 'plugin_information', $to_send );
267
+
268
+ // Expires in 3 hours
269
+ $this->set_version_info_cache( $api_response, $cache_key );
270
+
271
+ if ( false !== $api_response ) {
272
+ $_data = $api_response;
273
+ }
274
+
275
+ } else {
276
+ $_data = $edd_api_request_transient;
277
+ }
278
+
279
+ // Convert sections into an associative array, since we're getting an object, but Core expects an array.
280
+ if ( isset( $_data->sections ) && ! is_array( $_data->sections ) ) {
281
+ $new_sections = array();
282
+ foreach ( $_data->sections as $key => $value ) {
283
+ $new_sections[ $key ] = $value;
284
+ }
285
+
286
+ $_data->sections = $new_sections;
287
+ }
288
+
289
+ // Convert banners into an associative array, since we're getting an object, but Core expects an array.
290
+ if ( isset( $_data->banners ) && ! is_array( $_data->banners ) ) {
291
+ $new_banners = array();
292
+ foreach ( $_data->banners as $key => $value ) {
293
+ $new_banners[ $key ] = $value;
294
+ }
295
+
296
+ $_data->banners = $new_banners;
297
+ }
298
+
299
+ return $_data;
300
+ }
301
+
302
+ /**
303
+ * Disable SSL verification in order to prevent download update failures
304
+ *
305
+ * @param array $args
306
+ * @param string $url
307
+ * @return object $array
308
+ */
309
+ public function http_request_args( $args, $url ) {
310
+
311
+ $verify_ssl = $this->verify_ssl();
312
+ if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) {
313
+ $args['sslverify'] = $verify_ssl;
314
+ }
315
+ return $args;
316
+
317
+ }
318
+
319
+ /**
320
+ * Calls the API and, if successfull, returns the object delivered by the API.
321
+ *
322
+ * @uses get_bloginfo()
323
+ * @uses wp_remote_post()
324
+ * @uses is_wp_error()
325
+ *
326
+ * @param string $_action The requested action.
327
+ * @param array $_data Parameters for the API action.
328
+ * @return false|object
329
+ */
330
+ private function api_request( $_action, $_data ) {
331
+
332
+ global $wp_version;
333
+
334
+ $data = array_merge( $this->api_data, $_data );
335
+
336
+ if ( $data['slug'] != $this->slug ) {
337
+ return;
338
+ }
339
+
340
+ if( $this->api_url == trailingslashit (home_url() ) ) {
341
+ return false; // Don't allow a plugin to ping itself
342
+ }
343
+
344
+ $api_params = array(
345
+ 'edd_action' => 'get_version',
346
+ 'license' => ! empty( $data['license'] ) ? $data['license'] : '',
347
+ 'item_name' => isset( $data['item_name'] ) ? $data['item_name'] : false,
348
+ 'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false,
349
+ 'version' => isset( $data['version'] ) ? $data['version'] : false,
350
+ 'slug' => $data['slug'],
351
+ 'author' => $data['author'],
352
+ 'url' => home_url(),
353
+ 'beta' => ! empty( $data['beta'] ),
354
+ );
355
+
356
+ $verify_ssl = $this->verify_ssl();
357
+ $request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params ) );
358
+
359
+ if ( ! is_wp_error( $request ) ) {
360
+ $request = json_decode( wp_remote_retrieve_body( $request ) );
361
+ }
362
+
363
+ if ( $request && isset( $request->sections ) ) {
364
+ $request->sections = maybe_unserialize( $request->sections );
365
+ } else {
366
+ $request = false;
367
+ }
368
+
369
+ if ( $request && isset( $request->banners ) ) {
370
+ $request->banners = maybe_unserialize( $request->banners );
371
+ }
372
+
373
+ if( ! empty( $request->sections ) ) {
374
+ foreach( $request->sections as $key => $section ) {
375
+ $request->$key = (array) $section;
376
+ }
377
+ }
378
+
379
+ return $request;
380
+ }
381
+
382
+ public function show_changelog() {
383
+
384
+ global $edd_plugin_data;
385
+
386
+ if( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' != $_REQUEST['edd_sl_action'] ) {
387
+ return;
388
+ }
389
+
390
+ if( empty( $_REQUEST['plugin'] ) ) {
391
+ return;
392
+ }
393
+
394
+ if( empty( $_REQUEST['slug'] ) ) {
395
+ return;
396
+ }
397
+
398
+ if( ! current_user_can( 'update_plugins' ) ) {
399
+ wp_die( __( 'You do not have permission to install plugin updates', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) );
400
+ }
401
+
402
+ $data = $edd_plugin_data[ $_REQUEST['slug'] ];
403
+ $beta = ! empty( $data['beta'] ) ? true : false;
404
+ $cache_key = md5( 'edd_plugin_' . sanitize_key( $_REQUEST['plugin'] ) . '_' . $beta . '_version_info' );
405
+ $version_info = $this->get_cached_version_info( $cache_key );
406
+
407
+ if( false === $version_info ) {
408
+
409
+ $api_params = array(
410
+ 'edd_action' => 'get_version',
411
+ 'item_name' => isset( $data['item_name'] ) ? $data['item_name'] : false,
412
+ 'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false,
413
+ 'slug' => $_REQUEST['slug'],
414
+ 'author' => $data['author'],
415
+ 'url' => home_url(),
416
+ 'beta' => ! empty( $data['beta'] )
417
+ );
418
+
419
+ $verify_ssl = $this->verify_ssl();
420
+ $request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params ) );
421
+
422
+ if ( ! is_wp_error( $request ) ) {
423
+ $version_info = json_decode( wp_remote_retrieve_body( $request ) );
424
+ }
425
+
426
+
427
+ if ( ! empty( $version_info ) && isset( $version_info->sections ) ) {
428
+ $version_info->sections = maybe_unserialize( $version_info->sections );
429
+ } else {
430
+ $version_info = false;
431
+ }
432
+
433
+ if( ! empty( $version_info ) ) {
434
+ foreach( $version_info->sections as $key => $section ) {
435
+ $version_info->$key = (array) $section;
436
+ }
437
+ }
438
+
439
+ $this->set_version_info_cache( $version_info, $cache_key );
440
+
441
+ }
442
+
443
+ if( ! empty( $version_info ) && isset( $version_info->sections['changelog'] ) ) {
444
+ echo '<div style="background:#fff;padding:10px;">' . $version_info->sections['changelog'] . '</div>';
445
+ }
446
+
447
+ exit;
448
+ }
449
+
450
+ public function get_cached_version_info( $cache_key = '' ) {
451
+
452
+ if( empty( $cache_key ) ) {
453
+ $cache_key = $this->cache_key;
454
+ }
455
+
456
+ $cache = get_option( $cache_key );
457
+
458
+ if( empty( $cache['timeout'] ) || current_time( 'timestamp' ) > $cache['timeout'] ) {
459
+ return false; // Cache is expired
460
+ }
461
+
462
+ return json_decode( $cache['value'] );
463
+
464
+ }
465
+
466
+ public function set_version_info_cache( $value = '', $cache_key = '' ) {
467
+
468
+ if( empty( $cache_key ) ) {
469
+ $cache_key = $this->cache_key;
470
+ }
471
+
472
+ $data = array(
473
+ 'timeout' => strtotime( '+3 hours', current_time( 'timestamp' ) ),
474
+ 'value' => json_encode( $value )
475
+ );
476
+
477
+ update_option( $cache_key, $data, 'no' );
478
+
479
+ }
480
+
481
+ /**
482
+ * Returns if the SSL of the store should be verified.
483
+ *
484
+ * @since 1.6.13
485
+ * @return bool
486
+ */
487
+ private function verify_ssl() {
488
+ return (bool) apply_filters( 'edd_sl_api_request_verify_ssl', true, $this );
489
+ }
490
+
491
+ }
admin/class-admin.php CHANGED
@@ -1,517 +1,561 @@
1
- <?php
2
- /**
3
- * Admin class
4
- *
5
- * @author Tijmen Smit
6
- * @since 1.0.0
7
- */
8
-
9
- if ( !defined( 'ABSPATH' ) ) exit;
10
-
11
- if ( !class_exists( 'WPSL_Admin' ) ) {
12
-
13
- /**
14
- * Handle the backend of the store locator
15
- *
16
- * @since 1.0.0
17
- */
18
- class WPSL_Admin {
19
-
20
- /**
21
- * @since 2.0.0
22
- * @var WPSL_Metaboxes
23
- */
24
- public $metaboxes;
25
-
26
- /**
27
- * @since 2.0.0
28
- * @var WPSL_Geocode
29
- */
30
- public $geocode;
31
-
32
- /**
33
- * @since 2.0.0
34
- * @var WPSL_Notices
35
- */
36
- public $notices;
37
-
38
- /**
39
- * @since 2.0.0
40
- * @var WPSL_Settings
41
- */
42
- public $settings_page;
43
-
44
- /**
45
- * Class constructor
46
- */
47
- function __construct() {
48
-
49
- $this->includes();
50
-
51
- add_action( 'init', array( $this, 'init' ) );
52
- add_action( 'admin_menu', array( $this, 'create_admin_menu' ) );
53
- add_action( 'admin_init', array( $this, 'setting_warnings' ) );
54
- add_action( 'delete_post', array( $this, 'maybe_delete_autoload_transient' ) );
55
- add_action( 'wp_trash_post', array( $this, 'maybe_delete_autoload_transient' ) );
56
- add_action( 'untrash_post', array( $this, 'maybe_delete_autoload_transient' ) );
57
- add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
58
- add_filter( 'plugin_row_meta', array( $this, 'add_plugin_meta_row' ), 10, 2 );
59
- add_filter( 'plugin_action_links_' . WPSL_BASENAME, array( $this, 'add_action_links' ), 10, 2 );
60
- add_filter( 'admin_footer_text', array( $this, 'admin_footer_text' ), 1 );
61
- add_action( 'wp_loaded', array( $this, 'disable_setting_notices' ) );
62
- }
63
-
64
- /**
65
- * Include the required files.
66
- *
67
- * @since 2.0.0
68
- * @return void
69
- */
70
- public function includes() {
71
- require_once( WPSL_PLUGIN_DIR . 'admin/class-shortcode-generator.php' );
72
- require_once( WPSL_PLUGIN_DIR . 'admin/class-notices.php' );
73
- require_once( WPSL_PLUGIN_DIR . 'admin/class-license-manager.php' );
74
- require_once( WPSL_PLUGIN_DIR . 'admin/class-metaboxes.php' );
75
- require_once( WPSL_PLUGIN_DIR . 'admin/class-geocode.php' );
76
- require_once( WPSL_PLUGIN_DIR . 'admin/class-settings.php' );
77
- require_once( WPSL_PLUGIN_DIR . 'admin/upgrade.php' );
78
- require_once( WPSL_PLUGIN_DIR . 'admin/data-export.php' );
79
- }
80
-
81
- /**
82
- * Init the classes.
83
- *
84
- * @since 2.0.0
85
- * @return void
86
- */
87
- public function init() {
88
- $this->notices = new WPSL_Notices();
89
- $this->metaboxes = new WPSL_Metaboxes();
90
- $this->geocode = new WPSL_Geocode();
91
- $this->settings_page = new WPSL_Settings();
92
- }
93
-
94
- /**
95
- * Check if we need to show warnings after
96
- * the user installed the plugin.
97
- *
98
- * @since 1.0.0
99
- * @todo move to class-notices?
100
- * @return void
101
- */
102
- public function setting_warnings() {
103
-
104
- global $current_user, $wpsl_settings;
105
-
106
- $this->setting_warning = array();
107
-
108
- // The fields settings field to check for data.
109
- $warnings = array(
110
- 'start_latlng' => 'location',
111
- 'api_browser_key' => 'key'
112
- );
113
-
114
- if ( ( current_user_can( 'install_plugins' ) ) && is_admin() ) {
115
- foreach ( $warnings as $setting_name => $warning ) {
116
- if ( empty( $wpsl_settings[$setting_name] ) && !get_user_meta( $current_user->ID, 'wpsl_disable_' . $warning . '_warning' ) ) {
117
- if ( $warning == 'key' ) {
118
- $this->setting_warning[$warning] = sprintf( __( "You need to create %sAPI keys%s for Google Maps before you can use the store locator! %sDismiss%s", "wpsl" ), '<a href="https://wpstorelocator.co/document/create-google-api-keys/">', "</a>", "<a href='" . esc_url( wp_nonce_url( add_query_arg( 'wpsl-notice', 'key' ), 'wpsl_notices_nonce', '_wpsl_notice_nonce' ) ) . "'>", "</a>" );
119
- } else {
120
- $this->setting_warning[$warning] = sprintf( __( "Before adding the [wpsl] shortcode to a page, please don't forget to define a start point on the %ssettings%s page. %sDismiss%s", "wpsl" ), "<a href='" . admin_url( 'edit.php?post_type=wpsl_stores&page=wpsl_settings' ) . "'>", "</a>", "<a href='" . esc_url( wp_nonce_url( add_query_arg( 'wpsl-notice', 'location' ), 'wpsl_notices_nonce', '_wpsl_notice_nonce' ) ) . "'>", "</a>" );
121
- }
122
- }
123
- }
124
-
125
- if ( $this->setting_warning ) {
126
- add_action( 'admin_notices', array( $this, 'show_warning' ) );
127
- }
128
- }
129
- }
130
-
131
- /**
132
- * Show the admin warnings
133
- *
134
- * @since 1.2.0
135
- * @return void
136
- */
137
- public function show_warning() {
138
- foreach ( $this->setting_warning as $k => $warning ) {
139
- echo "<div id='message' class='error'><p>" . $warning . "</p></div>";
140
- }
141
- }
142
-
143
- /**
144
- * Disable notices about the plugin settings.
145
- *
146
- * @todo move to class-notices?
147
- * @since 2.2.3
148
- * @return void
149
- */
150
- public function disable_setting_notices() {
151
-
152
- global $current_user;
153
-
154
- if ( isset( $_GET['wpsl-notice'] ) && isset( $_GET['_wpsl_notice_nonce'] ) ) {
155
-
156
- if ( !wp_verify_nonce( $_GET['_wpsl_notice_nonce'], 'wpsl_notices_nonce' ) ) {
157
- wp_die( __( 'Security check failed. Please reload the page and try again.', 'wpsl' ) );
158
- }
159
-
160
- $notice = sanitize_text_field( $_GET['wpsl-notice'] );
161
-
162
- add_user_meta( $current_user->ID, 'wpsl_disable_' . $notice . '_warning', 'true', true );
163
- }
164
- }
165
-
166
- /**
167
- * Add the admin menu pages.
168
- *
169
- * @since 1.0.0
170
- * @return void
171
- */
172
- public function create_admin_menu() {
173
-
174
- $sub_menus = apply_filters( 'wpsl_sub_menu_items', array(
175
- array(
176
- 'page_title' => __( 'Settings', 'wpsl' ),
177
- 'menu_title' => __( 'Settings', 'wpsl' ),
178
- 'caps' => 'manage_wpsl_settings',
179
- 'menu_slug' => 'wpsl_settings',
180
- 'function' => array( $this, 'load_template' )
181
- ),
182
- array(
183
- 'page_title' => __( 'Add-Ons', 'wpsl' ),
184
- 'menu_title' => __( 'Add-Ons', 'wpsl' ),
185
- 'caps' => 'manage_wpsl_settings',
186
- 'menu_slug' => 'wpsl_add_ons',
187
- 'function' => array( $this, 'load_template' )
188
- )
189
- )
190
- );
191
-
192
- if ( count( $sub_menus ) ) {
193
- foreach ( $sub_menus as $sub_menu ) {
194
- add_submenu_page( 'edit.php?post_type=wpsl_stores', $sub_menu['page_title'], $sub_menu['menu_title'], $sub_menu['caps'], $sub_menu['menu_slug'], $sub_menu['function'] );
195
- }
196
- }
197
- }
198
-
199
- /**
200
- * Load the correct page template.
201
- *
202
- * @since 2.1.0
203
- * @return void
204
- */
205
- public function load_template() {
206
-
207
- switch ( $_GET['page'] ) {
208
- case 'wpsl_settings':
209
- require 'templates/map-settings.php';
210
- break;
211
- case 'wpsl_add_ons':
212
- require 'templates/add-ons.php';
213
- break;
214
- }
215
- }
216
-
217
- /**
218
- * Check if we need to delete the autoload transient.
219
- *
220
- * This is called when a post it saved, deleted, trashed or untrashed.
221
- *
222
- * @since 2.0.0
223
- * @return void
224
- */
225
- public function maybe_delete_autoload_transient( $post_id ) {
226
-
227
- global $wpsl_settings;
228
-
229
- if ( isset( $wpsl_settings['autoload'] ) && $wpsl_settings['autoload'] && get_post_type( $post_id ) == 'wpsl_stores' ) {
230
- $this->delete_autoload_transient();
231
- }
232
- }
233
-
234
- /**
235
- * Delete the transients that are used on the front-end
236
- * if the autoload option is enabled.
237
- *
238
- * The transient names used by the store locator are partly dynamic.
239
- * They always start with wpsl_autoload_, followed by the number of
240
- * stores to load and ends with the language code.
241
- *
242
- * So you get wpsl_autoload_20_de if the language is set to German
243
- * and 20 stores are set to show on page load.
244
- *
245
- * The language code has to be included in case a multilingual plugin is used.
246
- * Otherwise it can happen the user switches to Spanish,
247
- * but ends up seeing the store data in the wrong language.
248
- *
249
- * @since 2.0.0
250
- * @return void
251
- */
252
- public function delete_autoload_transient() {
253
-
254
- global $wpdb;
255
-
256
- $option_names = $wpdb->get_results( "SELECT option_name AS transient_name FROM " . $wpdb->options . " WHERE option_name LIKE ('\_transient\_wpsl\_autoload\_%')" );
257
-
258
- if ( $option_names ) {
259
- foreach ( $option_names as $option_name ) {
260
- $transient_name = str_replace( "_transient_", "", $option_name->transient_name );
261
-
262
- delete_transient( $transient_name );
263
- }
264
- }
265
- }
266
-
267
- /**
268
- * Check if we can use a font for the plugin icon.
269
- *
270
- * This is supported by WP 3.8 or higher
271
- *
272
- * @since 1.0.0
273
- * @return void
274
- */
275
- private function check_icon_font_usage() {
276
-
277
- global $wp_version;
278
-
279
- if ( ( version_compare( $wp_version, '3.8', '>=' ) == TRUE ) ) {
280
- $min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
281
-
282
- wp_enqueue_style( 'wpsl-admin-38', plugins_url( '/css/style-3.8'. $min .'.css', __FILE__ ), false );
283
- }
284
- }
285
-
286
- /**
287
- * The text messages used in wpsl-admin.js.
288
- *
289
- * @since 1.2.20
290
- * @return array $admin_js_l10n The texts used in the wpsl-admin.js
291
- */
292
- public function admin_js_l10n() {
293
-
294
- $admin_js_l10n = array(
295
- 'noAddress' => __( 'Cannot determine the address at this location.', 'wpsl' ),
296
- 'geocodeFail' => __( 'Geocode was not successful for the following reason', 'wpsl' ),
297
- 'securityFail' => __( 'Security check failed, reload the page and try again.', 'wpsl' ),
298
- 'requiredFields' => __( 'Please fill in all the required store details.', 'wpsl' ),
299
- 'missingGeoData' => __( 'The map preview requires all the location details.', 'wpsl' ),
300
- 'closedDate' => __( 'Closed', 'wpsl' ),
301
- 'styleError' => __( 'The code for the map style is invalid.', 'wpsl' ),
302
- 'browserKeyError' => sprintf( __( 'There\'s a problem with the provided %sbrowser key%s. %s You can read more about how to determine the exact issue, and how to solve it %shere%s.','wpsl' ), '<a href="https://wpstorelocator.co/document/create-google-api-keys/#browser-key">','</a>', '<br><br>', '<a href="https://wpstorelocator.co/document/create-google-api-keys/#troubleshooting">','</a>' ),
303
- 'dismissNotice' => __( 'Dismiss this notice.', 'wpsl' )
304
- );
305
-
306
- return $admin_js_l10n;
307
- }
308
-
309
- /**
310
- * Plugin settings that are used in the wpsl-admin.js.
311
- *
312
- * @since 2.0.0
313
- * @return array $settings_js The settings used in the wpsl-admin.js
314
- */
315
- public function js_settings() {
316
-
317
- global $wpsl_settings;
318
-
319
- $js_settings = array(
320
- 'hourFormat' => $wpsl_settings['editor_hour_format'],
321
- 'defaultLatLng' => $this->get_default_lat_lng(),
322
- 'defaultZoom' => 6,
323
- 'mapType' => $wpsl_settings['editor_map_type'],
324
- 'requiredFields' => array( 'address', 'city', 'country' ),
325
- 'ajaxurl' => wpsl_get_ajax_url()
326
- );
327
-
328
- return apply_filters( 'wpsl_admin_js_settings', $js_settings );
329
- }
330
-
331
- /**
332
- * Get the coordinates that are used to
333
- * show the map on the settings page.
334
- *
335
- * @since 2.2.5
336
- * @return string $startLatLng The start coordinates
337
- */
338
- public function get_default_lat_lng() {
339
-
340
- global $wpsl_settings;
341
-
342
- $startLatLng = $wpsl_settings['start_latlng'];
343
-
344
- // If no start coordinates exists, then set the default to Holland.
345
- if ( !$startLatLng ) {
346
- $startLatLng = '52.378153,4.899363';
347
- }
348
-
349
- return $startLatLng;
350
- }
351
-
352
- /**
353
- * Add the required admin script.
354
- *
355
- * @since 1.0.0
356
- * @return void
357
- */
358
- public function admin_scripts() {
359
-
360
- $min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
361
-
362
- // Always load the main js admin file to make sure the "dismiss" link in the location notice works.
363
- wp_enqueue_script( 'wpsl-admin-js', plugins_url( '/js/wpsl-admin'. $min .'.js', __FILE__ ), array( 'jquery' ), WPSL_VERSION_NUM, true );
364
-
365
- $this->maybe_show_pointer();
366
- $this->check_icon_font_usage();
367
-
368
- // Only enqueue the rest of the css/js files if we are on a page that belongs to the store locator.
369
- if ( ( get_post_type() == 'wpsl_stores' ) || ( isset( $_GET['post_type'] ) && ( $_GET['post_type'] == 'wpsl_stores' ) ) ) {
370
-
371
- // Make sure no other Google Map scripts can interfere with the one from the store locator.
372
- wpsl_deregister_other_gmaps();
373
-
374
- wp_enqueue_style( 'jquery-style', '//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/themes/smoothness/jquery-ui.css' );
375
- wp_enqueue_style( 'wpsl-admin-css', plugins_url( '/css/style'. $min .'.css', __FILE__ ), false );
376
-
377
- wp_enqueue_media();
378
- wp_enqueue_script( 'jquery-ui-dialog' );
379
- wp_enqueue_script( 'wpsl-gmap', ( '//maps.google.com/maps/api/js' . wpsl_get_gmap_api_params( 'browser_key' ) ), false, WPSL_VERSION_NUM, true );
380
-
381
- wp_enqueue_script( 'wpsl-queue', plugins_url( '/js/ajax-queue'. $min .'.js', __FILE__ ), array( 'jquery' ), WPSL_VERSION_NUM, true );
382
- wp_enqueue_script( 'wpsl-retina', plugins_url( '/js/retina'. $min .'.js', __FILE__ ), array( 'jquery' ), WPSL_VERSION_NUM, true );
383
-
384
- wp_localize_script( 'wpsl-admin-js', 'wpslL10n', $this->admin_js_l10n() );
385
- wp_localize_script( 'wpsl-admin-js', 'wpslSettings', $this->js_settings() );
386
- }
387
- }
388
-
389
- /**
390
- * Check if we need to show the wpsl pointer.
391
- *
392
- * @since 2.0.0
393
- * @return void
394
- */
395
- public function maybe_show_pointer() {
396
-
397
- $disable_pointer = apply_filters( 'wpsl_disable_welcome_pointer', false );
398
-
399
- if ( $disable_pointer ) {
400
- return;
401
- }
402
-
403
- $dismissed_pointers = explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) );
404
-
405
- // If the user hasn't dismissed the wpsl pointer, enqueue the script and style, and call the action hook.
406
- if ( !in_array( 'wpsl_signup_pointer', $dismissed_pointers ) ) {
407
- wp_enqueue_style( 'wp-pointer' );
408
- wp_enqueue_script( 'wp-pointer' );
409
-
410
- add_action( 'admin_print_footer_scripts', array( $this, 'welcome_pointer_script' ) );
411
- }
412
- }
413
-
414
- /**
415
- * Add the script for the welcome pointer.
416
- *
417
- * @since 2.0.0
418
- * @return void
419
- */
420
- public function welcome_pointer_script() {
421
-
422
- $pointer_content = '<h3>' . __( 'Welcome to WP Store Locator', 'wpsl' ) . '</h3>';
423
- $pointer_content .= '<p>' . __( 'Sign up for the latest plugin updates and announcements.', 'wpsl' ) . '</p>';
424
- $pointer_content .= '<div id="mc_embed_signup" class="wpsl-mc-wrap" style="padding:0 15px; margin-bottom:13px;"><form action="//wpstorelocator.us10.list-manage.com/subscribe/post?u=34e4c75c3dc990d14002e19f6&amp;id=4be03427d7" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank" novalidate><div id="mc_embed_signup_scroll"><input type="email" value="" name="EMAIL" class="email" id="mce-EMAIL" placeholder="email address" required style="margin-right:5px;width:230px;"><input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button"><div style="position: absolute; left: -5000px;"><input type="text" name="b_34e4c75c3dc990d14002e19f6_4be03427d7" tabindex="-1" value=""></div></div></form></div>';
425
- ?>
426
-
427
- <script type="text/javascript">
428
- //<![CDATA[
429
- jQuery( document ).ready( function( $ ) {
430
- $( '#menu-posts-wpsl_stores' ).pointer({
431
- content: '<?php echo $pointer_content; ?>',
432
- position: {
433
- edge: 'left',
434
- align: 'center'
435
- },
436
- pointerWidth: 350,
437
- close: function () {
438
- $.post( ajaxurl, {
439
- pointer: 'wpsl_signup_pointer',
440
- action: 'dismiss-wp-pointer'
441
- });
442
- }
443
- }).pointer( 'open' );
444
-
445
- // If a user clicked the "subscribe" button trigger the close button for the pointer.
446
- $( ".wpsl-mc-wrap #mc-embedded-subscribe" ).on( "click", function() {
447
- $( ".wp-pointer .close" ).trigger( "click" );
448
- });
449
- });
450
- //]]>
451
- </script>
452
-
453
- <?php
454
- }
455
-
456
- /**
457
- * Add link to the plugin action row.
458
- *
459
- * @since 2.0.0
460
- * @param array $links The existing action links
461
- * @param string $file The file path of the current plugin
462
- * @return array $links The modified links
463
- */
464
- public function add_action_links( $links, $file ) {
465
-
466
- if ( strpos( $file, 'wp-store-locator.php' ) !== false ) {
467
- $settings_link = '<a href="' . admin_url( 'edit.php?post_type=wpsl_stores&page=wpsl_settings' ) . '" title="View WP Store Locator Settings">' . __( 'Settings', 'wpsl' ) . '</a>';
468
- array_unshift( $links, $settings_link );
469
- }
470
-
471
- return $links;
472
- }
473
-
474
- /**
475
- * Add links to the plugin meta row.
476
- *
477
- * @since 2.1.1
478
- * @param array $links The existing meta links
479
- * @param string $file The file path of the current plugin
480
- * @return array $links The modified meta links
481
- */
482
- public function add_plugin_meta_row( $links, $file ) {
483
-
484
- if ( strpos( $file, 'wp-store-locator.php' ) !== false ) {
485
- $new_links = array(
486
- '<a href="https://wpstorelocator.co/documentation/" title="View Documentation">'. __( 'Documentation', 'wpsl' ).'</a>',
487
- '<a href="https://wpstorelocator.co/add-ons/" title="View Add-Ons">'. __( 'Add-Ons', 'wpsl' ).'</a>'
488
- );
489
-
490
- $links = array_merge( $links, $new_links );
491
- }
492
-
493
- return $links;
494
- }
495
-
496
- /**
497
- * Change the footer text on the settings page.
498
- *
499
- * @since 2.0.0
500
- * @param string $text The current footer text
501
- * @return string $text Either the original or modified footer text
502
- */
503
- public function admin_footer_text( $text ) {
504
-
505
- $current_screen = get_current_screen();
506
-
507
- // Only modify the footer text if we are on the settings page of the wp store locator.
508
- if ( isset( $current_screen->id ) && $current_screen->id == 'wpsl_stores_page_wpsl_settings' ) {
509
- $text = sprintf( __( 'If you like this plugin please leave us a %s5 star%s rating.', 'wpsl' ), '<a href="https://wordpress.org/support/view/plugin-reviews/wp-store-locator?filter=5#postform" target="_blank"><strong>', '</strong></a>' );
510
- }
511
-
512
- return $text;
513
- }
514
- }
515
-
516
- $GLOBALS['wpsl_admin'] = new WPSL_Admin();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
  }
1
+ <?php
2
+ /**
3
+ * Admin class
4
+ *
5
+ * @author Tijmen Smit
6
+ * @since 1.0.0
7
+ */
8
+
9
+ if ( !defined( 'ABSPATH' ) ) exit;
10
+
11
+ if ( !class_exists( 'WPSL_Admin' ) ) {
12
+
13
+ /**
14
+ * Handle the backend of the store locator
15
+ *
16
+ * @since 1.0.0
17
+ */
18
+ class WPSL_Admin {
19
+
20
+ /**
21
+ * @since 2.0.0
22
+ * @var WPSL_Metaboxes
23
+ */
24
+ public $metaboxes;
25
+
26
+ /**
27
+ * @since 2.0.0
28
+ * @var WPSL_Geocode
29
+ */
30
+ public $geocode;
31
+
32
+ /**
33
+ * @since 2.0.0
34
+ * @var WPSL_Notices
35
+ */
36
+ public $notices;
37
+
38
+ /**
39
+ * @since 2.0.0
40
+ * @var WPSL_Settings
41
+ */
42
+ public $settings_page;
43
+
44
+ /**
45
+ * Class constructor
46
+ */
47
+ function __construct() {
48
+
49
+ $this->includes();
50
+
51
+ add_action( 'init', array( $this, 'init' ) );
52
+ add_action( 'admin_menu', array( $this, 'create_admin_menu' ) );
53
+ add_action( 'admin_init', array( $this, 'setting_warnings' ) );
54
+ add_action( 'delete_post', array( $this, 'maybe_delete_autoload_transient' ) );
55
+ add_action( 'wp_trash_post', array( $this, 'maybe_delete_autoload_transient' ) );
56
+ add_action( 'untrash_post', array( $this, 'maybe_delete_autoload_transient' ) );
57
+ add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
58
+ add_filter( 'plugin_row_meta', array( $this, 'add_plugin_meta_row' ), 10, 2 );
59
+ add_filter( 'plugin_action_links_' . WPSL_BASENAME, array( $this, 'add_action_links' ), 10, 2 );
60
+ add_filter( 'admin_footer_text', array( $this, 'admin_footer_text' ), 1 );
61
+ add_action( 'wp_loaded', array( $this, 'disable_setting_notices' ) );
62
+
63
+ add_action( 'wp_ajax_validate_server_key', array( $this->settings_page, 'ajax_validate_server_key' ) );
64
+ add_action( 'wp_ajax_nopriv_validate_server_key', array( $this->settings_page, 'ajax_validate_server_key' ) );
65
+ }
66
+
67
+ /**
68
+ * Include the required files.
69
+ *
70
+ * @since 2.0.0
71
+ * @return void
72
+ */
73
+ public function includes() {
74
+ require_once( WPSL_PLUGIN_DIR . 'admin/class-shortcode-generator.php' );
75
+ require_once( WPSL_PLUGIN_DIR . 'admin/class-notices.php' );
76
+ require_once( WPSL_PLUGIN_DIR . 'admin/class-license-manager.php' );
77
+ require_once( WPSL_PLUGIN_DIR . 'admin/class-metaboxes.php' );
78
+ require_once( WPSL_PLUGIN_DIR . 'admin/class-geocode.php' );
79
+ require_once( WPSL_PLUGIN_DIR . 'admin/class-settings.php' );
80
+ require_once( WPSL_PLUGIN_DIR . 'admin/upgrade.php' );
81
+ require_once( WPSL_PLUGIN_DIR . 'admin/data-export.php' );
82
+ }
83
+
84
+ /**
85
+ * Init the classes.
86
+ *
87
+ * @since 2.0.0
88
+ * @return void
89
+ */
90
+ public function init() {
91
+ $this->notices = new WPSL_Notices();
92
+ $this->metaboxes = new WPSL_Metaboxes();
93
+ $this->geocode = new WPSL_Geocode();
94
+ $this->settings_page = new WPSL_Settings();
95
+ }
96
+
97
+ /**
98
+ * Check if we need to show warnings after
99
+ * the user installed the plugin.
100
+ *
101
+ * @since 1.0.0
102
+ * @todo move to class-notices?
103
+ * @return void
104
+ */
105
+ public function setting_warnings() {
106
+
107
+ global $current_user, $wpsl_settings;
108
+
109
+ $this->setting_warning = array();
110
+
111
+ // The fields settings field to check for data.
112
+ $warnings = array(
113
+ 'start_latlng' => 'location',
114
+ 'api_browser_key' => 'key'
115
+ );
116
+
117
+ if ( ( current_user_can( 'install_plugins' ) ) && is_admin() ) {
118
+ foreach ( $warnings as $setting_name => $warning ) {
119
+ if ( empty( $wpsl_settings[$setting_name] ) && !get_user_meta( $current_user->ID, 'wpsl_disable_' . $warning . '_warning' ) ) {
120
+ if ( $warning == 'key' ) {
121
+ $this->setting_warning[$warning] = sprintf( __( "You need to create %sAPI keys%s for Google Maps before you can use the store locator! %sDismiss%s", "wpsl" ), '<a href="https://wpstorelocator.co/document/create-google-api-keys/">', "</a>", "<a href='" . esc_url( wp_nonce_url( add_query_arg( 'wpsl-notice', 'key' ), 'wpsl_notices_nonce', '_wpsl_notice_nonce' ) ) . "'>", "</a>" );
122
+ } else {
123
+ $this->setting_warning[$warning] = sprintf( __( "Before adding the [wpsl] shortcode to a page, please don't forget to define a start point on the %ssettings%s page. %sDismiss%s", "wpsl" ), "<a href='" . admin_url( 'edit.php?post_type=wpsl_stores&page=wpsl_settings' ) . "'>", "</a>", "<a href='" . esc_url( wp_nonce_url( add_query_arg( 'wpsl-notice', 'location' ), 'wpsl_notices_nonce', '_wpsl_notice_nonce' ) ) . "'>", "</a>" );
124
+ }
125
+ }
126
+ }
127
+
128
+ if ( $this->setting_warning ) {
129
+ add_action( 'admin_notices', array( $this, 'show_warning' ) );
130
+ }
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Show the admin warnings
136
+ *
137
+ * @since 1.2.0
138
+ * @return void
139
+ */
140
+ public function show_warning() {
141
+ foreach ( $this->setting_warning as $k => $warning ) {
142
+ echo "<div id='message' class='error'><p>" . $warning . "</p></div>";
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Disable notices about the plugin settings.
148
+ *
149
+ * @todo move to class-notices?
150
+ * @since 2.2.3
151
+ * @return void
152
+ */
153
+ public function disable_setting_notices() {
154
+
155
+ global $current_user;
156
+
157
+ if ( isset( $_GET['wpsl-notice'] ) && isset( $_GET['_wpsl_notice_nonce'] ) ) {
158
+
159
+ if ( !wp_verify_nonce( $_GET['_wpsl_notice_nonce'], 'wpsl_notices_nonce' ) ) {
160
+ wp_die( __( 'Security check failed. Please reload the page and try again.', 'wpsl' ) );
161
+ }
162
+
163
+ $notice = sanitize_text_field( $_GET['wpsl-notice'] );
164
+
165
+ add_user_meta( $current_user->ID, 'wpsl_disable_' . $notice . '_warning', 'true', true );
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Add the admin menu pages.
171
+ *
172
+ * @since 1.0.0
173
+ * @return void
174
+ */
175
+ public function create_admin_menu() {
176
+
177
+ $sub_menus = apply_filters( 'wpsl_sub_menu_items', array(
178
+ array(
179
+ 'page_title' => __( 'Settings', 'wpsl' ),
180
+ 'menu_title' => __( 'Settings', 'wpsl' ),
181
+ 'caps' => 'manage_wpsl_settings',
182
+ 'menu_slug' => 'wpsl_settings',
183
+ 'function' => array( $this, 'load_template' )
184
+ ),
185
+ array(
186
+ 'page_title' => __( 'Add-Ons', 'wpsl' ),
187
+ 'menu_title' => __( 'Add-Ons', 'wpsl' ),
188
+ 'caps' => 'manage_wpsl_settings',
189
+ 'menu_slug' => 'wpsl_add_ons',
190
+ 'function' => array( $this, 'load_template' )
191
+ )
192
+ )
193
+ );
194
+
195
+ if ( count( $sub_menus ) ) {
196
+ foreach ( $sub_menus as $sub_menu ) {
197
+ add_submenu_page( 'edit.php?post_type=wpsl_stores', $sub_menu['page_title'], $sub_menu['menu_title'], $sub_menu['caps'], $sub_menu['menu_slug'], $sub_menu['function'] );
198
+ }
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Load the correct page template.
204
+ *
205
+ * @since 2.1.0
206
+ * @return void
207
+ */
208
+ public function load_template() {
209
+
210
+ switch ( $_GET['page'] ) {
211
+ case 'wpsl_settings':
212
+ require 'templates/map-settings.php';
213
+ break;
214
+ case 'wpsl_add_ons':
215
+ require 'templates/add-ons.php';
216
+ break;
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Check if we need to delete the autoload transient.
222
+ *
223
+ * This is called when a post it saved, deleted, trashed or untrashed.
224
+ *
225
+ * @since 2.0.0
226
+ * @return void
227
+ */
228
+ public function maybe_delete_autoload_transient( $post_id ) {
229
+
230
+ global $wpsl_settings;
231
+
232
+ if ( isset( $wpsl_settings['autoload'] ) && $wpsl_settings['autoload'] && get_post_type( $post_id ) == 'wpsl_stores' ) {
233
+ $this->delete_autoload_transient();
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Delete the transients that are used on the front-end
239
+ * if the autoload option is enabled.
240
+ *
241
+ * The transient names used by the store locator are partly dynamic.
242
+ * They always start with wpsl_autoload_, followed by the number of
243
+ * stores to load and ends with the language code.
244
+ *
245
+ * So you get wpsl_autoload_20_de if the language is set to German
246
+ * and 20 stores are set to show on page load.
247
+ *
248
+ * The language code has to be included in case a multilingual plugin is used.
249
+ * Otherwise it can happen the user switches to Spanish,
250
+ * but ends up seeing the store data in the wrong language.
251
+ *
252
+ * @since 2.0.0
253
+ * @return void
254
+ */
255
+ public function delete_autoload_transient() {
256
+
257
+ global $wpdb;
258
+
259
+ $option_names = $wpdb->get_results( "SELECT option_name AS transient_name FROM " . $wpdb->options . " WHERE option_name LIKE ('\_transient\_wpsl\_autoload\_%')" );
260
+
261
+ if ( $option_names ) {
262
+ foreach ( $option_names as $option_name ) {
263
+ $transient_name = str_replace( "_transient_", "", $option_name->transient_name );
264
+
265
+ delete_transient( $transient_name );
266
+ }
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Check if we can use a font for the plugin icon.
272
+ *
273
+ * This is supported by WP 3.8 or higher
274
+ *
275
+ * @since 1.0.0
276
+ * @return void
277
+ */
278
+ private function check_icon_font_usage() {
279
+
280
+ global $wp_version;
281
+
282
+ if ( ( version_compare( $wp_version, '3.8', '>=' ) == TRUE ) ) {
283
+ $min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
284
+
285
+ wp_enqueue_style( 'wpsl-admin-38', plugins_url( '/css/style-3.8'. $min .'.css', __FILE__ ), false );
286
+ }
287
+ }
288
+
289
+ /**
290
+ * The text messages used in wpsl-admin.js.
291
+ *
292
+ * @since 1.2.20
293
+ * @return array $admin_js_l10n The texts used in the wpsl-admin.js
294
+ */
295
+ public function admin_js_l10n() {
296
+
297
+ global $wpsl_settings;
298
+
299
+ $admin_js_l10n = array(
300
+ 'noAddress' => __( 'Cannot determine the address at this location.', 'wpsl' ),
301
+ 'geocodeFail' => __( 'Geocode was not successful for the following reason', 'wpsl' ),
302
+ 'securityFail' => __( 'Security check failed, reload the page and try again.', 'wpsl' ),
303
+ 'requiredFields' => __( 'Please fill in all the required store details.', 'wpsl' ),
304
+ 'missingGeoData' => __( 'The map preview requires all the location details.', 'wpsl' ),
305
+ 'closedDate' => __( 'Closed', 'wpsl' ),
306
+ 'styleError' => __( 'The code for the map style is invalid.', 'wpsl' ),
307
+ 'dismissNotice' => __( 'Dismiss this notice.', 'wpsl' ),
308
+ 'browserKeyError' => sprintf( __( 'There\'s a problem with the provided %sbrowser key%s. %s You will have to open the %sbrowser console%s ( %sctrl%s %sshift%s %sk%s in Firefox, or %sctrl%s %sshift%s %sj%s in Chrome ) to see the error details returned by the Google Maps API. %s The error itself includes a link explaining the problem in more detail. %s Common API errors are also covered in the %stroubleshooting section%s.', 'wpsl' ), '<a target="_blank" href="https://wpstorelocator.co/document/create-google-api-keys/#browser-key">','</a>', '<br><br>', '<a target="_blank" href="https://codex.wordpress.org/Using_Your_Browser_to_Diagnose_JavaScript_Errors#Step_3:_Diagnosis">', '</a>', '<kbd>', '</kbd>', '<kbd>', '</kbd>','<kbd>', '</kbd>', '<kbd>', '</kbd>', '<kbd>', '</kbd>','<kbd>', '</kbd>', '<br><br>', '<br><br>', '<a target="_blank" href="https://wpstorelocator.co/document/create-google-api-keys/#api-errors">', '</a>' ),
309
+ 'browserKeySuccess' => __( 'No problems found with the browser key.', 'wpsl' ),
310
+ 'serverKey' => __( 'Server key', 'wpsl' ),
311
+ 'serverKeyMissing' => sprintf( __( 'No %sserver key%s found!' ), '<a target="_blank" href="https://wpstorelocator.co/document/create-google-api-keys/#server-key">', '</a>' ),
312
+ 'browserKey' => __( 'Browser key', 'wpsl' ),
313
+ 'browserKeyMissing' => sprintf( __( 'No %sbrowser key%s found!' ), '<a target="_blank" href="https://wpstorelocator.co/document/create-google-api-keys/#browser-key">', '</a>' ),
314
+ 'restrictedZipCode' => __( 'and will only work for zip codes.', 'wpsl' ),
315
+ 'noRestriction' => sprintf( __( 'because no %smap region%s is selected the geocode API will search for matching results around the world. This may result in unexpected results.'), '<a class="wpsl-region-href" href="#wpsl-tabs">', '</a>' ),
316
+ 'loadingError' => sprintf( __( 'Google Maps didn\'t load correctly. Make sure you have an active %sbilling%s %saccount%s for Google Maps. %s If the "For development purposes only" text keeps showing after creating a billing account, then you will have to contact %sGoogle Billing Support%s.', 'wpsl' ), '<a target="_blank" href="https://wpstorelocator.co/document/create-google-api-keys/#billing">', '</a>', '<a href="http://g.co/dev/maps-no-account">', '</a>', '<br><br>', '<a target="_blank" href="https://cloud.google.com/support/billing/">', '</a>' ),
317
+ 'loadingFailed' => sprintf( __( 'Google Maps failed to load correctly. This is likely due to a problem with the provided %sbrowser key%s. %s You will have to open the %sbrowser console%s ( %sctrl%s %sshift%s %sk%s in Firefox, or %sctrl%s %sshift%s %sj%s in Chrome ) to see the error details returned by the Google Maps API. %s The error itself includes a link explaining the problem in more detail. %s Common API errors are also covered in the %stroubleshooting section%s.', 'wpsl' ), '<a target="_blank" href="https://wpstorelocator.co/document/create-google-api-keys/#browser-key">','</a>', '<br><br>', '<a target="_blank" href="https://codex.wordpress.org/Using_Your_Browser_to_Diagnose_JavaScript_Errors#Step_3:_Diagnosis">', '</a>', '<kbd>', '</kbd>', '<kbd>', '</kbd>','<kbd>', '</kbd>', '<kbd>', '</kbd>', '<kbd>', '</kbd>','<kbd>', '</kbd>', '<br><br>', '<br><br>', '<a target="_blank" href="https://wpstorelocator.co/document/create-google-api-keys/#api-errors">', '</a>' ),
318
+ 'close' => __( 'Close', 'wpsl' ),
319
+ );
320
+
321
+ /**
322
+ * This text is only shown when the user checks the API response
323
+ * for a provided address ( tools section ), and a map region is selected.
324
+ */
325
+ if ( $wpsl_settings['api_region'] ) {
326
+ if ( $wpsl_settings['api_geocode_component'] ) {
327
+ $restriction_type = 'restricted';
328
+ } else {
329
+ $restriction_type = 'biased';
330
+ }
331
+
332
+ $admin_js_l10n['resultsWarning'] = sprintf( __( 'with the current settings the results are %s to' ), $restriction_type );
333
+ }
334
+
335
+ return $admin_js_l10n;
336
+ }
337
+
338
+ /**
339
+ * Plugin settings that are used in the wpsl-admin.js.
340
+ *
341
+ * @since 2.0.0
342
+ * @return array $settings_js The settings used in the wpsl-admin.js
343
+ */
344
+ public function js_settings() {
345
+
346
+ global $wpsl_settings;
347
+
348
+ $js_settings = array(
349
+ 'hourFormat' => $wpsl_settings['editor_hour_format'],
350
+ 'defaultLatLng' => $this->get_default_lat_lng(),
351
+ 'defaultZoom' => 6,
352
+ 'mapType' => $wpsl_settings['editor_map_type'],
353
+ 'requiredFields' => array( 'address', 'city', 'country' ),
354
+ 'ajaxurl' => wpsl_get_ajax_url(),
355
+ 'url' => WPSL_URL,
356
+ 'storeMarker' => $wpsl_settings['store_marker']
357
+ );
358
+
359
+ // Make sure that the Geocode API testing tool correctly restricts the results if required.
360
+ if ( $wpsl_settings['api_region'] && $wpsl_settings['api_geocode_component'] ) {
361
+ $geocode_components = array();
362
+ $geocode_components['country'] = strtoupper( $wpsl_settings['api_region'] );
363
+
364
+ if ( $wpsl_settings['force_postalcode'] ) {
365
+ $geocode_components['postalCode'] = '';
366
+ }
367
+
368
+ $js_settings['geocodeComponents'] = $geocode_components;
369
+ }
370
+
371
+ return apply_filters( 'wpsl_admin_js_settings', $js_settings );
372
+ }
373
+
374
+ /**
375
+ * Get the coordinates that are used to
376
+ * show the map on the settings page.
377
+ *
378
+ * @since 2.2.5
379
+ * @return string $startLatLng The start coordinates
380
+ */
381
+ public function get_default_lat_lng() {
382
+
383
+ global $wpsl_settings;
384
+
385
+ $startLatLng = $wpsl_settings['start_latlng'];
386
+
387
+ // If no start coordinates exists, then set the default to Holland.
388
+ if ( !$startLatLng ) {
389
+ $startLatLng = '52.378153,4.899363';
390
+ }
391
+
392
+ return $startLatLng;
393
+ }
394
+
395
+ /**
396
+ * Add the required admin script.
397
+ *
398
+ * @since 1.0.0
399
+ * @return void
400
+ */
401
+ public function admin_scripts() {
402
+
403
+ $min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
404
+
405
+ // Always load the main js admin file to make sure the "dismiss" link in the location notice works.
406
+ wp_enqueue_script( 'wpsl-admin-js', plugins_url( '/js/wpsl-admin'. $min .'.js', __FILE__ ), array( 'jquery' ), WPSL_VERSION_NUM, true );
407
+
408
+ $this->maybe_show_pointer();
409
+ $this->check_icon_font_usage();
410
+
411
+ // Only enqueue the rest of the css/js files if we are on a page that belongs to the store locator.
412
+ if ( ( get_post_type() == 'wpsl_stores' ) || ( isset( $_GET['post_type'] ) && ( $_GET['post_type'] == 'wpsl_stores' ) ) ) {
413
+
414
+ // Make sure no other Google Map scripts can interfere with the one from the store locator.
415
+ wpsl_deregister_other_gmaps();
416
+
417
+ wp_enqueue_style( 'jquery-style', '//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/themes/smoothness/jquery-ui.css' );
418
+ wp_enqueue_style( 'wpsl-admin-css', plugins_url( '/css/style'. $min .'.css', __FILE__ ), false );
419
+
420
+ wp_enqueue_media();
421
+ wp_enqueue_script( 'jquery-ui-dialog' );
422
+ wp_enqueue_script( 'jquery-ui-tabs' );
423
+ wp_enqueue_script( 'wpsl-gmap', ( '//maps.google.com/maps/api/js' . wpsl_get_gmap_api_params( 'browser_key' ) ), false, WPSL_VERSION_NUM, true );
424
+
425
+ wp_enqueue_script( 'wpsl-queue', plugins_url( '/js/ajax-queue'. $min .'.js', __FILE__ ), array( 'jquery' ), WPSL_VERSION_NUM, true );
426
+ wp_enqueue_script( 'wpsl-retina', plugins_url( '/js/retina'. $min .'.js', __FILE__ ), array( 'jquery' ), WPSL_VERSION_NUM, true );
427
+
428
+ wp_localize_script( 'wpsl-admin-js', 'wpslL10n', $this->admin_js_l10n() );
429
+ wp_localize_script( 'wpsl-admin-js', 'wpslSettings', $this->js_settings() );
430
+ }
431
+ }
432
+
433
+ /**
434
+ * Check if we need to show the wpsl pointer.
435
+ *
436
+ * @since 2.0.0
437
+ * @return void
438
+ */
439
+ public function maybe_show_pointer() {
440
+
441
+ $disable_pointer = apply_filters( 'wpsl_disable_welcome_pointer', false );
442
+
443
+ if ( $disable_pointer ) {
444
+ return;
445
+ }
446
+
447
+ $dismissed_pointers = explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) );
448
+
449
+ // If the user hasn't dismissed the wpsl pointer, enqueue the script and style, and call the action hook.
450
+ if ( !in_array( 'wpsl_signup_pointer', $dismissed_pointers ) ) {
451
+ wp_enqueue_style( 'wp-pointer' );
452
+ wp_enqueue_script( 'wp-pointer' );
453
+
454
+ add_action( 'admin_print_footer_scripts', array( $this, 'welcome_pointer_script' ) );
455
+ }
456
+ }
457
+
458
+ /**
459
+ * Add the script for the welcome pointer.
460
+ *
461
+ * @since 2.0.0
462
+ * @return void
463
+ */
464
+ public function welcome_pointer_script() {
465
+
466
+ $pointer_content = '<h3>' . __( 'Welcome to WP Store Locator', 'wpsl' ) . '</h3>';
467
+ $pointer_content .= '<p>' . __( 'Sign up for the latest plugin updates and announcements.', 'wpsl' ) . '</p>';
468
+ $pointer_content .= '<div id="mc_embed_signup" class="wpsl-mc-wrap" style="padding:0 15px; margin-bottom:13px;"><form action="//wpstorelocator.us10.list-manage.com/subscribe/post?u=34e4c75c3dc990d14002e19f6&amp;id=4be03427d7" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank" novalidate><div id="mc_embed_signup_scroll"><input type="email" value="" name="EMAIL" class="email" id="mce-EMAIL" placeholder="email address" required style="margin-right:5px;width:230px;"><input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button"><div style="position: absolute; left: -5000px;"><input type="text" name="b_34e4c75c3dc990d14002e19f6_4be03427d7" tabindex="-1" value=""></div></div></form></div>';
469
+ ?>
470
+
471
+ <script type="text/javascript">
472
+ //<![CDATA[
473
+ jQuery( document ).ready( function( $ ) {
474
+ $( '#menu-posts-wpsl_stores' ).pointer({
475
+ content: '<?php echo $pointer_content; ?>',
476
+ position: {
477
+ edge: 'left',
478
+ align: 'center'
479
+ },
480
+ pointerWidth: 350,
481
+ close: function () {
482
+ $.post( ajaxurl, {
483
+ pointer: 'wpsl_signup_pointer',
484
+ action: 'dismiss-wp-pointer'
485
+ });
486
+ }
487
+ }).pointer( 'open' );
488
+
489
+ // If a user clicked the "subscribe" button trigger the close button for the pointer.
490
+ $( ".wpsl-mc-wrap #mc-embedded-subscribe" ).on( "click", function() {
491
+ $( ".wp-pointer .close" ).trigger( "click" );
492
+ });
493
+ });
494
+ //]]>
495
+ </script>
496
+
497
+ <?php
498
+ }
499
+
500
+ /**
501
+ * Add link to the plugin action row.
502
+ *
503
+ * @since 2.0.0
504
+ * @param array $links The existing action links
505
+ * @param string $file The file path of the current plugin
506
+ * @return array $links The modified links
507
+ */
508
+ public function add_action_links( $links, $file ) {
509
+
510
+ if ( strpos( $file, 'wp-store-locator.php' ) !== false ) {
511
+ $settings_link = '<a href="' . admin_url( 'edit.php?post_type=wpsl_stores&page=wpsl_settings' ) . '" title="View WP Store Locator Settings">' . __( 'Settings', 'wpsl' ) . '</a>';
512
+ array_unshift( $links, $settings_link );
513
+ }
514
+
515
+ return $links;
516
+ }
517
+
518
+ /**
519
+ * Add links to the plugin meta row.
520
+ *
521
+ * @since 2.1.1
522
+ * @param array $links The existing meta links
523
+ * @param string $file The file path of the current plugin
524
+ * @return array $links The modified meta links
525
+ */
526
+ public function add_plugin_meta_row( $links, $file ) {
527
+
528
+ if ( strpos( $file, 'wp-store-locator.php' ) !== false ) {
529
+ $new_links = array(
530
+ '<a href="https://wpstorelocator.co/documentation/" title="View Documentation">'. __( 'Documentation', 'wpsl' ).'</a>',
531
+ '<a href="https://wpstorelocator.co/add-ons/" title="View Add-Ons">'. __( 'Add-Ons', 'wpsl' ).'</a>'
532
+ );
533
+
534
+ $links = array_merge( $links, $new_links );
535
+ }
536
+
537
+ return $links;
538
+ }
539
+
540
+ /**
541
+ * Change the footer text on the settings page.
542
+ *
543
+ * @since 2.0.0
544
+ * @param string $text The current footer text
545
+ * @return string $text Either the original or modified footer text
546
+ */
547
+ public function admin_footer_text( $text ) {
548
+
549
+ $current_screen = get_current_screen();
550
+
551
+ // Only modify the footer text if we are on the settings page of the wp store locator.
552
+ if ( isset( $current_screen->id ) && $current_screen->id == 'wpsl_stores_page_wpsl_settings' ) {
553
+ $text = sprintf( __( 'If you like this plugin please leave us a %s5 star%s rating.', 'wpsl' ), '<a href="https://wordpress.org/support/view/plugin-reviews/wp-store-locator?filter=5#postform" target="_blank"><strong>', '</strong></a>' );
554
+ }
555
+
556
+ return $text;
557
+ }
558
+ }
559
+
560
+ $GLOBALS['wpsl_admin'] = new WPSL_Admin();
561
  }
admin/class-geocode.php CHANGED
@@ -114,9 +114,9 @@ if ( !class_exists( 'WPSL_Geocode' ) ) {
114
 
115
  // If the problem is IP based, then show a different error msg.
116
  if ( strpos( $geocode_response['error_message'], 'IP' ) !== false ) {
117
- $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, $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>' );
118
  } else {
119
- $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, $geocode_response['error_message'], $breaks, '<a href="https://wpstorelocator.co/document/create-google-api-keys/#troubleshooting">', '</a>', '<a href="https://wpstorelocator.co/support/">', '</a>' );
120
  }
121
  } else {
122
  $error_msg = '';
@@ -287,5 +287,29 @@ if ( !class_exists( 'WPSL_Geocode' ) ) {
287
 
288
  return $latlng;
289
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  }
291
  }
114
 
115
  // If the problem is IP based, then show a different error msg.
116
  if ( strpos( $geocode_response['error_message'], 'IP' ) !== false ) {
117
+ $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>' );
118
  } else {
119
+ $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>' );
120
  }
121
  } else {
122
  $error_msg = '';
287
 
288
  return $latlng;
289
  }
290
+
291
+ /**
292
+ * Error messages returned by the Google Maps API
293
+ * don't always contain clickable links.
294
+ *
295
+ * They now just look like this http://g.co/dev/maps-no-account
296
+ * and are not clickable. To change this we wrap an href around it.
297
+ *
298
+ * @since 2.2.22
299
+ * @return void
300
+ */
301
+ public function clickable_error_links( $msg ) {
302
+
303
+ // Make sure the URLS aren't clickable yet. They aren't at the moment, but maybe Google changes this in the future.
304
+ if ( strpos( $msg,'href' ) === false ) {
305
+ preg_match_all( '#\bhttp(s?)?://[^,\s()<>]+(?:\([\w\d]+\)|([^,[:punct:]\s]|/))#', $msg, $match );
306
+
307
+ foreach ( $match[0] as $k => $url ) {
308
+ $msg = str_replace( $url, '<a href="' . esc_url( $url ) . '">' . esc_html( $url ) . '</a>', $msg );
309
+ }
310
+ }
311
+
312
+ return $msg;
313
+ }
314
  }
315
  }
admin/class-settings.php CHANGED
@@ -315,7 +315,8 @@ if ( !class_exists( 'WPSL_Settings' ) ) {
315
  $output['show_credits'] = isset( $_POST['wpsl_credits'] ) ? 1 : 0;
316
  $output['debug'] = isset( $_POST['wpsl_tools']['debug'] ) ? 1 : 0;
317
  $output['deregister_gmaps'] = isset( $_POST['wpsl_tools']['deregister_gmaps'] ) ? 1 : 0;
318
-
 
319
  // Check if we need to flush the permalinks.
320
  $this->set_flush_rewrite_option( $output );
321
 
@@ -336,7 +337,7 @@ if ( !class_exists( 'WPSL_Settings' ) ) {
336
  */
337
  public function ajax_validate_server_key() {
338
 
339
- if ( ( current_user_can( 'manage_wpsl_settings' ) ) && is_admin() ) {
340
  $server_key = sanitize_text_field( $_GET['server_key'] );
341
 
342
  if ( $server_key ) {
@@ -368,7 +369,8 @@ if ( !class_exists( 'WPSL_Settings' ) ) {
368
  // If the state is not OK, then there's a problem with the key.
369
  if ( $response['status'] !== 'OK' ) {
370
  $geocode_errors = $wpsl_admin->geocode->check_geocode_error_msg( $response, true );
371
- $error_msg = sprintf( __( 'There\'s a problem with the provided %sserver key%s. %s' ), '<a href="https://wpstorelocator.co/document/create-google-api-keys/#server-key">', '</a>', $geocode_errors );
 
372
 
373
  update_option( 'wpsl_valid_server_key', 0 );
374
 
@@ -387,6 +389,17 @@ if ( !class_exists( 'WPSL_Settings' ) ) {
387
  }
388
  } else {
389
  update_option( 'wpsl_valid_server_key', 1 );
 
 
 
 
 
 
 
 
 
 
 
390
  }
391
  }
392
  }
@@ -408,7 +421,7 @@ if ( !class_exists( 'WPSL_Settings' ) ) {
408
 
409
  foreach ( $fields as $k => $field ) {
410
  if ( $wpsl_settings[$field] != $new_settings[$field] ) {
411
- update_option( 'wpsl_flush_rewrite', 1 );
412
 
413
  break;
414
  }
@@ -846,7 +859,7 @@ if ( !class_exists( 'WPSL_Settings' ) ) {
846
  $selected = ( $wpsl_settings['api_'.$list] == $api_option_value ) ? 'selected="selected"' : '';
847
  }
848
 
849
- $option_list .= '<option value="' . esc_attr( $api_option_value ) . '" ' . $selected . '> ' . esc_html( $api_option_key ) . '</option>';
850
  $i++;
851
  }
852
 
315
  $output['show_credits'] = isset( $_POST['wpsl_credits'] ) ? 1 : 0;
316
  $output['debug'] = isset( $_POST['wpsl_tools']['debug'] ) ? 1 : 0;
317
  $output['deregister_gmaps'] = isset( $_POST['wpsl_tools']['deregister_gmaps'] ) ? 1 : 0;
318
+ $output['delay_loading'] = isset( $_POST['wpsl_tools']['delay_loading'] ) ? 1 : 0;
319
+
320
  // Check if we need to flush the permalinks.
321
  $this->set_flush_rewrite_option( $output );
322
 
337
  */
338
  public function ajax_validate_server_key() {
339
 
340
+ if ( ( current_user_can( 'manage_wpsl_settings' ) ) && is_admin() && defined( 'DOING_AJAX' ) && DOING_AJAX ) {
341
  $server_key = sanitize_text_field( $_GET['server_key'] );
342
 
343
  if ( $server_key ) {
369
  // If the state is not OK, then there's a problem with the key.
370
  if ( $response['status'] !== 'OK' ) {
371
  $geocode_errors = $wpsl_admin->geocode->check_geocode_error_msg( $response, true );
372
+
373
+ $error_msg = sprintf( __( 'There\'s a problem with the provided %sserver key%s. %s' ), '<a href="https://wpstorelocator.co/document/create-google-api-keys/#server-key">', '</a>', $geocode_errors );
374
 
375
  update_option( 'wpsl_valid_server_key', 0 );
376
 
389
  }
390
  } else {
391
  update_option( 'wpsl_valid_server_key', 1 );
392
+
393
+ if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
394
+ $key_status = array(
395
+ 'valid' => 1,
396
+ 'msg' => __( 'No problems found with the server key.', 'wpsl' )
397
+ );
398
+
399
+ wp_send_json( $key_status );
400
+
401
+ exit();
402
+ }
403
  }
404
  }
405
  }
421
 
422
  foreach ( $fields as $k => $field ) {
423
  if ( $wpsl_settings[$field] != $new_settings[$field] ) {
424
+ update_option( 'wpsl_flush_rewrite', 1 );
425
 
426
  break;
427
  }
859
  $selected = ( $wpsl_settings['api_'.$list] == $api_option_value ) ? 'selected="selected"' : '';
860
  }
861
 
862
+ $option_list .= '<option value="' . esc_attr( $api_option_value ) . '" ' . $selected . '>' . esc_html( $api_option_key ) . '</option>';
863
  $i++;
864
  }
865
 
admin/class-shortcode-generator.php CHANGED
@@ -1,366 +1,366 @@
1
- <?php
2
- /**
3
- * Shortcode Generator class
4
- *
5
- * @author Tijmen Smit
6
- * @since 2.2.10
7
- */
8
-
9
- if ( !defined( 'ABSPATH' ) ) exit;
10
-
11
- if ( !class_exists( 'WPSL_Shortcode_Generator' ) ) {
12
-
13
- /**
14
- * Handle the generation of the WPSL shortcode through the media button
15
- *
16
- * @since 2.2.10
17
- */
18
- class WPSL_Shortcode_Generator {
19
-
20
- /**
21
- * Constructor
22
- */
23
- public function __construct() {
24
- add_action( 'media_buttons', array( $this, 'add_wpsl_media_button' ) );
25
- add_action( 'admin_init', array( $this, 'show_thickbox_iframe_content' ) );
26
- }
27
-
28
- /**
29
- * Add the WPSL media button to the media button row
30
- *
31
- * @since 2.2.10
32
- * @return void
33
- */
34
- public function add_wpsl_media_button() {
35
-
36
- global $pagenow, $typenow;
37
-
38
- /* Make sure we're on a post/page or edit screen in the admin area */
39
- if ( in_array( $pagenow, array( 'post.php', 'page.php', 'post-new.php', 'post-edit.php' ) ) && $typenow != 'wpsl_stores' ) {
40
- $changelog_link = self_admin_url( '?wpsl_media_action=store_locator&KeepThis=true&TB_iframe=true&width=783&height=800' );
41
-
42
- echo '<a href="' . esc_url( $changelog_link ) . '" class="thickbox button wpsl-thickbox" name="' . __( 'WP Store Locator' ,'wpsl' ) . '">' . __( 'Insert Store Locator', 'wpsl' ) . '</a>';
43
- }
44
- }
45
-
46
- /**
47
- * Show the shortcode thickbox content
48
- *
49
- * @since 2.2.10
50
- * @return void
51
- */
52
- function show_thickbox_iframe_content() {
53
-
54
- global $wpsl_settings, $wpsl_admin;
55
-
56
- if ( empty( $_REQUEST['wpsl_media_action'] ) ) {
57
- return;
58
- }
59
-
60
- if ( !current_user_can( 'edit_pages' ) ) {
61
- wp_die( __( 'You do not have permission to perform this action', 'wpsl' ), __( 'Error', 'wpsl' ), array( 'response' => 403 ) );
62
- }
63
-
64
- $min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
65
-
66
- // Make sure the required JS / CSS files are loaded in the Thickbox iframe
67
- wp_print_scripts( 'jquery-ui-core' );
68
- wp_print_scripts( 'jquery-ui-tabs' );
69
- wp_print_scripts( 'media-upload' );
70
- ?>
71
- <script type="text/javascript" src="<?php echo plugins_url( '/js/wpsl-shortcode-generator' . $min . '.js?ver='. WPSL_VERSION_NUM .'', __FILE__ ); ?>"></script>
72
- <?php
73
- wp_print_styles('buttons' );
74
- wp_print_styles('forms' );
75
- ?>
76
-
77
- <link rel="stylesheet" type="text/css" href="<?php echo plugins_url( '/css/style' . $min . '.css?ver='. WPSL_VERSION_NUM .'', __FILE__ ); ?>" media="all" />
78
- <style>
79
- body {
80
- color: #444;
81
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
82
- font-size: 13px;
83
- margin: 0;
84
- }
85
-
86
- #wpsl-media-tabs .ui-tabs-nav {
87
- padding-left: 15px;
88
- background: #fff !important;
89
- border-bottom: 1px solid #dfdfdf;
90
- border-collapse: collapse;
91
- padding-top: .2em;
92
- }
93
-
94
- #wpsl-media-tabs .ui-tabs-nav::after {
95
- clear: both;
96
- content: "";
97
- display: table;
98
- border-collapse: collapse;
99
- }
100
-
101
- #wpsl-media-tabs .ui-tabs-nav li {
102
- list-style: none;
103
- float: left;
104
- position: relative;
105
- top: 0;
106
- margin: 1px .2em 0 0;
107
- padding: 0;
108
- white-space: nowrap;
109
- border-bottom-width: 0;
110
- }
111
-
112
- #wpsl-media-tabs .ui-tabs-anchor {
113
- float: left;
114
- padding: .5em 1em;
115
- text-decoration: none;
116
- font-size: 14.3px;
117
- }
118
-
119
- #wpsl-media-tabs .ui-tabs-active a {
120
- color: #212121;
121
- cursor: text;
122
- }
123
-
124
- #wpsl-media-tabs .ui-tabs .ui-tabs-anchor {
125
- float: left;
126
- padding: .5em 1em;
127
- text-decoration: none;
128
- }
129
-
130
- #wpsl-media-tabs.ui-widget-content {
131
- border: none;
132
- padding: 10px 0 0 0;
133
- }
134
-
135
- #wpsl-media-tabs .ui-tabs-anchor {
136
- outline: none;
137
- }
138
-
139
- #wpsl-shortcode-config tr > td {
140
- width: 25%;
141
- }
142
-
143
- #wpsl-markers-tab .wpsl-marker-list {
144
- display: block;
145
- overflow: hidden;
146
- padding: 0;
147
- list-style-type: none;
148
- }
149
-
150
- #wpsl-markers-tab .wpsl-marker-list li input {
151
- padding: 0;
152
- margin: 0;
153
- }
154
-
155
- #wpsl-shortcode-config .form-table,
156
- #wpsl-shortcode-config .form-table td,
157
- #wpsl-shortcode-config .form-table th,
158
- #wpsl-shortcode-config .form-table td p {
159
- font-size: 13px;
160
- }
161
-
162
- #wpsl-shortcode-config .ui-tabs .ui-tabs-nav {
163
- padding-left: 15px;
164
- border-radius: 0;
165
- margin: 0;
166
- }
167
-
168
- .wpsl-shortcode-markers {
169
- padding: 0 10px;
170
- margin-top: 27px;
171
- font-size: 13px;
172
- }
173
-
174
- #wpsl-insert-shortcode {
175
- margin-left: 19px;
176
- }
177
-
178
- #wpsl-shortcode-config .ui-state-default {
179
- border: 1px solid #d3d3d3;
180
- border-top-left-radius: 4px;
181
- border-top-right-radius: 4px;
182
- background: none;
183
- }
184
-
185
- #wpsl-shortcode-config .ui-state-default a {
186
- color: #909090;
187
- }
188
-
189
- #wpsl-shortcode-config .ui-state-default.ui-tabs-active a {
190
- color: #212121;
191
- }
192
-
193
- #wpsl-shortcode-config .ui-state-hover {
194
- border-bottom: none;
195
- }
196
-
197
- #wpsl-shortcode-config .ui-state-hover a {
198
- color: #72777c;
199
- }
200
-
201
- #wpsl-media-tabs .ui-state-active {
202
- border: 1px solid #aaa;
203
- border-bottom: 1px solid #fff;
204
- padding-bottom: 0;
205
- }
206
-
207
- #wpsl-shortcode-config li.ui-tabs-active.ui-state-hover,
208
- #wpsl-shortcode-config li.ui-tabs-active {
209
- border-bottom: 1px solid #fff;
210
- padding-bottom: 0;
211
- }
212
-
213
- #wpsl-media-tabs li.ui-tabs-active {
214
- margin-bottom: -1px;
215
- }
216
-
217
- #wpsl-general-tab,
218
- #wpsl-markers-tab {
219
- border: 0;
220
- padding: 1em 1.4em;
221
- background: none;
222
- }
223
-
224
- @media ( max-width: 782px ) {
225
- #wpsl-shortcode-config tr > td {
226
- width: 100%;
227
- }
228
- }
229
- </style>
230
- <div id="wpsl-shortcode-config" class="wp-core-ui">
231
- <div id="wpsl-media-tabs">
232
- <ul>
233
- <li><a href="#wpsl-general-tab"><?php _e( 'General Options', 'wpsl' ); ?></a></li>
234
- <li><a href="#wpsl-markers-tab"><?php _e('Markers', 'wpsl' ); ?></a></li>
235
- </ul>
236
- <div id="wpsl-general-tab">
237
- <table class="form-table wpsl-shortcode-config">
238
- <tbody>
239
- <tr>
240
- <td><label for="wpsl-store-template"><?php _e('Select the used template', 'wpsl' ); ?></label></td>
241
- <td><?php echo $wpsl_admin->settings_page->show_template_options(); ?></td>
242
- </tr>
243
- <tr>
244
- <td><label for="wpsl-start-location"><?php _e( 'Start point', 'wpsl' ); ?></label><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'If nothing it set, then the start point from the %ssettings%s page is used.', '' ), '<a href=' . admin_url( 'edit.php?post_type=wpsl_stores&page=wpsl_settings#wpsl-map-settings' ) . '>', '</a>' ); ?></span></span></p></td>
245
- <td><input type="text" placeholder="Optional" value="" id="wpsl-start-location"></td>
246
- </tr>
247
- <tr>
248
- <td>
249
- <label for="wpsl-auto-locate"><?php _e( 'Attempt to auto-locate the user', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'Most modern browsers %srequire%s a HTTPS connection before the Geolocation feature works.', 'wpsl_csv' ), '<a href="https://wpstorelocator.co/document/html-5-geolocation-not-working/">', '</a>' ); ?></span></span></label>
250
- </td>
251
- <td><input type="checkbox" value="" <?php checked( $wpsl_settings['auto_locate'], true ); ?> name="wpsl_map[auto_locate]" id="wpsl-auto-locate"></td>
252
- </tr>
253
- <?php
254
- $terms = get_terms( 'wpsl_store_category', 'hide_empty=1' );
255
-
256
- if ( $terms ) {
257
- ?>
258
- <tr>
259
- <td><label for="wpsl-cat-filter-types"><?php _e( 'Category filter type', 'wpsl' ); ?></label></p></td>
260
- <td>
261
- <select id="wpsl-cat-filter-types" autocomplete="off">
262
- <option value="" selected="selected"><?php _e( 'None', 'wpsl' ); ?></option>
263
- <option value="dropdown"><?php _e( 'Dropdown', 'wpsl' ); ?></option>
264
- <option value="checkboxes"><?php _e( 'Checkboxes', 'wpsl' ); ?></option>
265
- </select>
266
- </td>
267
- </tr>
268
- <tr class="wpsl-cat-restriction">
269
- <td style="vertical-align:top;"><label for="wpsl-cat-restriction"><?php _e('Automatically restrict the returned results to one or more categories?', 'wpsl' ); ?></label></td>
270
- <td>
271
- <?php
272
- $cat_restricton = '<select id="wpsl-cat-restriction" multiple="multiple" autocomplete="off">';
273
-
274
- foreach ( $terms as $term ) {
275
- $cat_restricton .= '<option value="' . esc_attr( $term->slug ) . '">' . esc_html( $term->name ) . '</option>';
276
- }
277
-
278
- $cat_restricton .= '</select>';
279
-
280
- echo $cat_restricton;
281
- ?>
282
- </td>
283
- </tr>
284
- <tr class="wpsl-cat-selection wpsl-hide">
285
- <td style="vertical-align:top;"><label for="wpsl-cat-selection"><?php _e('Set a selected category?', 'wpsl' ); ?></label></td>
286
- <td>
287
- <?php
288
- $cat_selection = '<select id="wpsl-cat-selection" autocomplete="off">';
289
-
290
- $cat_selection .= '<option value="" selected="selected">' . __( 'Select category', 'wpsl' ) . '</option>';
291
-
292
- foreach ( $terms as $term ) {
293
- $cat_selection .= '<option value="' . esc_attr( $term->slug ) . '">' . esc_html( $term->name ) . '</option>';
294
- }
295
-
296
- $cat_selection .= '</select>';
297
-
298
- echo $cat_selection;
299
- ?>
300
- </td>
301
- </tr>
302
- <?php
303
- }
304
- ?>
305
- <tr class="wpsl-checkbox-options wpsl-hide">
306
- <td><label for="wpsl-checkbox-columns"><?php _e('Checkbox columns', 'wpsl' ); ?></label></td>
307
- <td>
308
- <?php
309
- echo '<select id="wpsl-checkbox-columns">';
310
-
311
- $i = 1;
312
-
313
- while ( $i <= 4 ) {
314
- $selected = ( $i == 3 ) ? "selected='selected'" : ''; // 3 is the default
315
-
316
- echo '<option value="' . $i . '" ' . $selected . '>' . $i . '</option>';
317
- $i++;
318
- }
319
-
320
- echo '</select>';
321
- ?>
322
- </td>
323
- </tr>
324
- <tr class="wpsl-checkbox-selection wpsl-hide">
325
- <td><label for="wpsl-checkbox-columns"><?php _e('Set selected checkboxes', 'wpsl' ); ?></label></td>
326
- <td>
327
- <?php
328
- $checkbox_selection = '<select id="wpsl-checkbox-selection" multiple="multiple" autocomplete="off">';
329
-
330
- foreach ( $terms as $term ) {
331
- $checkbox_selection .= '<option value="' . esc_attr( $term->slug ) . '">' . esc_html( $term->name ) . '</option>';
332
- }
333
-
334
- $checkbox_selection .= '</select>';
335
-
336
- echo $checkbox_selection;
337
- ?>
338
- </td>
339
- </tr>
340
- <tr>
341
- <td><label for="wpsl-map-type"><?php _e( 'Map type', 'wpsl' ); ?>:</label></td>
342
- <td><?php echo $wpsl_admin->settings_page->create_dropdown( 'map_types' ); ?></td>
343
- </tr>
344
- </tbody>
345
- </table>
346
- </div>
347
- <div id="wpsl-markers-tab">
348
- <div class="wpsl-shortcode-markers">
349
- <?php echo $wpsl_admin->settings_page->show_marker_options(); ?>
350
- </div>
351
- </div>
352
- </div>
353
-
354
- <p class="submit">
355
- <input type="button" id="wpsl-insert-shortcode" class="button-primary" value="<?php echo _e( 'Insert Store Locator', 'wpsl' ); ?>" onclick="WPSL_InsertShortcode();" />
356
- </p>
357
- </div>
358
-
359
- <?php
360
-
361
- exit();
362
- }
363
- }
364
-
365
- new WPSL_Shortcode_Generator();
366
  }
1
+ <?php
2
+ /**
3
+ * Shortcode Generator class
4
+ *
5
+ * @author Tijmen Smit
6
+ * @since 2.2.10
7
+ */
8
+
9
+ if ( !defined( 'ABSPATH' ) ) exit;
10
+
11
+ if ( !class_exists( 'WPSL_Shortcode_Generator' ) ) {
12
+
13
+ /**
14
+ * Handle the generation of the WPSL shortcode through the media button
15
+ *
16
+ * @since 2.2.10
17
+ */
18
+ class WPSL_Shortcode_Generator {
19
+
20
+ /**
21
+ * Constructor
22
+ */
23
+ public function __construct() {
24
+ add_action( 'media_buttons', array( $this, 'add_wpsl_media_button' ) );
25
+ add_action( 'admin_init', array( $this, 'show_thickbox_iframe_content' ) );
26
+ }
27
+
28
+ /**
29
+ * Add the WPSL media button to the media button row
30
+ *
31
+ * @since 2.2.10
32
+ * @return void
33
+ */
34
+ public function add_wpsl_media_button() {
35
+
36
+ global $pagenow, $typenow;
37
+
38
+ /* Make sure we're on a post/page or edit screen in the admin area */
39
+ if ( in_array( $pagenow, array( 'post.php', 'page.php', 'post-new.php', 'post-edit.php' ) ) && $typenow != 'wpsl_stores' ) {
40
+ $changelog_link = self_admin_url( '?wpsl_media_action=store_locator&KeepThis=true&TB_iframe=true&width=783&height=800' );
41
+
42
+ echo '<a href="' . esc_url( $changelog_link ) . '" class="thickbox button wpsl-thickbox" name="' . __( 'WP Store Locator' ,'wpsl' ) . '">' . __( 'Insert Store Locator', 'wpsl' ) . '</a>';
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Show the shortcode thickbox content
48
+ *
49
+ * @since 2.2.10
50
+ * @return void
51
+ */
52
+ function show_thickbox_iframe_content() {
53
+
54
+ global $wpsl_settings, $wpsl_admin;
55
+
56
+ if ( empty( $_REQUEST['wpsl_media_action'] ) ) {
57
+ return;
58
+ }
59
+
60
+ if ( !current_user_can( 'edit_pages' ) ) {
61
+ wp_die( __( 'You do not have permission to perform this action', 'wpsl' ), __( 'Error', 'wpsl' ), array( 'response' => 403 ) );
62
+ }
63
+
64
+ $min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
65
+
66
+ // Make sure the required JS / CSS files are loaded in the Thickbox iframe
67
+ wp_print_scripts( 'jquery-ui-core' );
68
+ wp_print_scripts( 'jquery-ui-tabs' );
69
+ wp_print_scripts( 'media-upload' );
70
+ ?>
71
+ <script type="text/javascript" src="<?php echo plugins_url( '/js/wpsl-shortcode-generator' . $min . '.js?ver='. WPSL_VERSION_NUM .'', __FILE__ ); ?>"></script>
72
+ <?php
73
+ wp_print_styles('buttons' );
74
+ wp_print_styles('forms' );
75
+ ?>
76
+
77
+ <link rel="stylesheet" type="text/css" href="<?php echo plugins_url( '/css/style' . $min . '.css?ver='. WPSL_VERSION_NUM .'', __FILE__ ); ?>" media="all" />
78
+ <style>
79
+ body {
80
+ color: #444;
81
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
82
+ font-size: 13px;
83
+ margin: 0;
84
+ }
85
+
86
+ #wpsl-media-tabs .ui-tabs-nav {
87
+ padding-left: 15px;
88
+ background: #fff !important;
89
+ border-bottom: 1px solid #dfdfdf;
90
+ border-collapse: collapse;
91
+ padding-top: .2em;
92
+ }
93
+
94
+ #wpsl-media-tabs .ui-tabs-nav::after {
95
+ clear: both;
96
+ content: "";
97
+ display: table;
98
+ border-collapse: collapse;
99
+ }
100
+
101
+ #wpsl-media-tabs .ui-tabs-nav li {
102
+ list-style: none;
103
+ float: left;
104
+ position: relative;
105
+ top: 0;
106
+ margin: 1px .2em 0 0;
107
+ padding: 0;
108
+ white-space: nowrap;
109
+ border-bottom-width: 0;
110
+ }
111
+
112
+ #wpsl-media-tabs .ui-tabs-anchor {
113
+ float: left;
114
+ padding: .5em 1em;
115
+ text-decoration: none;
116
+ font-size: 14.3px;
117
+ }
118
+
119
+ #wpsl-media-tabs .ui-tabs-active a {
120
+ color: #212121;
121
+ cursor: text;
122
+ }
123
+
124
+ #wpsl-media-tabs .ui-tabs .ui-tabs-anchor {
125
+ float: left;
126
+ padding: .5em 1em;
127
+ text-decoration: none;
128
+ }
129
+
130
+ #wpsl-media-tabs.ui-widget-content {
131
+ border: none;
132
+ padding: 10px 0 0 0;
133
+ }
134
+
135
+ #wpsl-media-tabs .ui-tabs-anchor {
136
+ outline: none;
137
+ }
138
+
139
+ #wpsl-shortcode-config tr > td {
140
+ width: 25%;
141
+ }
142
+
143
+ #wpsl-markers-tab .wpsl-marker-list {
144
+ display: block;
145
+ overflow: hidden;
146
+ padding: 0;
147
+ list-style-type: none;
148
+ }
149
+
150
+ #wpsl-markers-tab .wpsl-marker-list li input {
151
+ padding: 0;
152
+ margin: 0;
153
+ }
154
+
155
+ #wpsl-shortcode-config .form-table,
156
+ #wpsl-shortcode-config .form-table td,
157
+ #wpsl-shortcode-config .form-table th,
158
+ #wpsl-shortcode-config .form-table td p {
159
+ font-size: 13px;
160
+ }
161
+
162
+ #wpsl-shortcode-config .ui-tabs .ui-tabs-nav {
163
+ padding-left: 15px;
164
+ border-radius: 0;
165
+ margin: 0;
166
+ }
167
+
168
+ .wpsl-shortcode-markers {
169
+ padding: 0 10px;
170
+ margin-top: 27px;
171
+ font-size: 13px;
172
+ }
173
+
174
+ #wpsl-insert-shortcode {
175
+ margin-left: 19px;
176
+ }
177
+
178
+ #wpsl-shortcode-config .ui-state-default {
179
+ border: 1px solid #d3d3d3;
180
+ border-top-left-radius: 4px;
181
+ border-top-right-radius: 4px;
182
+ background: none;
183
+ }
184
+
185
+ #wpsl-shortcode-config .ui-state-default a {
186
+ color: #909090;
187
+ }
188
+
189
+ #wpsl-shortcode-config .ui-state-default.ui-tabs-active a {
190
+ color: #212121;
191
+ }
192
+
193
+ #wpsl-shortcode-config .ui-state-hover {
194
+ border-bottom: none;
195
+ }
196
+
197
+ #wpsl-shortcode-config .ui-state-hover a {
198
+ color: #72777c;
199
+ }
200
+
201
+ #wpsl-media-tabs .ui-state-active {
202
+ border: 1px solid #aaa;
203
+ border-bottom: 1px solid #fff;
204
+ padding-bottom: 0;
205
+ }
206
+
207
+ #wpsl-shortcode-config li.ui-tabs-active.ui-state-hover,
208
+ #wpsl-shortcode-config li.ui-tabs-active {
209
+ border-bottom: 1px solid #fff;
210
+ padding-bottom: 0;
211
+ }
212
+
213
+ #wpsl-media-tabs li.ui-tabs-active {
214
+ margin-bottom: -1px;
215
+ }
216
+
217
+ #wpsl-general-tab,
218
+ #wpsl-markers-tab {
219
+ border: 0;
220
+ padding: 1em 1.4em;
221
+ background: none;
222
+ }
223
+
224
+ @media ( max-width: 782px ) {
225
+ #wpsl-shortcode-config tr > td {
226
+ width: 100%;
227
+ }
228
+ }
229
+ </style>
230
+ <div id="wpsl-shortcode-config" class="wp-core-ui">
231
+ <div id="wpsl-media-tabs">
232
+ <ul>
233
+ <li><a href="#wpsl-general-tab"><?php _e( 'General Options', 'wpsl' ); ?></a></li>
234
+ <li><a href="#wpsl-markers-tab"><?php _e('Markers', 'wpsl' ); ?></a></li>
235
+ </ul>
236
+ <div id="wpsl-general-tab">
237
+ <table class="form-table wpsl-shortcode-config">
238
+ <tbody>
239
+ <tr>
240
+ <td><label for="wpsl-store-template"><?php _e('Select the used template', 'wpsl' ); ?></label></td>
241
+ <td><?php echo $wpsl_admin->settings_page->show_template_options(); ?></td>
242
+ </tr>
243
+ <tr>
244
+ <td><label for="wpsl-start-location"><?php _e( 'Start point', 'wpsl' ); ?></label><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'If nothing it set, then the start point from the %ssettings%s page is used.', '' ), '<a href=' . admin_url( 'edit.php?post_type=wpsl_stores&page=wpsl_settings#wpsl-map-settings' ) . '>', '</a>' ); ?></span></span></p></td>
245
+ <td><input type="text" placeholder="Optional" value="" id="wpsl-start-location"></td>
246
+ </tr>
247
+ <tr>
248
+ <td>
249
+ <label for="wpsl-auto-locate"><?php _e( 'Attempt to auto-locate the user', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'Most modern browsers %srequire%s a HTTPS connection before the Geolocation feature works.', 'wpsl_csv' ), '<a href="https://wpstorelocator.co/document/html-5-geolocation-not-working/">', '</a>' ); ?></span></span></label>
250
+ </td>
251
+ <td><input type="checkbox" value="" <?php checked( $wpsl_settings['auto_locate'], true ); ?> name="wpsl_map[auto_locate]" id="wpsl-auto-locate"></td>
252
+ </tr>
253
+ <?php
254
+ $terms = get_terms( 'wpsl_store_category', 'hide_empty=1' );
255
+
256
+ if ( $terms ) {
257
+ ?>
258
+ <tr>
259
+ <td><label for="wpsl-cat-filter-types"><?php _e( 'Category filter type', 'wpsl' ); ?></label></p></td>
260
+ <td>
261
+ <select id="wpsl-cat-filter-types" autocomplete="off">
262
+ <option value="" selected="selected"><?php _e( 'None', 'wpsl' ); ?></option>
263
+ <option value="dropdown"><?php _e( 'Dropdown', 'wpsl' ); ?></option>
264
+ <option value="checkboxes"><?php _e( 'Checkboxes', 'wpsl' ); ?></option>
265
+ </select>
266
+ </td>
267
+ </tr>
268
+ <tr class="wpsl-cat-restriction">
269
+ <td style="vertical-align:top;"><label for="wpsl-cat-restriction"><?php _e('Automatically restrict the returned results to one or more categories?', 'wpsl' ); ?></label></td>
270
+ <td>
271
+ <?php
272
+ $cat_restricton = '<select id="wpsl-cat-restriction" multiple="multiple" autocomplete="off">';
273
+
274
+ foreach ( $terms as $term ) {
275
+ $cat_restricton .= '<option value="' . esc_attr( $term->slug ) . '">' . esc_html( $term->name ) . '</option>';
276
+ }
277
+
278
+ $cat_restricton .= '</select>';
279
+
280
+ echo $cat_restricton;
281
+ ?>
282
+ </td>
283
+ </tr>
284
+ <tr class="wpsl-cat-selection wpsl-hide">
285
+ <td style="vertical-align:top;"><label for="wpsl-cat-selection"><?php _e('Set a selected category?', 'wpsl' ); ?></label></td>
286
+ <td>
287
+ <?php
288
+ $cat_selection = '<select id="wpsl-cat-selection" autocomplete="off">';
289
+
290
+ $cat_selection .= '<option value="" selected="selected">' . __( 'Select category', 'wpsl' ) . '</option>';
291
+
292
+ foreach ( $terms as $term ) {
293
+ $cat_selection .= '<option value="' . esc_attr( $term->slug ) . '">' . esc_html( $term->name ) . '</option>';
294
+ }
295
+
296
+ $cat_selection .= '</select>';
297
+
298
+ echo $cat_selection;
299
+ ?>
300
+ </td>
301
+ </tr>
302
+ <?php
303
+ }
304
+ ?>
305
+ <tr class="wpsl-checkbox-options wpsl-hide">
306
+ <td><label for="wpsl-checkbox-columns"><?php _e('Checkbox columns', 'wpsl' ); ?></label></td>
307
+ <td>
308
+ <?php
309
+ echo '<select id="wpsl-checkbox-columns">';
310
+
311
+ $i = 1;
312
+
313
+ while ( $i <= 4 ) {
314
+ $selected = ( $i == 3 ) ? "selected='selected'" : ''; // 3 is the default
315
+
316
+ echo '<option value="' . $i . '" ' . $selected . '>' . $i . '</option>';
317
+ $i++;
318
+ }
319
+
320
+ echo '</select>';
321
+ ?>
322
+ </td>
323
+ </tr>
324
+ <tr class="wpsl-checkbox-selection wpsl-hide">
325
+ <td><label for="wpsl-checkbox-columns"><?php _e('Set selected checkboxes', 'wpsl' ); ?></label></td>
326
+ <td>
327
+ <?php
328
+ $checkbox_selection = '<select id="wpsl-checkbox-selection" multiple="multiple" autocomplete="off">';
329
+
330
+ foreach ( $terms as $term ) {
331
+ $checkbox_selection .= '<option value="' . esc_attr( $term->slug ) . '">' . esc_html( $term->name ) . '</option>';
332
+ }
333
+
334
+ $checkbox_selection .= '</select>';
335
+
336
+ echo $checkbox_selection;
337
+ ?>
338
+ </td>
339
+ </tr>
340
+ <tr>
341
+ <td><label for="wpsl-map-type"><?php _e( 'Map type', 'wpsl' ); ?>:</label></td>
342
+ <td><?php echo $wpsl_admin->settings_page->create_dropdown( 'map_types' ); ?></td>
343
+ </tr>
344
+ </tbody>
345
+ </table>
346
+ </div>
347
+ <div id="wpsl-markers-tab">
348
+ <div class="wpsl-shortcode-markers">
349
+ <?php echo $wpsl_admin->settings_page->show_marker_options(); ?>
350
+ </div>
351
+ </div>
352
+ </div>
353
+
354
+ <p class="submit">
355
+ <input type="button" id="wpsl-insert-shortcode" class="button-primary" value="<?php echo _e( 'Insert Store Locator', 'wpsl' ); ?>" onclick="WPSL_InsertShortcode();" />
356
+ </p>
357
+ </div>
358
+
359
+ <?php
360
+
361
+ exit();
362
+ }
363
+ }
364
+
365
+ new WPSL_Shortcode_Generator();
366
  }
admin/css/style-3.8.css CHANGED
@@ -1,41 +1,41 @@
1
- @font-face {
2
- font-family: 'fontello';
3
- src: url('../font/fontello.eot?54620740');
4
- src: url('../font/fontello.eot?54620740#iefix') format('embedded-opentype'),
5
- url('../font/fontello.woff?54620740') format('woff'),
6
- url('../font/fontello.ttf?54620740') format('truetype'),
7
- url('../font/fontello.svg?54620740#fontello') format('svg');
8
- font-weight: normal;
9
- font-style: normal;
10
- }
11
-
12
- #adminmenu #menu-posts-wpsl_stores .wp-menu-image:before,
13
- .wpsl-thickbox:before {
14
- content: '\e801';
15
- display: inline-block;
16
- font-family: "fontello" !important;
17
- font-style: normal;
18
- font-variant: normal;
19
- font-weight: 400;
20
- font-size: 22px;
21
- line-height: 1em;
22
- text-align: center;
23
- text-decoration: inherit;
24
- text-transform: none;
25
- width: 1em;
26
- }
27
-
28
- .wpsl-thickbox:before {
29
- padding-top: 1px;
30
- font-size: 18px;
31
- color: #82878c;
32
- }
33
-
34
- /* Hide the pre 3.8 menu icon */
35
- #adminmenu #menu-posts-wpsl_stores .wp-menu-image img {
36
- display: none;
37
- }
38
-
39
- #wpsl-store-overview .widefat td.thumb {
40
- padding-top: 8px;
41
  }
1
+ @font-face {
2
+ font-family: 'fontello';
3
+ src: url('../font/fontello.eot?54620740');
4
+ src: url('../font/fontello.eot?54620740#iefix') format('embedded-opentype'),
5
+ url('../font/fontello.woff?54620740') format('woff'),
6
+ url('../font/fontello.ttf?54620740') format('truetype'),
7
+ url('../font/fontello.svg?54620740#fontello') format('svg');
8
+ font-weight: normal;
9
+ font-style: normal;
10
+ }
11
+
12
+ #adminmenu #menu-posts-wpsl_stores .wp-menu-image:before,
13
+ .wpsl-thickbox:before {
14
+ content: '\e801';
15
+ display: inline-block;
16
+ font-family: "fontello" !important;
17
+ font-style: normal;
18
+ font-variant: normal;
19
+ font-weight: 400;
20
+ font-size: 22px;
21
+ line-height: 1em;
22
+ text-align: center;
23
+ text-decoration: inherit;
24
+ text-transform: none;
25
+ width: 1em;
26
+ }
27
+
28
+ .wpsl-thickbox:before {
29
+ padding-top: 1px;
30
+ font-size: 18px;
31
+ color: #82878c;
32
+ }
33
+
34
+ /* Hide the pre 3.8 menu icon */
35
+ #adminmenu #menu-posts-wpsl_stores .wp-menu-image img {
36
+ display: none;
37
+ }
38
+
39
+ #wpsl-store-overview .widefat td.thumb {
40
+ padding-top: 8px;
41
  }
admin/css/style.css CHANGED
@@ -46,6 +46,10 @@
46
  margin:4px 0 0 4px;
47
  }
48
 
 
 
 
 
49
  /* Plugin nav */
50
  #wpsl-mainnav {
51
  border-bottom: 1px solid #CCCCCC;
@@ -115,6 +119,7 @@
115
  border-radius: 3px;
116
  }
117
 
 
118
  #wpsl-wrap.wpsl-settings .wpsl-error,
119
  .wpsl-store-meta .wpsl-error {
120
  border: 1px solid #c01313;
@@ -249,6 +254,7 @@
249
  cursor: pointer;
250
  }
251
 
 
252
  .wpsl-info.wpsl-required-setting:before {
253
  color: #b91111;
254
  }
@@ -666,4 +672,48 @@ div.wpsl-active {
666
  .wpsl-api-error a {
667
  font-weight: bold;
668
  color: #fff !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
669
  }
46
  margin:4px 0 0 4px;
47
  }
48
 
49
+ .wpsl-api-key-preloader {
50
+ margin-left: 10px;
51
+ }
52
+
53
  /* Plugin nav */
54
  #wpsl-mainnav {
55
  border-bottom: 1px solid #CCCCCC;
119
  border-radius: 3px;
120
  }
121
 
122
+ #wpsl-geocode-test .wpsl-error,
123
  #wpsl-wrap.wpsl-settings .wpsl-error,
124
  .wpsl-store-meta .wpsl-error {
125
  border: 1px solid #c01313;
254
  cursor: pointer;
255
  }
256
 
257
+ .wpsl-warning,
258
  .wpsl-info.wpsl-required-setting:before {
259
  color: #b91111;
260
  }
672
  .wpsl-api-error a {
673
  font-weight: bold;
674
  color: #fff !important;
675
+ }
676
+
677
+ /* Test API response for any address */
678
+ #wpsl-geocode-test {
679
+ overflow: hidden;
680
+ }
681
+
682
+ #wpsl-geocode-tabs {
683
+ margin-top: 10px;
684
+ height: 370px;
685
+ }
686
+
687
+ #wpsl-geocode-tabs .ui-widget-header {
688
+ border: none;
689
+ border-bottom: 1px solid #aaa;
690
+ background: none;
691
+ }
692
+
693
+ #wpsl-geocode-preview {
694
+ margin-top:14px;
695
+ border-radius: 0;
696
+ }
697
+
698
+ #wpsl-geocode-response {
699
+ padding-left: 0;
700
+ padding-right: 0;
701
+ }
702
+
703
+ #wpsl-geocode-response textarea {
704
+ width: 100%;
705
+ height: 328px;
706
+ resize: none;
707
+ }
708
+
709
+ #wpsl-geocode-tabs.ui-widget-content {
710
+ border: none;
711
+ }
712
+
713
+ #wpsl-geocode-tabs.ui-tabs .ui-tabs-nav {
714
+ padding: 0;
715
+ }
716
+
717
+ #wpsl-geocode-tabs {
718
+ width: auto;
719
  }
admin/css/style.min.css CHANGED
@@ -1 +1 @@
1
- #wpsl-wrap.wpsl-add-stores p,.wpsl-marker-list,.wpsl-store-meta p{overflow:hidden}.wpsl-info:before,[class*=" wpsl-icon-"]:before,[class^=wpsl-icon-]:before{font-family:fontello;font-style:normal;speak:none;text-decoration:inherit;font-variant:normal;text-transform:none}@font-face{font-family:fontello;src:url(../font/fontello.eot?54620740);src:url(../font/fontello.eot?54620740#iefix) format('embedded-opentype'),url(../font/fontello.woff?54620740) format('woff'),url(../font/fontello.ttf?54620740) format('truetype'),url(../font/fontello.svg?54620740#fontello) format('svg');font-weight:400;font-style:normal}#wpsl-store-overview .widefat td,#wpsl-wrap .widefat td{padding:12px 7px}#wpsl-wrap.wpsl-settings h2{margin-bottom:15px}#wpsl-wrap .submit{padding:0!important;margin-bottom:-10px!important}#wpsl-store-overview .column-action a{float:left;margin-right:5px}#wpsl-store-overview p.search-box{margin:0 0 1em}.column-action{width:130px}#wpsl-delete-confirmation,.wpsl-hide{display:none}.wpsl-preloader{float:right;margin:4px 0 0 4px}#wpsl-mainnav{border-bottom:1px solid #CCC;float:left;margin-bottom:15px;padding-left:7px;width:99.4%}#wpsl-mainnav li a{display:block;padding:9px 12px;text-decoration:none}#wpsl-mainnav li{float:left;margin:0}#wpsl-wrap label,#wpsl-wrap.wpsl-add-stores label{width:85px;margin-top:6px}#wpsl-wrap.wpsl-add-stores label{float:left}#wpsl-wrap.wpsl-add-stores .wpsl-radioboxes label{float:none;margin-right:10px}#wpsl-wrap textarea{width:489px;resize:none}#wpsl-wrap textarea,.wpsl-tab #wpsl-hours{height:185px}#wpsl-wrap .wpsl-style-input textarea{width:509px;resize:none;margin-bottom:12px;height:165px}#wpsl-style-preview{float:left;margin-bottom:12px}.wpsl-style-preview-error{float:left;margin:6px 0 0 10px;color:#b91111}.wpsl-curve{float:left;border-radius:3px}#wpsl-wrap.wpsl-settings .wpsl-error,.wpsl-store-meta .wpsl-error{border:1px solid #c01313}#wpsl-lookup-location{margin-bottom:7px}#wpsl-wrap input[type=email],#wpsl-wrap input[type=text],#wpsl-wrap input[type=url]{width:340px}#wpsl-api-region,#wpsl-wrap.wpsl-settings input[type=text].textinput{width:255px}.wpsl-add-store{float:left;width:100%;clear:both}#wpsl-wrap .metabox-holder{float:left;margin-right:20px}#wpsl-wrap .metabox-holder.wpsl-wide{width:100%;padding-top:0}#wpsl-wrap .wpsl-edit-header{margin-bottom:12px}#wpsl-wrap.wpsl-settings .metabox-holder{width:100%}#wpsl-wrap.wpsl-settings .metabox-holder h3:hover{cursor:auto}#wpsl-meta-nav li:hover,#wpsl-store-hours .dashicons:hover,.wpsl-add-period:hover,.wpsl-info:hover,[class*=" wpsl-icon-"]:hover,[class^=wpsl-icon-]:hover{cursor:pointer}#wpsl-gmap-wrap{float:left;width:100%;height:250px;border-radius:3px;margin-top:0;margin-bottom:20px}#wpsl-map-preview #wpsl-gmap-wrap{margin:6px 0 12px}#wpsl-gmap-wrap.wpsl-styles-preview{float:none;margin:0;border-radius:0;clear:both}#wpsl-style-url{display:none;margin:20px 0 0}.wpsl-marker-list li{float:left;padding:10px;margin-right:5px;text-align:center}.wpsl-marker-list li input[type=radio]{margin-right:0}.wpsl-marker-list img{display:block;margin-bottom:7px}.wpsl-active-marker,.wpsl-marker-list li:hover{background:#E4E4E4;border-radius:5px;cursor:pointer}#wpsl-license-form .postbox-container,#wpsl-settings-form .postbox-container{width:535px;clear:both}#wpsl-wrap .metabox-holder{padding-top:0}.wpsl-info{position:relative;margin-left:3px}.wpsl-info:before{content:'\e802';font-size:14px;font-weight:400;display:inline-block;width:1em;margin-right:.2em;text-align:center;line-height:1em;margin-left:.2em}.wpsl-info.wpsl-required-setting:before{color:#b91111}.wpsl-info-text{position:absolute;padding:10px;left:-29px;bottom:28px;color:#eee;min-width:200px;background:#222;border-radius:3px;line-height:1.4em}#wpsl-map-preview .wpsl-info-text{width:175px;min-width:0;left:-88px}#wpsl-map-preview .wpsl-info-text::after{left:auto;right:87px}#wpsl-map-preview .wpsl-info{position:absolute;margin-left:5px;top:5px}.wpsl-submit-wrap{position:relative;clear:both}.wpsl-info-text:after{position:absolute;border-left:11px solid transparent;border-right:11px solid transparent;border-top:11px solid #222;content:"";left:27px;bottom:-10px}.wpsl-info-text a{color:#fff}#wpsl-settings-form label{position:relative;display:inline-block;font-weight:400;margin:0 10px 0 0;width:220px}#wpsl-save-settings{float:left;clear:both}#wpsl-settings-form .wpsl-radioboxes label{float:none;margin-right:10px;width:auto}#wpsl-faq dt{margin-bottom:4px;font-weight:700;font-size:110%}#wpsl-faq dd{margin-left:0}#wpsl-faq dl{margin-bottom:25px}.wp-list-table .column-action .button{margin:3px 5px 3px 0}.wpsl-store-meta label,.wpsl-store-meta legend{float:left;width:95px;margin-top:3px}.wpsl-store-meta fieldset label{display:inline-block;line-height:1.4em;margin:.25em 0 .5em!important}.wpsl-store-meta input[type=email],.wpsl-store-meta input[type=url],.wpsl-store-meta input[type=text],.wpsl-store-meta textarea{width:340px}.wpsl-store-meta textarea{resize:none}#wpsl-map-preview em,#wpsl-settings-form em,.wpsl-store-meta em{display:block}#wpsl-settings-form .wpsl-info em{display:inline}#wpsl-meta-nav{margin:19px 0 6px}#wpsl-meta-nav li{display:inline;margin-right:5px}#wpsl-meta-nav li a{padding:6px 9px;border-radius:3px 3px 0 0;border-bottom:none;text-decoration:none;outline:0}.wpsl-tab{padding:5px 15px;display:none;border:1px solid #eee;border-radius:0 3px 3px}div.wpsl-active{display:block;background:#fdfdfd}#wpsl-meta-nav .wpsl-active a{border:1px solid #eee;border-bottom:1px solid #fdfdfd;background:#fdfdfd;color:#444}.wpsl-star{color:#c01313}#wpsl-store-hours{border-collapse:collapse;margin:5px 0 20px}#wpsl-settings-form #wpsl-store-hours{width:100%}#wpsl-store-hours div{margin:0;padding:3px;background:#eee;border:1px solid #eee;border-radius:3px;white-space:nowrap}#wpsl-store-hours .wpsl-store-closed{border:none;background:0 0;margin-top:9px;margin-bottom:0}.wpsl-add-period,.wpsl-current-period{float:left}#wpsl-store-hours .wpsl-multiple-periods{float:left;clear:both;margin-top:8px}.wpsl-add-period span,.wpsl-current-period span{float:left;margin:6px 7px 0}.wpsl-add-period span{margin:6px 0 0 7px}#wpsl-store-hours .wpsl-remove-period{background:#999;border-radius:9px}.wpsl-add-period{border:none;background:#eee;border-radius:3px;font-size:13px;padding:3px 10px}.wpsl-default-hours{margin-top:25px}#wpsl-store-hours select{float:left}#wpsl-store-hours th{text-align:left;padding:8px 10px 8px 0;border-bottom:1px solid #eee}#wpsl-settings-form #wpsl-store-hours th{text-align:left}#wpsl-store-hours td{border-bottom:1px solid #eee;padding:7px 10px 7px 0;vertical-align:top}#wpsl-store-hours .wpsl-opening-day{min-width:80px;padding:17px 17px 0 0;text-align:left;vertical-align:top}.wpsl-twentyfour-format .wpsl-opening-hours{width:197px}.wpsl-twelve-format .wpsl-opening-hours{width:245px}#wpsl-settings-form #wpsl-store-hours .wpsl-opening-day{width:150px}#wpsl-settings-form #wpsl-store-hours td p{padding:10px 0 0;margin:0;text-align:left}#wpsl-store-hours .wpsl-add-period{height:30px}.wpsl-pre-38 .wpsl-add-period{height:27px}#wpsl-store-hours .dashicons{color:#999;margin:0 3px}#wpsl-store-hours .dashicons:hover,#wpsl-store-hours .wpsl-add-period:hover .dashicons{color:#444}#wpsl-wrap.wpsl-pre-38 .submit{margin-bottom:0!important}[class*=" wpsl-icon-"]:before,[class^=wpsl-icon-]:before{font-weight:400;display:inline-block;width:1em;margin-right:.2em;text-align:center;line-height:1em;margin-left:.2em;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.wpsl-icon-location:before{content:'\e801'}.wpsl-icon-attention-circled:before{content:'\e802'}.wpsl-icon-cancel-circled:before{content:'\e803'}.wpsl-icon-plus-circled:before{content:'\e805'}#wpsl-store-hours .wpsl-icon-cancel-circled,#wpsl-store-hours .wpsl-icon-plus-circled{margin-top:1px;font-size:18px;display:inline-block;color:#999}#wpsl-store-hours .wpsl-icon-cancel-circled:hover,#wpsl-store-hours .wpsl-icon-plus-circled:hover{color:#444}.wpsl-add-on{float:left;position:relative;width:300px;height:240px;background:#fff;margin:20px 20px 0 0;border:1px solid #e8e8e8;border-radius:3px}.wpsl-add-on p{margin-top:0}.wpsl-add-on img{height:auto;max-width:100%;vertical-align:bottom}.wpsl-add-on>a{width:300px;display:inline-block}.wpsl-add-on a img:hover{opacity:.95}.wpsl-add-on .wpsl-add-on-desc{padding:20px}.wpsl-add-on-status{position:absolute;left:20px;bottom:20px}.wpsl-add-on-status p{margin:0 0 4px}.wpsl-api-error{margin-top:13px;padding:10px;color:#fff;border-radius:3px;background:#c01313}.wpsl-api-error a{font-weight:700;color:#fff!important}
1
+ @font-face{font-family:fontello;src:url(../font/fontello.eot?54620740);src:url(../font/fontello.eot?54620740#iefix) format('embedded-opentype'),url(../font/fontello.woff?54620740) format('woff'),url(../font/fontello.ttf?54620740) format('truetype'),url(../font/fontello.svg?54620740#fontello) format('svg');font-weight:400;font-style:normal}#wpsl-store-overview .widefat td,#wpsl-wrap .widefat td{padding:12px 7px}#wpsl-wrap.wpsl-settings h2{margin-bottom:15px}#wpsl-wrap .submit{padding:0!important;margin-bottom:-10px!important}#wpsl-store-overview .column-action a{float:left;margin-right:5px}#wpsl-store-overview p.search-box{margin:0 0 1em 0}.column-action{width:130px}#wpsl-delete-confirmation,.wpsl-hide{display:none}.wpsl-preloader{float:right;margin:4px 0 0 4px}.wpsl-api-key-preloader{margin-left:10px}#wpsl-mainnav{border-bottom:1px solid #ccc;float:left;margin-bottom:15px;padding-left:7px;width:99.4%}#wpsl-mainnav li a{display:block;padding:9px 12px;text-decoration:none}#wpsl-mainnav li{float:left;margin:0}#wpsl-wrap label,#wpsl-wrap.wpsl-add-stores label{width:85px;margin-top:6px}#wpsl-wrap.wpsl-add-stores label{float:left}#wpsl-wrap.wpsl-add-stores p{overflow:hidden}#wpsl-wrap.wpsl-add-stores .wpsl-radioboxes label{float:none;margin-right:10px}#wpsl-wrap textarea{width:489px;resize:none}#wpsl-wrap textarea,.wpsl-tab #wpsl-hours{height:185px}#wpsl-wrap .wpsl-style-input textarea{width:509px;resize:none;margin-bottom:12px;height:165px}#wpsl-style-preview{float:left;margin-bottom:12px}.wpsl-style-preview-error{float:left;margin:6px 0 0 10px;color:#b91111}.wpsl-curve{float:left;border-radius:3px}#wpsl-geocode-test .wpsl-error,#wpsl-wrap.wpsl-settings .wpsl-error,.wpsl-store-meta .wpsl-error{border:1px solid #c01313}#wpsl-lookup-location{margin-bottom:7px}#wpsl-wrap input[type=email],#wpsl-wrap input[type=text],#wpsl-wrap input[type=url]{width:340px}#wpsl-api-region,#wpsl-wrap.wpsl-settings input[type=text].textinput{width:255px}.wpsl-add-store{float:left;width:100%;clear:both}#wpsl-wrap .metabox-holder{float:left;margin-right:20px}#wpsl-wrap .metabox-holder.wpsl-wide{width:100%;padding-top:0}#wpsl-wrap .wpsl-edit-header{margin-bottom:12px}#wpsl-wrap.wpsl-settings .metabox-holder{width:100%}#wpsl-wrap.wpsl-settings .metabox-holder h3:hover{cursor:auto}#wpsl-gmap-wrap{float:left;width:100%;height:250px;border-radius:3px;margin-top:0;margin-bottom:20px}#wpsl-map-preview #wpsl-gmap-wrap{margin:6px 0 12px 0}#wpsl-gmap-wrap.wpsl-styles-preview{float:none;margin:0;border-radius:0;clear:both}#wpsl-style-url{display:none;margin:20px 0 0 0}.wpsl-marker-list{overflow:hidden}.wpsl-marker-list li{float:left;padding:10px;margin-right:5px;text-align:center}.wpsl-marker-list li input[type=radio]{margin-right:0}.wpsl-marker-list img{display:block;margin-bottom:7px}.wpsl-active-marker,.wpsl-marker-list li:hover{background:#e4e4e4;border-radius:5px;cursor:pointer}#wpsl-license-form .postbox-container,#wpsl-settings-form .postbox-container{width:535px;clear:both}#wpsl-wrap .metabox-holder{padding-top:0}.wpsl-info{position:relative;margin-left:3px}.wpsl-info:before{content:'\e802';font-size:14px;font-family:fontello;font-style:normal;font-weight:400;speak:none;display:inline-block;text-decoration:inherit;width:1em;margin-right:.2em;text-align:center;font-variant:normal;text-transform:none;line-height:1em;margin-left:.2em}.wpsl-info:hover{cursor:pointer}.wpsl-info.wpsl-required-setting:before,.wpsl-warning{color:#b91111}.wpsl-info-text{position:absolute;padding:10px;left:-29px;bottom:28px;color:#eee;min-width:200px;background:#222;border-radius:3px;line-height:1.4em}#wpsl-map-preview .wpsl-info-text{width:175px;min-width:0;left:-88px}#wpsl-map-preview .wpsl-info-text::after{left:auto;right:87px}#wpsl-map-preview .wpsl-info{position:absolute;margin-left:5px;top:5px}.wpsl-submit-wrap{position:relative;clear:both}.wpsl-info-text:after{position:absolute;border-left:11px solid transparent;border-right:11px solid transparent;border-top:11px solid #222;content:"";left:27px;bottom:-10px}.wpsl-info-text a{color:#fff}#wpsl-settings-form label{position:relative;display:inline-block;font-weight:400;margin:0 10px 0 0;width:220px}#wpsl-save-settings{float:left;clear:both}#wpsl-settings-form .wpsl-radioboxes label{float:none;margin-right:10px;width:auto}#wpsl-faq dt{margin-bottom:4px;font-weight:700;font-size:110%}#wpsl-faq dd{margin-left:0}#wpsl-faq dl{margin-bottom:25px}.wp-list-table .column-action .button{margin:3px 5px 3px 0}.wpsl-store-meta p{overflow:hidden}.wpsl-store-meta label,.wpsl-store-meta legend{float:left;width:95px;margin-top:3px}.wpsl-store-meta fieldset label{display:inline-block;line-height:1.4em;margin:.25em 0 .5em!important}.wpsl-store-meta input[type=email],.wpsl-store-meta input[type=text],.wpsl-store-meta input[type=url],.wpsl-store-meta textarea{width:340px}.wpsl-store-meta textarea{resize:none}#wpsl-map-preview em,#wpsl-settings-form em,.wpsl-store-meta em{display:block}#wpsl-settings-form .wpsl-info em{display:inline}#wpsl-meta-nav{margin:19px 0 6px 0}#wpsl-meta-nav li{display:inline;margin-right:5px}#wpsl-meta-nav li:hover{cursor:pointer}#wpsl-meta-nav li a{padding:6px 9px;border-radius:3px 3px 0 0;border-bottom:none;text-decoration:none;outline:0}.wpsl-tab{padding:5px 15px;display:none;border:1px solid #eee;border-radius:0 3px 3px 3px}div.wpsl-active{display:block;background:#fdfdfd}#wpsl-meta-nav .wpsl-active a{border:1px solid #eee;border-bottom:1px solid #fdfdfd;background:#fdfdfd;color:#444}.wpsl-star{color:#c01313}#wpsl-store-hours{border-collapse:collapse;margin:5px 0 20px 0}#wpsl-settings-form #wpsl-store-hours{width:100%}#wpsl-store-hours div{margin:0;padding:3px;background:#eee;border:1px solid #eee;border-radius:3px;white-space:nowrap}#wpsl-store-hours .wpsl-store-closed{border:none;background:0 0;margin-top:9px;margin-bottom:0}.wpsl-add-period,.wpsl-current-period{float:left}#wpsl-store-hours .wpsl-multiple-periods{float:left;clear:both;margin-top:8px}.wpsl-add-period span,.wpsl-current-period span{float:left;margin:6px 7px 0}.wpsl-add-period span{margin:6px 0 0 7px}#wpsl-store-hours .wpsl-remove-period{background:#999;border-radius:9px}.wpsl-add-period{border:none;background:#eee;border-radius:3px;font-size:13px;padding:3px 10px}.wpsl-default-hours{margin-top:25px}#wpsl-store-hours select{float:left}#wpsl-store-hours th{text-align:left;padding:8px 10px 8px 0;border-bottom:1px solid #eee}#wpsl-settings-form #wpsl-store-hours th{text-align:left}#wpsl-store-hours td{border-bottom:1px solid #eee;padding:7px 10px 7px 0;vertical-align:top}#wpsl-store-hours .wpsl-opening-day{min-width:80px;padding:17px 17px 0 0;text-align:left;vertical-align:top}.wpsl-twentyfour-format .wpsl-opening-hours{width:197px}.wpsl-twelve-format .wpsl-opening-hours{width:245px}#wpsl-settings-form #wpsl-store-hours .wpsl-opening-day{width:150px}#wpsl-settings-form #wpsl-store-hours td p{padding:10px 0 0 0;margin:0;text-align:left}#wpsl-store-hours .wpsl-add-period{height:30px}.wpsl-pre-38 .wpsl-add-period{height:27px}#wpsl-store-hours .dashicons:hover,.wpsl-add-period:hover{cursor:pointer}#wpsl-store-hours .dashicons{color:#999;margin:0 3px}#wpsl-store-hours .dashicons:hover,#wpsl-store-hours .wpsl-add-period:hover .dashicons{color:#444}#wpsl-wrap.wpsl-pre-38 .submit{margin-bottom:0!important}[class*=" wpsl-icon-"]:before,[class^=wpsl-icon-]:before{font-family:fontello;font-style:normal;font-weight:400;speak:none;display:inline-block;text-decoration:inherit;width:1em;margin-right:.2em;text-align:center;font-variant:normal;text-transform:none;line-height:1em;margin-left:.2em;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}[class*=" wpsl-icon-"]:hover,[class^=wpsl-icon-]:hover{cursor:pointer}.wpsl-icon-location:before{content:'\e801'}.wpsl-icon-attention-circled:before{content:'\e802'}.wpsl-icon-cancel-circled:before{content:'\e803'}.wpsl-icon-plus-circled:before{content:'\e805'}#wpsl-store-hours .wpsl-icon-cancel-circled,#wpsl-store-hours .wpsl-icon-plus-circled{margin-top:1px;font-size:18px;display:inline-block;color:#999}#wpsl-store-hours .wpsl-icon-cancel-circled:hover,#wpsl-store-hours .wpsl-icon-plus-circled:hover{color:#444}.wpsl-add-on{float:left;position:relative;width:300px;height:240px;background:#fff;margin:20px 20px 0 0;border:1px solid #e8e8e8;border-radius:3px}.wpsl-add-on p{margin-top:0}.wpsl-add-on img{height:auto;max-width:100%;vertical-align:bottom}.wpsl-add-on>a{width:300px;display:inline-block}.wpsl-add-on a img:hover{opacity:.95}.wpsl-add-on .wpsl-add-on-desc{padding:20px}.wpsl-add-on-status{position:absolute;left:20px;bottom:20px}.wpsl-add-on-status p{margin:0 0 4px 0}.wpsl-api-error{margin-top:13px;padding:10px;color:#fff;border-radius:3px;background:#c01313}.wpsl-api-error a{font-weight:700;color:#fff!important}#wpsl-geocode-test{overflow:hidden}#wpsl-geocode-tabs{margin-top:10px;height:370px}#wpsl-geocode-tabs .ui-widget-header{border:none;border-bottom:1px solid #aaa;background:0 0}#wpsl-geocode-preview{margin-top:14px;border-radius:0}#wpsl-geocode-response{padding-left:0;padding-right:0}#wpsl-geocode-response textarea{width:100%;height:328px;resize:none}#wpsl-geocode-tabs.ui-widget-content{border:none}#wpsl-geocode-tabs.ui-tabs .ui-tabs-nav{padding:0}#wpsl-geocode-tabs{width:auto}
admin/js/retina.js CHANGED
@@ -1,182 +1,182 @@
1
- /*!
2
- * Retina.js v1.3.0
3
- *
4
- * Copyright 2014 Imulus, LLC
5
- * Released under the MIT license
6
- *
7
- * Retina.js is an open source script that makes it easy to serve
8
- * high-resolution images to devices with retina displays.
9
- */
10
-
11
- (function() {
12
- var root = (typeof exports === 'undefined' ? window : exports);
13
- var config = {
14
- // An option to choose a suffix for 2x images
15
- retinaImageSuffix : '@2x',
16
-
17
- // Ensure Content-Type is an image before trying to load @2x image
18
- // https://github.com/imulus/retinajs/pull/45)
19
- check_mime_type: true,
20
-
21
- // Resize high-resolution images to original image's pixel dimensions
22
- // https://github.com/imulus/retinajs/issues/8
23
- force_original_dimensions: true
24
- };
25
-
26
- function Retina() {}
27
-
28
- root.Retina = Retina;
29
-
30
- Retina.configure = function(options) {
31
- if (options === null) {
32
- options = {};
33
- }
34
-
35
- for (var prop in options) {
36
- if (options.hasOwnProperty(prop)) {
37
- config[prop] = options[prop];
38
- }
39
- }
40
- };
41
-
42
- Retina.init = function(context) {
43
- if (context === null) {
44
- context = root;
45
- }
46
-
47
- var existing_onload = context.onload || function(){};
48
-
49
- context.onload = function() {
50
- var images = document.getElementsByTagName('img'), retinaImages = [], i, image;
51
- for (i = 0; i < images.length; i += 1) {
52
- image = images[i];
53
- if (!!!image.getAttributeNode('data-no-retina')) {
54
- retinaImages.push(new RetinaImage(image));
55
- }
56
- }
57
- existing_onload();
58
- };
59
- };
60
-
61
- Retina.isRetina = function(){
62
- var mediaQuery = '(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (min-resolution: 1.5dppx)';
63
-
64
- if (root.devicePixelRatio > 1) {
65
- return true;
66
- }
67
-
68
- if (root.matchMedia && root.matchMedia(mediaQuery).matches) {
69
- return true;
70
- }
71
-
72
- return false;
73
- };
74
-
75
-
76
- var regexMatch = /\.\w+$/;
77
- function suffixReplace (match) {
78
- return config.retinaImageSuffix + match;
79
- }
80
-
81
- function RetinaImagePath(path, at_2x_path) {
82
- this.path = path || '';
83
- if (typeof at_2x_path !== 'undefined' && at_2x_path !== null) {
84
- this.at_2x_path = at_2x_path;
85
- this.perform_check = false;
86
- } else {
87
- if (undefined !== document.createElement) {
88
- var locationObject = document.createElement('a');
89
- locationObject.href = this.path;
90
- locationObject.pathname = locationObject.pathname.replace(regexMatch, suffixReplace);
91
- this.at_2x_path = locationObject.href;
92
- } else {
93
- var parts = this.path.split('?');
94
- parts[0] = parts[0].replace(regexMatch, suffixReplace);
95
- this.at_2x_path = parts.join('?');
96
- }
97
- this.perform_check = true;
98
- }
99
- }
100
-
101
- root.RetinaImagePath = RetinaImagePath;
102
-
103
- RetinaImagePath.confirmed_paths = [];
104
-
105
- RetinaImagePath.prototype.is_external = function() {
106
- return !!(this.path.match(/^https?\:/i) && !this.path.match('//' + document.domain) );
107
- };
108
-
109
- RetinaImagePath.prototype.check_2x_variant = function(callback) {
110
- var http, that = this;
111
- if (this.is_external()) {
112
- return callback(false);
113
- } else if (!this.perform_check && typeof this.at_2x_path !== 'undefined' && this.at_2x_path !== null) {
114
- return callback(true);
115
- } else if (this.at_2x_path in RetinaImagePath.confirmed_paths) {
116
- return callback(true);
117
- } else {
118
- http = new XMLHttpRequest();
119
- http.open('HEAD', this.at_2x_path);
120
- http.onreadystatechange = function() {
121
- if (http.readyState !== 4) {
122
- return callback(false);
123
- }
124
-
125
- if (http.status >= 200 && http.status <= 399) {
126
- if (config.check_mime_type) {
127
- var type = http.getResponseHeader('Content-Type');
128
- if (type === null || !type.match(/^image/i)) {
129
- return callback(false);
130
- }
131
- }
132
-
133
- RetinaImagePath.confirmed_paths.push(that.at_2x_path);
134
- return callback(true);
135
- } else {
136
- return callback(false);
137
- }
138
- };
139
- http.send();
140
- }
141
- };
142
-
143
-
144
- function RetinaImage(el) {
145
- this.el = el;
146
- this.path = new RetinaImagePath(this.el.getAttribute('src'), this.el.getAttribute('data-at2x'));
147
- var that = this;
148
- this.path.check_2x_variant(function(hasVariant) {
149
- if (hasVariant) {
150
- that.swap();
151
- }
152
- });
153
- }
154
-
155
- root.RetinaImage = RetinaImage;
156
-
157
- RetinaImage.prototype.swap = function(path) {
158
- if (typeof path === 'undefined') {
159
- path = this.path.at_2x_path;
160
- }
161
-
162
- var that = this;
163
- function load() {
164
- if (! that.el.complete) {
165
- setTimeout(load, 5);
166
- } else {
167
- if (config.force_original_dimensions) {
168
- that.el.setAttribute('width', that.el.offsetWidth);
169
- that.el.setAttribute('height', that.el.offsetHeight);
170
- }
171
-
172
- that.el.setAttribute('src', path);
173
- }
174
- }
175
- load();
176
- };
177
-
178
-
179
- if (Retina.isRetina()) {
180
- Retina.init(root);
181
- }
182
- })();
1
+ /*!
2
+ * Retina.js v1.3.0
3
+ *
4
+ * Copyright 2014 Imulus, LLC
5
+ * Released under the MIT license
6
+ *
7
+ * Retina.js is an open source script that makes it easy to serve
8
+ * high-resolution images to devices with retina displays.
9
+ */
10
+
11
+ (function() {
12
+ var root = (typeof exports === 'undefined' ? window : exports);
13
+ var config = {
14
+ // An option to choose a suffix for 2x images
15
+ retinaImageSuffix : '@2x',
16
+
17
+ // Ensure Content-Type is an image before trying to load @2x image
18
+ // https://github.com/imulus/retinajs/pull/45)
19
+ check_mime_type: true,
20
+
21
+ // Resize high-resolution images to original image's pixel dimensions
22
+ // https://github.com/imulus/retinajs/issues/8
23
+ force_original_dimensions: true
24
+ };
25
+
26
+ function Retina() {}
27
+
28
+ root.Retina = Retina;
29
+
30
+ Retina.configure = function(options) {
31
+ if (options === null) {
32
+ options = {};
33
+ }
34
+
35
+ for (var prop in options) {
36
+ if (options.hasOwnProperty(prop)) {
37
+ config[prop] = options[prop];
38
+ }
39
+ }
40
+ };
41
+
42
+ Retina.init = function(context) {
43
+ if (context === null) {
44
+ context = root;
45
+ }
46
+
47
+ var existing_onload = context.onload || function(){};
48
+
49
+ context.onload = function() {
50
+ var images = document.getElementsByTagName('img'), retinaImages = [], i, image;
51
+ for (i = 0; i < images.length; i += 1) {
52
+ image = images[i];
53
+ if (!!!image.getAttributeNode('data-no-retina')) {
54
+ retinaImages.push(new RetinaImage(image));
55
+ }
56
+ }
57
+ existing_onload();
58
+ };
59
+ };
60
+
61
+ Retina.isRetina = function(){
62
+ var mediaQuery = '(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (min-resolution: 1.5dppx)';
63
+
64
+ if (root.devicePixelRatio > 1) {
65
+ return true;
66
+ }
67
+
68
+ if (root.matchMedia && root.matchMedia(mediaQuery).matches) {
69
+ return true;
70
+ }
71
+
72
+ return false;
73
+ };
74
+
75
+
76
+ var regexMatch = /\.\w+$/;
77
+ function suffixReplace (match) {
78
+ return config.retinaImageSuffix + match;
79
+ }
80
+
81
+ function RetinaImagePath(path, at_2x_path) {
82
+ this.path = path || '';
83
+ if (typeof at_2x_path !== 'undefined' && at_2x_path !== null) {
84
+ this.at_2x_path = at_2x_path;
85
+ this.perform_check = false;
86
+ } else {
87
+ if (undefined !== document.createElement) {
88
+ var locationObject = document.createElement('a');
89
+ locationObject.href = this.path;
90
+ locationObject.pathname = locationObject.pathname.replace(regexMatch, suffixReplace);
91
+ this.at_2x_path = locationObject.href;
92
+ } else {
93
+ var parts = this.path.split('?');
94
+ parts[0] = parts[0].replace(regexMatch, suffixReplace);
95
+ this.at_2x_path = parts.join('?');
96
+ }
97
+ this.perform_check = true;
98
+ }
99
+ }
100
+
101
+ root.RetinaImagePath = RetinaImagePath;
102
+
103
+ RetinaImagePath.confirmed_paths = [];
104
+
105
+ RetinaImagePath.prototype.is_external = function() {
106
+ return !!(this.path.match(/^https?\:/i) && !this.path.match('//' + document.domain) );
107
+ };
108
+
109
+ RetinaImagePath.prototype.check_2x_variant = function(callback) {
110
+ var http, that = this;
111
+ if (this.is_external()) {
112
+ return callback(false);
113
+ } else if (!this.perform_check && typeof this.at_2x_path !== 'undefined' && this.at_2x_path !== null) {
114
+ return callback(true);
115
+ } else if (this.at_2x_path in RetinaImagePath.confirmed_paths) {
116
+ return callback(true);
117
+ } else {
118
+ http = new XMLHttpRequest();
119
+ http.open('HEAD', this.at_2x_path);
120
+ http.onreadystatechange = function() {
121
+ if (http.readyState !== 4) {
122
+ return callback(false);
123
+ }
124
+
125
+ if (http.status >= 200 && http.status <= 399) {
126
+ if (config.check_mime_type) {
127
+ var type = http.getResponseHeader('Content-Type');
128
+ if (type === null || !type.match(/^image/i)) {
129
+ return callback(false);
130
+ }
131
+ }
132
+
133
+ RetinaImagePath.confirmed_paths.push(that.at_2x_path);
134
+ return callback(true);
135
+ } else {
136
+ return callback(false);
137
+ }
138
+ };
139
+ http.send();
140
+ }
141
+ };
142
+
143
+
144
+ function RetinaImage(el) {
145
+ this.el = el;
146
+ this.path = new RetinaImagePath(this.el.getAttribute('src'), this.el.getAttribute('data-at2x'));
147
+ var that = this;
148
+ this.path.check_2x_variant(function(hasVariant) {
149
+ if (hasVariant) {
150
+ that.swap();
151
+ }
152
+ });
153
+ }
154
+
155
+ root.RetinaImage = RetinaImage;
156
+
157
+ RetinaImage.prototype.swap = function(path) {
158
+ if (typeof path === 'undefined') {
159
+ path = this.path.at_2x_path;
160
+ }
161
+
162
+ var that = this;
163
+ function load() {
164
+ if (! that.el.complete) {
165
+ setTimeout(load, 5);
166
+ } else {
167
+ if (config.force_original_dimensions) {
168
+ that.el.setAttribute('width', that.el.offsetWidth);
169
+ that.el.setAttribute('height', that.el.offsetHeight);
170
+ }
171
+
172
+ that.el.setAttribute('src', path);
173
+ }
174
+ }
175
+ load();
176
+ };
177
+
178
+
179
+ if (Retina.isRetina()) {
180
+ Retina.init(root);
181
+ }
182
+ })();
admin/js/retina.min.js CHANGED
@@ -1,10 +1,10 @@
1
- /*!
2
- * Retina.js v1.3.0
3
- *
4
- * Copyright 2014 Imulus, LLC
5
- * Released under the MIT license
6
- *
7
- * Retina.js is an open source script that makes it easy to serve
8
- * high-resolution images to devices with retina displays.
9
- */
10
  !function(){function a(){}function b(a){return f.retinaImageSuffix+a}function c(a,c){if(this.path=a||"","undefined"!=typeof c&&null!==c)this.at_2x_path=c,this.perform_check=!1;else{if(void 0!==document.createElement){var d=document.createElement("a");d.href=this.path,d.pathname=d.pathname.replace(g,b),this.at_2x_path=d.href}else{var e=this.path.split("?");e[0]=e[0].replace(g,b),this.at_2x_path=e.join("?")}this.perform_check=!0}}function d(a){this.el=a,this.path=new c(this.el.getAttribute("src"),this.el.getAttribute("data-at2x"));var b=this;this.path.check_2x_variant(function(a){a&&b.swap()})}var e="undefined"==typeof exports?window:exports,f={retinaImageSuffix:"@2x",check_mime_type:!0,force_original_dimensions:!0};e.Retina=a,a.configure=function(a){null===a&&(a={});for(var b in a)a.hasOwnProperty(b)&&(f[b]=a[b])},a.init=function(a){null===a&&(a=e);var b=a.onload||function(){};a.onload=function(){var a,c,e=document.getElementsByTagName("img"),f=[];for(a=0;a<e.length;a+=1)c=e[a],c.getAttributeNode("data-no-retina")||f.push(new d(c));b()}},a.isRetina=function(){var a="(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (min-resolution: 1.5dppx)";return e.devicePixelRatio>1?!0:e.matchMedia&&e.matchMedia(a).matches?!0:!1};var g=/\.\w+$/;e.RetinaImagePath=c,c.confirmed_paths=[],c.prototype.is_external=function(){return!(!this.path.match(/^https?\:/i)||this.path.match("//"+document.domain))},c.prototype.check_2x_variant=function(a){var b,d=this;return this.is_external()?a(!1):this.perform_check||"undefined"==typeof this.at_2x_path||null===this.at_2x_path?this.at_2x_path in c.confirmed_paths?a(!0):(b=new XMLHttpRequest,b.open("HEAD",this.at_2x_path),b.onreadystatechange=function(){if(4!==b.readyState)return a(!1);if(b.status>=200&&b.status<=399){if(f.check_mime_type){var e=b.getResponseHeader("Content-Type");if(null===e||!e.match(/^image/i))return a(!1)}return c.confirmed_paths.push(d.at_2x_path),a(!0)}return a(!1)},b.send(),void 0):a(!0)},e.RetinaImage=d,d.prototype.swap=function(a){function b(){c.el.complete?(f.force_original_dimensions&&(c.el.setAttribute("width",c.el.offsetWidth),c.el.setAttribute("height",c.el.offsetHeight)),c.el.setAttribute("src",a)):setTimeout(b,5)}"undefined"==typeof a&&(a=this.path.at_2x_path);var c=this;b()},a.isRetina()&&a.init(e)}();
1
+ /*!
2
+ * Retina.js v1.3.0
3
+ *
4
+ * Copyright 2014 Imulus, LLC
5
+ * Released under the MIT license
6
+ *
7
+ * Retina.js is an open source script that makes it easy to serve
8
+ * high-resolution images to devices with retina displays.
9
+ */
10
  !function(){function a(){}function b(a){return f.retinaImageSuffix+a}function c(a,c){if(this.path=a||"","undefined"!=typeof c&&null!==c)this.at_2x_path=c,this.perform_check=!1;else{if(void 0!==document.createElement){var d=document.createElement("a");d.href=this.path,d.pathname=d.pathname.replace(g,b),this.at_2x_path=d.href}else{var e=this.path.split("?");e[0]=e[0].replace(g,b),this.at_2x_path=e.join("?")}this.perform_check=!0}}function d(a){this.el=a,this.path=new c(this.el.getAttribute("src"),this.el.getAttribute("data-at2x"));var b=this;this.path.check_2x_variant(function(a){a&&b.swap()})}var e="undefined"==typeof exports?window:exports,f={retinaImageSuffix:"@2x",check_mime_type:!0,force_original_dimensions:!0};e.Retina=a,a.configure=function(a){null===a&&(a={});for(var b in a)a.hasOwnProperty(b)&&(f[b]=a[b])},a.init=function(a){null===a&&(a=e);var b=a.onload||function(){};a.onload=function(){var a,c,e=document.getElementsByTagName("img"),f=[];for(a=0;a<e.length;a+=1)c=e[a],c.getAttributeNode("data-no-retina")||f.push(new d(c));b()}},a.isRetina=function(){var a="(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (min-resolution: 1.5dppx)";return e.devicePixelRatio>1?!0:e.matchMedia&&e.matchMedia(a).matches?!0:!1};var g=/\.\w+$/;e.RetinaImagePath=c,c.confirmed_paths=[],c.prototype.is_external=function(){return!(!this.path.match(/^https?\:/i)||this.path.match("//"+document.domain))},c.prototype.check_2x_variant=function(a){var b,d=this;return this.is_external()?a(!1):this.perform_check||"undefined"==typeof this.at_2x_path||null===this.at_2x_path?this.at_2x_path in c.confirmed_paths?a(!0):(b=new XMLHttpRequest,b.open("HEAD",this.at_2x_path),b.onreadystatechange=function(){if(4!==b.readyState)return a(!1);if(b.status>=200&&b.status<=399){if(f.check_mime_type){var e=b.getResponseHeader("Content-Type");if(null===e||!e.match(/^image/i))return a(!1)}return c.confirmed_paths.push(d.at_2x_path),a(!0)}return a(!1)},b.send(),void 0):a(!0)},e.RetinaImage=d,d.prototype.swap=function(a){function b(){c.el.complete?(f.force_original_dimensions&&(c.el.setAttribute("width",c.el.offsetWidth),c.el.setAttribute("height",c.el.offsetHeight)),c.el.setAttribute("src",a)):setTimeout(b,5)}"undefined"==typeof a&&(a=this.path.at_2x_path);var c=this;b()},a.isRetina()&&a.init(e)}();
admin/js/wpsl-admin.js CHANGED
@@ -1,948 +1,1247 @@
1
  jQuery( document ).ready( function( $ ) {
2
- var map, geocoder, markersArray = [];
 
3
 
4
- if ( $( "#wpsl-gmap-wrap" ).length ) {
5
- initializeGmap();
6
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
- /*
9
- * If we're on the settings page, then check for returned
10
- * browser key errors from the autocomplete field
11
- * and validate the server key when necessary.
12
- */
13
- if ( $( "#wpsl-map-settings").length ) {
14
- observeBrowserKeyErrors();
15
 
16
  /**
17
- * Check if we need to validate the server key, and if no
18
- * error message is already visible after saving the setting page.
 
 
 
19
  */
20
- if ( $( "#wpsl-api-server-key" ).hasClass( "wpsl-validate-me" ) && !$( "#setting-error-server-key" ).length ) {
21
- validateServerKey();
22
- }
23
- }
24
-
25
- /**
26
- * Initialize the map with the correct settings.
27
- *
28
- * @since 1.0.0
29
- * @returns {void}
30
- */
31
- function initializeGmap() {
32
- var defaultLatLng = wpslSettings.defaultLatLng.split( "," ),
33
- latLng = new google.maps.LatLng( defaultLatLng[0], defaultLatLng[1] ),
34
- mapOptions = {};
35
-
36
- mapOptions = {
37
- zoom: parseInt( wpslSettings.defaultZoom ),
38
- center: latLng,
39
- mapTypeId: google.maps.MapTypeId[ wpslSettings.mapType.toUpperCase() ],
40
- mapTypeControl: false,
41
- streetViewControl: false,
42
- zoomControlOptions: {
43
- position: google.maps.ControlPosition.RIGHT_TOP
44
- }
45
- };
46
-
47
- geocoder = new google.maps.Geocoder();
48
- map = new google.maps.Map( document.getElementById( "wpsl-gmap-wrap" ), mapOptions );
49
-
50
- checkEditStoreMarker();
51
- }
52
-
53
- /**
54
- * Check if we have an existing latlng value.
55
- *
56
- * If there is an latlng value, then we add a marker to the map.
57
- * This can only happen on the edit store page.
58
- *
59
- * @since 1.0.0
60
- * @returns {void}
61
- */
62
- function checkEditStoreMarker() {
63
- var location,
64
- lat = $( "#wpsl-lat" ).val(),
65
- lng = $( "#wpsl-lng" ).val();
66
-
67
- if ( ( lat ) && ( lng ) ) {
68
- location = new google.maps.LatLng( lat, lng );
69
-
70
- map.setCenter( location );
71
- map.setZoom( 16 );
72
- addMarker( location );
73
- }
74
- }
75
-
76
- // If we have a city/country input field enable the autocomplete.
77
- if ( $( "#wpsl-start-name" ).length ) {
78
- activateAutoComplete();
79
- }
80
-
81
- /**
82
- * Activate the autocomplete function for the city/country field.
83
- *
84
- * @since 1.0.0
85
- * @returns {void}
86
- */
87
- function activateAutoComplete() {
88
- var latlng,
89
- input = document.getElementById( "wpsl-start-name" ),
90
- options = {
91
- types: ['geocode']
92
- },
93
- autocomplete = new google.maps.places.Autocomplete( input, options );
94
-
95
- google.maps.event.addListener( autocomplete, "place_changed", function() {
96
- latlng = autocomplete.getPlace().geometry.location;
97
- setLatlng( latlng, "zoom" );
98
- });
99
- }
100
-
101
- /**
102
- * Add a new marker to the map based on the provided location (latlng).
103
- *
104
- * @since 1.0.0
105
- * @param {object} location The latlng value
106
- * @returns {void}
107
- */
108
- function addMarker( location ) {
109
- var marker = new google.maps.Marker({
110
- position: location,
111
- map: map,
112
- draggable: true
113
- });
114
-
115
- markersArray.push( marker );
116
-
117
- // If the marker is dragged on the map, make sure the latlng values are updated.
118
- google.maps.event.addListener( marker, "dragend", function() {
119
- setLatlng( marker.getPosition(), "store" );
120
- });
121
- }
122
-
123
- // Lookup the provided location with the Google Maps API.
124
- $( "#wpsl-lookup-location" ).on( "click", function( e ) {
125
- e.preventDefault();
126
- codeAddress();
127
- });
128
-
129
- /**
130
- * Update the hidden input field with the current latlng values.
131
- *
132
- * @since 1.0.0
133
- * @param {object} latLng The latLng values
134
- * @param {string} target The location where we need to set the latLng
135
- * @returns {void}
136
- */
137
- function setLatlng( latLng, target ) {
138
- var coordinates = stripCoordinates( latLng ),
139
- lat = roundCoordinate( coordinates[0] ),
140
- lng = roundCoordinate( coordinates[1] );
141
-
142
- if ( target == "store" ) {
143
- $( "#wpsl-lat" ).val( lat );
144
- $( "#wpsl-lng" ).val( lng );
145
- } else if ( target == "zoom" ) {
146
- $( "#wpsl-latlng" ).val( lat + ',' + lng );
147
- }
148
- }
149
-
150
- /**
151
- * Geocode the user input.
152
- *
153
- * @since 1.0.0
154
- * @returns {void}
155
- */
156
- function codeAddress() {
157
- var filteredResponse, geocodeAddress;
158
-
159
- // Check if we have all the required data before attempting to geocode the address.
160
- if ( !validatePreviewFields() ) {
161
- geocodeAddress = createGeocodeAddress();
162
-
163
- geocoder.geocode( { 'address': geocodeAddress }, function( response, status ) {
164
- if ( status === google.maps.GeocoderStatus.OK ) {
165
-
166
- // If we have a previous marker on the map we remove it.
167
- if ( typeof( markersArray[0] ) !== "undefined" ) {
168
- if ( markersArray[0].draggable ) {
169
- markersArray[0].setMap( null );
170
- markersArray.splice(0, 1);
171
- }
172
- }
173
-
174
- // Center and zoom to the searched location.
175
- map.setCenter( response[0].geometry.location );
176
- map.setZoom( 16 );
177
-
178
- addMarker( response[0].geometry.location );
179
- setLatlng( response[0].geometry.location, "store" );
180
-
181
- filteredResponse = filterApiResponse( response );
182
-
183
- $( "#wpsl-country" ).val( filteredResponse.country.long_name );
184
- $( "#wpsl-country_iso" ).val( filteredResponse.country.short_name );
185
- } else {
186
- alert( wpslL10n.geocodeFail + ": " + status );
187
- }
188
- });
189
-
190
- return false;
191
- } else {
192
- activateStoreTab( "first" );
193
-
194
- alert( wpslL10n.missingGeoData );
195
-
196
- return true;
197
- }
198
- }
199
-
200
- /**
201
- * Check that all required fields for the map preview are there.
202
- *
203
- * @since 1.0.0
204
- * @returns {boolean} error Whether all the required fields contained data.
205
- */
206
- function validatePreviewFields() {
207
- var i, fieldData, requiredFields,
208
- error = false;
209
-
210
- $( ".wpsl-store-meta input" ).removeClass( "wpsl-error" );
211
-
212
- // Check which fields are required.
213
- if ( typeof wpslSettings.requiredFields !== "undefined" && _.isArray( wpslSettings.requiredFields ) ) {
214
- requiredFields = wpslSettings.requiredFields;
215
-
216
- // Check if all the required fields contain data.
217
- for ( i = 0; i < requiredFields.length; i++ ) {
218
- fieldData = $.trim( $( "#wpsl-" + requiredFields[i] ).val() );
219
-
220
- if ( !fieldData ) {
221
- $( "#wpsl-" + requiredFields[i] ).addClass( "wpsl-error" );
222
- error = true;
223
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
225
- fieldData = '';
 
 
 
 
 
226
  }
227
  }
228
 
229
- return error;
230
- }
231
-
232
- /**
233
- * Build the address that's send to the Geocode API.
234
- *
235
- * @since 2.1.0
236
- * @returns {string} geocodeAddress The address separated by , that's send to the Geocoder.
237
- */
238
- function createGeocodeAddress() {
239
- var i, part,
240
- address = [],
241
- addressParts = [ "address", "city", "state", "zip", "country" ];
242
-
243
- for ( i = 0; i < addressParts.length; i++ ) {
244
- part = $.trim( $( "#wpsl-" + addressParts[i] ).val() );
245
-
246
- /*
247
- * At this point we already know the address, city and country fields contain data.
248
- * But no need to include the zip and state if they are empty.
249
- */
250
- if ( part ) {
251
- address.push( part );
252
- }
253
-
254
- part = "";
255
- }
256
-
257
- return address.join();
258
- }
259
-
260
- /**
261
- * Filter out the country name from the API response.
262
- *
263
- * @since 1.0.0
264
- * @param {object} response The response of the geocode API
265
- * @returns {object} collectedData The short and long country name
266
- */
267
- function filterApiResponse( response ) {
268
- var i, responseType,
269
- country = {},
270
- collectedData = {},
271
- addressLength = response[0].address_components.length;
272
-
273
- // Loop over the API response.
274
- for ( i = 0; i < addressLength; i++ ) {
275
- responseType = response[0].address_components[i].types;
276
-
277
- // Filter out the country name.
278
- if ( /^country,political$/.test( responseType ) ) {
279
- country = {
280
- long_name: response[0].address_components[i].long_name,
281
- short_name: response[0].address_components[i].short_name
282
- };
283
- }
284
- }
285
-
286
- collectedData = {
287
- country: country
288
- };
289
-
290
- return collectedData;
291
- }
292
-
293
- /**
294
- * Round the coordinate to 6 digits after the comma.
295
- *
296
- * @since 1.0.0
297
- * @param {string} coordinate The coordinate
298
- * @returns {number} roundedCoord The rounded coordinate
299
- */
300
- function roundCoordinate( coordinate ) {
301
- var roundedCoord, decimals = 6;
302
-
303
- roundedCoord = Math.round( coordinate * Math.pow( 10, decimals ) ) / Math.pow( 10, decimals );
304
-
305
- return roundedCoord;
306
- }
307
-
308
- /**
309
- * Strip the '(' and ')' from the captured coordinates and split them.
310
- *
311
- * @since 1.0.0
312
- * @param {string} coordinates The coordinates
313
- * @returns {object} latLng The latlng coordinates
314
- */
315
- function stripCoordinates( coordinates ) {
316
- var latLng = [],
317
- selected = coordinates.toString(),
318
- latLngStr = selected.split( ",", 2 );
319
-
320
- latLng[0] = latLngStr[0].replace( "(", "" );
321
- latLng[1] = latLngStr[1].replace( ")", "" );
322
-
323
- return latLng;
324
- }
325
-
326
- $( ".wpsl-marker-list input[type=radio]" ).click( function() {
327
- $( this ).parents( ".wpsl-marker-list" ).find( "li" ).removeClass();
328
- $( this ).parent( "li" ).addClass( "wpsl-active-marker" );
329
- });
330
-
331
- $( ".wpsl-marker-list li" ).click( function() {
332
- $( this ).parents( ".wpsl-marker-list" ).find( "input" ).prop( "checked", false );
333
- $( this ).find( "input" ).prop( "checked", true );
334
- $( this ).siblings().removeClass();
335
- $( this ).addClass( "wpsl-active-marker" );
336
- });
337
-
338
- // Detect changes in checkboxes that have a conditional option.
339
- $( ".wpsl-has-conditional-option" ).on( "change", function() {
340
- $( this ).parent().next( ".wpsl-conditional-option" ).toggle();
341
- });
342
-
343
- /*
344
- * Detect changes to the store template dropdown. If the template is selected to
345
- * show the store list under the map then we show the option to hide the scrollbar.
346
- */
347
- $( "#wpsl-store-template" ).on( "change", function() {
348
- var $scrollOption = $( "#wpsl-listing-below-no-scroll" );
349
-
350
- if ( $( this ).val() == "below_map" ) {
351
- $scrollOption.show();
352
- } else {
353
- $scrollOption.hide();
354
- }
355
- });
356
-
357
- $( "#wpsl-api-region" ).on( "change", function() {
358
- var $geocodeComponent = $( "#wpsl-geocode-component" );
359
-
360
- if ( $( this ).val() ) {
361
- $geocodeComponent.show();
362
- } else {
363
- $geocodeComponent.hide();
364
- }
365
- });
366
-
367
- // Make sure the correct hour input format is visible.
368
- $( "#wpsl-editor-hour-input" ).on( "change", function() {
369
- $( ".wpsl-" + $( this ).val() + "-hours" ).show().siblings( "div" ).hide();
370
- $( ".wpsl-hour-notice" ).toggle();
371
- });
372
-
373
- // Set the correct tab to active and show the correct content block.
374
- $( "#wpsl-meta-nav li" ).on( "click", function( e ) {
375
- var activeClass = $( this ).attr( "class" );
376
- activeClass = activeClass.split( "-tab" );
377
-
378
- e.stopPropagation();
379
-
380
- // Set the correct tab and metabox to active.
381
- $( this ).addClass( "wpsl-active" ).siblings().removeClass( "wpsl-active" );
382
- $( ".wpsl-store-meta ." + activeClass[0] + "" ).show().addClass( "wpsl-active" ).siblings( "div" ).hide().removeClass( "wpsl-active" );
383
- });
384
-
385
- // Make sure the required store fields contain data.
386
- if ( $( "#wpsl-store-details" ).length ) {
387
- $( "#publish" ).click( function() {
388
- var firstErrorElem, currentTabClass, elemClass,
389
- errorMsg = '<div id="message" class="error"><p>' + wpslL10n.requiredFields + '</p></div>',
390
- missingData = false;
391
-
392
- // Remove error messages and css classes from previous submissions.
393
- $( "#wpbody-content .wrap #message" ).remove();
394
- $( ".wpsl-required" ).removeClass( "wpsl-error" );
395
-
396
- // Loop over the required fields and check for a value.
397
- $( ".wpsl-required" ).each( function() {
398
- if ( $( this ).val() == "" ) {
399
- $( this ).addClass( "wpsl-error" );
400
-
401
- if ( typeof firstErrorElem === "undefined" ) {
402
- firstErrorElem = getFirstErrorElemAttr( $( this ) );
403
- }
404
-
405
- missingData = true;
406
- }
407
- });
408
-
409
- // If one of the required fields are empty, then show the error msg and make sure the correct tab is visible.
410
- if ( missingData ) {
411
- $( "#wpbody-content .wrap > h2" ).after( errorMsg );
412
-
413
- if ( typeof firstErrorElem.val !== "undefined" ) {
414
- if ( firstErrorElem.type == "id" ) {
415
- currentTabClass = $( "#" + firstErrorElem.val + "" ).parents( ".wpsl-tab" ).attr( "class" );
416
- $( "html, body" ).scrollTop( Math.round( $( "#" + firstErrorElem.val + "" ).offset().top - 100 ) );
417
- } else if ( firstErrorElem.type == "class" ) {
418
- elemClass = firstErrorElem.val.replace( /wpsl-required|wpsl-error/g, "" );
419
- currentTabClass = $( "." + elemClass + "" ).parents( ".wpsl-tab" ).attr( "class" );
420
- $( "html, body" ).scrollTop( Math.round( $( "." + elemClass + "" ).offset().top - 100 ) );
421
- }
422
-
423
- currentTabClass = $.trim( currentTabClass.replace( /wpsl-tab|wpsl-active/g, "" ) );
424
- }
425
-
426
- // If we don't have a class of the tab that should be set to visible, we just show the first one.
427
- if ( !currentTabClass ) {
428
- activateStoreTab( 'first' );
429
- } else {
430
- activateStoreTab( currentTabClass );
431
- }
432
-
433
- /*
434
- * If not all required fields contains data, and the user has
435
- * clicked the submit button. Then an extra css class is added to the
436
- * button that will disabled it. This only happens in WP 3.8 or earlier.
437
- *
438
- * We need to make sure this css class doesn't exist otherwise
439
- * the user can never resubmit the page.
440
- */
441
- $( "#publish" ).removeClass( "button-primary-disabled" );
442
- $( ".spinner" ).hide();
443
-
444
- return false;
445
- } else {
446
- return true;
447
- }
448
- });
449
- }
450
-
451
- /**
452
- * Set the correct tab to visible, and hide all other metaboxes
453
- *
454
- * @since 2.0.0
455
- * @param {string} $target The name of the tab to show
456
- * @returns {void}
457
- */
458
- function activateStoreTab( $target ) {
459
- if ( $target == 'first' ) {
460
- $target = ':first-child';
461
- } else {
462
- $target = '.' + $target;
463
- }
464
-
465
- if ( !$( "#wpsl-meta-nav li" + $target + "-tab" ).hasClass( "wpsl-active" ) ) {
466
- $( "#wpsl-meta-nav li" + $target + "-tab" ).addClass( "wpsl-active" ).siblings().removeClass( "wpsl-active" );
467
- $( ".wpsl-store-meta > div" + $target + "" ).show().addClass( "wpsl-active" ).siblings( "div" ).hide().removeClass( "wpsl-active" );
468
- }
469
- }
470
-
471
- /**
472
- * Get the id or class of the first element that's an required field, but is empty.
473
- *
474
- * We need this to determine which tab we need to set active,
475
- * which will be the tab were the first error occured.
476
- *
477
- * @since 2.0.0
478
- * @param {object} elem The element the error occured on
479
- * @returns {object} firstErrorElem The id/class set on the first elem that an error occured on and the attr value
480
- */
481
- function getFirstErrorElemAttr( elem ) {
482
- var firstErrorElem = { "type": "id", "val" : elem.attr( "id" ) };
483
-
484
- // If no ID value exists, then check if we can get the class name.
485
- if ( typeof firstErrorElem.val === "undefined" ) {
486
- firstErrorElem = { "type": "class", "val" : elem.attr( "class" ) };
487
- }
488
-
489
- return firstErrorElem;
490
- }
491
-
492
- // If we have a store hours dropdown, init the event handler.
493
- if ( $( "#wpsl-store-hours" ).length ) {
494
- initHourEvents();
495
- }
496
-
497
- /**
498
- * Assign an event handler to the button that enables
499
- * users to remove an opening hour period.
500
- *
501
- * @since 2.0.0
502
- * @returns {void}
503
- */
504
- function initHourEvents() {
505
- $( "#wpsl-store-hours .wpsl-icon-cancel-circled" ).off();
506
- $( "#wpsl-store-hours .wpsl-icon-cancel-circled" ).on( "click", function() {
507
- removePeriod( $( this ) );
508
- });
509
- }
510
-
511
- // Add new openings period to the openings hours table.
512
- $( ".wpsl-add-period" ).on( "click", function( e ) {
513
- var newPeriod,
514
- hours = {},
515
- returnList = true,
516
- $tr = $( this ).parents( "tr" ),
517
- periodCount = currentPeriodCount( $( this ) ),
518
- periodCss = ( periodCount >= 1 ) ? "wpsl-current-period wpsl-multiple-periods" : "wpsl-current-period",
519
- day = $tr.find( ".wpsl-opening-hours" ).attr( "data-day" ),
520
- selectName = ( $( "#wpsl-settings-form" ).length ) ? "wpsl_editor[dropdown]" : "wpsl[hours]";
521
-
522
- newPeriod = '<div class="' + periodCss +'">';
523
- newPeriod += '<select autocomplete="off" name="' + selectName + '[' + day + '_open][]" class="wpsl-open-hour">' + createHourOptionList( returnList ) + '</select>';
524
- newPeriod += '<span> - </span>';
525
- newPeriod += '<select autocomplete="off" name="' + selectName + '[' + day + '_close][]" class="wpsl-close-hour">' + createHourOptionList( returnList ) + '</select>';
526
- newPeriod += '<div class="wpsl-icon-cancel-circled"></div>';
527
- newPeriod += '</div>';
528
-
529
- $tr.find( ".wpsl-store-closed" ).remove();
530
- $( "#wpsl-hours-" + day + "" ).append( newPeriod ).end();
531
-
532
- initHourEvents();
533
-
534
- if ( $( "#wpsl-editor-hour-format" ).val() == 24 ) {
535
- hours = {
536
- "open": "09:00",
537
- "close": "17:00"
538
- };
539
- } else {
540
- hours = {
541
- "open": "9:00 AM",
542
- "close": "5:00 PM"
543
- };
544
- }
545
-
546
- $tr.find( ".wpsl-open-hour:last option[value='" + hours.open + "']" ).attr( "selected", "selected" );
547
- $tr.find( ".wpsl-close-hour:last option[value='" + hours.close + "']" ).attr( "selected", "selected" );
548
-
549
- e.preventDefault();
550
- });
551
-
552
- /**
553
- * Remove an openings period
554
- *
555
- * @since 2.0.0
556
- * @param {object} elem The clicked element
557
- * @return {void}
558
- */
559
- function removePeriod( elem ) {
560
- var periodsLeft = currentPeriodCount( elem ),
561
- $tr = elem.parents( "tr" ),
562
- day = $tr.find( ".wpsl-opening-hours" ).attr( "data-day" );
563
-
564
- // If there was 1 opening hour left then we add the 'Closed' text.
565
- if ( periodsLeft == 1 ) {
566
- $tr.find( ".wpsl-opening-hours" ).html( "<p class='wpsl-store-closed'>" + wpslL10n.closedDate + "<input type='hidden' name='wpsl[hours][" + day + "_open]' value='' /></p>" );
567
- }
568
-
569
- // Remove the selected openings period.
570
- elem.parent().closest( ".wpsl-current-period" ).remove();
571
-
572
- // If the first element has the multiple class, then we need to remove it.
573
- if ( $tr.find( ".wpsl-opening-hours div:first-child" ).hasClass( "wpsl-multiple-periods" ) ) {
574
- $tr.find( ".wpsl-opening-hours div:first-child" ).removeClass( "wpsl-multiple-periods" );
575
- }
576
- }
577
-
578
- /**
579
- * Count the current opening periods in a day block
580
- *
581
- * @since 2.0.0
582
- * @param {object} elem The clicked element
583
- * @return {string} currentPeriods The ammount of period divs found
584
- */
585
- function currentPeriodCount( elem ) {
586
- var currentPeriods = elem.parents( "tr" ).find( ".wpsl-current-period" ).length;
587
-
588
- return currentPeriods;
589
- }
590
-
591
- /**
592
- * Create an option list with the correct opening hour format and interval
593
- *
594
- * @since 2.0.0
595
- * @param {string} returnList Whether to return the option list or call the setSelectedOpeningHours function
596
- * @return {mixed} optionList The html for the option list of or void
597
- */
598
- function createHourOptionList( returnList ) {
599
- var openingHours, openingHourInterval, hour, hrFormat,
600
- pm = false,
601
- twelveHrsAfternoon = false,
602
- pmOrAm = "",
603
- optionList = "",
604
- openingTimes = [],
605
- openingHourOptions = {
606
- "hours": {
607
- "hr12": [ 12, 1, 2, 3 ,4 ,5 ,6, 7, 8, 9, 10, 11, 12, 1, 2, 3 , 4, 5, 6, 7, 8, 9, 10, 11 ],
608
- "hr24": [ 0, 1, 2, 3 ,4 ,5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 ,16, 17, 18, 19, 20, 21, 22, 23 ]
609
- },
610
- "interval": [ '00', '15', '30', '45' ]
611
- };
612
-
613
- if ( $( "#wpsl-editor-hour-format" ).length ) {
614
- hrFormat = $( "#wpsl-editor-hour-format" ).val();
615
- } else {
616
- hrFormat = wpslSettings.hourFormat;
617
- }
618
-
619
- $( "#wpsl-store-hours td" ).removeAttr( "style" );
620
-
621
- if ( hrFormat == 12 ) {
622
- $( "#wpsl-store-hours" ).removeClass().addClass( "wpsl-twelve-format" );
623
- openingHours = openingHourOptions.hours.hr12;
624
- } else {
625
- $( "#wpsl-store-hours" ).removeClass().addClass( "wpsl-twentyfour-format" );
626
- openingHours = openingHourOptions.hours.hr24;
627
- }
628
-
629
- openingHourInterval = openingHourOptions.interval;
630
-
631
- for ( var i = 0; i < openingHours.length; i++ ) {
632
- hour = openingHours[i];
633
-
634
- /*
635
- * If the 12hr format is selected, then check if we need to show AM or PM.
636
- *
637
- * If the 24hr format is selected and the hour is a single digit
638
- * then we add a 0 to the start so 5:00 becomes 05:00.
639
- */
640
- if ( hrFormat == 12 ) {
641
- if ( hour >= 12 ) {
642
- pm = ( twelveHrsAfternoon ) ? true : false;
643
-
644
- twelveHrsAfternoon = true;
645
- }
646
-
647
- pmOrAm = ( pm ) ? "PM" : "AM";
648
- } else if ( ( hrFormat == 24 ) && ( hour.toString().length == 1 ) ) {
649
- hour = "0" + hour;
650
- }
651
-
652
- // Collect the new opening hour format and interval.
653
- for ( var j = 0; j < openingHourInterval.length; j++ ) {
654
- openingTimes.push( hour + ":" + openingHourInterval[j] + " " + pmOrAm );
655
- }
656
- }
657
-
658
- // Create the <option> list.
659
- for ( var i = 0; i < openingTimes.length; i++ ) {
660
- optionList = optionList + '<option value="' + $.trim( openingTimes[i] ) + '">' + $.trim( openingTimes[i] ) + '</option>';
661
- }
662
-
663
- if ( returnList ) {
664
- return optionList;
665
- } else {
666
- setSelectedOpeningHours( optionList, hrFormat );
667
- }
668
- }
669
-
670
- /**
671
- * Set the correct selected opening hour in the dropdown
672
- *
673
- * @since 2.0.0
674
- * @param {string} optionList The html for the option list
675
- * @param {string} hrFormat The html for the option list
676
- * @return {void}
677
- */
678
- function setSelectedOpeningHours( optionList, hrFormat ) {
679
- var splitHour, hourType, periodBlock,
680
- hours = {};
681
-
682
- /*
683
- * Loop over each open/close block and make sure the selected
684
- * value is still set as selected after changing the hr format.
685
- */
686
- $( ".wpsl-current-period" ).each( function() {
687
- periodBlock = $( this ),
688
- hours = {
689
- "open": $( this ).find( ".wpsl-open-hour" ).val(),
690
- "close": $( this ).find( ".wpsl-close-hour" ).val()
691
- };
692
-
693
- // Set the new hour format for both dropdowns.
694
- $( this ).find( "select" ).html( optionList ).promise().done( function() {
695
-
696
- // Select the correct start/end hours as selected.
697
- for ( var key in hours ) {
698
- if ( hours.hasOwnProperty( key ) ) {
699
-
700
- // Breakup the hour, so we can check the part before and after the : separately.
701
- splitHour = hours[key].split( ":" );
702
-
703
- if ( hrFormat == 12 ) {
704
- hourType = "";
705
-
706
- // Change the hours to a 12hr format and add the correct AM or PM.
707
- if ( hours[key].charAt( 0 ) == 0 ) {
708
- hours[key] = hours[key].substr( 1 );
709
- hourType = " AM";
710
- } else if ( ( splitHour[0].length == 2 ) && ( splitHour[0] > 12 ) ) {
711
- hours[key] = ( splitHour[0] - 12 ) + ":" + splitHour[1];
712
- hourType = " PM";
713
- } else if ( splitHour[0] < 12 ) {
714
- hours[key] = splitHour[0] + ":" + splitHour[1];
715
- hourType = " AM";
716
- } else if ( splitHour[0] == 12 ) {
717
- hours[key] = splitHour[0] + ":" + splitHour[1];
718
- hourType = " PM";
719
- }
720
-
721
- // Add either AM or PM behind the time.
722
- if ( ( splitHour[1].indexOf( "PM" ) == -1 ) && ( splitHour[1].indexOf( "AM" ) == -1 ) ) {
723
- hours[key] = hours[key] + hourType;
724
- }
725
-
726
- } else if ( hrFormat == 24 ) {
727
-
728
- // Change the hours to a 24hr format and remove the AM or PM.
729
- if ( splitHour[1].indexOf( "PM" ) != -1 ) {
730
- if ( splitHour[0] == 12 ) {
731
- hours[key] = "12:" + splitHour[1].replace( " PM", "" );
732
- } else {
733
- hours[key] = ( + splitHour[0] + 12 ) + ":" + splitHour[1].replace( " PM", "" );
734
- }
735
- } else if ( splitHour[1].indexOf( "AM" ) != -1 ) {
736
- if ( splitHour[0].toString().length == 1 ) {
737
- hours[key] = "0" + splitHour[0] + ":" + splitHour[1].replace( " AM", "" );
738
- } else {
739
- hours[key] = splitHour[0] + ":" + splitHour[1].replace( " AM", "" );
740
- }
741
- } else {
742
- hours[key] = splitHour[0] + ":" + splitHour[1]; // When the interval is changed
743
- }
744
- }
745
-
746
- // Set the correct value as the selected one.
747
- periodBlock.find( ".wpsl-" + key + "-hour option[value='" + $.trim( hours[key] ) + "']" ).attr( "selected", "selected" );
748
- }
749
- }
750
-
751
- });
752
- });
753
- }
754
-
755
- // Update the opening hours format if one of the dropdown values change.
756
- $( "#wpsl-editor-hour-format, #wpsl-editor-hour-interval" ).on( "change", function() {
757
- createHourOptionList();
758
- });
759
-
760
- // Show the tooltips.
761
- $( ".wpsl-info" ).on( "mouseover", function() {
762
- $( this ).find( ".wpsl-info-text" ).show();
763
- });
764
-
765
- $( ".wpsl-info" ).on( "mouseout", function() {
766
- $( this ).find( ".wpsl-info-text" ).hide();
767
- });
768
-
769
- // If the start location is empty, then we color the info icon red instead of black.
770
- if ( $( "#wpsl-latlng" ).length && !$( "#wpsl-latlng" ).val() ) {
771
- $( "#wpsl-latlng" ).siblings( "label" ).find( ".wpsl-info" ).addClass( "wpsl-required-setting" );
772
- }
773
-
774
- /**
775
- * Try to apply the custom style data to the map.
776
- *
777
- * If the style data is invalid json we show an error.
778
- *
779
- * @since 2.0.0
780
- * @return {void}
781
- */
782
- function tryCustomMapStyle() {
783
- var validStyle = "",
784
- mapStyle = $.trim( $( "#wpsl-map-style" ).val() );
785
-
786
- $( ".wpsl-style-preview-error" ).remove();
787
-
788
- if ( mapStyle ) {
789
-
790
- // Make sure the data is valid json.
791
- validStyle = tryParseJSON( mapStyle );
792
-
793
- if ( !validStyle ) {
794
- $( "#wpsl-style-preview" ).after( "<div class='wpsl-style-preview-error'>" + wpslL10n.styleError + "</div>" );
795
- }
796
- }
797
-
798
- map.setOptions({ styles: validStyle });
799
- }
800
-
801
- // Handle the map style changes on the settings page.
802
- if ( $( "#wpsl-map-style" ).val() ) {
803
- tryCustomMapStyle();
804
- }
805
-
806
- // Handle clicks on the map style preview button.
807
- $( "#wpsl-style-preview" ).on( "click", function() {
808
- tryCustomMapStyle();
809
-
810
- return false;
811
- });
812
-
813
- /**
814
- * Make sure the JSON is valid.
815
- *
816
- * @link http://stackoverflow.com/a/20392392/1065294
817
- * @since 2.0.0
818
- * @param {string} jsonString The JSON data
819
- * @return {object|boolean} The JSON string or false if it's invalid json.
820
- */
821
- function tryParseJSON( jsonString ) {
822
-
823
- try {
824
- var o = JSON.parse( jsonString );
825
-
826
- /*
827
- * Handle non-exception-throwing cases:
828
- * Neither JSON.parse(false) or JSON.parse(1234) throw errors, hence the type-checking,
829
- * but... JSON.parse(null) returns 'null', and typeof null === "object",
830
- * so we must check for that, too.
831
- */
832
- if ( o && typeof o === "object" && o !== null ) {
833
- return o;
834
  }
835
  }
836
- catch ( e ) { }
837
-
838
- return false;
839
- }
840
-
841
- /**
842
- * Look for changes on the start location input field.
843
- *
844
- * If there's a problem with the browser API key,
845
- * then a 'gm-err-autocomplete' class is added to the input field.
846
- *
847
- * When this happens we create a notice explaining how to fix the issue.
848
- *
849
- * @since 2.2.10
850
- * @return void
851
- */
852
- function observeBrowserKeyErrors() {
853
- var observer,
854
- attributeValue = '',
855
- MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver,
856
- $startName = $( "#wpsl-start-name" );
857
-
858
- if ( typeof MutationObserver !== "undefined" ) {
859
- observer = new MutationObserver( function( mutations ) {
860
-
861
- // Loop over the mutations.
862
- mutations.forEach( function( mutation ) {
863
- if ( mutation.attributeName === "class" ) {
864
- attributeValue = $( mutation.target ).prop( mutation.attributeName );
865
-
866
- // Look for a specific class that's added when there's a problem with the browser API key
867
- if ( ( attributeValue.indexOf( "gm-err-autocomplete" ) !== -1 ) ) {
868
- createErrorNotice( wpslL10n.browserKeyError, "browser-key" );
869
- }
870
- }
871
- });
872
- });
873
-
874
- observer.observe( $startName[0], {
875
- attributes: true
876
- });
877
- }
878
- }
879
-
880
- /**
881
- * Make a request to the geocode API with the
882
- * provided server key to check for any errors.
883
- *
884
- * @since 2.2.10
885
- * @return void
886
- */
887
- function validateServerKey() {
888
- var ajaxData = {
889
- action: "validate_server_key",
890
- server_key: $( "#wpsl-api-server-key" ).val()
891
- };
892
 
893
- $.get( wpslSettings.ajaxurl, ajaxData, function( response ) {
894
- if ( !response.valid && typeof response.msg !== "undefined" ) {
895
- createErrorNotice( response.msg, "server-key" );
896
- } else {
897
- $( "#wpsl-api-server-key" ).removeClass( "wpsl-error" );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
898
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
899
  });
900
- }
901
-
902
- /**
903
- * Create the error notice.
904
- *
905
- * @since 2.2.10
906
- * @param {string} errorMsg The error message to show
907
- * @param {string} type The type of API key we need to show the notice for
908
- * @return void
909
- */
910
- function createErrorNotice( errorMsg, type ) {
911
- var errorNotice, noticeLocation;
912
-
913
- errorNotice = '<div id="setting-error-' + type + '" class="error settings-error notice is-dismissible">';
914
- errorNotice += '<p><strong>' + errorMsg + '</strong></p>';
915
- errorNotice += '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' + wpslL10n.dismissNotice + '</span></button>';
916
- errorNotice += '</div>';
917
-
918
- noticeLocation = ( $( "#wpsl-tabs" ).length ) ? 'wpsl-tabs' : 'wpsl-settings-form';
919
-
920
- $( "#" + noticeLocation + "" ).before( errorNotice );
921
- $( "#wpsl-api-" + type + "").addClass( "wpsl-error" );
922
- }
923
-
924
- // Make sure the custom error notices can be removed
925
- $( "#wpsl-wrap" ).on( "click", "button.notice-dismiss", function() {
926
- $( this ).closest( 'div.notice' ).remove();
927
- })
928
-
929
- /**
930
- * Handle the red warning that's shown next to the
931
- * force zipcode search option if the autocomplete
932
- * value is changed.
933
- *
934
- * The autocomplete option itself doesn't support
935
- * zip only searches, so having both of them enabled
936
- * gives the user the wrong expectation.
937
- */
938
- $( "#wpsl-search-autocomplete, #wpsl-force-postalcode" ).change( function() {
939
- var $info = $( "#wpsl-force-postalcode" ).parent( "p" ).find( ".wpsl-info-zip-only" );
940
-
941
- if ( $( "#wpsl-search-autocomplete" ).is( ":checked" ) && $( "#wpsl-force-postalcode" ).is( ":checked" ) ) {
942
- $info.show();
943
- } else {
944
- $info.hide();
945
- }
946
- });
947
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
948
  });
1
  jQuery( document ).ready( function( $ ) {
2
+ var map, geocoder, startLatLng, markersArray = [],
3
+ wpslAdmin = wpslAdmin || {};
4
 
5
+ /**
6
+ * Verify the provided API keys
7
+ *
8
+ * @since 2.2.22
9
+ */
10
+ wpslAdmin.verifyKeys = {
11
+ init: function() {
12
+ var self = this,
13
+ $btn = $( "#wpsl-verify-keys" ),
14
+ preloader = wpslSettings.url + "img/ajax-loader.gif",
15
+ mapService = ( typeof wpslSettings.mapService !== "undefined" ) ? wpslSettings.mapService : "gmaps";
16
+
17
+ $btn.on( "click", function() {
18
+ $( "#wpsl-wrap .notice" ).remove();
19
+
20
+ self[mapService].check();
21
+
22
+ $btn.after( '<img src="' + preloader + '" class="wpsl-api-key-preloader" />' );
23
+
24
+ return false;
25
+ });
26
+ },
27
+ /**
28
+ * Show the status of the API keys.
29
+ *
30
+ * @since 2.2.22
31
+ * @param {string} response The API response
32
+ * @param {string} keyType The type of API key we need to show the notice for
33
+ * @param {string} noticeType Show either an error or success notice.
34
+ * @returns {void}
35
+ */
36
+ showStatus: function( response, keyType, noticeType = "error" ) {
37
+
38
+ this.createNotice( response, keyType, noticeType );
39
+
40
+ // After the browser check has finished we remove the preloader.
41
+ if ( keyType == "browser" ) {
42
+ $( ".wpsl-api-key-preloader" ).remove();
43
+ }
44
+ },
45
+ /**
46
+ * Create the error notice.
47
+ *
48
+ * @since 2.2.10
49
+ * @param {string} response The API response to show
50
+ * @param {string} keyType The type of API key we need to show the notice for
51
+ * @param {string} noticeType Show either an error or success notice.
52
+ * @returns void
53
+ */
54
+ createNotice: function( response, keyType, noticeType ) {
55
+ var notice, noticeLocation, cssClass;
56
+
57
+ cssClass = ( noticeType == "error" ) ? "error" : "updated";
58
+
59
+ notice = '<div class="' + cssClass + ' notice is-dismissible">';
60
+ notice += '<p><strong>' + response + '</strong></p>';
61
+ notice += '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' + wpslL10n.dismissNotice + '</span></button>';
62
+ notice += '</div>';
63
+
64
+ noticeLocation = ( $( "#wpsl-tabs" ).length ) ? "wpsl-tabs" : "wpsl-settings-form";
65
+
66
+ $( "#" + noticeLocation + "" ).before( notice );
67
+
68
+ if ( noticeType == "error" ) {
69
+ $( "#wpsl-api-" + keyType + "-key" ).addClass( "wpsl-error" );
70
+ } else {
71
+ $( "#wpsl-api-" + keyType + "-key" ).removeClass( "wpsl-error" );
72
+ }
73
+ },
74
+ gmaps: {
75
+ /**
76
+ * Check for any issues with the used API keys.
77
+ *
78
+ * @since 2.2.22
79
+ * @returns {void}
80
+ */
81
+ check: function() {
82
+ this.server(function() {
83
+ wpslAdmin.verifyKeys.gmaps.browser();
84
+ });
85
+ },
86
+ /**
87
+ * Make a request to the Google Geocode API to
88
+ * check if the server key is valid or not.
89
+ *
90
+ * @since 2.2.22
91
+ * @returns {void}
92
+ */
93
+ server: function( callback ) {
94
+ var status,
95
+ ajaxData = {
96
+ action: "validate_server_key",
97
+ server_key: $( "#wpsl-api-server-key" ).val()
98
+ };
99
+
100
+ if ( ajaxData.server_key ) {
101
+ $.get( wpslSettings.ajaxurl, ajaxData, function( response ) {
102
+ status = ( response.valid ) ? "updated" : "error";
103
+
104
+ wpslAdmin.verifyKeys.showStatus( response.msg, "server", status );
105
+
106
+ callback();
107
+ });
108
+ } else {
109
+ wpslAdmin.verifyKeys.showStatus( wpslL10n.serverKeyMissing, "server" );
110
+
111
+ callback();
112
+ }
113
+ },
114
+ /**
115
+ * Make a request to the Google JavaScript API to
116
+ * check if the browser key is valid or not.
117
+ *
118
+ * @since 2.2.22
119
+ * @returns {void}
120
+ */
121
+ browser: function() {
122
+ var browserAPICheck,
123
+ browserKey = $( "#wpsl-api-browser-key" ).val();
124
+
125
+ if ( browserKey ) {
126
+
127
+ /**
128
+ * Wait 3 seconds before checking if the
129
+ * Geocode API returned data.
130
+ *
131
+ * If this hasn't happened, then there has to
132
+ * be a problem with the API keys.
133
+ */
134
+ browserAPICheck = setInterval(function() {
135
+ wpslAdmin.verifyKeys.showStatus( wpslL10n.browserKeyError, "browser" );
136
+
137
+ clearInterval( browserAPICheck );
138
+ }, 3000 );
139
+
140
+ /**
141
+ * This will only complete if there are no issues
142
+ * with the API key, otherwise it won't even make a request.
143
+ *
144
+ * To check this we use the setInterval in the above section.
145
+ */
146
+ geocoder.geocode( { 'address': 'Manhattan, NY 10036, USA' }, function( response, status ) {
147
+ if ( status == google.maps.GeocoderStatus.OK ) {
148
+ wpslAdmin.verifyKeys.showStatus( wpslL10n.browserKeySuccess, "browser", "success" );
149
+ } else {
150
+ wpslAdmin.verifyKeys.showStatus( wpslL10n.browserKeyError, "browser" );
151
+ }
152
+
153
+ clearInterval( browserAPICheck );
154
+ });
155
+ } else {
156
+ wpslAdmin.verifyKeys.showStatus( wpslL10n.browserKeyMissing, "browser" );
157
+ }
158
+ }
159
+ }
160
+ };
161
+
162
+ /**
163
+ * Handle the Geocode requests made from
164
+ * the Tools section on the settings page.
165
+ *
166
+ * This can be used to check the API response
167
+ * for any input the user provides, and see if
168
+ * it's in the expected location.
169
+ *
170
+ * Will show error messages if there are any
171
+ * issues with the used browser API keys.
172
+ *
173
+ * @since 2.2.22
174
+ */
175
+ wpslAdmin.showApiResponse = {
176
+ init: function() {
177
+ var $geocodeInput = $( "#wpsl-geocode-input" ),
178
+ self = this,
179
+ mapLoaded = false;
180
+
181
+ $( "#wpsl-show-geocode-response" ).on( "click", function( e ) {
182
+ self.createDialog();
183
+
184
+ initializeGmap("wpsl-geocode-preview" );
185
+
186
+ // Make sure we don't add the same message twice.
187
+ if ( !$( ".wpsl-geocode-warning span" ).length ) {
188
+ self.createRestrictionsMsg();
189
+ }
190
+
191
+ // Check for map errors after it finished loading.
192
+ google.maps.event.addListenerOnce( map, "tilesloaded", function() {
193
+ mapLoaded = true;
194
+ self.checkQuotaError();
195
+ });
196
+
197
+ // Check if the map was load succesfully, if not show an error message explaining it.
198
+ setTimeout(function() {
199
+ if ( !mapLoaded ) {
200
+ $(".wpsl-geocode-warning, #wpsl-geocode-test input, #wpsl-geocode-tabs").remove();
201
+ $(".wpsl-geocode-api-notice").show().html( wpslL10n.loadingFailed );
202
+ }
203
+ }, 1000 );
204
+
205
+ return false;
206
+ });
207
+
208
+ // Submit the geocode request.
209
+ $( "#wpsl-geocode-submit" ).on( "click", function( e ) {
210
+ $geocodeInput.removeClass( "wpsl-error" );
211
+
212
+ if ( !$geocodeInput.val() ) {
213
+ $geocodeInput.addClass( "wpsl-error" );
214
+ $( ".wpsl-geocode-api-notice" ).hide();
215
+ } else {
216
+ self.geocoding.makeRequest();
217
+ }
218
+ });
219
+
220
+ // Handle users using the enter key in the dialog box.
221
+ $( "#wpsl-geocode-test" ).keydown( function( event ) {
222
+ var keyCode = ( event.keyCode ? event.keyCode : event.which );
223
+
224
+ if ( keyCode == 13 ) {
225
+ $( "#wpsl-geocode-submit" ).trigger( "click" );
226
+ }
227
+ });
228
+ },
229
+ /**
230
+ * Create the dialog box
231
+ *
232
+ * @since 2.2.22
233
+ * @returns {void}
234
+ */
235
+ createDialog: function() {
236
+ $( "#wpsl-geocode-test" ).dialog({
237
+ resizable: false,
238
+ height: "auto",
239
+ width: 550,
240
+ modal: true,
241
+ open: function() {
242
+
243
+ // Move it closer to the top then it normally would
244
+ $( this ).parent().css({ "top": window.pageYOffset + 50 });
245
+
246
+ $( "#wpsl-geocode-tabs" ).tabs();
247
+ $( "#wpsl-geocode-input" ).focus();
248
+ $( ".wpsl-geocode-api-notice" ).hide();
249
+
250
+ // Make sure the first tab is always selected after the dialog is opened a second time
251
+ $( "#wpsl-geocode-tabs" ).tabs( "option", "active", $( "li" ).index( $( "li:visible:eq(0)" ) ) );
252
+
253
+ // Make sure to remove any previous input
254
+ $( "#wpsl-geocode-input, #wpsl-geocode-response textarea" ).val( "" );
255
+
256
+ $( ".ui-widget-overlay" ).bind( "click", function() {
257
+ $( "#wpsl-geocode-test" ).dialog( "close" );
258
+ });
259
+ },
260
+ buttons: {
261
+ Cancel: function() {
262
+ $( this ).dialog( "close" );
263
+ }
264
+ }
265
+ });
266
+ },
267
+ /**
268
+ * If there's a problem with the billing account,
269
+ * then a 'dismissButton' class will exist in the map itself.
270
+ *
271
+ * If this is the case, then we remove everything and
272
+ * show an error explaining the problem.
273
+ *
274
+ * @since 2.2.22
275
+ * @returns {void}
276
+ */
277
+ checkQuotaError: function() {
278
+
279
+ setTimeout(function() {
280
+ if ( $( "#wpsl-geocode-preview .dismissButton" ).length > 0 ) {
281
+ $( ".wpsl-geocode-warning, #wpsl-geocode-test input" ).remove();
282
+
283
+ $( ".wpsl-geocode-api-notice" ).show();
284
+ $( ".wpsl-geocode-api-notice span" ).html( wpslL10n.loadingError );
285
+ }
286
+
287
+ }, 1000 );
288
+ },
289
+ /**
290
+ * Create a message explaning the user that the
291
+ * results are restricted to the selected map region,
292
+ * and possibly only work for zip codes.
293
+ *
294
+ * @since 2.2.22
295
+ * @returns {void}
296
+ */
297
+ createRestrictionsMsg: function() {
298
+ var countryName, zipcodeOnly,
299
+ $warningElem = $( ".wpsl-geocode-warning" ).show().find( "strong" );
300
+
301
+ if ( $( "#wpsl-api-region" ).val() ) {
302
+ countryName = $( "#wpsl-api-region option:selected" ).text();
303
+ zipcodeOnly = ( $( "#wpsl-force-postalcode" ).is( ":checked" ) ) ? wpslL10n.restrictedZipCode : '';
304
+
305
+ $warningElem.after( "<span>" + wpslL10n.resultsWarning + ' ' + countryName + ' ' + zipcodeOnly + "</span>" );
306
+ } else {
307
+ $warningElem.after( "<span>" + wpslL10n.noRestriction + "</span>" );
308
+
309
+ $( ".wpsl-region-href" ).on( "click", function() {
310
+ $( ".ui-widget-overlay" ).trigger( "click" );
311
+ });
312
+ }
313
+ },
314
+ geocoding: {
315
+ /**
316
+ * Geocode the provided user input
317
+ *
318
+ * @since 2.2.22
319
+ * @returns {void}
320
+ */
321
+ makeRequest: function() {
322
+ var request = this.createParams();
323
+
324
+ geocoder.geocode( request, function( response, status ) {
325
+
326
+ // Show an error message if there is a problem with the browser API key.
327
+ if ( status == "OK" || status == "ZERO_RESULTS" ) {
328
+
329
+ // Make sure to remove the marker from the map if one exists.
330
+ if ( typeof markersArray[0] !== "undefined" ) {
331
+ markersArray[0].setMap( null );
332
+ markersArray.length = 0;
333
+ }
334
+
335
+ if ( status == "OK" ) {
336
+ addMarker( response[0].geometry.location, false );
337
+
338
+ map.setZoom( 12 );
339
+ map.setCenter( response[0].geometry.location );
340
+ } else {
341
+ map.setZoom( parseInt( wpslSettings.defaultZoom ) );
342
+ map.setCenter( startLatLng );
343
+ }
344
+ } else {
345
+ status = wpslL10n.browserKeyError;
346
+
347
+ $( "#wpsl-geocode-preview, #wpsl-geocode-response textarea" ).remove();
348
+ }
349
+
350
+ $( ".wpsl-geocode-api-notice" ).show();
351
+ $( ".wpsl-geocode-api-notice span" ).html( status );
352
+ $( "#wpsl-geocode-response textarea" ).val( JSON.stringify( response, null, 4 ) );
353
+ });
354
+ },
355
+ /**
356
+ * Create the params used in the geocode request
357
+ * made through the test tool in the tools section.
358
+ *
359
+ * @since 2.2.22
360
+ * @return {object} request The parameters included in the geocode API request
361
+ */
362
+ createParams: function() {
363
+ var request = {};
364
+
365
+ // Check if we need to set the geocode component restrictions.
366
+ if ( typeof wpslSettings.geocodeComponents !== "undefined" && !$.isEmptyObject( wpslSettings.geocodeComponents ) ) {
367
+ request.componentRestrictions = wpslSettings.geocodeComponents;
368
+
369
+ if ( typeof request.componentRestrictions.postalCode !== "undefined" ) {
370
+ request.componentRestrictions.postalCode = $( "#wpsl-geocode-input" ).val();
371
+ } else {
372
+ request.address = $( "#wpsl-geocode-input" ).val();
373
+ }
374
+ } else {
375
+ request.address = $( "#wpsl-geocode-input" ).val();
376
+ }
377
+
378
+ return request;
379
+ },
380
+ },
381
+ };
382
+
383
+ if ( $( "#wpsl-gmap-wrap" ).length ) {
384
+ initializeGmap();
385
+ }
386
 
387
+ /**
388
+ * If we are on the settings page, then init the API tools.
389
+ */
390
+ if ( $( "#wpsl-map-settings").length ) {
391
+ wpslAdmin.verifyKeys.init();
392
+ wpslAdmin.showApiResponse.init();
393
+ }
394
 
395
  /**
396
+ * Initialize the map with the correct settings.
397
+ *
398
+ * @since 1.0.0
399
+ * @param string mapId The ID of the element to render the map in
400
+ * @returns {void}
401
  */
402
+ function initializeGmap( mapId = "wpsl-gmap-wrap" ) {
403
+ var defaultLatLng = wpslSettings.defaultLatLng.split( "," ),
404
+ mapOptions;
405
+
406
+ startLatLng = new google.maps.LatLng( defaultLatLng[0], defaultLatLng[1] );
407
+ mapOptions = {
408
+ zoom: parseInt( wpslSettings.defaultZoom ),
409
+ center: startLatLng,
410
+ mapTypeId: google.maps.MapTypeId[ wpslSettings.mapType.toUpperCase() ],
411
+ mapTypeControl: false,
412
+ streetViewControl: false,
413
+ zoomControlOptions: {
414
+ position: google.maps.ControlPosition.RIGHT_TOP
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
  }
416
+ };
417
+
418
+ geocoder = new google.maps.Geocoder();
419
+ map = new google.maps.Map( document.getElementById( mapId ), mapOptions );
420
+
421
+ checkEditStoreMarker();
422
+ }
423
+
424
+ /**
425
+ * Check if we have an existing latlng value.
426
+ *
427
+ * If there is an latlng value, then we add a marker to the map.
428
+ * This can only happen on the edit store page.
429
+ *
430
+ * @since 1.0.0
431
+ * @returns {void}
432
+ */
433
+ function checkEditStoreMarker() {
434
+ var location,
435
+ lat = $( "#wpsl-lat" ).val(),
436
+ lng = $( "#wpsl-lng" ).val();
437
 
438
+ if ( ( lat ) && ( lng ) ) {
439
+ location = new google.maps.LatLng( lat, lng );
440
+
441
+ map.setCenter( location );
442
+ map.setZoom( 16 );
443
+ addMarker( location );
444
  }
445
  }
446
 
447
+ // If we have a city/country input field enable the autocomplete.
448
+ if ( $( "#wpsl-start-name" ).length ) {
449
+ activateAutoComplete();
450
+ }
451
+
452
+ /**
453
+ * Activate the autocomplete function for the city/country field.
454
+ *
455
+ * @since 1.0.0
456
+ * @returns {void}
457
+ */
458
+ function activateAutoComplete() {
459
+ var latlng,
460
+ input = document.getElementById( "wpsl-start-name" ),
461
+ options = {
462
+ types: ['geocode']
463
+ },
464
+ autocomplete = new google.maps.places.Autocomplete( input, options );
465
+
466
+ google.maps.event.addListener( autocomplete, "place_changed", function() {
467
+ latlng = autocomplete.getPlace().geometry.location;
468
+ setLatlng( latlng, "zoom" );
469
+ });
470
+ }
471
+
472
+ /**
473
+ * Add a new marker to the map based on the provided location (latlng).
474
+ *
475
+ * @since 1.0.0
476
+ * @param {object} location The latlng value
477
+ * @param {boolean} draggable Whether the marker should be draggable or not
478
+ * @returns {void}
479
+ */
480
+ function addMarker( location, draggable = true ) {
481
+ var marker = new google.maps.Marker({
482
+ position: location,
483
+ map: map,
484
+ draggable: draggable
485
+ });
486
+
487
+ markersArray.push( marker );
488
+
489
+ // If the marker is dragged on the map, make sure the latlng values are updated.
490
+ google.maps.event.addListener( marker, "dragend", function() {
491
+ setLatlng( marker.getPosition(), "store" );
492
+ });
493
+ }
494
+
495
+ // Lookup the provided location with the Google Maps API.
496
+ $( "#wpsl-lookup-location" ).on( "click", function( e ) {
497
+ e.preventDefault();
498
+ codeAddress();
499
+ });
500
+
501
+ /**
502
+ * Update the hidden input field with the current latlng values.
503
+ *
504
+ * @since 1.0.0
505
+ * @param {object} latLng The latLng values
506
+ * @param {string} target The location where we need to set the latLng
507
+ * @returns {void}
508
+ */
509
+ function setLatlng( latLng, target ) {
510
+ var coordinates = stripCoordinates( latLng ),
511
+ lat = roundCoordinate( coordinates[0] ),
512
+ lng = roundCoordinate( coordinates[1] );
513
+
514
+ if ( target == "store" ) {
515
+ $( "#wpsl-lat" ).val( lat );
516
+ $( "#wpsl-lng" ).val( lng );
517
+ } else if ( target == "zoom" ) {
518
+ $( "#wpsl-latlng" ).val( lat + ',' + lng );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
519
  }
520
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
521
 
522
+ /**
523
+ * Geocode the user input.
524
+ *
525
+ * @since 1.0.0
526
+ * @returns {void}
527
+ */
528
+ function codeAddress() {
529
+ var filteredResponse, geocodeAddress;
530
+
531
+ // Check if we have all the required data before attempting to geocode the address.
532
+ if ( !validatePreviewFields() ) {
533
+ geocodeAddress = createGeocodeAddress();
534
+
535
+ geocoder.geocode( { 'address': geocodeAddress }, function( response, status ) {
536
+ if ( status === google.maps.GeocoderStatus.OK ) {
537
+
538
+ // If we have a previous marker on the map we remove it.
539
+ if ( typeof( markersArray[0] ) !== "undefined" ) {
540
+ if ( markersArray[0].draggable ) {
541
+ markersArray[0].setMap( null );
542
+ markersArray.splice(0, 1);
543
+ }
544
+ }
545
+
546
+ // Center and zoom to the searched location.
547
+ map.setCenter( response[0].geometry.location );
548
+ map.setZoom( 16 );
549
+
550
+ addMarker( response[0].geometry.location );
551
+ setLatlng( response[0].geometry.location, "store" );
552
+
553
+ filteredResponse = filterApiResponse( response );
554
+
555
+ $( "#wpsl-country" ).val( filteredResponse.country.long_name );
556
+ $( "#wpsl-country_iso" ).val( filteredResponse.country.short_name );
557
+ } else {
558
+ alert( wpslL10n.geocodeFail + ": " + status );
559
+ }
560
+ });
561
+
562
+ return false;
563
+ } else {
564
+ activateStoreTab( "first" );
565
+
566
+ alert( wpslL10n.missingGeoData );
567
+
568
+ return true;
569
  }
570
+ }
571
+
572
+ /**
573
+ * Check that all required fields for the map preview are there.
574
+ *
575
+ * @since 1.0.0
576
+ * @returns {boolean} error Whether all the required fields contained data.
577
+ */
578
+ function validatePreviewFields() {
579
+ var i, fieldData, requiredFields,
580
+ error = false;
581
+
582
+ $( ".wpsl-store-meta input" ).removeClass( "wpsl-error" );
583
+
584
+ // Check which fields are required.
585
+ if ( typeof wpslSettings.requiredFields !== "undefined" && _.isArray( wpslSettings.requiredFields ) ) {
586
+ requiredFields = wpslSettings.requiredFields;
587
+
588
+ // Check if all the required fields contain data.
589
+ for ( i = 0; i < requiredFields.length; i++ ) {
590
+ fieldData = $.trim( $( "#wpsl-" + requiredFields[i] ).val() );
591
+
592
+ if ( !fieldData ) {
593
+ $( "#wpsl-" + requiredFields[i] ).addClass( "wpsl-error" );
594
+ error = true;
595
+ }
596
+
597
+ fieldData = '';
598
+ }
599
+ }
600
+
601
+ return error;
602
+ }
603
+
604
+ /**
605
+ * Build the address that's send to the Geocode API.
606
+ *
607
+ * @since 2.1.0
608
+ * @returns {string} geocodeAddress The address separated by , that's send to the Geocoder.
609
+ */
610
+ function createGeocodeAddress() {
611
+ var i, part,
612
+ address = [],
613
+ addressParts = [ "address", "city", "state", "zip", "country" ];
614
+
615
+ for ( i = 0; i < addressParts.length; i++ ) {
616
+ part = $.trim( $( "#wpsl-" + addressParts[i] ).val() );
617
+
618
+ /*
619
+ * At this point we already know the address, city and country fields contain data.
620
+ * But no need to include the zip and state if they are empty.
621
+ */
622
+ if ( part ) {
623
+ address.push( part );
624
+ }
625
+
626
+ part = "";
627
+ }
628
+
629
+ return address.join();
630
+ }
631
+
632
+ /**
633
+ * Filter out the country name from the API response.
634
+ *
635
+ * @since 1.0.0
636
+ * @param {object} response The response of the geocode API
637
+ * @returns {object} collectedData The short and long country name
638
+ */
639
+ function filterApiResponse( response ) {
640
+ var i, responseType, collectedData,
641
+ country = {},
642
+ addressLength = response[0].address_components.length;
643
+
644
+ // Loop over the API response.
645
+ for ( i = 0; i < addressLength; i++ ) {
646
+ responseType = response[0].address_components[i].types;
647
+
648
+ // Filter out the country name.
649
+ if ( /^country,political$/.test( responseType ) ) {
650
+ country = {
651
+ long_name: response[0].address_components[i].long_name,
652
+ short_name: response[0].address_components[i].short_name
653
+ };
654
+ }
655
+ }
656
+
657
+ collectedData = {
658
+ country: country
659
+ };
660
+
661
+ return collectedData;
662
+ }
663
+
664
+ /**
665
+ * Round the coordinate to 6 digits after the comma.
666
+ *
667
+ * @since 1.0.0
668
+ * @param {string} coordinate The coordinate
669
+ * @returns {number} roundedCoord The rounded coordinate
670
+ */
671
+ function roundCoordinate( coordinate ) {
672
+ var roundedCoord, decimals = 6;
673
+
674
+ roundedCoord = Math.round( coordinate * Math.pow( 10, decimals ) ) / Math.pow( 10, decimals );
675
+
676
+ return roundedCoord;
677
+ }
678
+
679
+ /**
680
+ * Strip the '(' and ')' from the captured coordinates and split them.
681
+ *
682
+ * @since 1.0.0
683
+ * @param {string} coordinates The coordinates
684
+ * @returns {object} latLng The latlng coordinates
685
+ */
686
+ function stripCoordinates( coordinates ) {
687
+ var latLng = [],
688
+ selected = coordinates.toString(),
689
+ latLngStr = selected.split( ",", 2 );
690
+
691
+ latLng[0] = latLngStr[0].replace( "(", "" );
692
+ latLng[1] = latLngStr[1].replace( ")", "" );
693
+
694
+ return latLng;
695
+ }
696
+
697
+ $( ".wpsl-marker-list input[type=radio]" ).click( function() {
698
+ $( this ).parents( ".wpsl-marker-list" ).find( "li" ).removeClass();
699
+ $( this ).parent( "li" ).addClass( "wpsl-active-marker" );
700
+ });
701
+
702
+ $( ".wpsl-marker-list li" ).click( function() {
703
+ $( this ).parents( ".wpsl-marker-list" ).find( "input" ).prop( "checked", false );
704
+ $( this ).find( "input" ).prop( "checked", true );
705
+ $( this ).siblings().removeClass();
706
+ $( this ).addClass( "wpsl-active-marker" );
707
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
708
 
709
+ // Detect changes in checkboxes that have a conditional option.
710
+ $( ".wpsl-has-conditional-option" ).on( "change", function() {
711
+ $( this ).parent().next( ".wpsl-conditional-option" ).toggle();
712
+ });
713
+
714
+ /*
715
+ * Detect changes to the store template dropdown. If the template is selected to
716
+ * show the store list under the map then we show the option to hide the scrollbar.
717
+ */
718
+ $( "#wpsl-store-template" ).on( "change", function() {
719
+ var $scrollOption = $( "#wpsl-listing-below-no-scroll" );
720
+
721
+ if ( $( this ).val() == "below_map" ) {
722
+ $scrollOption.show();
723
+ } else {
724
+ $scrollOption.hide();
725
+ }
726
+ });
727
+
728
+ $( "#wpsl-api-region" ).on( "change", function() {
729
+ var $geocodeComponent = $( "#wpsl-geocode-component" );
730
+
731
+ if ( $( this ).val() ) {
732
+ $geocodeComponent.show();
733
+ } else {
734
+ $geocodeComponent.hide();
735
+ }
736
+ });
737
+
738
+ // Make sure the correct hour input format is visible.
739
+ $( "#wpsl-editor-hour-input" ).on( "change", function() {
740
+ $( ".wpsl-" + $( this ).val() + "-hours" ).show().siblings( "div" ).hide();
741
+ $( ".wpsl-hour-notice" ).toggle();
742
+ });
743
+
744
+ // Set the correct tab to active and show the correct content block.
745
+ $( "#wpsl-meta-nav li" ).on( "click", function( e ) {
746
+ var activeClass = $( this ).attr( "class" );
747
+ activeClass = activeClass.split( "-tab" );
748
+
749
+ e.stopPropagation();
750
+
751
+ // Set the correct tab and metabox to active.
752
+ $( this ).addClass( "wpsl-active" ).siblings().removeClass( "wpsl-active" );
753
+ $( ".wpsl-store-meta ." + activeClass[0] + "" ).show().addClass( "wpsl-active" ).siblings( "div" ).hide().removeClass( "wpsl-active" );
754
+ });
755
+
756
+ // Make sure the required store fields contain data.
757
+ if ( $( "#wpsl-store-details" ).length ) {
758
+ $( "#publish" ).click( function() {
759
+ var firstErrorElem, currentTabClass, elemClass,
760
+ errorMsg = '<div id="message" class="error"><p>' + wpslL10n.requiredFields + '</p></div>',
761
+ missingData = false;
762
+
763
+ // Remove error messages and css classes from previous submissions.
764
+ $( "#wpbody-content .wrap #message" ).remove();
765
+ $( ".wpsl-required" ).removeClass( "wpsl-error" );
766
+
767
+ // Loop over the required fields and check for a value.
768
+ $( ".wpsl-required" ).each( function() {
769
+ if ( $( this ).val() == "" ) {
770
+ $( this ).addClass( "wpsl-error" );
771
+
772
+ if ( typeof firstErrorElem === "undefined" ) {
773
+ firstErrorElem = getFirstErrorElemAttr( $( this ) );
774
+ }
775
+
776
+ missingData = true;
777
+ }
778
+ });
779
+
780
+ // If one of the required fields are empty, then show the error msg and make sure the correct tab is visible.
781
+ if ( missingData ) {
782
+ $( "#wpbody-content .wrap > h2" ).after( errorMsg );
783
+
784
+ if ( typeof firstErrorElem.val !== "undefined" ) {
785
+ if ( firstErrorElem.type == "id" ) {
786
+ currentTabClass = $( "#" + firstErrorElem.val + "" ).parents( ".wpsl-tab" ).attr( "class" );
787
+ $( "html, body" ).scrollTop( Math.round( $( "#" + firstErrorElem.val + "" ).offset().top - 100 ) );
788
+ } else if ( firstErrorElem.type == "class" ) {
789
+ elemClass = firstErrorElem.val.replace( /wpsl-required|wpsl-error/g, "" );
790
+ currentTabClass = $( "." + elemClass + "" ).parents( ".wpsl-tab" ).attr( "class" );
791
+ $( "html, body" ).scrollTop( Math.round( $( "." + elemClass + "" ).offset().top - 100 ) );
792
+ }
793
+
794
+ currentTabClass = $.trim( currentTabClass.replace( /wpsl-tab|wpsl-active/g, "" ) );
795
+ }
796
+
797
+ // If we don't have a class of the tab that should be set to visible, we just show the first one.
798
+ if ( !currentTabClass ) {
799
+ activateStoreTab( 'first' );
800
+ } else {
801
+ activateStoreTab( currentTabClass );
802
+ }
803
+
804
+ /*
805
+ * If not all required fields contains data, and the user has
806
+ * clicked the submit button. Then an extra css class is added to the
807
+ * button that will disabled it. This only happens in WP 3.8 or earlier.
808
+ *
809
+ * We need to make sure this css class doesn't exist otherwise
810
+ * the user can never resubmit the page.
811
+ */
812
+ $( "#publish" ).removeClass( "button-primary-disabled" );
813
+ $( ".spinner" ).hide();
814
+
815
+ return false;
816
+ } else {
817
+ return true;
818
+ }
819
+ });
820
+ }
821
+
822
+ /**
823
+ * Set the correct tab to visible, and hide all other metaboxes
824
+ *
825
+ * @since 2.0.0
826
+ * @param {string} $target The name of the tab to show
827
+ * @returns {void}
828
+ */
829
+ function activateStoreTab( $target ) {
830
+ if ( $target == 'first' ) {
831
+ $target = ':first-child';
832
+ } else {
833
+ $target = '.' + $target;
834
+ }
835
+
836
+ if ( !$( "#wpsl-meta-nav li" + $target + "-tab" ).hasClass( "wpsl-active" ) ) {
837
+ $( "#wpsl-meta-nav li" + $target + "-tab" ).addClass( "wpsl-active" ).siblings().removeClass( "wpsl-active" );
838
+ $( ".wpsl-store-meta > div" + $target + "" ).show().addClass( "wpsl-active" ).siblings( "div" ).hide().removeClass( "wpsl-active" );
839
+ }
840
+ }
841
+
842
+ /**
843
+ * Get the id or class of the first element that's an required field, but is empty.
844
+ *
845
+ * We need this to determine which tab we need to set active,
846
+ * which will be the tab were the first error occured.
847
+ *
848
+ * @since 2.0.0
849
+ * @param {object} elem The element the error occured on
850
+ * @returns {object} firstErrorElem The id/class set on the first elem that an error occured on and the attr value
851
+ */
852
+ function getFirstErrorElemAttr( elem ) {
853
+ var firstErrorElem = { "type": "id", "val" : elem.attr( "id" ) };
854
+
855
+ // If no ID value exists, then check if we can get the class name.
856
+ if ( typeof firstErrorElem.val === "undefined" ) {
857
+ firstErrorElem = { "type": "class", "val" : elem.attr( "class" ) };
858
+ }
859
+
860
+ return firstErrorElem;
861
+ }
862
+
863
+ // If we have a store hours dropdown, init the event handler.
864
+ if ( $( "#wpsl-store-hours" ).length ) {
865
+ initHourEvents();
866
+ }
867
+
868
+ /**
869
+ * Assign an event handler to the button that enables
870
+ * users to remove an opening hour period.
871
+ *
872
+ * @since 2.0.0
873
+ * @returns {void}
874
+ */
875
+ function initHourEvents() {
876
+ $( "#wpsl-store-hours .wpsl-icon-cancel-circled" ).off();
877
+ $( "#wpsl-store-hours .wpsl-icon-cancel-circled" ).on( "click", function() {
878
+ removePeriod( $( this ) );
879
+ });
880
+ }
881
+
882
+ // Add new openings period to the openings hours table.
883
+ $( ".wpsl-add-period" ).on( "click", function( e ) {
884
+ var newPeriod,
885
+ hours = {},
886
+ returnList = true,
887
+ $tr = $( this ).parents( "tr" ),
888
+ periodCount = currentPeriodCount( $( this ) ),
889
+ periodCss = ( periodCount >= 1 ) ? "wpsl-current-period wpsl-multiple-periods" : "wpsl-current-period",
890
+ day = $tr.find( ".wpsl-opening-hours" ).attr( "data-day" ),
891
+ selectName = ( $( "#wpsl-settings-form" ).length ) ? "wpsl_editor[dropdown]" : "wpsl[hours]";
892
+
893
+ newPeriod = '<div class="' + periodCss +'">';
894
+ newPeriod += '<select autocomplete="off" name="' + selectName + '[' + day + '_open][]" class="wpsl-open-hour">' + createHourOptionList( returnList ) + '</select>';
895
+ newPeriod += '<span> - </span>';
896
+ newPeriod += '<select autocomplete="off" name="' + selectName + '[' + day + '_close][]" class="wpsl-close-hour">' + createHourOptionList( returnList ) + '</select>';
897
+ newPeriod += '<div class="wpsl-icon-cancel-circled"></div>';
898
+ newPeriod += '</div>';
899
+
900
+ $tr.find( ".wpsl-store-closed" ).remove();
901
+ $( "#wpsl-hours-" + day + "" ).append( newPeriod ).end();
902
+
903
+ initHourEvents();
904
+
905
+ if ( $( "#wpsl-editor-hour-format" ).val() == 24 ) {
906
+ hours = {
907
+ "open": "09:00",
908
+ "close": "17:00"
909
+ };
910
+ } else {
911
+ hours = {
912
+ "open": "9:00 AM",
913
+ "close": "5:00 PM"
914
+ };
915
+ }
916
+
917
+ $tr.find( ".wpsl-open-hour:last option[value='" + hours.open + "']" ).attr( "selected", "selected" );
918
+ $tr.find( ".wpsl-close-hour:last option[value='" + hours.close + "']" ).attr( "selected", "selected" );
919
+
920
+ e.preventDefault();
921
+ });
922
+
923
+ /**
924
+ * Remove an openings period
925
+ *
926
+ * @since 2.0.0
927
+ * @param {object} elem The clicked element
928
+ * @return {void}
929
+ */
930
+ function removePeriod( elem ) {
931
+ var periodsLeft = currentPeriodCount( elem ),
932
+ $tr = elem.parents( "tr" ),
933
+ day = $tr.find( ".wpsl-opening-hours" ).attr( "data-day" );
934
+
935
+ // If there was 1 opening hour left then we add the 'Closed' text.
936
+ if ( periodsLeft == 1 ) {
937
+ $tr.find( ".wpsl-opening-hours" ).html( "<p class='wpsl-store-closed'>" + wpslL10n.closedDate + "<input type='hidden' name='wpsl[hours][" + day + "_open]' value='' /></p>" );
938
+ }
939
+
940
+ // Remove the selected openings period.
941
+ elem.parent().closest( ".wpsl-current-period" ).remove();
942
+
943
+ // If the first element has the multiple class, then we need to remove it.
944
+ if ( $tr.find( ".wpsl-opening-hours div:first-child" ).hasClass( "wpsl-multiple-periods" ) ) {
945
+ $tr.find( ".wpsl-opening-hours div:first-child" ).removeClass( "wpsl-multiple-periods" );
946
+ }
947
+ }
948
+
949
+ /**
950
+ * Count the current opening periods in a day block
951
+ *
952
+ * @since 2.0.0
953
+ * @param {object} elem The clicked element
954
+ * @return {string} currentPeriods The ammount of period divs found
955
+ */
956
+ function currentPeriodCount( elem ) {
957
+ var currentPeriods = elem.parents( "tr" ).find( ".wpsl-current-period" ).length;
958
+
959
+ return currentPeriods;
960
+ }
961
+
962
+ /**
963
+ * Create an option list with the correct opening hour format and interval
964
+ *
965
+ * @since 2.0.0
966
+ * @param {string} returnList Whether to return the option list or call the setSelectedOpeningHours function
967
+ * @return {mixed} optionList The html for the option list of or void
968
+ */
969
+ function createHourOptionList( returnList ) {
970
+ var openingHours, openingHourInterval, hour, hrFormat,
971
+ pm = false,
972
+ twelveHrsAfternoon = false,
973
+ pmOrAm = "",
974
+ optionList = "",
975
+ openingTimes = [],
976
+ openingHourOptions = {
977
+ "hours": {
978
+ "hr12": [ 12, 1, 2, 3 ,4 ,5 ,6, 7, 8, 9, 10, 11, 12, 1, 2, 3 , 4, 5, 6, 7, 8, 9, 10, 11 ],
979
+ "hr24": [ 0, 1, 2, 3 ,4 ,5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 ,16, 17, 18, 19, 20, 21, 22, 23 ]
980
+ },
981
+ "interval": [ '00', '15', '30', '45' ]
982
+ };
983
+
984
+ if ( $( "#wpsl-editor-hour-format" ).length ) {
985
+ hrFormat = $( "#wpsl-editor-hour-format" ).val();
986
+ } else {
987
+ hrFormat = wpslSettings.hourFormat;
988
+ }
989
+
990
+ $( "#wpsl-store-hours td" ).removeAttr( "style" );
991
+
992
+ if ( hrFormat == 12 ) {
993
+ $( "#wpsl-store-hours" ).removeClass().addClass( "wpsl-twelve-format" );
994
+ openingHours = openingHourOptions.hours.hr12;
995
+ } else {
996
+ $( "#wpsl-store-hours" ).removeClass().addClass( "wpsl-twentyfour-format" );
997
+ openingHours = openingHourOptions.hours.hr24;
998
+ }
999
+
1000
+ openingHourInterval = openingHourOptions.interval;
1001
+
1002
+ for ( var i = 0; i < openingHours.length; i++ ) {
1003
+ hour = openingHours[i];
1004
+
1005
+ /*
1006
+ * If the 12hr format is selected, then check if we need to show AM or PM.
1007
+ *
1008
+ * If the 24hr format is selected and the hour is a single digit
1009
+ * then we add a 0 to the start so 5:00 becomes 05:00.
1010
+ */
1011
+ if ( hrFormat == 12 ) {
1012
+ if ( hour >= 12 ) {
1013
+ pm = ( twelveHrsAfternoon ) ? true : false;
1014
+
1015
+ twelveHrsAfternoon = true;
1016
+ }
1017
+
1018
+ pmOrAm = ( pm ) ? "PM" : "AM";
1019
+ } else if ( ( hrFormat == 24 ) && ( hour.toString().length == 1 ) ) {
1020
+ hour = "0" + hour;
1021
+ }
1022
+
1023
+ // Collect the new opening hour format and interval.
1024
+ for ( var j = 0; j < openingHourInterval.length; j++ ) {
1025
+ openingTimes.push( hour + ":" + openingHourInterval[j] + " " + pmOrAm );
1026
+ }
1027
+ }
1028
+
1029
+ // Create the <option> list.
1030
+ for ( var i = 0; i < openingTimes.length; i++ ) {
1031
+ optionList = optionList + '<option value="' + $.trim( openingTimes[i] ) + '">' + $.trim( openingTimes[i] ) + '</option>';
1032
+ }
1033
+
1034
+ if ( returnList ) {
1035
+ return optionList;
1036
+ } else {
1037
+ setSelectedOpeningHours( optionList, hrFormat );
1038
+ }
1039
+ }
1040
+
1041
+ /**
1042
+ * Set the correct selected opening hour in the dropdown
1043
+ *
1044
+ * @since 2.0.0
1045
+ * @param {string} optionList The html for the option list
1046
+ * @param {string} hrFormat The html for the option list
1047
+ * @return {void}
1048
+ */
1049
+ function setSelectedOpeningHours( optionList, hrFormat ) {
1050
+ var splitHour, hourType, periodBlock,
1051
+ hours = {};
1052
+
1053
+ /*
1054
+ * Loop over each open/close block and make sure the selected
1055
+ * value is still set as selected after changing the hr format.
1056
+ */
1057
+ $( ".wpsl-current-period" ).each( function() {
1058
+ periodBlock = $( this ),
1059
+ hours = {
1060
+ "open": $( this ).find( ".wpsl-open-hour" ).val(),
1061
+ "close": $( this ).find( ".wpsl-close-hour" ).val()
1062
+ };
1063
+
1064
+ // Set the new hour format for both dropdowns.
1065
+ $( this ).find( "select" ).html( optionList ).promise().done( function() {
1066
+
1067
+ // Select the correct start/end hours as selected.
1068
+ for ( var key in hours ) {
1069
+ if ( hours.hasOwnProperty( key ) ) {
1070
+
1071
+ // Breakup the hour, so we can check the part before and after the : separately.
1072
+ splitHour = hours[key].split( ":" );
1073
+
1074
+ if ( hrFormat == 12 ) {
1075
+ hourType = "";
1076
+
1077
+ // Change the hours to a 12hr format and add the correct AM or PM.
1078
+ if ( hours[key].charAt( 0 ) == 0 ) {
1079
+ hours[key] = hours[key].substr( 1 );
1080
+ hourType = " AM";
1081
+ } else if ( ( splitHour[0].length == 2 ) && ( splitHour[0] > 12 ) ) {
1082
+ hours[key] = ( splitHour[0] - 12 ) + ":" + splitHour[1];
1083
+ hourType = " PM";
1084
+ } else if ( splitHour[0] < 12 ) {
1085
+ hours[key] = splitHour[0] + ":" + splitHour[1];
1086
+ hourType = " AM";
1087
+ } else if ( splitHour[0] == 12 ) {
1088
+ hours[key] = splitHour[0] + ":" + splitHour[1];
1089
+ hourType = " PM";
1090
+ }
1091
+
1092
+ // Add either AM or PM behind the time.
1093
+ if ( ( splitHour[1].indexOf( "PM" ) == -1 ) && ( splitHour[1].indexOf( "AM" ) == -1 ) ) {
1094
+ hours[key] = hours[key] + hourType;
1095
+ }
1096
+
1097
+ } else if ( hrFormat == 24 ) {
1098
+
1099
+ // Change the hours to a 24hr format and remove the AM or PM.
1100
+ if ( splitHour[1].indexOf( "PM" ) != -1 ) {
1101
+ if ( splitHour[0] == 12 ) {
1102
+ hours[key] = "12:" + splitHour[1].replace( " PM", "" );
1103
+ } else {
1104
+ hours[key] = ( + splitHour[0] + 12 ) + ":" + splitHour[1].replace( " PM", "" );
1105
+ }
1106
+ } else if ( splitHour[1].indexOf( "AM" ) != -1 ) {
1107
+ if ( splitHour[0].toString().length == 1 ) {
1108
+ hours[key] = "0" + splitHour[0] + ":" + splitHour[1].replace( " AM", "" );
1109
+ } else {
1110
+ hours[key] = splitHour[0] + ":" + splitHour[1].replace( " AM", "" );
1111
+ }
1112
+ } else {
1113
+ hours[key] = splitHour[0] + ":" + splitHour[1]; // When the interval is changed
1114
+ }
1115
+ }
1116
+
1117
+ // Set the correct value as the selected one.
1118
+ periodBlock.find( ".wpsl-" + key + "-hour option[value='" + $.trim( hours[key] ) + "']" ).attr( "selected", "selected" );
1119
+ }
1120
+ }
1121
+
1122
+ });
1123
+ });
1124
+ }
1125
+
1126
+ // Update the opening hours format if one of the dropdown values change.
1127
+ $( "#wpsl-editor-hour-format, #wpsl-editor-hour-interval" ).on( "change", function() {
1128
+ createHourOptionList();
1129
+ });
1130
+
1131
+ // Show the tooltips.
1132
+ $( ".wpsl-info" ).on( "mouseover", function() {
1133
+ $( this ).find( ".wpsl-info-text" ).show();
1134
+ });
1135
+
1136
+ $( ".wpsl-info" ).on( "mouseout", function() {
1137
+ $( this ).find( ".wpsl-info-text" ).hide();
1138
+ });
1139
+
1140
+ // If the start location is empty, then we color the info icon red instead of black.
1141
+ if ( $( "#wpsl-latlng" ).length && !$( "#wpsl-latlng" ).val() ) {
1142
+ $( "#wpsl-latlng" ).siblings( "label" ).find( ".wpsl-info" ).addClass( "wpsl-required-setting" );
1143
+ }
1144
+
1145
+ /**
1146
+ * Try to apply the custom style data to the map.
1147
+ *
1148
+ * If the style data is invalid json we show an error.
1149
+ *
1150
+ * @since 2.0.0
1151
+ * @return {void}
1152
+ */
1153
+ function tryCustomMapStyle() {
1154
+ var validStyle = "",
1155
+ mapStyle = $.trim( $( "#wpsl-map-style" ).val() );
1156
+
1157
+ $( ".wpsl-style-preview-error" ).remove();
1158
+
1159
+ if ( mapStyle ) {
1160
+
1161
+ // Make sure the data is valid json.
1162
+ validStyle = tryParseJSON( mapStyle );
1163
+
1164
+ if ( !validStyle ) {
1165
+ $( "#wpsl-style-preview" ).after( "<div class='wpsl-style-preview-error'>" + wpslL10n.styleError + "</div>" );
1166
+ }
1167
+ }
1168
+
1169
+ map.setOptions({ styles: validStyle });
1170
+ }
1171
+
1172
+ // Handle the map style changes on the settings page.
1173
+ if ( $( "#wpsl-map-style" ).val() ) {
1174
+ tryCustomMapStyle();
1175
+ }
1176
+
1177
+ // Handle clicks on the map style preview button.
1178
+ $( "#wpsl-style-preview" ).on( "click", function() {
1179
+ tryCustomMapStyle();
1180
+
1181
+ return false;
1182
+ });
1183
+
1184
+ /**
1185
+ * Make sure the JSON is valid.
1186
+ *
1187
+ * @link http://stackoverflow.com/a/20392392/1065294
1188
+ * @since 2.0.0
1189
+ * @param {string} jsonString The JSON data
1190
+ * @return {object|boolean} The JSON string or false if it's invalid json.
1191
+ */
1192
+ function tryParseJSON( jsonString ) {
1193
+
1194
+ try {
1195
+ var o = JSON.parse( jsonString );
1196
+
1197
+ /*
1198
+ * Handle non-exception-throwing cases:
1199
+ * Neither JSON.parse(false) or JSON.parse(1234) throw errors, hence the type-checking,
1200
+ * but... JSON.parse(null) returns 'null', and typeof null === "object",
1201
+ * so we must check for that, too.
1202
+ */
1203
+ if ( o && typeof o === "object" && o !== null ) {
1204
+ return o;
1205
+ }
1206
+ }
1207
+ catch ( e ) { }
1208
+
1209
+ return false;
1210
+ }
1211
+
1212
+ // Make sure the custom error notices can be removed
1213
+ $( "#wpsl-wrap" ).on( "click", "button.notice-dismiss", function() {
1214
+ $( this ).closest( 'div.notice' ).remove();
1215
+ });
1216
+
1217
+ /**
1218
+ * Handle the red warning that's shown next to the
1219
+ * force zipcode search option if the autocomplete
1220
+ * value is changed.
1221
+ *
1222
+ * The autocomplete option itself doesn't support
1223
+ * zip only searches, so having both of them enabled
1224
+ * gives the user the wrong expectation.
1225
+ */
1226
+ $( "#wpsl-search-autocomplete, #wpsl-force-postalcode" ).change( function() {
1227
+ var $info = $( "#wpsl-force-postalcode" ).parent( "p" ).find( ".wpsl-info-zip-only" );
1228
+
1229
+ if ( $( "#wpsl-search-autocomplete" ).is( ":checked" ) && $( "#wpsl-force-postalcode" ).is( ":checked" ) ) {
1230
+ $info.show();
1231
+ } else {
1232
+ $info.hide();
1233
+ }
1234
+ });
1235
+
1236
+ $( "#wpsl-delay-loading" ).change( function() {
1237
+ if ( $( this ).is( ":checked" ) ) {
1238
+ $( this ).parent( "p" ).find( ".wpsl-info" ).trigger( "mouseover" );
1239
+ } else {
1240
+ $( this ).parent( "p" ).find( ".wpsl-info" ).trigger( "mouseout" );
1241
+ }
1242
+ });
1243
+
1244
+ $( "#wpsl-wrap" ).on( "click", function( e ) {
1245
+ $( ".wpsl-info-text" ).hide();
1246
+ });
1247
  });
admin/js/wpsl-admin.min.js CHANGED
@@ -1 +1 @@
1
- jQuery(document).ready(function(e){var s,t,o,l,r,n,i,a,p,c,d,w,u,h=[];function v(e){var t=new google.maps.Marker({position:e,map:s,draggable:!0});h.push(t),google.maps.event.addListener(t,"dragend",function(){m(t.getPosition(),"store")})}function m(s,t){var o,l,r=(o=[],l=s.toString().split(",",2),o[0]=l[0].replace("(",""),o[1]=l[1].replace(")",""),o),n=g(r[0]),i=g(r[1]);"store"==t?(e("#wpsl-lat").val(n),e("#wpsl-lng").val(i)):"zoom"==t&&e("#wpsl-latlng").val(n+","+i)}function g(e){return Math.round(e*Math.pow(10,6))/Math.pow(10,6)}function f(s){e("#wpsl-meta-nav li"+(s="first"==s?":first-child":"."+s)+"-tab").hasClass("wpsl-active")||(e("#wpsl-meta-nav li"+s+"-tab").addClass("wpsl-active").siblings().removeClass("wpsl-active"),e(".wpsl-store-meta > div"+s).show().addClass("wpsl-active").siblings("div").hide().removeClass("wpsl-active"))}function y(){e("#wpsl-store-hours .wpsl-icon-cancel-circled").off(),e("#wpsl-store-hours .wpsl-icon-cancel-circled").on("click",function(){!function(e){var s=b(e),t=e.parents("tr"),o=t.find(".wpsl-opening-hours").attr("data-day");1==s&&t.find(".wpsl-opening-hours").html("<p class='wpsl-store-closed'>"+wpslL10n.closedDate+"<input type='hidden' name='wpsl[hours]["+o+"_open]' value='' /></p>");e.parent().closest(".wpsl-current-period").remove(),t.find(".wpsl-opening-hours div:first-child").hasClass("wpsl-multiple-periods")&&t.find(".wpsl-opening-hours div:first-child").removeClass("wpsl-multiple-periods")}(e(this))})}function b(e){return e.parents("tr").find(".wpsl-current-period").length}function C(s){var t,o,l,r,n,i,a,p,c,d,w=!1,u=!1,h="",v="",m=[],g={hr12:[12,1,2,3,4,5,6,7,8,9,10,11,12,1,2,3,4,5,6,7,8,9,10,11],hr24:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]},f=["00","15","30","45"];r=e("#wpsl-editor-hour-format").length?e("#wpsl-editor-hour-format").val():wpslSettings.hourFormat,e("#wpsl-store-hours td").removeAttr("style"),12==r?(e("#wpsl-store-hours").removeClass().addClass("wpsl-twelve-format"),t=g.hr12):(e("#wpsl-store-hours").removeClass().addClass("wpsl-twentyfour-format"),t=g.hr24),o=f;for(var y=0;y<t.length;y++){l=t[y],12==r?(l>=12&&(w=!!u,u=!0),h=w?"PM":"AM"):24==r&&1==l.toString().length&&(l="0"+l);for(var b=0;b<o.length;b++)m.push(l+":"+o[b]+" "+h)}for(y=0;y<m.length;y++)v=v+'<option value="'+e.trim(m[y])+'">'+e.trim(m[y])+"</option>";if(s)return v;n=v,i=r,d={},e(".wpsl-current-period").each(function(){c=e(this),d={open:e(this).find(".wpsl-open-hour").val(),close:e(this).find(".wpsl-close-hour").val()},e(this).find("select").html(n).promise().done(function(){for(var s in d)d.hasOwnProperty(s)&&(a=d[s].split(":"),12==i?(p="",0==d[s].charAt(0)?(d[s]=d[s].substr(1),p=" AM"):2==a[0].length&&a[0]>12?(d[s]=a[0]-12+":"+a[1],p=" PM"):a[0]<12?(d[s]=a[0]+":"+a[1],p=" AM"):12==a[0]&&(d[s]=a[0]+":"+a[1],p=" PM"),-1==a[1].indexOf("PM")&&-1==a[1].indexOf("AM")&&(d[s]=d[s]+p)):24==i&&(-1!=a[1].indexOf("PM")?12==a[0]?d[s]="12:"+a[1].replace(" PM",""):d[s]=+a[0]+12+":"+a[1].replace(" PM",""):-1!=a[1].indexOf("AM")?1==a[0].toString().length?d[s]="0"+a[0]+":"+a[1].replace(" AM",""):d[s]=a[0]+":"+a[1].replace(" AM",""):d[s]=a[0]+":"+a[1]),c.find(".wpsl-"+s+"-hour option[value='"+e.trim(d[s])+"']").attr("selected","selected"))})})}function k(){var t="",o=e.trim(e("#wpsl-map-style").val());e(".wpsl-style-preview-error").remove(),o&&((t=function(e){try{var s=JSON.parse(e);if(s&&"object"==typeof s&&null!==s)return s}catch(e){}return!1}(o))||e("#wpsl-style-preview").after("<div class='wpsl-style-preview-error'>"+wpslL10n.styleError+"</div>")),s.setOptions({styles:t})}function M(s,t){var o,l;o='<div id="setting-error-'+t+'" class="error settings-error notice is-dismissible">',o+="<p><strong>"+s+"</strong></p>",o+='<button type="button" class="notice-dismiss"><span class="screen-reader-text">'+wpslL10n.dismissNotice+"</span></button>",o+="</div>",l=e("#wpsl-tabs").length?"wpsl-tabs":"wpsl-settings-form",e("#"+l).before(o),e("#wpsl-api-"+t).addClass("wpsl-error")}e("#wpsl-gmap-wrap").length&&(o=wpslSettings.defaultLatLng.split(","),l=new google.maps.LatLng(o[0],o[1]),r={},r={zoom:parseInt(wpslSettings.defaultZoom),center:l,mapTypeId:google.maps.MapTypeId[wpslSettings.mapType.toUpperCase()],mapTypeControl:!1,streetViewControl:!1,zoomControlOptions:{position:google.maps.ControlPosition.RIGHT_TOP}},t=new google.maps.Geocoder,s=new google.maps.Map(document.getElementById("wpsl-gmap-wrap"),r),i=e("#wpsl-lat").val(),a=e("#wpsl-lng").val(),i&&a&&(n=new google.maps.LatLng(i,a),s.setCenter(n),s.setZoom(16),v(n))),e("#wpsl-map-settings").length&&(c=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver,d=e("#wpsl-start-name"),void 0!==c&&new c(function(s){s.forEach(function(s){"class"===s.attributeName&&-1!==e(s.target).prop(s.attributeName).indexOf("gm-err-autocomplete")&&M(wpslL10n.browserKeyError,"browser-key")})}).observe(d[0],{attributes:!0}),e("#wpsl-api-server-key").hasClass("wpsl-validate-me")&&!e("#setting-error-server-key").length&&(p={action:"validate_server_key",server_key:e("#wpsl-api-server-key").val()},e.get(wpslSettings.ajaxurl,p,function(s){s.valid||void 0===s.msg?e("#wpsl-api-server-key").removeClass("wpsl-error"):M(s.msg,"server-key")}))),e("#wpsl-start-name").length&&(w=document.getElementById("wpsl-start-name"),u=new google.maps.places.Autocomplete(w,{types:["geocode"]}),google.maps.event.addListener(u,"place_changed",function(){m(u.getPlace().geometry.location,"zoom")})),e("#wpsl-lookup-location").on("click",function(o){var l,r;o.preventDefault(),!function(){var s,t,o=!1;if(e(".wpsl-store-meta input").removeClass("wpsl-error"),void 0!==wpslSettings.requiredFields&&_.isArray(wpslSettings.requiredFields))for(t=wpslSettings.requiredFields,s=0;s<t.length;s++)e.trim(e("#wpsl-"+t[s]).val())||(e("#wpsl-"+t[s]).addClass("wpsl-error"),o=!0);return o}()?(r=function(){var s,t,o=[],l=["address","city","state","zip","country"];for(s=0;s<l.length;s++)(t=e.trim(e("#wpsl-"+l[s]).val()))&&o.push(t),t="";return o.join()}(),t.geocode({address:r},function(t,o){o===google.maps.GeocoderStatus.OK?(void 0!==h[0]&&h[0].draggable&&(h[0].setMap(null),h.splice(0,1)),s.setCenter(t[0].geometry.location),s.setZoom(16),v(t[0].geometry.location),m(t[0].geometry.location,"store"),l=function(e){var s,t,o={},l=e[0].address_components.length;for(s=0;s<l;s++)t=e[0].address_components[s].types,/^country,political$/.test(t)&&(o={long_name:e[0].address_components[s].long_name,short_name:e[0].address_components[s].short_name});return{country:o}}(t),e("#wpsl-country").val(l.country.long_name),e("#wpsl-country_iso").val(l.country.short_name)):alert(wpslL10n.geocodeFail+": "+o)})):(f("first"),alert(wpslL10n.missingGeoData))}),e(".wpsl-marker-list input[type=radio]").click(function(){e(this).parents(".wpsl-marker-list").find("li").removeClass(),e(this).parent("li").addClass("wpsl-active-marker")}),e(".wpsl-marker-list li").click(function(){e(this).parents(".wpsl-marker-list").find("input").prop("checked",!1),e(this).find("input").prop("checked",!0),e(this).siblings().removeClass(),e(this).addClass("wpsl-active-marker")}),e(".wpsl-has-conditional-option").on("change",function(){e(this).parent().next(".wpsl-conditional-option").toggle()}),e("#wpsl-store-template").on("change",function(){var s=e("#wpsl-listing-below-no-scroll");"below_map"==e(this).val()?s.show():s.hide()}),e("#wpsl-api-region").on("change",function(){var s=e("#wpsl-geocode-component");e(this).val()?s.show():s.hide()}),e("#wpsl-editor-hour-input").on("change",function(){e(".wpsl-"+e(this).val()+"-hours").show().siblings("div").hide(),e(".wpsl-hour-notice").toggle()}),e("#wpsl-meta-nav li").on("click",function(s){var t=e(this).attr("class");t=t.split("-tab"),s.stopPropagation(),e(this).addClass("wpsl-active").siblings().removeClass("wpsl-active"),e(".wpsl-store-meta ."+t[0]).show().addClass("wpsl-active").siblings("div").hide().removeClass("wpsl-active")}),e("#wpsl-store-details").length&&e("#publish").click(function(){var s,t,o,l='<div id="message" class="error"><p>'+wpslL10n.requiredFields+"</p></div>",r=!1;return e("#wpbody-content .wrap #message").remove(),e(".wpsl-required").removeClass("wpsl-error"),e(".wpsl-required").each(function(){""==e(this).val()&&(e(this).addClass("wpsl-error"),void 0===s&&(s=function(e){var s={type:"id",val:e.attr("id")};void 0===s.val&&(s={type:"class",val:e.attr("class")});return s}(e(this))),r=!0)}),!r||(e("#wpbody-content .wrap > h2").after(l),void 0!==s.val&&("id"==s.type?(t=e("#"+s.val).parents(".wpsl-tab").attr("class"),e("html, body").scrollTop(Math.round(e("#"+s.val).offset().top-100))):"class"==s.type&&(o=s.val.replace(/wpsl-required|wpsl-error/g,""),t=e("."+o).parents(".wpsl-tab").attr("class"),e("html, body").scrollTop(Math.round(e("."+o).offset().top-100))),t=e.trim(t.replace(/wpsl-tab|wpsl-active/g,""))),f(t||"first"),e("#publish").removeClass("button-primary-disabled"),e(".spinner").hide(),!1)}),e("#wpsl-store-hours").length&&y(),e(".wpsl-add-period").on("click",function(s){var t,o={},l=e(this).parents("tr"),r=b(e(this))>=1?"wpsl-current-period wpsl-multiple-periods":"wpsl-current-period",n=l.find(".wpsl-opening-hours").attr("data-day"),i=e("#wpsl-settings-form").length?"wpsl_editor[dropdown]":"wpsl[hours]";t='<div class="'+r+'">',t+='<select autocomplete="off" name="'+i+"["+n+'_open][]" class="wpsl-open-hour">'+C(!0)+"</select>",t+="<span> - </span>",t+='<select autocomplete="off" name="'+i+"["+n+'_close][]" class="wpsl-close-hour">'+C(!0)+"</select>",t+='<div class="wpsl-icon-cancel-circled"></div>',t+="</div>",l.find(".wpsl-store-closed").remove(),e("#wpsl-hours-"+n).append(t).end(),y(),o=24==e("#wpsl-editor-hour-format").val()?{open:"09:00",close:"17:00"}:{open:"9:00 AM",close:"5:00 PM"},l.find(".wpsl-open-hour:last option[value='"+o.open+"']").attr("selected","selected"),l.find(".wpsl-close-hour:last option[value='"+o.close+"']").attr("selected","selected"),s.preventDefault()}),e("#wpsl-editor-hour-format, #wpsl-editor-hour-interval").on("change",function(){C()}),e(".wpsl-info").on("mouseover",function(){e(this).find(".wpsl-info-text").show()}),e(".wpsl-info").on("mouseout",function(){e(this).find(".wpsl-info-text").hide()}),e("#wpsl-latlng").length&&!e("#wpsl-latlng").val()&&e("#wpsl-latlng").siblings("label").find(".wpsl-info").addClass("wpsl-required-setting"),e("#wpsl-map-style").val()&&k(),e("#wpsl-style-preview").on("click",function(){return k(),!1}),e("#wpsl-wrap").on("click","button.notice-dismiss",function(){e(this).closest("div.notice").remove()}),e("#wpsl-search-autocomplete, #wpsl-force-postalcode").change(function(){var s=e("#wpsl-force-postalcode").parent("p").find(".wpsl-info-zip-only");e("#wpsl-search-autocomplete").is(":checked")&&e("#wpsl-force-postalcode").is(":checked")?s.show():s.hide()})});
1
+ jQuery(document).ready(function(e){var s,o,t,n,l,r=[],i=i||{};function a(n="wpsl-gmap-wrap"){var l,r,i,a,c=wpslSettings.defaultLatLng.split(",");t=new google.maps.LatLng(c[0],c[1]),l={zoom:parseInt(wpslSettings.defaultZoom),center:t,mapTypeId:google.maps.MapTypeId[wpslSettings.mapType.toUpperCase()],mapTypeControl:!1,streetViewControl:!1,zoomControlOptions:{position:google.maps.ControlPosition.RIGHT_TOP}},o=new google.maps.Geocoder,s=new google.maps.Map(document.getElementById(n),l),i=e("#wpsl-lat").val(),a=e("#wpsl-lng").val(),i&&a&&(r=new google.maps.LatLng(i,a),s.setCenter(r),s.setZoom(16),p(r))}function p(e,o=!0){var t=new google.maps.Marker({position:e,map:s,draggable:o});r.push(t),google.maps.event.addListener(t,"dragend",function(){c(t.getPosition(),"store")})}function c(s,o){var t=function(e){var s=[],o=e.toString().split(",",2);return s[0]=o[0].replace("(",""),s[1]=o[1].replace(")",""),s}(s),n=d(t[0]),l=d(t[1]);"store"==o?(e("#wpsl-lat").val(n),e("#wpsl-lng").val(l)):"zoom"==o&&e("#wpsl-latlng").val(n+","+l)}function d(e){return Math.round(e*Math.pow(10,6))/Math.pow(10,6)}function w(s){e("#wpsl-meta-nav li"+(s="first"==s?":first-child":"."+s)+"-tab").hasClass("wpsl-active")||(e("#wpsl-meta-nav li"+s+"-tab").addClass("wpsl-active").siblings().removeClass("wpsl-active"),e(".wpsl-store-meta > div"+s).show().addClass("wpsl-active").siblings("div").hide().removeClass("wpsl-active"))}function u(){e("#wpsl-store-hours .wpsl-icon-cancel-circled").off(),e("#wpsl-store-hours .wpsl-icon-cancel-circled").on("click",function(){!function(e){var s=g(e),o=e.parents("tr"),t=o.find(".wpsl-opening-hours").attr("data-day");1==s&&o.find(".wpsl-opening-hours").html("<p class='wpsl-store-closed'>"+wpslL10n.closedDate+"<input type='hidden' name='wpsl[hours]["+t+"_open]' value='' /></p>");e.parent().closest(".wpsl-current-period").remove(),o.find(".wpsl-opening-hours div:first-child").hasClass("wpsl-multiple-periods")&&o.find(".wpsl-opening-hours div:first-child").removeClass("wpsl-multiple-periods")}(e(this))})}function g(e){return e.parents("tr").find(".wpsl-current-period").length}function v(s){var o,t,n,l,r=!1,i=!1,a="",p="",c=[],d={hr12:[12,1,2,3,4,5,6,7,8,9,10,11,12,1,2,3,4,5,6,7,8,9,10,11],hr24:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]},w=["00","15","30","45"];l=e("#wpsl-editor-hour-format").length?e("#wpsl-editor-hour-format").val():wpslSettings.hourFormat,e("#wpsl-store-hours td").removeAttr("style"),12==l?(e("#wpsl-store-hours").removeClass().addClass("wpsl-twelve-format"),o=d.hr12):(e("#wpsl-store-hours").removeClass().addClass("wpsl-twentyfour-format"),o=d.hr24),t=w;for(var u=0;u<o.length;u++){n=o[u],12==l?(n>=12&&(r=!!i,i=!0),a=r?"PM":"AM"):24==l&&1==n.toString().length&&(n="0"+n);for(var g=0;g<t.length;g++)c.push(n+":"+t[g]+" "+a)}for(u=0;u<c.length;u++)p=p+'<option value="'+e.trim(c[u])+'">'+e.trim(c[u])+"</option>";if(s)return p;!function(s,o){var t,n,l,r={};e(".wpsl-current-period").each(function(){l=e(this),r={open:e(this).find(".wpsl-open-hour").val(),close:e(this).find(".wpsl-close-hour").val()},e(this).find("select").html(s).promise().done(function(){for(var s in r)r.hasOwnProperty(s)&&(t=r[s].split(":"),12==o?(n="",0==r[s].charAt(0)?(r[s]=r[s].substr(1),n=" AM"):2==t[0].length&&t[0]>12?(r[s]=t[0]-12+":"+t[1],n=" PM"):t[0]<12?(r[s]=t[0]+":"+t[1],n=" AM"):12==t[0]&&(r[s]=t[0]+":"+t[1],n=" PM"),-1==t[1].indexOf("PM")&&-1==t[1].indexOf("AM")&&(r[s]=r[s]+n)):24==o&&(-1!=t[1].indexOf("PM")?12==t[0]?r[s]="12:"+t[1].replace(" PM",""):r[s]=+t[0]+12+":"+t[1].replace(" PM",""):-1!=t[1].indexOf("AM")?1==t[0].toString().length?r[s]="0"+t[0]+":"+t[1].replace(" AM",""):r[s]=t[0]+":"+t[1].replace(" AM",""):r[s]=t[0]+":"+t[1]),l.find(".wpsl-"+s+"-hour option[value='"+e.trim(r[s])+"']").attr("selected","selected"))})})}(p,l)}function h(){var o="",t=e.trim(e("#wpsl-map-style").val());e(".wpsl-style-preview-error").remove(),t&&((o=function(e){try{var s=JSON.parse(e);if(s&&"object"==typeof s&&null!==s)return s}catch(e){}return!1}(t))||e("#wpsl-style-preview").after("<div class='wpsl-style-preview-error'>"+wpslL10n.styleError+"</div>")),s.setOptions({styles:o})}i.verifyKeys={init:function(){var s=this,o=e("#wpsl-verify-keys"),t=wpslSettings.url+"img/ajax-loader.gif",n=void 0!==wpslSettings.mapService?wpslSettings.mapService:"gmaps";o.on("click",function(){return e("#wpsl-wrap .notice").remove(),s[n].check(),o.after('<img src="'+t+'" class="wpsl-api-key-preloader" />'),!1})},showStatus:function(s,o,t="error"){this.createNotice(s,o,t),"browser"==o&&e(".wpsl-api-key-preloader").remove()},createNotice:function(s,o,t){var n,l;n='<div class="'+("error"==t?"error":"updated")+' notice is-dismissible">',n+="<p><strong>"+s+"</strong></p>",n+='<button type="button" class="notice-dismiss"><span class="screen-reader-text">'+wpslL10n.dismissNotice+"</span></button>",n+="</div>",l=e("#wpsl-tabs").length?"wpsl-tabs":"wpsl-settings-form",e("#"+l).before(n),"error"==t?e("#wpsl-api-"+o+"-key").addClass("wpsl-error"):e("#wpsl-api-"+o+"-key").removeClass("wpsl-error")},gmaps:{check:function(){this.server(function(){i.verifyKeys.gmaps.browser()})},server:function(s){var o,t={action:"validate_server_key",server_key:e("#wpsl-api-server-key").val()};t.server_key?e.get(wpslSettings.ajaxurl,t,function(e){o=e.valid?"updated":"error",i.verifyKeys.showStatus(e.msg,"server",o),s()}):(i.verifyKeys.showStatus(wpslL10n.serverKeyMissing,"server"),s())},browser:function(){var s;e("#wpsl-api-browser-key").val()?(s=setInterval(function(){i.verifyKeys.showStatus(wpslL10n.browserKeyError,"browser"),clearInterval(s)},3e3),o.geocode({address:"Manhattan, NY 10036, USA"},function(e,o){o==google.maps.GeocoderStatus.OK?i.verifyKeys.showStatus(wpslL10n.browserKeySuccess,"browser","success"):i.verifyKeys.showStatus(wpslL10n.browserKeyError,"browser"),clearInterval(s)})):i.verifyKeys.showStatus(wpslL10n.browserKeyMissing,"browser")}}},i.showApiResponse={init:function(){var o=e("#wpsl-geocode-input"),t=this,n=!1;e("#wpsl-show-geocode-response").on("click",function(o){return t.createDialog(),a("wpsl-geocode-preview"),e(".wpsl-geocode-warning span").length||t.createRestrictionsMsg(),google.maps.event.addListenerOnce(s,"tilesloaded",function(){n=!0,t.checkQuotaError()}),setTimeout(function(){n||(e(".wpsl-geocode-warning, #wpsl-geocode-test input, #wpsl-geocode-tabs").remove(),e(".wpsl-geocode-api-notice").show().html(wpslL10n.loadingFailed))},1e3),!1}),e("#wpsl-geocode-submit").on("click",function(s){o.removeClass("wpsl-error"),o.val()?t.geocoding.makeRequest():(o.addClass("wpsl-error"),e(".wpsl-geocode-api-notice").hide())}),e("#wpsl-geocode-test").keydown(function(s){13==(s.keyCode?s.keyCode:s.which)&&e("#wpsl-geocode-submit").trigger("click")})},createDialog:function(){e("#wpsl-geocode-test").dialog({resizable:!1,height:"auto",width:550,modal:!0,open:function(){e(this).parent().css({top:window.pageYOffset+50}),e("#wpsl-geocode-tabs").tabs(),e("#wpsl-geocode-input").focus(),e(".wpsl-geocode-api-notice").hide(),e("#wpsl-geocode-tabs").tabs("option","active",e("li").index(e("li:visible:eq(0)"))),e("#wpsl-geocode-input, #wpsl-geocode-response textarea").val(""),e(".ui-widget-overlay").bind("click",function(){e("#wpsl-geocode-test").dialog("close")})},buttons:{Cancel:function(){e(this).dialog("close")}}})},checkQuotaError:function(){setTimeout(function(){e("#wpsl-geocode-preview .dismissButton").length>0&&(e(".wpsl-geocode-warning, #wpsl-geocode-test input").remove(),e(".wpsl-geocode-api-notice").show(),e(".wpsl-geocode-api-notice span").html(wpslL10n.loadingError))},1e3)},createRestrictionsMsg:function(){var s,o,t=e(".wpsl-geocode-warning").show().find("strong");e("#wpsl-api-region").val()?(s=e("#wpsl-api-region option:selected").text(),o=e("#wpsl-force-postalcode").is(":checked")?wpslL10n.restrictedZipCode:"",t.after("<span>"+wpslL10n.resultsWarning+" "+s+" "+o+"</span>")):(t.after("<span>"+wpslL10n.noRestriction+"</span>"),e(".wpsl-region-href").on("click",function(){e(".ui-widget-overlay").trigger("click")}))},geocoding:{makeRequest:function(){var n=this.createParams();o.geocode(n,function(o,n){"OK"==n||"ZERO_RESULTS"==n?(void 0!==r[0]&&(r[0].setMap(null),r.length=0),"OK"==n?(p(o[0].geometry.location,!1),s.setZoom(12),s.setCenter(o[0].geometry.location)):(s.setZoom(parseInt(wpslSettings.defaultZoom)),s.setCenter(t))):(n=wpslL10n.browserKeyError,e("#wpsl-geocode-preview, #wpsl-geocode-response textarea").remove()),e(".wpsl-geocode-api-notice").show(),e(".wpsl-geocode-api-notice span").html(n),e("#wpsl-geocode-response textarea").val(JSON.stringify(o,null,4))})},createParams:function(){var s={};return void 0===wpslSettings.geocodeComponents||e.isEmptyObject(wpslSettings.geocodeComponents)?s.address=e("#wpsl-geocode-input").val():(s.componentRestrictions=wpslSettings.geocodeComponents,void 0!==s.componentRestrictions.postalCode?s.componentRestrictions.postalCode=e("#wpsl-geocode-input").val():s.address=e("#wpsl-geocode-input").val()),s}}},e("#wpsl-gmap-wrap").length&&a(),e("#wpsl-map-settings").length&&(i.verifyKeys.init(),i.showApiResponse.init()),e("#wpsl-start-name").length&&(n=document.getElementById("wpsl-start-name"),l=new google.maps.places.Autocomplete(n,{types:["geocode"]}),google.maps.event.addListener(l,"place_changed",function(){c(l.getPlace().geometry.location,"zoom")})),e("#wpsl-lookup-location").on("click",function(t){var n,l;t.preventDefault(),!function(){var s,o,t=!1;if(e(".wpsl-store-meta input").removeClass("wpsl-error"),void 0!==wpslSettings.requiredFields&&_.isArray(wpslSettings.requiredFields))for(o=wpslSettings.requiredFields,s=0;s<o.length;s++)e.trim(e("#wpsl-"+o[s]).val())||(e("#wpsl-"+o[s]).addClass("wpsl-error"),t=!0);return t}()?(l=function(){var s,o,t=[],n=["address","city","state","zip","country"];for(s=0;s<n.length;s++)(o=e.trim(e("#wpsl-"+n[s]).val()))&&t.push(o),o="";return t.join()}(),o.geocode({address:l},function(o,t){t===google.maps.GeocoderStatus.OK?(void 0!==r[0]&&r[0].draggable&&(r[0].setMap(null),r.splice(0,1)),s.setCenter(o[0].geometry.location),s.setZoom(16),p(o[0].geometry.location),c(o[0].geometry.location,"store"),n=function(e){var s,o,t={},n=e[0].address_components.length;for(s=0;s<n;s++)o=e[0].address_components[s].types,/^country,political$/.test(o)&&(t={long_name:e[0].address_components[s].long_name,short_name:e[0].address_components[s].short_name});return{country:t}}(o),e("#wpsl-country").val(n.country.long_name),e("#wpsl-country_iso").val(n.country.short_name)):alert(wpslL10n.geocodeFail+": "+t)})):(w("first"),alert(wpslL10n.missingGeoData))}),e(".wpsl-marker-list input[type=radio]").click(function(){e(this).parents(".wpsl-marker-list").find("li").removeClass(),e(this).parent("li").addClass("wpsl-active-marker")}),e(".wpsl-marker-list li").click(function(){e(this).parents(".wpsl-marker-list").find("input").prop("checked",!1),e(this).find("input").prop("checked",!0),e(this).siblings().removeClass(),e(this).addClass("wpsl-active-marker")}),e(".wpsl-has-conditional-option").on("change",function(){e(this).parent().next(".wpsl-conditional-option").toggle()}),e("#wpsl-store-template").on("change",function(){var s=e("#wpsl-listing-below-no-scroll");"below_map"==e(this).val()?s.show():s.hide()}),e("#wpsl-api-region").on("change",function(){var s=e("#wpsl-geocode-component");e(this).val()?s.show():s.hide()}),e("#wpsl-editor-hour-input").on("change",function(){e(".wpsl-"+e(this).val()+"-hours").show().siblings("div").hide(),e(".wpsl-hour-notice").toggle()}),e("#wpsl-meta-nav li").on("click",function(s){var o=e(this).attr("class");o=o.split("-tab"),s.stopPropagation(),e(this).addClass("wpsl-active").siblings().removeClass("wpsl-active"),e(".wpsl-store-meta ."+o[0]).show().addClass("wpsl-active").siblings("div").hide().removeClass("wpsl-active")}),e("#wpsl-store-details").length&&e("#publish").click(function(){var s,o,t,n='<div id="message" class="error"><p>'+wpslL10n.requiredFields+"</p></div>",l=!1;return e("#wpbody-content .wrap #message").remove(),e(".wpsl-required").removeClass("wpsl-error"),e(".wpsl-required").each(function(){""==e(this).val()&&(e(this).addClass("wpsl-error"),void 0===s&&(s=function(e){var s={type:"id",val:e.attr("id")};void 0===s.val&&(s={type:"class",val:e.attr("class")});return s}(e(this))),l=!0)}),!l||(e("#wpbody-content .wrap > h2").after(n),void 0!==s.val&&("id"==s.type?(o=e("#"+s.val).parents(".wpsl-tab").attr("class"),e("html, body").scrollTop(Math.round(e("#"+s.val).offset().top-100))):"class"==s.type&&(t=s.val.replace(/wpsl-required|wpsl-error/g,""),o=e("."+t).parents(".wpsl-tab").attr("class"),e("html, body").scrollTop(Math.round(e("."+t).offset().top-100))),o=e.trim(o.replace(/wpsl-tab|wpsl-active/g,""))),w(o||"first"),e("#publish").removeClass("button-primary-disabled"),e(".spinner").hide(),!1)}),e("#wpsl-store-hours").length&&u(),e(".wpsl-add-period").on("click",function(s){var o,t={},n=e(this).parents("tr"),l=g(e(this))>=1?"wpsl-current-period wpsl-multiple-periods":"wpsl-current-period",r=n.find(".wpsl-opening-hours").attr("data-day"),i=e("#wpsl-settings-form").length?"wpsl_editor[dropdown]":"wpsl[hours]";o='<div class="'+l+'">',o+='<select autocomplete="off" name="'+i+"["+r+'_open][]" class="wpsl-open-hour">'+v(!0)+"</select>",o+="<span> - </span>",o+='<select autocomplete="off" name="'+i+"["+r+'_close][]" class="wpsl-close-hour">'+v(!0)+"</select>",o+='<div class="wpsl-icon-cancel-circled"></div>',o+="</div>",n.find(".wpsl-store-closed").remove(),e("#wpsl-hours-"+r).append(o).end(),u(),t=24==e("#wpsl-editor-hour-format").val()?{open:"09:00",close:"17:00"}:{open:"9:00 AM",close:"5:00 PM"},n.find(".wpsl-open-hour:last option[value='"+t.open+"']").attr("selected","selected"),n.find(".wpsl-close-hour:last option[value='"+t.close+"']").attr("selected","selected"),s.preventDefault()}),e("#wpsl-editor-hour-format, #wpsl-editor-hour-interval").on("change",function(){v()}),e(".wpsl-info").on("mouseover",function(){e(this).find(".wpsl-info-text").show()}),e(".wpsl-info").on("mouseout",function(){e(this).find(".wpsl-info-text").hide()}),e("#wpsl-latlng").length&&!e("#wpsl-latlng").val()&&e("#wpsl-latlng").siblings("label").find(".wpsl-info").addClass("wpsl-required-setting"),e("#wpsl-map-style").val()&&h(),e("#wpsl-style-preview").on("click",function(){return h(),!1}),e("#wpsl-wrap").on("click","button.notice-dismiss",function(){e(this).closest("div.notice").remove()}),e("#wpsl-search-autocomplete, #wpsl-force-postalcode").change(function(){var s=e("#wpsl-force-postalcode").parent("p").find(".wpsl-info-zip-only");e("#wpsl-search-autocomplete").is(":checked")&&e("#wpsl-force-postalcode").is(":checked")?s.show():s.hide()}),e("#wpsl-delay-loading").change(function(){e(this).is(":checked")?e(this).parent("p").find(".wpsl-info").trigger("mouseover"):e(this).parent("p").find(".wpsl-info").trigger("mouseout")}),e("#wpsl-wrap").on("click",function(s){e(".wpsl-info-text").hide()})});
admin/roles.php CHANGED
@@ -1,134 +1,134 @@
1
- <?php
2
-
3
- /**
4
- * Add WPSL Roles.
5
- *
6
- * @since 2.0.0
7
- * @return void
8
- */
9
- function wpsl_add_roles() {
10
-
11
- global $wp_roles;
12
-
13
- if ( class_exists( 'WP_Roles' ) ) {
14
- if ( !isset( $wp_roles ) ) {
15
- $wp_roles = new WP_Roles();
16
- }
17
- }
18
-
19
- if ( is_object( $wp_roles ) ) {
20
- add_role( 'wpsl_store_locator_manager', __( 'Store Locator Manager', 'wpsl' ), array(
21
- 'read' => true,
22
- 'edit_posts' => true,
23
- 'delete_posts' => true,
24
- 'unfiltered_html' => true,
25
- 'upload_files' => true,
26
- 'delete_others_pages' => true,
27
- 'delete_others_posts' => true,
28
- 'delete_pages' => true,
29
- 'delete_private_pages' => true,
30
- 'delete_private_posts' => true,
31
- 'delete_published_pages' => true,
32
- 'delete_published_posts' => true,
33
- 'edit_others_pages' => true,
34
- 'edit_others_posts' => true,
35
- 'edit_pages' => true,
36
- 'edit_private_pages' => true,
37
- 'edit_private_posts' => true,
38
- 'edit_published_pages' => true,
39
- 'edit_published_posts' => true,
40
- 'moderate_comments' => true,
41
- 'publish_pages' => true,
42
- 'publish_posts' => true,
43
- 'read_private_pages' => true,
44
- 'read_private_posts' => true
45
- ) );
46
- }
47
- }
48
-
49
- /**
50
- * Add WPSL user capabilities.
51
- *
52
- * @since 2.0.0
53
- * @return void
54
- */
55
- function wpsl_add_caps() {
56
-
57
- global $wp_roles;
58
-
59
- if ( class_exists( 'WP_Roles' ) ) {
60
- if ( !isset( $wp_roles ) ) {
61
- $wp_roles = new WP_Roles();
62
- }
63
- }
64
-
65
- if ( is_object( $wp_roles ) ) {
66
- $wp_roles->add_cap( 'administrator', 'manage_wpsl_settings' );
67
-
68
- $capabilities = wpsl_get_post_caps();
69
-
70
- foreach ( $capabilities as $cap ) {
71
- $wp_roles->add_cap( 'wpsl_store_locator_manager', $cap );
72
- $wp_roles->add_cap( 'administrator', $cap );
73
- }
74
- }
75
- }
76
-
77
- /**
78
- * Get the WPSL post type capabilities.
79
- *
80
- * @since 2.0.0
81
- * @return array $capabilities The post type capabilities
82
- */
83
- function wpsl_get_post_caps() {
84
-
85
- $capabilities = array(
86
- 'edit_store',
87
- 'read_store',
88
- 'delete_store',
89
- 'edit_stores',
90
- 'edit_others_stores',
91
- 'publish_stores',
92
- 'read_private_stores',
93
- 'delete_stores',
94
- 'delete_private_stores',
95
- 'delete_published_stores',
96
- 'delete_others_stores',
97
- 'edit_private_stores',
98
- 'edit_published_stores'
99
- );
100
-
101
- return $capabilities;
102
- }
103
-
104
- /**
105
- * Remove the WPSL caps and roles.
106
- *
107
- * Only called from uninstall.php
108
- *
109
- * @since 2.0.0
110
- * @return void
111
- */
112
- function wpsl_remove_caps_and_roles() {
113
-
114
- global $wp_roles;
115
-
116
- if ( class_exists( 'WP_Roles' ) ) {
117
- if ( !isset( $wp_roles ) ) {
118
- $wp_roles = new WP_Roles();
119
- }
120
- }
121
-
122
- if ( is_object( $wp_roles ) ) {
123
- $wp_roles->remove_cap( 'administrator', 'manage_wpsl_settings' );
124
-
125
- $capabilities = wpsl_get_post_caps();
126
-
127
- foreach ( $capabilities as $cap ) {
128
- $wp_roles->remove_cap( 'wpsl_store_locator_manager', $cap );
129
- $wp_roles->remove_cap( 'administrator', $cap );
130
- }
131
- }
132
-
133
- remove_role( 'wpsl_store_locator_manager' );
134
  }
1
+ <?php
2
+
3
+ /**
4
+ * Add WPSL Roles.
5
+ *
6
+ * @since 2.0.0
7
+ * @return void
8
+ */
9
+ function wpsl_add_roles() {
10
+
11
+ global $wp_roles;
12
+
13
+ if ( class_exists( 'WP_Roles' ) ) {
14
+ if ( !isset( $wp_roles ) ) {
15
+ $wp_roles = new WP_Roles();
16
+ }
17
+ }
18
+
19
+ if ( is_object( $wp_roles ) ) {
20
+ add_role( 'wpsl_store_locator_manager', __( 'Store Locator Manager', 'wpsl' ), array(
21
+ 'read' => true,
22
+ 'edit_posts' => true,
23
+ 'delete_posts' => true,
24
+ 'unfiltered_html' => true,
25
+ 'upload_files' => true,
26
+ 'delete_others_pages' => true,
27
+ 'delete_others_posts' => true,
28
+ 'delete_pages' => true,
29
+ 'delete_private_pages' => true,
30
+ 'delete_private_posts' => true,
31
+ 'delete_published_pages' => true,
32
+ 'delete_published_posts' => true,
33
+ 'edit_others_pages' => true,
34
+ 'edit_others_posts' => true,
35
+ 'edit_pages' => true,
36
+ 'edit_private_pages' => true,
37
+ 'edit_private_posts' => true,
38
+ 'edit_published_pages' => true,
39
+ 'edit_published_posts' => true,
40
+ 'moderate_comments' => true,
41
+ 'publish_pages' => true,
42
+ 'publish_posts' => true,
43
+ 'read_private_pages' => true,
44
+ 'read_private_posts' => true
45
+ ) );
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Add WPSL user capabilities.
51
+ *
52
+ * @since 2.0.0
53
+ * @return void
54
+ */
55
+ function wpsl_add_caps() {
56
+
57
+ global $wp_roles;
58
+
59
+ if ( class_exists( 'WP_Roles' ) ) {
60
+ if ( !isset( $wp_roles ) ) {
61
+ $wp_roles = new WP_Roles();
62
+ }
63
+ }
64
+
65
+ if ( is_object( $wp_roles ) ) {
66
+ $wp_roles->add_cap( 'administrator', 'manage_wpsl_settings' );
67
+
68
+ $capabilities = wpsl_get_post_caps();
69
+
70
+ foreach ( $capabilities as $cap ) {
71
+ $wp_roles->add_cap( 'wpsl_store_locator_manager', $cap );
72
+ $wp_roles->add_cap( 'administrator', $cap );
73
+ }
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Get the WPSL post type capabilities.
79
+ *
80
+ * @since 2.0.0
81
+ * @return array $capabilities The post type capabilities
82
+ */
83
+ function wpsl_get_post_caps() {
84
+
85
+ $capabilities = array(
86
+ 'edit_store',
87
+ 'read_store',
88
+ 'delete_store',
89
+ 'edit_stores',
90
+ 'edit_others_stores',
91
+ 'publish_stores',
92
+ 'read_private_stores',
93
+ 'delete_stores',
94
+ 'delete_private_stores',
95
+ 'delete_published_stores',
96
+ 'delete_others_stores',
97
+ 'edit_private_stores',
98
+ 'edit_published_stores'
99
+ );
100
+
101
+ return $capabilities;
102
+ }
103
+
104
+ /**
105
+ * Remove the WPSL caps and roles.
106
+ *
107
+ * Only called from uninstall.php
108
+ *
109
+ * @since 2.0.0
110
+ * @return void
111
+ */
112
+ function wpsl_remove_caps_and_roles() {
113
+
114
+ global $wp_roles;
115
+
116
+ if ( class_exists( 'WP_Roles' ) ) {
117
+ if ( !isset( $wp_roles ) ) {
118
+ $wp_roles = new WP_Roles();
119
+ }
120
+ }
121
+
122
+ if ( is_object( $wp_roles ) ) {
123
+ $wp_roles->remove_cap( 'administrator', 'manage_wpsl_settings' );
124
+
125
+ $capabilities = wpsl_get_post_caps();
126
+
127
+ foreach ( $capabilities as $cap ) {
128
+ $wp_roles->remove_cap( 'wpsl_store_locator_manager', $cap );
129
+ $wp_roles->remove_cap( 'administrator', $cap );
130
+ }
131
+ }
132
+
133
+ remove_role( 'wpsl_store_locator_manager' );
134
  }
admin/templates/add-ons.php CHANGED
@@ -1,59 +1,59 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- $campaign_params = '?utm_source=wpsl-add-ons&utm_medium=banner&utm_campaign=add-ons';
5
-
6
- // Load the add-on data from an existing transient, or grab new data from the remote URL.
7
- if ( false === ( $add_ons = get_transient( 'wpsl_addons' ) ) ) {
8
- $response = wp_remote_get( 'https://s3.amazonaws.com/wpsl/add-ons.json' );
9
-
10
- if ( !is_wp_error( $response ) ) {
11
- $add_ons = json_decode( wp_remote_retrieve_body( $response ) );
12
-
13
- if ( $add_ons ) {
14
- set_transient( 'wpsl_addons', $add_ons, WEEK_IN_SECONDS );
15
- }
16
- }
17
- }
18
- ?>
19
-
20
- <div class="wrap wpsl-add-ons">
21
- <h2><?php _e( 'WP Store Locator Add-Ons', 'wpsl' ); ?></h2>
22
-
23
- <?php
24
- if ( $add_ons ) {
25
- foreach ( $add_ons as $add_on ) {
26
- ?>
27
- <div class="wpsl-add-on">
28
- <?php if ( !empty( $add_on->url ) ) { ?>
29
- <a title="<?php echo esc_attr( $add_on->name ); ?>" href="<?php echo esc_url( $add_on->url ) . $campaign_params; ?>">
30
- <img src="<?php echo esc_url( $add_on->img ); ?>"/>
31
- </a>
32
- <?php } else { ?>
33
- <img src="<?php echo esc_url( $add_on->img ); ?>"/>
34
- <?php } ?>
35
-
36
- <div class="wpsl-add-on-desc">
37
- <p><?php echo esc_html( $add_on->desc ); ?></p>
38
-
39
- <div class="wpsl-add-on-status">
40
- <?php if ( !empty( $add_on->class ) && class_exists( $add_on->class ) ) { ?>
41
- <p><strong><?php _e( 'Already Installed.', 'wpsl' ); ?></strong></p>
42
- <?php } else if ( isset( $add_on->soon ) && $add_on->soon ) { ?>
43
- <p><strong><?php _e( 'Coming soon!', 'wpsl' ); ?></strong></p>
44
- <?php } else { ?>
45
- <a class="button-primary" href="<?php echo esc_url( $add_on->url ) . $campaign_params; ?>">
46
- <?php esc_html_e( 'Get This Add-On', 'wpsl' ); ?>
47
- </a>
48
- <?php } ?>
49
- </div>
50
- </div>
51
- </div>
52
- <?php
53
- }
54
- } else {
55
- echo '<p>'. __( 'Failed to load the add-on list from the server.', 'wpsl' ) . '</p>';
56
- echo '<p>'. __( 'Please try again later!', 'wpsl' ) . '</p>';
57
- }
58
- ?>
59
  </div>
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ $campaign_params = '?utm_source=wpsl-add-ons&utm_medium=banner&utm_campaign=add-ons';
5
+
6
+ // Load the add-on data from an existing transient, or grab new data from the remote URL.
7
+ if ( false === ( $add_ons = get_transient( 'wpsl_addons' ) ) ) {
8
+ $response = wp_remote_get( 'https://s3.amazonaws.com/wpsl/add-ons.json' );
9
+
10
+ if ( !is_wp_error( $response ) ) {
11
+ $add_ons = json_decode( wp_remote_retrieve_body( $response ) );
12
+
13
+ if ( $add_ons ) {
14
+ set_transient( 'wpsl_addons', $add_ons, WEEK_IN_SECONDS );
15
+ }
16
+ }
17
+ }
18
+ ?>
19
+
20
+ <div class="wrap wpsl-add-ons">
21
+ <h2><?php _e( 'WP Store Locator Add-Ons', 'wpsl' ); ?></h2>
22
+
23
+ <?php
24
+ if ( $add_ons ) {
25
+ foreach ( $add_ons as $add_on ) {
26
+ ?>
27
+ <div class="wpsl-add-on">
28
+ <?php if ( !empty( $add_on->url ) ) { ?>
29
+ <a title="<?php echo esc_attr( $add_on->name ); ?>" href="<?php echo esc_url( $add_on->url ) . $campaign_params; ?>">
30
+ <img src="<?php echo esc_url( $add_on->img ); ?>"/>
31
+ </a>
32
+ <?php } else { ?>
33
+ <img src="<?php echo esc_url( $add_on->img ); ?>"/>
34
+ <?php } ?>
35
+
36
+ <div class="wpsl-add-on-desc">
37
+ <p><?php echo esc_html( $add_on->desc ); ?></p>
38
+
39
+ <div class="wpsl-add-on-status">
40
+ <?php if ( !empty( $add_on->class ) && class_exists( $add_on->class ) ) { ?>
41
+ <p><strong><?php _e( 'Already Installed.', 'wpsl' ); ?></strong></p>
42
+ <?php } else if ( isset( $add_on->soon ) && $add_on->soon ) { ?>
43
+ <p><strong><?php _e( 'Coming soon!', 'wpsl' ); ?></strong></p>
44
+ <?php } else { ?>
45
+ <a class="button-primary" href="<?php echo esc_url( $add_on->url ) . $campaign_params; ?>">
46
+ <?php esc_html_e( 'Get This Add-On', 'wpsl' ); ?>
47
+ </a>
48
+ <?php } ?>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ <?php
53
+ }
54
+ } else {
55
+ echo '<p>'. __( 'Failed to load the add-on list from the server.', 'wpsl' ) . '</p>';
56
+ echo '<p>'. __( 'Please try again later!', 'wpsl' ) . '</p>';
57
+ }
58
+ ?>
59
  </div>
admin/templates/map-settings.php CHANGED
@@ -1,647 +1,701 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- global $wpdb, $wpsl, $wpsl_admin, $wp_version, $wpsl_settings;
5
- ?>
6
-
7
- <div id="wpsl-wrap" class="wrap wpsl-settings <?php if ( floatval( $wp_version ) < 3.8 ) { echo 'wpsl-pre-38'; } // Fix CSS issue with < 3.8 versions ?>">
8
- <h2>WP Store Locator <?php _e( 'Settings', 'wpsl' ); ?></h2>
9
-
10
- <?php
11
- settings_errors();
12
-
13
- $tabs = apply_filters( 'wpsl_settings_tab', array( 'general' => __( 'General', 'wpsl' ) ) );
14
- $wpsl_licenses = apply_filters( 'wpsl_license_settings', array() );
15
- $current_tab = isset( $_GET['tab'] ) ? $_GET['tab'] : '';
16
-
17
- if ( $wpsl_licenses ) {
18
- $tabs['licenses'] = __( 'Licenses', 'wpsl' );
19
- }
20
-
21
- // Default to the general tab if an unknow tab value is set
22
- if ( !array_key_exists( $current_tab, $tabs ) ) {
23
- $current_tab = 'general';
24
- }
25
-
26
- if ( count( $tabs ) > 1 ) {
27
- echo '<h2 id="wpsl-tabs" class="nav-tab-wrapper">';
28
-
29
- foreach ( $tabs as $tab_key => $tab_name ) {
30
- if ( !$current_tab && $tab_key == 'general' || $current_tab == $tab_key ) {
31
- $active_tab = 'nav-tab-active';
32
- } else {
33
- $active_tab = '';
34
- }
35
-
36
- echo '<a class="nav-tab ' . $active_tab . '" title="' . esc_attr( $tab_name ) . '" href="' . admin_url( 'edit.php?post_type=wpsl_stores&page=wpsl_settings&tab=' . $tab_key ) . '">' . esc_attr( $tab_name ) . '</a>';
37
- }
38
-
39
- echo '</h2>';
40
- }
41
-
42
- if ( $wpsl_licenses && $current_tab == 'licenses' ) {
43
- ?>
44
-
45
- <form action="" method="post">
46
- <table class="wp-list-table widefat">
47
- <thead>
48
- <tr>
49
- <th scope="col"><?php _e( 'Add-On', 'wpsl' ); ?></th>
50
- <th scope="col"><?php _e( 'License Key', 'wpsl' ); ?></th>
51
- <th scope="col"><?php _e( 'License Expiry Date', 'wpsl' ); ?></th>
52
- </tr>
53
- </thead>
54
- <tbody id="the-list">
55
- <?php
56
- foreach ( $wpsl_licenses as $wpsl_license ) {
57
- $key = ( $wpsl_license['status'] == 'valid' ) ? esc_attr( $wpsl_license['key'] ) : '';
58
-
59
- echo '<tr>';
60
- echo '<td>' . esc_html( $wpsl_license['name'] ) . '</td>';
61
- echo '<td>';
62
- echo '<input type="text" value="' . $key . '" name="wpsl_licenses[' . esc_attr( $wpsl_license['short_name'] ) . ']" />';
63
-
64
- if ( $wpsl_license['status'] == 'valid' ) {
65
- echo '<input type="submit" class="button-secondary" name="' . esc_attr( $wpsl_license['short_name'] ) . '_license_key_deactivate" value="' . __( 'Deactivate License', 'wpsl' ) . '"/>';
66
- }
67
-
68
- wp_nonce_field( $wpsl_license['short_name'] . '_license-nonce', $wpsl_license['short_name'] . '_license-nonce' );
69
-
70
- echo '</td>';
71
- echo '<td>';
72
-
73
- if ( $wpsl_license['expiration'] && $wpsl_license['status'] == 'valid' ) {
74
- echo esc_html( date_i18n( get_option( 'date_format' ), strtotime( $wpsl_license['expiration'] ) ) );
75
- }
76
-
77
- echo '</td>';
78
- echo '</tr>';
79
- }
80
- ?>
81
- </tbody>
82
- </table>
83
-
84
- <p class="submit">
85
- <input type="submit" value="<?php _e( 'Save Changes', 'wpsl' ); ?>" class="button button-primary" id="submit" name="submit">
86
- </p>
87
- </form>
88
- <?php
89
- } else if ( $current_tab == 'general' || !$current_tab ) {
90
- ?>
91
-
92
- <div id="general">
93
- <form id="wpsl-settings-form" method="post" action="options.php" autocomplete="off" accept-charset="utf-8">
94
- <div class="postbox-container">
95
- <div class="metabox-holder">
96
- <div id="wpsl-api-settings" class="postbox">
97
- <h3 class="hndle"><span><?php _e( 'Google Maps API', 'wpsl' ); ?></span></h3>
98
- <div class="inside">
99
- <p>
100
- <label for="wpsl-api-browser-key"><?php _e( 'Browser key', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'A %sbrowser key%s allows you to monitor the usage of the Google Maps %sJavaScript API%s. %s %sRequired%s for %sapplications%s created after June 22, 2016.', 'wpsl' ), '<a href="https://wpstorelocator.co/document/create-google-api-keys/#browser-key" target="_blank">', '</a>', '<a href="https://developers.google.com/maps/documentation/javascript/">', '</a>', '<br><br>', '<strong>', '</strong>', '<a href="https://googlegeodevelopers.blogspot.nl/2016/06/building-for-scale-updates-to-google.html">', '</a>' ); ?></span></span></label>
101
- <input type="text" value="<?php echo esc_attr( $wpsl_settings['api_browser_key'] ); ?>" name="wpsl_api[browser_key]" class="textinput" id="wpsl-api-browser-key">
102
- </p>
103
- <p>
104
- <label for="wpsl-api-server-key"><?php _e( 'Server key', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'A %sserver key%s allows you to monitor the usage of the Google Maps %sGeocoding API%s. %s %sRequired%s for %sapplications%s created after June 22, 2016.', 'wpsl' ), '<a href="https://wpstorelocator.co/document/create-google-api-keys/#server-key" target="_blank">', '</a>', '<a href="https://developers.google.com/maps/documentation/geocoding/intro">', '</a>', '<br><br>', '<strong>', '</strong>', '<a href="https://googlegeodevelopers.blogspot.nl/2016/06/building-for-scale-updates-to-google.html">', '</a>' ); ?></span></span></label>
105
- <input type="text" value="<?php echo esc_attr( $wpsl_settings['api_server_key'] ); ?>" name="wpsl_api[server_key]" class="textinput<?php if ( !get_option( 'wpsl_valid_server_key' ) ) { echo ' wpsl-validate-me wpsl-error'; } ?>" id="wpsl-api-server-key">
106
- </p>
107
- <p>
108
- <label for="wpsl-api-language"><?php _e( 'Map language', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php _e( 'If no map language is selected the browser\'s prefered language is used.', 'wpsl' ); ?></span></span></label>
109
- <select id="wpsl-api-language" name="wpsl_api[language]">
110
- <?php echo $wpsl_admin->settings_page->get_api_option_list( 'language' ); ?>
111
- </select>
112
- </p>
113
- <p>
114
- <label for="wpsl-api-region"><?php _e( 'Map region', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'This will bias the %sgeocoding%s results towards the selected region. %s If no region is selected the bias is set to the United States.', 'wpsl' ), '<a href="https://developers.google.com/maps/documentation/javascript/geocoding#Geocoding">', '</a>', '<br><br>' ); ?></span></span></label>
115
- <select id="wpsl-api-region" name="wpsl_api[region]">
116
- <?php echo $wpsl_admin->settings_page->get_api_option_list( 'region' ); ?>
117
- </select>
118
- </p>
119
- <p id="wpsl-geocode-component" <?php if ( !$wpsl_settings['api_region'] ) { echo 'style="display:none;"'; } ?>>
120
- <label for="wpsl-api-component"><?php _e( 'Restrict the geocoding results to the selected map region?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'If the %sgeocoding%s API finds more relevant results outside of the set map region ( some location names exist in multiple regions ), the user will likely see a "No results found" message. %s To rule this out you can restrict the results to the set map region. %s You can modify the used restrictions with %sthis%s filter.', 'wpsl' ), '<a href="https://developers.google.com/maps/documentation/javascript/geocoding#Geocoding">', '</a>', '<br><br>', '<br><br>', '<a href="http://wpstorelocator.co/document/wpsl_geocode_components">', '</a>' ); ?></span></span></label>
121
- <input type="checkbox" value="" <?php checked( $wpsl_settings['api_geocode_component'], true ); ?> name="wpsl_api[geocode_component]" id="wpsl-api-component">
122
- </p>
123
- <p class="submit">
124
- <input type="submit" value="<?php _e( 'Save Changes', 'wpsl' ); ?>" class="button-primary">
125
- </p>
126
- </div>
127
- </div>
128
- </div>
129
- </div>
130
-
131
- <div class="postbox-container">
132
- <div class="metabox-holder">
133
- <div id="wpsl-search-settings" class="postbox">
134
- <h3 class="hndle"><span><?php _e( 'Search', 'wpsl' ); ?></span></h3>
135
- <div class="inside">
136
- <p>
137
- <label for="wpsl-search-autocomplete"><?php _e( 'Enable autocomplete?', 'wpsl' ); ?></label>
138
- <input type="checkbox" value="" <?php checked( $wpsl_settings['autocomplete'], true ); ?> name="wpsl_search[autocomplete]" id="wpsl-search-autocomplete">
139
- </p>
140
- <?php $autocomplete_warning = false; ?>
141
- <p>
142
- <label for="wpsl-force-postalcode"><?php _e( 'Force zipcode only search', 'wpsl' ); ?>:
143
- <?php
144
- if ( $wpsl_settings['force_postalcode'] && ( !$wpsl_settings['api_geocode_component'] || !$wpsl_settings['api_region'] ) ) {
145
- ?>
146
- <span class="wpsl-info wpsl-required-setting"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'For this option to work correctly you need to set a map region and restrict the results to the selected region. %s You can do this in the %sGoogle Maps API section%s.', 'wpsl' ), '<br><br>', '<a href="#wpsl-api-settings">', '</a>' ); ?></span></span>
147
- <?php
148
- }
149
-
150
- if ( $wpsl_settings['autocomplete'] && $wpsl_settings['force_postalcode'] ) {
151
- $autocomplete_warning = true;
152
- }
153
- ?>
154
- <span class="wpsl-info <?php if ( !$autocomplete_warning ) { echo 'wpsl-hide'; } ?> wpsl-required-setting wpsl-info-zip-only"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( "Zipcode only search does unfortunately not work well in combination with the autocomplete option. %s It's recommended to not have both options active at the same time.", "wpsl" ), "<br><br>" ); ?></span></span>
155
- </label>
156
- <input type="checkbox" value="" <?php checked( $wpsl_settings['force_postalcode'], true ); ?> name="wpsl_search[force_postalcode]" id="wpsl-force-postalcode">
157
- </p>
158
- <p>
159
- <label for="wpsl-results-dropdown"><?php _e( 'Show the max results dropdown?', 'wpsl' ); ?></label>
160
- <input type="checkbox" value="" <?php checked( $wpsl_settings['results_dropdown'], true ); ?> name="wpsl_search[results_dropdown]" id="wpsl-results-dropdown">
161
- </p>
162
- <p>
163
- <label for="wpsl-radius-dropdown"><?php _e( 'Show the search radius dropdown?', 'wpsl' ); ?></label>
164
- <input type="checkbox" value="" <?php checked( $wpsl_settings['radius_dropdown'], true ); ?> name="wpsl_search[radius_dropdown]" id="wpsl-radius-dropdown">
165
- </p>
166
- <p>
167
- <label for="wpsl-category-filters"><?php _e( 'Enable category filters?', 'wpsl' ); ?></label>
168
- <input type="checkbox" value="" <?php checked( $wpsl_settings['category_filter'], true ); ?> name="wpsl_search[category_filter]" id="wpsl-category-filters" class="wpsl-has-conditional-option">
169
- </p>
170
- <div class="wpsl-conditional-option" <?php if ( !$wpsl_settings['category_filter'] ) { echo 'style="display:none;"'; } ?>>
171
- <p>
172
- <label for="wpsl-cat-filter-types"><?php _e( 'Filter type:', 'wpsl' ); ?></label>
173
- <?php echo $wpsl_admin->settings_page->create_dropdown( 'filter_types' ); ?>
174
- </p>
175
- </div>
176
- <p>
177
- <label for="wpsl-distance-unit"><?php _e( 'Distance unit', 'wpsl' ); ?>:</label>
178
- <span class="wpsl-radioboxes">
179
- <input type="radio" autocomplete="off" value="km" <?php checked( 'km', $wpsl_settings['distance_unit'] ); ?> name="wpsl_search[distance_unit]" id="wpsl-distance-km">
180
- <label for="wpsl-distance-km"><?php _e( 'km', 'wpsl' ); ?></label>
181
- <input type="radio" autocomplete="off" value="mi" <?php checked( 'mi', $wpsl_settings['distance_unit'] ); ?> name="wpsl_search[distance_unit]" id="wpsl-distance-mi">
182
- <label for="wpsl-distance-mi"><?php _e( 'mi', 'wpsl' ); ?></label>
183
- </span>
184
- </p>
185
- <p>
186
- <label for="wpsl-max-results"><?php _e( 'Max search results', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php _e( 'The default value is set between the [ ].', 'wpsl' ); ?></span></span></label>
187
- <input type="text" value="<?php echo esc_attr( $wpsl_settings['max_results'] ); ?>" name="wpsl_search[max_results]" class="textinput" id="wpsl-max-results">
188
- </p>
189
- <p>
190
- <label for="wpsl-search-radius"><?php _e( 'Search radius options', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php _e( 'The default value is set between the [ ].', 'wpsl' ); ?></span></span></label>
191
- <input type="text" value="<?php echo esc_attr( $wpsl_settings['search_radius'] ); ?>" name="wpsl_search[radius]" class="textinput" id="wpsl-search-radius">
192
- </p>
193
- <p class="submit">
194
- <input type="submit" value="<?php _e( 'Save Changes', 'wpsl' ); ?>" class="button-primary">
195
- </p>
196
- </div>
197
- </div>
198
- </div>
199
- </div>
200
-
201
- <div class="postbox-container">
202
- <div class="metabox-holder">
203
- <div id="wpsl-map-settings" class="postbox">
204
- <h3 class="hndle"><span><?php _e( 'Map', 'wpsl' ); ?></span></h3>
205
- <div class="inside">
206
- <p>
207
- <label for="wpsl-auto-locate"><?php _e( 'Attempt to auto-locate the user', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'Most modern browsers %srequire%s a HTTPS connection before the Geolocation feature works.', 'wpsl' ), '<a href="https://wpstorelocator.co/document/html-5-geolocation-not-working/">', '</a>' ); ?></span></span></label>
208
- <input type="checkbox" value="" <?php checked( $wpsl_settings['auto_locate'], true ); ?> name="wpsl_map[auto_locate]" id="wpsl-auto-locate">
209
- </p>
210
- <p>
211
- <label for="wpsl-autoload"><?php _e( 'Load locations on page load', 'wpsl' ); ?>:</label>
212
- <input type="checkbox" value="" <?php checked( $wpsl_settings['autoload'], true ); ?> name="wpsl_map[autoload]" id="wpsl-autoload" class="wpsl-has-conditional-option">
213
- </p>
214
- <div class="wpsl-conditional-option" <?php if ( !$wpsl_settings['autoload'] ) { echo 'style="display:none;"'; } ?>>
215
- <p>
216
- <label for="wpsl-autoload-limit"><?php _e( 'Number of locations to show', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'Although the location data is cached after the first load, a lower number will result in the map being more responsive. %s If this field is left empty or set to 0, then all locations are loaded.', 'wpsl' ), '<br><br>' ); ?></span></span></label>
217
- <input type="text" value="<?php echo esc_attr( $wpsl_settings['autoload_limit'] ); ?>" name="wpsl_map[autoload_limit]" class="textinput" id="wpsl-autoload-limit">
218
- </p>
219
- </div>
220
- <p>
221
- <label for="wpsl-start-name"><?php _e( 'Start point', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( '%sRequired field.%s %s If auto-locating the user is disabled or fails, the center of the provided city or country will be used as the initial starting point for the user.', 'wpsl' ), '<strong>', '</strong>', '<br><br>' ); ?></span></span></label>
222
- <input type="text" value="<?php echo esc_attr( $wpsl_settings['start_name'] ); ?>" name="wpsl_map[start_name]" class="textinput" id="wpsl-start-name">
223
- <input type="hidden" value="<?php echo esc_attr( $wpsl_settings['start_latlng'] ); ?>" name="wpsl_map[start_latlng]" id="wpsl-latlng" />
224
- </p>
225
- <p>
226
- <label for="wpsl-run-fitbounds"><?php _e( 'Auto adjust the zoom level to make sure all markers are visible?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php _e( 'This runs after a search is made, and makes sure all the returned locations are visible in the viewport.', 'wpsl' ); ?></span></span></label>
227
- <input type="checkbox" value="" <?php checked( $wpsl_settings['run_fitbounds'], true ); ?> name="wpsl_map[run_fitbounds]" id="wpsl-run-fitbounds">
228
- </p>
229
- <p>
230
- <label for="wpsl-zoom-level"><?php _e( 'Initial zoom level', 'wpsl' ); ?>:</label>
231
- <?php echo $wpsl_admin->settings_page->show_zoom_levels(); ?>
232
- </p>
233
- <p>
234
- <label for="wpsl-max-zoom-level"><?php _e( 'Max auto zoom level', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'This value sets the zoom level for the "Zoom here" link in the info window. %s It is also used to limit the zooming when the viewport of the map is changed to make all the markers fit on the screen.', 'wpsl' ), '<br><br>' ); ?></span></span></label>
235
- <?php echo $wpsl_admin->settings_page->create_dropdown( 'max_zoom_level' ); ?>
236
- </p>
237
- <p>
238
- <label for="wpsl-streetview"><?php _e( 'Show the street view controls?', 'wpsl' ); ?></label>
239
- <input type="checkbox" value="" <?php checked( $wpsl_settings['streetview'], true ); ?> name="wpsl_map[streetview]" id="wpsl-streetview">
240
- </p>
241
- <p>
242
- <label for="wpsl-type-control"><?php _e( 'Show the map type control?', 'wpsl' ); ?></label>
243
- <input type="checkbox" value="" <?php checked( $wpsl_settings['type_control'], true ); ?> name="wpsl_map[type_control]" id="wpsl-type-control">
244
- </p>
245
- <p>
246
- <label for="wpsl-scollwheel-zoom"><?php _e( 'Enable scroll wheel zooming?', 'wpsl' ); ?></label>
247
- <input type="checkbox" value="" <?php checked( $wpsl_settings['scrollwheel'], true ); ?> name="wpsl_map[scrollwheel]" id="wpsl-scollwheel-zoom">
248
- </p>
249
- <p>
250
- <label><?php _e( 'Zoom control position', 'wpsl' ); ?>:</label>
251
- <span class="wpsl-radioboxes">
252
- <input type="radio" autocomplete="off" value="left" <?php checked( 'left', $wpsl_settings['control_position'], true ); ?> name="wpsl_map[control_position]" id="wpsl-control-left">
253
- <label for="wpsl-control-left"><?php _e( 'Left', 'wpsl' ); ?></label>
254
- <input type="radio" autocomplete="off" value="right" <?php checked( 'right', $wpsl_settings['control_position'], true ); ?> name="wpsl_map[control_position]" id="wpsl-control-right">
255
- <label for="wpsl-control-right"><?php _e( 'Right', 'wpsl' ); ?></label>
256
- </span>
257
- </p>
258
- <p>
259
- <label for="wpsl-map-type"><?php _e( 'Map type', 'wpsl' ); ?>:</label>
260
- <?php echo $wpsl_admin->settings_page->create_dropdown( 'map_types' ); ?>
261
- </p>
262
- <p>
263
- <label for="wpsl-map-style"><?php _e( 'Map style', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php _e( 'Custom map styles only work if the map type is set to "Roadmap" or "Terrain".', 'wpsl' ); ?></span></span></label>
264
- </p>
265
- <div class="wpsl-style-input">
266
- <p><?php echo sprintf( __( 'You can use existing map styles from %sSnazzy Maps%s or %sMap Stylr%s and paste it in the textarea below, or you can generate a custom map style through the %sMap Style Editor%s or %sStyled Maps Wizard%s.', 'wpsl' ), '<a target="_blank" href="http://snazzymaps.com">', '</a>', '<a target="_blank" href="http://mapstylr.com">', '</a>', '<a target="_blank" href="http://mapstylr.com/map-style-editor/">', '</a>', '<a target="_blank" href="http://gmaps-samples-v3.googlecode.com/svn/trunk/styledmaps/wizard/index.html">', '</a>' ); ?></p>
267
- <p><?php echo sprintf( __( 'If you like to write the style code yourself, then you can find the documentation from Google %shere%s.', 'wpsl' ), '<a target="_blank" href="https://developers.google.com/maps/documentation/javascript/styling">', '</a>' ); ?></p>
268
- <textarea id="wpsl-map-style" name="wpsl_map[map_style]"><?php echo strip_tags( stripslashes( json_decode( $wpsl_settings['map_style'] ) ) ); ?></textarea>
269
- <input type="submit" value="<?php _e( 'Preview Map Style', 'wpsl' ); ?>" class="button-primary" name="wpsl-style-preview" id="wpsl-style-preview">
270
- </div>
271
- <div id="wpsl-gmap-wrap" class="wpsl-styles-preview"></div>
272
- <p>
273
- <label for="wpsl-show-credits"><?php _e( 'Show credits?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php _e( 'This will place a "Search provided by WP Store Locator" backlink below the map.', 'wpsl' ); ?></span></span></label>
274
- <input type="checkbox" value="" <?php checked( $wpsl_settings['show_credits'], true ); ?> name="wpsl_credits" id="wpsl-show-credits">
275
- </p>
276
- <p class="submit">
277
- <input type="submit" value="<?php _e( 'Save Changes', 'wpsl' ); ?>" class="button-primary">
278
- </p>
279
- </div>
280
- </div>
281
- </div>
282
- </div>
283
-
284
- <div class="postbox-container">
285
- <div class="metabox-holder">
286
- <div id="wpsl-user-experience" class="postbox">
287
- <h3 class="hndle"><span><?php _e( 'User Experience', 'wpsl' ); ?></span></h3>
288
- <div class="inside">
289
- <p>
290
- <label for="wpsl-design-height"><?php _e( 'Store Locator height', 'wpsl' ); ?>:</label>
291
- <input size="3" value="<?php echo esc_attr( $wpsl_settings['height'] ); ?>" id="wpsl-design-height" name="wpsl_ux[height]"> px
292
- </p>
293
- <p>
294
- <label for="wpsl-infowindow-width"><?php _e( 'Max width for the info window content', 'wpsl' ); ?>:</label>
295
- <input size="3" value="<?php echo esc_attr( $wpsl_settings['infowindow_width'] ); ?>" id="wpsl-infowindow-width" name="wpsl_ux[infowindow_width]"> px
296
- </p>
297
- <p>
298
- <label for="wpsl-search-width"><?php _e( 'Search field width', 'wpsl' ); ?>:</label>
299
- <input size="3" value="<?php echo esc_attr( $wpsl_settings['search_width'] ); ?>" id="wpsl-search-width" name="wpsl_ux[search_width]"> px
300
- </p>
301
- <p>
302
- <label for="wpsl-label-width"><?php _e( 'Search and radius label width', 'wpsl' ); ?>:</label>
303
- <input size="3" value="<?php echo esc_attr( $wpsl_settings['label_width'] ); ?>" id="wpsl-label-width" name="wpsl_ux[label_width]"> px
304
- </p>
305
- <p>
306
- <label for="wpsl-store-template"><?php _e( 'Store Locator template', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'The selected template is used with the [wpsl] shortcode. %s You can add a custom template with the %swpsl_templates%s filter.', 'wpsl' ), '<br><br>', '<a href="http://wpstorelocator.co/document/wpsl_templates/">', '</a>' ); ?></span></span></label>
307
- <?php echo $wpsl_admin->settings_page->show_template_options(); ?>
308
- </p>
309
- <p id="wpsl-listing-below-no-scroll" <?php if ( $wpsl_settings['template_id'] != 'below_map' ) { echo 'style="display:none;"'; } ?>>
310
- <label for="wpsl-more-info-list"><?php _e( 'Hide the scrollbar?', 'wpsl' ); ?></label>
311
- <input type="checkbox" value="" <?php checked( $wpsl_settings['listing_below_no_scroll'], true ); ?> name="wpsl_ux[listing_below_no_scroll]" id="wpsl-listing-below-no-scroll">
312
- </p>
313
- <p>
314
- <label for="wpsl-new-window"><?php _e( 'Open links in a new window?', 'wpsl' ); ?></label>
315
- <input type="checkbox" value="" <?php checked( $wpsl_settings['new_window'], true ); ?> name="wpsl_ux[new_window]" id="wpsl-new-window">
316
- </p>
317
- <p>
318
- <label for="wpsl-reset-map"><?php _e( 'Show a reset map button?', 'wpsl' ); ?></label>
319
- <input type="checkbox" value="" <?php checked( $wpsl_settings['reset_map'], true ); ?> name="wpsl_ux[reset_map]" id="wpsl-reset-map">
320
- </p>
321
- <p>
322
- <label for="wpsl-direction-redirect"><?php _e( 'When a user clicks on "Directions", open a new window, and show the route on google.com/maps ?', 'wpsl' ); ?></label>
323
- <input type="checkbox" value="" <?php checked( $wpsl_settings['direction_redirect'], true ); ?> name="wpsl_ux[direction_redirect]" id="wpsl-direction-redirect">
324
- </p>
325
- <p>
326
- <label for="wpsl-more-info"><?php _e( 'Show a "More info" link in the store listings?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'This places a "More Info" link below the address and will show the phone, fax, email, opening hours and description once the link is clicked.', 'wpsl' ) ); ?></span></span></label>
327
- <input type="checkbox" value="" <?php checked( $wpsl_settings['more_info'], true ); ?> name="wpsl_ux[more_info]" id="wpsl-more-info" class="wpsl-has-conditional-option">
328
- </p>
329
- <div class="wpsl-conditional-option" <?php if ( !$wpsl_settings['more_info'] ) { echo 'style="display:none;"'; } ?>>
330
- <p>
331
- <label for="wpsl-more-info-list"><?php _e( 'Where do you want to show the "More info" details?', 'wpsl' ); ?></label>
332
- <?php echo $wpsl_admin->settings_page->create_dropdown( 'more_info' ); ?>
333
- </p>
334
- </div>
335
- <p>
336
- <label for="wpsl-contact-details"><?php _e( 'Always show the contact details below the address in the search results?', 'wpsl' ); ?></label>
337
- <input type="checkbox" value="" <?php checked( $wpsl_settings['show_contact_details'], true ); ?> name="wpsl_ux[show_contact_details]" id="wpsl-contact-details">
338
- </p>
339
- <p>
340
- <label for="wpsl-clickable-contact-details"><?php _e( 'Make the contact details always clickable?', 'wpsl' ); ?></label>
341
- <input type="checkbox" value="" <?php checked( $wpsl_settings['clickable_contact_details'], true ); ?> name="wpsl_ux[clickable_contact_details]" id="wpsl-clickable-contact-details">
342
- </p>
343
- <p>
344
- <label for="wpsl-store-url"><?php _e( 'Make the store name clickable if a store URL exists?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'If %spermalinks%s are enabled, the store name will always link to the store page.', 'wpsl' ), '<a href="' . admin_url( 'edit.php?post_type=wpsl_stores&page=wpsl_settings#wpsl-permalink-settings' ) . '">', '</a>' ); ?></span></span></label>
345
- <input type="checkbox" value="" <?php checked( $wpsl_settings['store_url'], true ); ?> name="wpsl_ux[store_url]" id="wpsl-store-url">
346
- </p>
347
- <p>
348
- <label for="wpsl-phone-url"><?php _e( 'Make the phone number clickable on mobile devices?', 'wpsl' ); ?></label>
349
- <input type="checkbox" value="" <?php checked( $wpsl_settings['phone_url'], true ); ?> name="wpsl_ux[phone_url]" id="wpsl-phone-url">
350
- </p>
351
- <p>
352
- <label for="wpsl-marker-streetview"><?php _e( 'If street view is available for the current location, then show a "Street view" link in the info window?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'Enabling this option can sometimes result in a small delay in the opening of the info window. %s This happens because an API request is made to Google Maps to check if street view is available for the current location.', 'wpsl' ), '<br><br>' ); ?></span></span></label>
353
- <input type="checkbox" value="" <?php checked( $wpsl_settings['marker_streetview'], true ); ?> name="wpsl_ux[marker_streetview]" id="wpsl-marker-streetview">
354
- </p>
355
- <p>
356
- <label for="wpsl-marker-zoom-to"><?php _e( 'Show a "Zoom here" link in the info window?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'Clicking this link will make the map zoom in to the %s max auto zoom level %s.', 'wpsl' ), '<a href="#wpsl-zoom-level">', '</a>' ); ?></span></span></label>
357
- <input type="checkbox" value="" <?php checked( $wpsl_settings['marker_zoom_to'], true ); ?> name="wpsl_ux[marker_zoom_to]" id="wpsl-marker-zoom-to">
358
- </p>
359
- <p>
360
- <label for="wpsl-mouse-focus"><?php _e( 'On page load move the mouse cursor to the search field?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'If the store locator is not placed at the top of the page, enabling this feature can result in the page scrolling down. %s %sThis option is disabled on mobile devices.%s', 'wpsl' ), '<br><br>', '<em>', '</em>' ); ?></span></span></label>
361
- <input type="checkbox" value="" <?php checked( $wpsl_settings['mouse_focus'], true ); ?> name="wpsl_ux[mouse_focus]" id="wpsl-mouse-focus">
362
- </p>
363
- <p>
364
- <label for="wpsl-infowindow-style"><?php _e( 'Use the default style for the info window?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'If the default style is disabled the %sInfoBox%s library will be used instead. %s This enables you to easily change the look and feel of the info window through the .wpsl-infobox css class.', 'wpsl' ), '<a href="http://google-maps-utility-library-v3.googlecode.com/svn/trunk/infobox/docs/reference.html" target="_blank">', '</a>', '<br><br>' ); ?></span></span></label>
365
- <input type="checkbox" value="default" <?php checked( $wpsl_settings['infowindow_style'], 'default' ); ?> name="wpsl_ux[infowindow_style]" id="wpsl-infowindow-style">
366
- </p>
367
- <p>
368
- <label for="wpsl-hide-country"><?php _e( 'Hide the country in the search results?', 'wpsl' ); ?></label>
369
- <input type="checkbox" value="" <?php checked( $wpsl_settings['hide_country'], true ); ?> name="wpsl_ux[hide_country]" id="wpsl-hide-country">
370
- </p>
371
- <p>
372
- <label for="wpsl-hide-distance"><?php _e( 'Hide the distance in the search results?', 'wpsl' ); ?></label>
373
- <input type="checkbox" value="" <?php checked( $wpsl_settings['hide_distance'], true ); ?> name="wpsl_ux[hide_distance]" id="wpsl-hide-distance">
374
- </p>
375
- <p>
376
- <label for="wpsl-bounce"><?php _e( 'If a user hovers over the search results the store marker', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'If marker clusters are enabled this option will not work as expected as long as the markers are clustered. %s The bouncing of the marker won\'t be visible at all unless a user zooms in far enough for the marker cluster to change back in to individual markers. %s The info window will open as expected, but it won\'t be clear to which marker it belongs to. ', 'wpsl' ), '<br><br>' , '<br><br>' ); ?></span></span></label>
377
- <?php echo $wpsl_admin->settings_page->create_dropdown( 'marker_effects' ); ?>
378
- </p>
379
- <p>
380
- <label for="wpsl-address-format"><?php _e( 'Address format', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'You can add custom address formats with the %swpsl_address_formats%s filter.', 'wpsl' ), '<a href="http://wpstorelocator.co/document/wpsl_address_formats/">', '</a>' ); ?></span></span></label>
381
- <?php echo $wpsl_admin->settings_page->create_dropdown( 'address_format' ); ?>
382
- </p>
383
- <p class="submit">
384
- <input type="submit" value="<?php _e( 'Save Changes', 'wpsl' ); ?>" class="button-primary">
385
- </p>
386
- </div>
387
- </div>
388
- </div>
389
- </div>
390
-
391
- <div class="postbox-container">
392
- <div class="metabox-holder">
393
- <div id="wpsl-marker-settings" class="postbox">
394
- <h3 class="hndle"><span><?php _e( 'Markers', 'wpsl' ); ?></span></h3>
395
- <div class="inside">
396
- <?php echo $wpsl_admin->settings_page->show_marker_options(); ?>
397
- <p>
398
- <label for="wpsl-marker-clusters"><?php _e( 'Enable marker clusters?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php _e( 'Recommended for maps with a large amount of markers.', 'wpsl' ); ?></span></span></label>
399
- <input type="checkbox" value="" <?php checked( $wpsl_settings['marker_clusters'], true ); ?> name="wpsl_map[marker_clusters]" id="wpsl-marker-clusters" class="wpsl-has-conditional-option">
400
- </p>
401
- <div class="wpsl-conditional-option" <?php if ( !$wpsl_settings['marker_clusters'] ) { echo 'style="display:none;"'; } ?>>
402
- <p>
403
- <label for="wpsl-marker-zoom"><?php _e( 'Max zoom level', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php _e( 'If this zoom level is reached or exceeded, then all markers are moved out of the marker cluster and shown as individual markers.', 'wpsl' ); ?></span></span></label>
404
- <?php echo $wpsl_admin->settings_page->show_cluster_options( 'cluster_zoom' ); ?>
405
- </p>
406
- <p>
407
- <label for="wpsl-marker-cluster-size"><?php _e( 'Cluster size', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'The grid size of a cluster in pixels. %s A larger number will result in a lower amount of clusters and also make the algorithm run faster.', 'wpsl' ), '<br><br>' ); ?></span></span></label>
408
- <?php echo $wpsl_admin->settings_page->show_cluster_options( 'cluster_size' ); ?>
409
- </p>
410
- </div>
411
- <p class="submit">
412
- <input type="submit" value="<?php _e( 'Save Changes', 'wpsl' ); ?>" class="button-primary">
413
- </p>
414
- </div>
415
- </div>
416
- </div>
417
- </div>
418
-
419
- <div class="postbox-container">
420
- <div class="metabox-holder">
421
- <div id="wpsl-store-editor-settings" class="postbox">
422
- <h3 class="hndle"><span><?php _e( 'Store Editor', 'wpsl' ); ?></span></h3>
423
- <div class="inside">
424
- <p>
425
- <label for="wpsl-editor-country"><?php _e( 'Default country', 'wpsl' ); ?>:</label>
426
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'editor_country', '' ) ); ?>" name="wpsl_editor[default_country]" class="textinput" id="wpsl-editor-country">
427
- </p>
428
- <p>
429
- <label for="wpsl-editor-map-type"><?php _e( 'Map type for the location preview', 'wpsl' ); ?>:</label>
430
- <?php echo $wpsl_admin->settings_page->create_dropdown( 'editor_map_types' ); ?>
431
- </p>
432
- <p>
433
- <label for="wpsl-editor-hide-hours"><?php _e( 'Hide the opening hours?', 'wpsl' ); ?></label>
434
- <input type="checkbox" value="" <?php checked( $wpsl_settings['hide_hours'], true ); ?> name="wpsl_editor[hide_hours]" id="wpsl-editor-hide-hours" class="wpsl-has-conditional-option">
435
- </p>
436
- <div class="wpsl-conditional-option" <?php if ( $wpsl_settings['hide_hours'] ) { echo 'style="display:none"'; } ?>>
437
- <?php if ( get_option( 'wpsl_legacy_support' ) ) { // Is only set for users who upgraded from 1.x ?>
438
- <p>
439
- <label for="wpsl-editor-hour-input"><?php _e( 'Opening hours input type', 'wpsl' ); ?>:</label>
440
- <?php echo $wpsl_admin->settings_page->create_dropdown( 'hour_input' ); ?>
441
- </p>
442
- <p class="wpsl-hour-notice <?php if ( $wpsl_settings['editor_hour_input'] !== 'dropdown' ) { echo 'style="display:none"'; } ?>">
443
- <em><?php echo sprintf( __( 'Opening hours created in version 1.x %sare not%s automatically converted to the new dropdown format.', 'wpsl' ), '<strong>', '</strong>' ); ?></em>
444
- </p>
445
- <div class="wpsl-textarea-hours" <?php if ( $wpsl_settings['editor_hour_input'] !== 'textarea' ) { echo 'style="display:none"'; } ?>>
446
- <p class="wpsl-default-hours"><strong><?php _e( 'The default opening hours', 'wpsl' ); ?></strong></p>
447
- <textarea rows="5" cols="5" name="wpsl_editor[textarea]" id="wpsl-textarea-hours"><?php if ( isset( $wpsl_settings['editor_hours']['textarea'] ) ) { echo esc_textarea( stripslashes( $wpsl_settings['editor_hours']['textarea'] ) ); } ?></textarea>
448
- </div>
449
- <?php } ?>
450
- <div class="wpsl-dropdown-hours" <?php if ( $wpsl_settings['editor_hour_input'] !== 'dropdown' ) { echo 'style="display:none"'; } ?>>
451
- <p>
452
- <label for="wpsl-editor-hour-format"><?php _e( 'Opening hours format', 'wpsl' ); ?>:</label>
453
- <?php echo $wpsl_admin->settings_page->show_opening_hours_format(); ?>
454
- </p>
455
- <p class="wpsl-default-hours"><strong><?php _e( 'The default opening hours', 'wpsl' ); ?></strong></p>
456
- <?php echo $wpsl_admin->metaboxes->opening_hours( 'settings' ); ?>
457
- </div>
458
- </div>
459
- <p><em><?php _e( 'The default country and opening hours are only used when a new store is created. So changing the default values will have no effect on existing store locations.', 'wpsl' ); ?></em></p>
460
-
461
- <p class="submit">
462
- <input type="submit" value="<?php _e( 'Save Changes', 'wpsl' ); ?>" class="button-primary">
463
- </p>
464
- </div>
465
- </div>
466
- </div>
467
- </div>
468
-
469
- <div class="postbox-container">
470
- <div class="metabox-holder">
471
- <div id="wpsl-permalink-settings" class="postbox">
472
- <h3 class="hndle"><span><?php _e( 'Permalink', 'wpsl' ); ?></span></h3>
473
- <div class="inside">
474
- <p>
475
- <label for="wpsl-permalinks-active"><?php _e( 'Enable permalink?', 'wpsl' ); ?></label>
476
- <input type="checkbox" value="" <?php checked( $wpsl_settings['permalinks'], true ); ?> name="wpsl_permalinks[active]" id="wpsl-permalinks-active" class="wpsl-has-conditional-option">
477
- </p>
478
- <div class="wpsl-conditional-option" <?php if ( !$wpsl_settings['permalinks'] ) { echo 'style="display:none;"'; } ?>>
479
- <p>
480
- <label for="wpsl-permalink-remove-front"><?php _e( 'Remove the front base from the permalink structure?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'The front base is set on the %spermalink settings%s page in the "Custom structure" field. %s If a front base is set ( for example /blog/ ), then enabling this option will remove it from the store locator permalinks.', 'wpsl' ), '<a href="https://codex.wordpress.org/Settings_Permalinks_Screen#Customize_Permalink_Structure" target="_blank">', '</a>', '<br><br>' ); ?></span></span></label>
481
- <input type="checkbox" value="" <?php checked( $wpsl_settings['permalink_remove_front'], true ); ?> name="wpsl_permalinks[remove_front]" id="wpsl-permalink-remove-front">
482
- </p>
483
- <p>
484
- <label for="wpsl-permalinks-slug"><?php _e( 'Store slug', 'wpsl' ); ?>:</label>
485
- <input type="text" value="<?php echo esc_attr( $wpsl_settings['permalink_slug'] ); ?>" name="wpsl_permalinks[slug]" class="textinput" id="wpsl-permalinks-slug">
486
- </p>
487
- <p>
488
- <label for="wpsl-category-slug"><?php _e( 'Category slug', 'wpsl' ); ?>:</label>
489
- <input type="text" value="<?php echo esc_attr( $wpsl_settings['category_slug'] ); ?>" name="wpsl_permalinks[category_slug]" class="textinput" id="wpsl-category-slug">
490
- </p>
491
- <em><?php echo sprintf( __( 'The permalink slugs %smust be unique%s on your site.', 'wpsl' ), '<strong>', '</strong>' ); ?></em>
492
- </div>
493
- <p class="submit">
494
- <input type="submit" value="<?php _e( 'Save Changes', 'wpsl' ); ?>" class="button-primary">
495
- </p>
496
- </div>
497
- </div>
498
- </div>
499
- </div>
500
-
501
- <div class="postbox-container">
502
- <div class="metabox-holder">
503
- <div id="wpsl-label-settings" class="postbox">
504
- <h3 class="hndle"><span><?php _e( 'Labels', 'wpsl' ); ?></span></h3>
505
- <div class="inside">
506
- <?php
507
- /*
508
- * Show a msg to make sure that when a WPML compatible plugin
509
- * is active users use the 'String Translations' page to change the labels,
510
- * instead of the 'Label' section.
511
- */
512
- if ( $wpsl->i18n->wpml_exists() ) {
513
- echo '<p>' . sprintf( __( '%sWarning!%s %sWPML%s, or a plugin using the WPML API is active.', 'wpsl' ), '<strong>', '</strong>', '<a href="https://wpml.org/">', '</a>' ) . '</p>';
514
- echo '<p>' . __( 'Please use the "String Translations" section in the used multilingual plugin to change the labels. Changing them here will have no effect as long as the multilingual plugin remains active.', 'wpsl' ) . '</p>';
515
- }
516
- ?>
517
- <p>
518
- <label for="wpsl-search"><?php _e( 'Your location', 'wpsl' ); ?>:</label>
519
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'search_label', __( 'Your location', 'wpsl' ) ) ); ?>" name="wpsl_label[search]" class="textinput" id="wpsl-search">
520
- </p>
521
- <p>
522
- <label for="wpsl-search-radius"><?php _e( 'Search radius', 'wpsl' ); ?>:</label>
523
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'radius_label', __( 'Search radius', 'wpsl' ) ) ); ?>" name="wpsl_label[radius]" class="textinput" id="wpsl-search-radius">
524
- </p>
525
- <p>
526
- <label for="wpsl-no-results"><?php _e( 'No results found', 'wpsl' ); ?>:</label>
527
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'no_results_label', __( 'No results found', 'wpsl' ) ) ); ?>" name="wpsl_label[no_results]" class="textinput" id="wpsl-no-results">
528
- </p>
529
- <p>
530
- <label for="wpsl-search-btn"><?php _e( 'Search', 'wpsl' ); ?>:</label>
531
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'search_btn_label', __( 'Search', 'wpsl' ) ) ); ?>" name="wpsl_label[search_btn]" class="textinput" id="wpsl-search-btn">
532
- </p>
533
- <p>
534
- <label for="wpsl-preloader"><?php _e( 'Searching (preloader text)', 'wpsl' ); ?>:</label>
535
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'preloader_label', __( 'Searching...', 'wpsl' ) ) ); ?>" name="wpsl_label[preloader]" class="textinput" id="wpsl-preloader">
536
- </p>
537
- <p>
538
- <label for="wpsl-results"><?php _e( 'Results', 'wpsl' ); ?>:</label>
539
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'results_label', __( 'Results', 'wpsl' ) ) ); ?>" name="wpsl_label[results]" class="textinput" id="wpsl-results">
540
- </p>
541
- <p>
542
- <label for="wpsl-category"><?php _e( 'Category filter', 'wpsl' ); ?>:</label>
543
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'category_label', __( 'Category', 'wpsl' ) ) ); ?>" name="wpsl_label[category]" class="textinput" id="wpsl-category">
544
- </p>
545
- <p>
546
- <label for="wpsl-category-default"><?php _e( 'Category first item', 'wpsl' ); ?>:</label>
547
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'category_default_label', __( 'Any', 'wpsl' ) ) ); ?>" name="wpsl_label[category_default]" class="textinput" id="wpsl-category-default">
548
- </p>
549
- <p>
550
- <label for="wpsl-more-info"><?php _e( 'More info', 'wpsl' ); ?>:</label>
551
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'more_label', __( 'More info', 'wpsl' ) ) ); ?>" name="wpsl_label[more]" class="textinput" id="wpsl-more-info">
552
- </p>
553
- <p>
554
- <label for="wpsl-phone"><?php _e( 'Phone', 'wpsl' ); ?>:</label>
555
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'phone_label', __( 'Phone', 'wpsl' ) ) ); ?>" name="wpsl_label[phone]" class="textinput" id="wpsl-phone">
556
- </p>
557
- <p>
558
- <label for="wpsl-fax"><?php _e( 'Fax', 'wpsl' ); ?>:</label>
559
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'fax_label', __( 'Fax', 'wpsl' ) ) ); ?>" name="wpsl_label[fax]" class="textinput" id="wpsl-fax">
560
- </p>
561
- <p>
562
- <label for="wpsl-email"><?php _e( 'Email', 'wpsl' ); ?>:</label>
563
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'email_label', __( 'Email', 'wpsl' ) ) ); ?>" name="wpsl_label[email]" class="textinput" id="wpsl-email">
564
- </p>
565
- <p>
566
- <label for="wpsl-url"><?php _e( 'Url', 'wpsl' ); ?>:</label>
567
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'url_label', __( 'Url', 'wpsl' ) ) ); ?>" name="wpsl_label[url]" class="textinput" id="wpsl-url">
568
- </p>
569
- <p>
570
- <label for="wpsl-hours"><?php _e( 'Hours', 'wpsl' ); ?>:</label>
571
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'hours_label', __( 'Hours', 'wpsl' ) ) ); ?>" name="wpsl_label[hours]" class="textinput" id="wpsl-hours">
572
- </p>
573
- <p>
574
- <label for="wpsl-start"><?php _e( 'Start location', 'wpsl' ); ?>:</label>
575
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'start_label', __( 'Start location', 'wpsl' ) ) ); ?>" name="wpsl_label[start]" class="textinput" id="wpsl-start">
576
- </p>
577
- <p>
578
- <label for="wpsl-directions"><?php _e( 'Get directions', 'wpsl' ); ?>:</label>
579
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'directions_label', __( 'Directions', 'wpsl' ) ) ); ?>" name="wpsl_label[directions]" class="textinput" id="wpsl-directions">
580
- </p>
581
- <p>
582
- <label for="wpsl-no-directions"><?php _e( 'No directions found', 'wpsl' ); ?>:</label>
583
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'no_directions_label', __( 'No route could be found between the origin and destination', 'wpsl' ) ) ); ?>" name="wpsl_label[no_directions]" class="textinput" id="wpsl-no-directions">
584
- </p>
585
- <p>
586
- <label for="wpsl-back"><?php _e( 'Back', 'wpsl' ); ?>:</label>
587
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'back_label', __( 'Back', 'wpsl' ) ) ); ?>" name="wpsl_label[back]" class="textinput" id="wpsl-back">
588
- </p>
589
- <p>
590
- <label for="wpsl-street-view"><?php _e( 'Street view', 'wpsl' ); ?>:</label>
591
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'street_view_label', __( 'Street view', 'wpsl' ) ) ); ?>" name="wpsl_label[street_view]" class="textinput" id="wpsl-street-view">
592
- </p>
593
- <p>
594
- <label for="wpsl-zoom-here"><?php _e( 'Zoom here', 'wpsl' ); ?>:</label>
595
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'zoom_here_label', __( 'Zoom here', 'wpsl' ) ) ); ?>" name="wpsl_label[zoom_here]" class="textinput" id="wpsl-zoom-here">
596
- </p>
597
- <p>
598
- <label for="wpsl-error"><?php _e( 'General error', 'wpsl' ); ?>:</label>
599
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'error_label', __( 'Something went wrong, please try again!', 'wpsl' ) ) ); ?>" name="wpsl_label[error]" class="textinput" id="wpsl-error">
600
- </p>
601
- <p>
602
- <label for="wpsl-limit"><?php _e( 'Query limit error', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'You can raise the %susage limit%s by obtaining an API %skey%s, and fill in the "API key" field at the top of this page.', 'wpsl' ), '<a href="https://developers.google.com/maps/documentation/javascript/usage#usage_limits" target="_blank">', '</a>' ,'<a href="https://developers.google.com/maps/documentation/javascript/tutorial#api_key" target="_blank">', '</a>' ); ?></span></span></label>
603
- <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'limit_label', __( 'API usage limit reached', 'wpsl' ) ) ); ?>" name="wpsl_label[limit]" class="textinput" id="wpsl-limit">
604
- </p>
605
- <p class="submit">
606
- <input type="submit" value="<?php _e( 'Save Changes', 'wpsl' ); ?>" class="button-primary">
607
- </p>
608
- </div>
609
- </div>
610
- </div>
611
- </div>
612
-
613
- <div class="postbox-container">
614
- <div class="metabox-holder">
615
- <div id="wpsl-tools" class="postbox">
616
- <h3 class="hndle"><span><?php _e( 'Tools', 'wpsl' ); ?></span></h3>
617
- <div class="inside">
618
- <p>
619
- <label for="wpsl-debug"><?php _e( 'Enable store locator debug?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'This disables the WPSL transient cache. %sThe transient cache is only used if the %sLoad locations on page load%s option is enabled.', 'wpsl' ), '<br><br>', '<em>', '</em>' ); ?></span></span></label>
620
- <input type="checkbox" value="" <?php checked( $wpsl_settings['debug'], true ); ?> name="wpsl_tools[debug]" id="wpsl-debug">
621
- </p>
622
- <p>
623
- <label for="wpsl-deregister-gmaps"><?php _e( 'Enable compatibility mode?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'If the %sbrowser console%s shows the error below, then enabling this option should fix it. %s %sYou have included the Google Maps API multiple times on this page. This may cause unexpected errors.%s %s This error can in some situations break the store locator map.', 'wpsl' ), '<a href="https://codex.wordpress.org/Using_Your_Browser_to_Diagnose_JavaScript_Errors#Step_3:_Diagnosis">', '</a>', '<br><br>', '<em>', '</em>', '<br><br>' ); ?></span></span></label>
624
- <input type="checkbox" value="" <?php checked( $wpsl_settings['deregister_gmaps'], true ); ?> name="wpsl_tools[deregister_gmaps]" id="wpsl-deregister-gmaps">
625
- </p>
626
- <p>
627
- <label for="wpsl-transient"><?php _e( 'WPSL transients', 'wpsl' ); ?></label>
628
- <a class="button" href="<?php echo wp_nonce_url( admin_url( "edit.php?post_type=wpsl_stores&page=wpsl_settings&action=clear_wpsl_transients" ), 'clear_transients' ); ?>"><?php _e( 'Clear store locator transient cache', 'wpsl' ); ?></a>
629
- </p>
630
- <p class="submit">
631
- <input type="submit" value="<?php _e( 'Save Changes', 'wpsl' ); ?>" class="button-primary">
632
- </p>
633
- </div>
634
- </div>
635
- </div>
636
- </div>
637
-
638
- <?php settings_fields( 'wpsl_settings' ); ?>
639
- </form>
640
- </div>
641
-
642
- <?php
643
- } else {
644
- do_action( 'wpsl_settings_section', $current_tab );
645
- }
646
- ?>
647
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ global $wpdb, $wpsl, $wpsl_admin, $wp_version, $wpsl_settings;
5
+ ?>
6
+
7
+ <div id="wpsl-wrap" class="wrap wpsl-settings <?php if ( floatval( $wp_version ) < 3.8 ) { echo 'wpsl-pre-38'; } // Fix CSS issue with < 3.8 versions ?>">
8
+ <h2>WP Store Locator <?php _e( 'Settings', 'wpsl' ); ?></h2>
9
+
10
+ <?php
11
+ settings_errors();
12
+
13
+ $tabs = apply_filters( 'wpsl_settings_tab', array( 'general' => __( 'General', 'wpsl' ) ) );
14
+ $wpsl_licenses = apply_filters( 'wpsl_license_settings', array() );
15
+ $current_tab = isset( $_GET['tab'] ) ? $_GET['tab'] : '';
16
+
17
+ if ( $wpsl_licenses ) {
18
+ $tabs['licenses'] = __( 'Licenses', 'wpsl' );
19
+ }
20
+
21
+ // Default to the general tab if an unknow tab value is set
22
+ if ( !array_key_exists( $current_tab, $tabs ) ) {
23
+ $current_tab = 'general';
24
+ }
25
+
26
+ if ( count( $tabs ) > 1 ) {
27
+ echo '<h2 id="wpsl-tabs" class="nav-tab-wrapper">';
28
+
29
+ foreach ( $tabs as $tab_key => $tab_name ) {
30
+ if ( !$current_tab && $tab_key == 'general' || $current_tab == $tab_key ) {
31
+ $active_tab = 'nav-tab-active';
32
+ } else {
33
+ $active_tab = '';
34
+ }
35
+
36
+ echo '<a class="nav-tab ' . $active_tab . '" title="' . esc_attr( $tab_name ) . '" href="' . admin_url( 'edit.php?post_type=wpsl_stores&page=wpsl_settings&tab=' . $tab_key ) . '">' . esc_attr( $tab_name ) . '</a>';
37
+ }
38
+
39
+ echo '</h2>';
40
+ }
41
+
42
+ if ( $wpsl_licenses && $current_tab == 'licenses' ) {
43
+ ?>
44
+
45
+ <form action="" method="post">
46
+ <table class="wp-list-table widefat">
47
+ <thead>
48
+ <tr>
49
+ <th scope="col"><?php _e( 'Add-On', 'wpsl' ); ?></th>
50
+ <th scope="col"><?php _e( 'License Key', 'wpsl' ); ?></th>
51
+ <th scope="col"><?php _e( 'License Expiry Date', 'wpsl' ); ?></th>
52
+ </tr>
53
+ </thead>
54
+ <tbody id="the-list">
55
+ <?php
56
+ foreach ( $wpsl_licenses as $wpsl_license ) {
57
+ $key = ( $wpsl_license['status'] == 'valid' ) ? esc_attr( $wpsl_license['key'] ) : '';
58
+
59
+ echo '<tr>';
60
+ echo '<td>' . esc_html( $wpsl_license['name'] ) . '</td>';
61
+ echo '<td>';
62
+ echo '<input type="text" value="' . $key . '" name="wpsl_licenses[' . esc_attr( $wpsl_license['short_name'] ) . ']" />';
63
+
64
+ if ( $wpsl_license['status'] == 'valid' ) {
65
+ echo '<input type="submit" class="button-secondary" name="' . esc_attr( $wpsl_license['short_name'] ) . '_license_key_deactivate" value="' . __( 'Deactivate License', 'wpsl' ) . '"/>';
66
+ }
67
+
68
+ wp_nonce_field( $wpsl_license['short_name'] . '_license-nonce', $wpsl_license['short_name'] . '_license-nonce' );
69
+
70
+ echo '</td>';
71
+ echo '<td>';
72
+
73
+ if ( $wpsl_license['expiration'] && $wpsl_license['status'] == 'valid' ) {
74
+ echo esc_html( date_i18n( get_option( 'date_format' ), strtotime( $wpsl_license['expiration'] ) ) );
75
+ }
76
+
77
+ echo '</td>';
78
+ echo '</tr>';
79
+ }
80
+ ?>
81
+ </tbody>
82
+ </table>
83
+
84
+ <p class="submit">
85
+ <input type="submit" value="<?php _e( 'Save Changes', 'wpsl' ); ?>" class="button button-primary" id="submit" name="submit">
86
+ </p>
87
+ </form>
88
+ <?php
89
+ } else if ( $current_tab == 'general' || !$current_tab ) {
90
+ ?>
91
+
92
+ <div id="general">
93
+ <form id="wpsl-settings-form" method="post" action="options.php" autocomplete="off" accept-charset="utf-8">
94
+ <div class="postbox-container">
95
+ <div class="metabox-holder">
96
+ <div id="wpsl-api-settings" class="postbox">
97
+ <h3 class="hndle"><span><?php _e( 'Google Maps API', 'wpsl' ); ?></span></h3>
98
+ <div class="inside">
99
+ <p>
100
+ <label for="wpsl-api-browser-key"><?php _e( 'Browser key', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'A %sbrowser key%s allows you to monitor the usage of the Google Maps %sJavaScript API%s. %s %sRequired%s for %sapplications%s created after June 22, 2016.', 'wpsl' ), '<a href="https://wpstorelocator.co/document/create-google-api-keys/#browser-key" target="_blank">', '</a>', '<a href="https://developers.google.com/maps/documentation/javascript/">', '</a>', '<br><br>', '<strong>', '</strong>', '<a href="https://googlegeodevelopers.blogspot.nl/2016/06/building-for-scale-updates-to-google.html">', '</a>' ); ?></span></span></label>
101
+ <input type="text" value="<?php echo esc_attr( $wpsl_settings['api_browser_key'] ); ?>" name="wpsl_api[browser_key]" class="textinput" id="wpsl-api-browser-key">
102
+ </p>
103
+ <p>
104
+ <label for="wpsl-api-server-key"><?php _e( 'Server key', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'A %sserver key%s allows you to monitor the usage of the Google Maps %sGeocoding API%s. %s %sRequired%s for %sapplications%s created after June 22, 2016.', 'wpsl' ), '<a href="https://wpstorelocator.co/document/create-google-api-keys/#server-key" target="_blank">', '</a>', '<a href="https://developers.google.com/maps/documentation/geocoding/intro">', '</a>', '<br><br>', '<strong>', '</strong>', '<a href="https://googlegeodevelopers.blogspot.nl/2016/06/building-for-scale-updates-to-google.html">', '</a>' ); ?></span></span></label>
105
+ <input type="text" value="<?php echo esc_attr( $wpsl_settings['api_server_key'] ); ?>" name="wpsl_api[server_key]" class="textinput<?php if ( !get_option( 'wpsl_valid_server_key' ) ) { echo ' wpsl-validate-me wpsl-error'; } ?>" id="wpsl-api-server-key">
106
+ </p>
107
+ <p>
108
+ <label for="wpsl-verify-keys"><?php _e( 'Validate API keys', 'wpsl' ); ?></label>
109
+ <a id="wpsl-verify-keys" class="button" href="#"><?php _e( 'Show response', 'wpsl' ); ?></a>
110
+ </p>
111
+ <p>
112
+ <label for="wpsl-api-language"><?php _e( 'Map language', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php _e( 'If no map language is selected the browser\'s prefered language is used.', 'wpsl' ); ?></span></span></label>
113
+ <select id="wpsl-api-language" name="wpsl_api[language]">
114
+ <?php echo $wpsl_admin->settings_page->get_api_option_list( 'language' ); ?>
115
+ </select>
116
+ </p>
117
+ <p>
118
+ <label for="wpsl-api-region"><?php _e( 'Map region', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'This will bias the %sgeocoding%s results towards the selected region. %s If no region is selected the bias is set to the United States.', 'wpsl' ), '<a href="https://developers.google.com/maps/documentation/javascript/geocoding#Geocoding">', '</a>', '<br><br>' ); ?></span></span></label>
119
+ <select id="wpsl-api-region" name="wpsl_api[region]">
120
+ <?php echo $wpsl_admin->settings_page->get_api_option_list( 'region' ); ?>
121
+ </select>
122
+ </p>
123
+ <p id="wpsl-geocode-component" <?php if ( !$wpsl_settings['api_region'] ) { echo 'style="display:none;"'; } ?>>
124
+ <label for="wpsl-api-component"><?php _e( 'Restrict the geocoding results to the selected map region?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'If the %sgeocoding%s API finds more relevant results outside of the set map region ( some location names exist in multiple regions ), the user will likely see a "No results found" message. %s To rule this out you can restrict the results to the set map region. %s You can modify the used restrictions with %sthis%s filter.', 'wpsl' ), '<a href="https://developers.google.com/maps/documentation/javascript/geocoding#Geocoding">', '</a>', '<br><br>', '<br><br>', '<a href="http://wpstorelocator.co/document/wpsl_geocode_components">', '</a>' ); ?></span></span></label>
125
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['api_geocode_component'], true ); ?> name="wpsl_api[geocode_component]" id="wpsl-api-component">
126
+ </p>
127
+ <p class="submit">
128
+ <input type="submit" value="<?php _e( 'Save Changes', 'wpsl' ); ?>" class="button-primary">
129
+ </p>
130
+ </div>
131
+ </div>
132
+ </div>
133
+ </div>
134
+
135
+ <div class="postbox-container">
136
+ <div class="metabox-holder">
137
+ <div id="wpsl-search-settings" class="postbox">
138
+ <h3 class="hndle"><span><?php _e( 'Search', 'wpsl' ); ?></span></h3>
139
+ <div class="inside">
140
+ <p>
141
+ <label for="wpsl-search-autocomplete"><?php _e( 'Enable autocomplete?', 'wpsl' ); ?></label>
142
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['autocomplete'], true ); ?> name="wpsl_search[autocomplete]" id="wpsl-search-autocomplete">
143
+ </p>
144
+ <?php $autocomplete_warning = false; ?>
145
+ <p>
146
+ <label for="wpsl-force-postalcode"><?php _e( 'Force zipcode only search', 'wpsl' ); ?>:
147
+ <?php
148
+ if ( $wpsl_settings['force_postalcode'] && ( !$wpsl_settings['api_geocode_component'] || !$wpsl_settings['api_region'] ) ) {
149
+ ?>
150
+ <span class="wpsl-info wpsl-required-setting"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'For this option to work correctly you need to set a map region and restrict the results to the selected region. %s You can do this in the %sGoogle Maps API section%s.', 'wpsl' ), '<br><br>', '<a href="#wpsl-api-settings">', '</a>' ); ?></span></span>
151
+ <?php
152
+ }
153
+
154
+ if ( $wpsl_settings['autocomplete'] && $wpsl_settings['force_postalcode'] ) {
155
+ $autocomplete_warning = true;
156
+ }
157
+ ?>
158
+ <span class="wpsl-info <?php if ( !$autocomplete_warning ) { echo 'wpsl-hide'; } ?> wpsl-required-setting wpsl-info-zip-only"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( "Zipcode only search does unfortunately not work well in combination with the autocomplete option. %s It's recommended to not have both options active at the same time.", "wpsl" ), "<br><br>" ); ?></span></span>
159
+ </label>
160
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['force_postalcode'], true ); ?> name="wpsl_search[force_postalcode]" id="wpsl-force-postalcode">
161
+ </p>
162
+ <p>
163
+ <label for="wpsl-results-dropdown"><?php _e( 'Show the max results dropdown?', 'wpsl' ); ?></label>
164
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['results_dropdown'], true ); ?> name="wpsl_search[results_dropdown]" id="wpsl-results-dropdown">
165
+ </p>
166
+ <p>
167
+ <label for="wpsl-radius-dropdown"><?php _e( 'Show the search radius dropdown?', 'wpsl' ); ?></label>
168
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['radius_dropdown'], true ); ?> name="wpsl_search[radius_dropdown]" id="wpsl-radius-dropdown">
169
+ </p>
170
+ <p>
171
+ <label for="wpsl-category-filters"><?php _e( 'Enable category filters?', 'wpsl' ); ?></label>
172
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['category_filter'], true ); ?> name="wpsl_search[category_filter]" id="wpsl-category-filters" class="wpsl-has-conditional-option">
173
+ </p>
174
+ <div class="wpsl-conditional-option" <?php if ( !$wpsl_settings['category_filter'] ) { echo 'style="display:none;"'; } ?>>
175
+ <p>
176
+ <label for="wpsl-cat-filter-types"><?php _e( 'Filter type:', 'wpsl' ); ?></label>
177
+ <?php echo $wpsl_admin->settings_page->create_dropdown( 'filter_types' ); ?>
178
+ </p>
179
+ </div>
180
+ <p>
181
+ <label for="wpsl-distance-unit"><?php _e( 'Distance unit', 'wpsl' ); ?>:</label>
182
+ <span class="wpsl-radioboxes">
183
+ <input type="radio" autocomplete="off" value="km" <?php checked( 'km', $wpsl_settings['distance_unit'] ); ?> name="wpsl_search[distance_unit]" id="wpsl-distance-km">
184
+ <label for="wpsl-distance-km"><?php _e( 'km', 'wpsl' ); ?></label>
185
+ <input type="radio" autocomplete="off" value="mi" <?php checked( 'mi', $wpsl_settings['distance_unit'] ); ?> name="wpsl_search[distance_unit]" id="wpsl-distance-mi">
186
+ <label for="wpsl-distance-mi"><?php _e( 'mi', 'wpsl' ); ?></label>
187
+ </span>
188
+ </p>
189
+ <p>
190
+ <label for="wpsl-max-results"><?php _e( 'Max search results', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php _e( 'The default value is set between the [ ].', 'wpsl' ); ?></span></span></label>
191
+ <input type="text" value="<?php echo esc_attr( $wpsl_settings['max_results'] ); ?>" name="wpsl_search[max_results]" class="textinput" id="wpsl-max-results">
192
+ </p>
193
+ <p>
194
+ <label for="wpsl-search-radius"><?php _e( 'Search radius options', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php _e( 'The default value is set between the [ ].', 'wpsl' ); ?></span></span></label>
195
+ <input type="text" value="<?php echo esc_attr( $wpsl_settings['search_radius'] ); ?>" name="wpsl_search[radius]" class="textinput" id="wpsl-search-radius">
196
+ </p>
197
+ <p class="submit">
198
+ <input type="submit" value="<?php _e( 'Save Changes', 'wpsl' ); ?>" class="button-primary">
199
+ </p>
200
+ </div>
201
+ </div>
202
+ </div>
203
+ </div>
204
+
205
+ <div class="postbox-container">
206
+ <div class="metabox-holder">
207
+ <div id="wpsl-map-settings" class="postbox">
208
+ <h3 class="hndle"><span><?php _e( 'Map', 'wpsl' ); ?></span></h3>
209
+ <div class="inside">
210
+ <p>
211
+ <label for="wpsl-auto-locate"><?php _e( 'Attempt to auto-locate the user', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'Most modern browsers %srequire%s a HTTPS connection before the Geolocation feature works.', 'wpsl' ), '<a href="https://wpstorelocator.co/document/html-5-geolocation-not-working/">', '</a>' ); ?></span></span></label>
212
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['auto_locate'], true ); ?> name="wpsl_map[auto_locate]" id="wpsl-auto-locate">
213
+ </p>
214
+ <p>
215
+ <label for="wpsl-autoload"><?php _e( 'Load locations on page load', 'wpsl' ); ?>:</label>
216
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['autoload'], true ); ?> name="wpsl_map[autoload]" id="wpsl-autoload" class="wpsl-has-conditional-option">
217
+ </p>
218
+ <div class="wpsl-conditional-option" <?php if ( !$wpsl_settings['autoload'] ) { echo 'style="display:none;"'; } ?>>
219
+ <p>
220
+ <label for="wpsl-autoload-limit"><?php _e( 'Number of locations to show', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'Although the location data is cached after the first load, a lower number will result in the map being more responsive. %s If this field is left empty or set to 0, then all locations are loaded.', 'wpsl' ), '<br><br>' ); ?></span></span></label>
221
+ <input type="text" value="<?php echo esc_attr( $wpsl_settings['autoload_limit'] ); ?>" name="wpsl_map[autoload_limit]" class="textinput" id="wpsl-autoload-limit">
222
+ </p>
223
+ </div>
224
+ <p>
225
+ <label for="wpsl-start-name"><?php _e( 'Start point', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( '%sRequired field.%s %s If auto-locating the user is disabled or fails, the center of the provided city or country will be used as the initial starting point for the user.', 'wpsl' ), '<strong>', '</strong>', '<br><br>' ); ?></span></span></label>
226
+ <input type="text" value="<?php echo esc_attr( $wpsl_settings['start_name'] ); ?>" name="wpsl_map[start_name]" class="textinput" id="wpsl-start-name">
227
+ <input type="hidden" value="<?php echo esc_attr( $wpsl_settings['start_latlng'] ); ?>" name="wpsl_map[start_latlng]" id="wpsl-latlng" />
228
+ </p>
229
+ <p>
230
+ <label for="wpsl-run-fitbounds"><?php _e( 'Auto adjust the zoom level to make sure all markers are visible?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php _e( 'This runs after a search is made, and makes sure all the returned locations are visible in the viewport.', 'wpsl' ); ?></span></span></label>
231
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['run_fitbounds'], true ); ?> name="wpsl_map[run_fitbounds]" id="wpsl-run-fitbounds">
232
+ </p>
233
+ <p>
234
+ <label for="wpsl-zoom-level"><?php _e( 'Initial zoom level', 'wpsl' ); ?>:</label>
235
+ <?php echo $wpsl_admin->settings_page->show_zoom_levels(); ?>
236
+ </p>
237
+ <p>
238
+ <label for="wpsl-max-zoom-level"><?php _e( 'Max auto zoom level', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'This value sets the zoom level for the "Zoom here" link in the info window. %s It is also used to limit the zooming when the viewport of the map is changed to make all the markers fit on the screen.', 'wpsl' ), '<br><br>' ); ?></span></span></label>
239
+ <?php echo $wpsl_admin->settings_page->create_dropdown( 'max_zoom_level' ); ?>
240
+ </p>
241
+ <p>
242
+ <label for="wpsl-streetview"><?php _e( 'Show the street view controls?', 'wpsl' ); ?></label>
243
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['streetview'], true ); ?> name="wpsl_map[streetview]" id="wpsl-streetview">
244
+ </p>
245
+ <p>
246
+ <label for="wpsl-type-control"><?php _e( 'Show the map type control?', 'wpsl' ); ?></label>
247
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['type_control'], true ); ?> name="wpsl_map[type_control]" id="wpsl-type-control">
248
+ </p>
249
+ <p>
250
+ <label for="wpsl-scollwheel-zoom"><?php _e( 'Enable scroll wheel zooming?', 'wpsl' ); ?></label>
251
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['scrollwheel'], true ); ?> name="wpsl_map[scrollwheel]" id="wpsl-scollwheel-zoom">
252
+ </p>
253
+ <p>
254
+ <label><?php _e( 'Zoom control position', 'wpsl' ); ?>:</label>
255
+ <span class="wpsl-radioboxes">
256
+ <input type="radio" autocomplete="off" value="left" <?php checked( 'left', $wpsl_settings['control_position'], true ); ?> name="wpsl_map[control_position]" id="wpsl-control-left">
257
+ <label for="wpsl-control-left"><?php _e( 'Left', 'wpsl' ); ?></label>
258
+ <input type="radio" autocomplete="off" value="right" <?php checked( 'right', $wpsl_settings['control_position'], true ); ?> name="wpsl_map[control_position]" id="wpsl-control-right">
259
+ <label for="wpsl-control-right"><?php _e( 'Right', 'wpsl' ); ?></label>
260
+ </span>
261
+ </p>
262
+ <p>
263
+ <label for="wpsl-map-type"><?php _e( 'Map type', 'wpsl' ); ?>:</label>
264
+ <?php echo $wpsl_admin->settings_page->create_dropdown( 'map_types' ); ?>
265
+ </p>
266
+ <p>
267
+ <label for="wpsl-map-style"><?php _e( 'Map style', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php _e( 'Custom map styles only work if the map type is set to "Roadmap" or "Terrain".', 'wpsl' ); ?></span></span></label>
268
+ </p>
269
+ <div class="wpsl-style-input">
270
+ <p><?php echo sprintf( __( 'You can use existing map styles from %sSnazzy Maps%s or %sMap Stylr%s and paste it in the textarea below, or you can generate a custom map style through the %sMap Style Editor%s or %sStyled Maps Wizard%s.', 'wpsl' ), '<a target="_blank" href="http://snazzymaps.com">', '</a>', '<a target="_blank" href="http://mapstylr.com">', '</a>', '<a target="_blank" href="http://mapstylr.com/map-style-editor/">', '</a>', '<a target="_blank" href="http://gmaps-samples-v3.googlecode.com/svn/trunk/styledmaps/wizard/index.html">', '</a>' ); ?></p>
271
+ <p><?php echo sprintf( __( 'If you like to write the style code yourself, then you can find the documentation from Google %shere%s.', 'wpsl' ), '<a target="_blank" href="https://developers.google.com/maps/documentation/javascript/styling">', '</a>' ); ?></p>
272
+ <textarea id="wpsl-map-style" name="wpsl_map[map_style]"><?php echo strip_tags( stripslashes( json_decode( $wpsl_settings['map_style'] ) ) ); ?></textarea>
273
+ <input type="submit" value="<?php _e( 'Preview Map Style', 'wpsl' ); ?>" class="button-primary" name="wpsl-style-preview" id="wpsl-style-preview">
274
+ </div>
275
+ <div id="wpsl-gmap-wrap" class="wpsl-styles-preview"></div>
276
+ <p>
277
+ <label for="wpsl-show-credits"><?php _e( 'Show credits?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php _e( 'This will place a "Search provided by WP Store Locator" backlink below the map.', 'wpsl' ); ?></span></span></label>
278
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['show_credits'], true ); ?> name="wpsl_credits" id="wpsl-show-credits">
279
+ </p>
280
+ <p class="submit">
281
+ <input type="submit" value="<?php _e( 'Save Changes', 'wpsl' ); ?>" class="button-primary">
282
+ </p>
283
+ </div>
284
+ </div>
285
+ </div>
286
+ </div>
287
+
288
+ <div class="postbox-container">
289
+ <div class="metabox-holder">
290
+ <div id="wpsl-user-experience" class="postbox">
291
+ <h3 class="hndle"><span><?php _e( 'User Experience', 'wpsl' ); ?></span></h3>
292
+ <div class="inside">
293
+ <p>
294
+ <label for="wpsl-design-height"><?php _e( 'Store Locator height', 'wpsl' ); ?>:</label>
295
+ <input size="3" value="<?php echo esc_attr( $wpsl_settings['height'] ); ?>" id="wpsl-design-height" name="wpsl_ux[height]"> px
296
+ </p>
297
+ <p>
298
+ <label for="wpsl-infowindow-width"><?php _e( 'Max width for the info window content', 'wpsl' ); ?>:</label>
299
+ <input size="3" value="<?php echo esc_attr( $wpsl_settings['infowindow_width'] ); ?>" id="wpsl-infowindow-width" name="wpsl_ux[infowindow_width]"> px
300
+ </p>
301
+ <p>
302
+ <label for="wpsl-search-width"><?php _e( 'Search field width', 'wpsl' ); ?>:</label>
303
+ <input size="3" value="<?php echo esc_attr( $wpsl_settings['search_width'] ); ?>" id="wpsl-search-width" name="wpsl_ux[search_width]"> px
304
+ </p>
305
+ <p>
306
+ <label for="wpsl-label-width"><?php _e( 'Search and radius label width', 'wpsl' ); ?>:</label>
307
+ <input size="3" value="<?php echo esc_attr( $wpsl_settings['label_width'] ); ?>" id="wpsl-label-width" name="wpsl_ux[label_width]"> px
308
+ </p>
309
+ <p>
310
+ <label for="wpsl-store-template"><?php _e( 'Store Locator template', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'The selected template is used with the [wpsl] shortcode. %s You can add a custom template with the %swpsl_templates%s filter.', 'wpsl' ), '<br><br>', '<a href="http://wpstorelocator.co/document/wpsl_templates/">', '</a>' ); ?></span></span></label>
311
+ <?php echo $wpsl_admin->settings_page->show_template_options(); ?>
312
+ </p>
313
+ <p id="wpsl-listing-below-no-scroll" <?php if ( $wpsl_settings['template_id'] != 'below_map' ) { echo 'style="display:none;"'; } ?>>
314
+ <label for="wpsl-more-info-list"><?php _e( 'Hide the scrollbar?', 'wpsl' ); ?></label>
315
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['listing_below_no_scroll'], true ); ?> name="wpsl_ux[listing_below_no_scroll]" id="wpsl-listing-below-no-scroll">
316
+ </p>
317
+ <p>
318
+ <label for="wpsl-new-window"><?php _e( 'Open links in a new window?', 'wpsl' ); ?></label>
319
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['new_window'], true ); ?> name="wpsl_ux[new_window]" id="wpsl-new-window">
320
+ </p>
321
+ <p>
322
+ <label for="wpsl-reset-map"><?php _e( 'Show a reset map button?', 'wpsl' ); ?></label>
323
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['reset_map'], true ); ?> name="wpsl_ux[reset_map]" id="wpsl-reset-map">
324
+ </p>
325
+ <p>
326
+ <label for="wpsl-direction-redirect"><?php _e( 'When a user clicks on "Directions", open a new window, and show the route on google.com/maps ?', 'wpsl' ); ?></label>
327
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['direction_redirect'], true ); ?> name="wpsl_ux[direction_redirect]" id="wpsl-direction-redirect">
328
+ </p>
329
+ <p>
330
+ <label for="wpsl-more-info"><?php _e( 'Show a "More info" link in the store listings?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'This places a "More Info" link below the address and will show the phone, fax, email, opening hours and description once the link is clicked.', 'wpsl' ) ); ?></span></span></label>
331
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['more_info'], true ); ?> name="wpsl_ux[more_info]" id="wpsl-more-info" class="wpsl-has-conditional-option">
332
+ </p>
333
+ <div class="wpsl-conditional-option" <?php if ( !$wpsl_settings['more_info'] ) { echo 'style="display:none;"'; } ?>>
334
+ <p>
335
+ <label for="wpsl-more-info-list"><?php _e( 'Where do you want to show the "More info" details?', 'wpsl' ); ?></label>
336
+ <?php echo $wpsl_admin->settings_page->create_dropdown( 'more_info' ); ?>
337
+ </p>
338
+ </div>
339
+ <p>
340
+ <label for="wpsl-contact-details"><?php _e( 'Always show the contact details below the address in the search results?', 'wpsl' ); ?></label>
341
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['show_contact_details'], true ); ?> name="wpsl_ux[show_contact_details]" id="wpsl-contact-details">
342
+ </p>
343
+ <p>
344
+ <label for="wpsl-clickable-contact-details"><?php _e( 'Make the contact details always clickable?', 'wpsl' ); ?></label>
345
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['clickable_contact_details'], true ); ?> name="wpsl_ux[clickable_contact_details]" id="wpsl-clickable-contact-details">
346
+ </p>
347
+ <p>
348
+ <label for="wpsl-store-url"><?php _e( 'Make the store name clickable if a store URL exists?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'If %spermalinks%s are enabled, the store name will always link to the store page.', 'wpsl' ), '<a href="' . admin_url( 'edit.php?post_type=wpsl_stores&page=wpsl_settings#wpsl-permalink-settings' ) . '">', '</a>' ); ?></span></span></label>
349
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['store_url'], true ); ?> name="wpsl_ux[store_url]" id="wpsl-store-url">
350
+ </p>
351
+ <p>
352
+ <label for="wpsl-phone-url"><?php _e( 'Make the phone number clickable on mobile devices?', 'wpsl' ); ?></label>
353
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['phone_url'], true ); ?> name="wpsl_ux[phone_url]" id="wpsl-phone-url">
354
+ </p>
355
+ <p>
356
+ <label for="wpsl-marker-streetview"><?php _e( 'If street view is available for the current location, then show a "Street view" link in the info window?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'Enabling this option can sometimes result in a small delay in the opening of the info window. %s This happens because an API request is made to Google Maps to check if street view is available for the current location.', 'wpsl' ), '<br><br>' ); ?></span></span></label>
357
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['marker_streetview'], true ); ?> name="wpsl_ux[marker_streetview]" id="wpsl-marker-streetview">
358
+ </p>
359
+ <p>
360
+ <label for="wpsl-marker-zoom-to"><?php _e( 'Show a "Zoom here" link in the info window?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'Clicking this link will make the map zoom in to the %s max auto zoom level %s.', 'wpsl' ), '<a href="#wpsl-zoom-level">', '</a>' ); ?></span></span></label>
361
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['marker_zoom_to'], true ); ?> name="wpsl_ux[marker_zoom_to]" id="wpsl-marker-zoom-to">
362
+ </p>
363
+ <p>
364
+ <label for="wpsl-mouse-focus"><?php _e( 'On page load move the mouse cursor to the search field?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'If the store locator is not placed at the top of the page, enabling this feature can result in the page scrolling down. %s %sThis option is disabled on mobile devices.%s', 'wpsl' ), '<br><br>', '<em>', '</em>' ); ?></span></span></label>
365
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['mouse_focus'], true ); ?> name="wpsl_ux[mouse_focus]" id="wpsl-mouse-focus">
366
+ </p>
367
+ <p>
368
+ <label for="wpsl-infowindow-style"><?php _e( 'Use the default style for the info window?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'If the default style is disabled the %sInfoBox%s library will be used instead. %s This enables you to easily change the look and feel of the info window through the .wpsl-infobox css class.', 'wpsl' ), '<a href="http://google-maps-utility-library-v3.googlecode.com/svn/trunk/infobox/docs/reference.html" target="_blank">', '</a>', '<br><br>' ); ?></span></span></label>
369
+ <input type="checkbox" value="default" <?php checked( $wpsl_settings['infowindow_style'], 'default' ); ?> name="wpsl_ux[infowindow_style]" id="wpsl-infowindow-style">
370
+ </p>
371
+ <p>
372
+ <label for="wpsl-hide-country"><?php _e( 'Hide the country in the search results?', 'wpsl' ); ?></label>
373
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['hide_country'], true ); ?> name="wpsl_ux[hide_country]" id="wpsl-hide-country">
374
+ </p>
375
+ <p>
376
+ <label for="wpsl-hide-distance"><?php _e( 'Hide the distance in the search results?', 'wpsl' ); ?></label>
377
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['hide_distance'], true ); ?> name="wpsl_ux[hide_distance]" id="wpsl-hide-distance">
378
+ </p>
379
+ <p>
380
+ <label for="wpsl-bounce"><?php _e( 'If a user hovers over the search results the store marker', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'If marker clusters are enabled this option will not work as expected as long as the markers are clustered. %s The bouncing of the marker won\'t be visible at all unless a user zooms in far enough for the marker cluster to change back in to individual markers. %s The info window will open as expected, but it won\'t be clear to which marker it belongs to. ', 'wpsl' ), '<br><br>' , '<br><br>' ); ?></span></span></label>
381
+ <?php echo $wpsl_admin->settings_page->create_dropdown( 'marker_effects' ); ?>
382
+ </p>
383
+ <p>
384
+ <label for="wpsl-address-format"><?php _e( 'Address format', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'You can add custom address formats with the %swpsl_address_formats%s filter.', 'wpsl' ), '<a href="http://wpstorelocator.co/document/wpsl_address_formats/">', '</a>' ); ?></span></span></label>
385
+ <?php echo $wpsl_admin->settings_page->create_dropdown( 'address_format' ); ?>
386
+ </p>
387
+ <p class="submit">
388
+ <input type="submit" value="<?php _e( 'Save Changes', 'wpsl' ); ?>" class="button-primary">
389
+ </p>
390
+ </div>
391
+ </div>
392
+ </div>
393
+ </div>
394
+
395
+ <div class="postbox-container">
396
+ <div class="metabox-holder">
397
+ <div id="wpsl-marker-settings" class="postbox">
398
+ <h3 class="hndle"><span><?php _e( 'Markers', 'wpsl' ); ?></span></h3>
399
+ <div class="inside">
400
+ <?php echo $wpsl_admin->settings_page->show_marker_options(); ?>
401
+ <p>
402
+ <label for="wpsl-marker-clusters"><?php _e( 'Enable marker clusters?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php _e( 'Recommended for maps with a large amount of markers.', 'wpsl' ); ?></span></span></label>
403
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['marker_clusters'], true ); ?> name="wpsl_map[marker_clusters]" id="wpsl-marker-clusters" class="wpsl-has-conditional-option">
404
+ </p>
405
+ <div class="wpsl-conditional-option" <?php if ( !$wpsl_settings['marker_clusters'] ) { echo 'style="display:none;"'; } ?>>
406
+ <p>
407
+ <label for="wpsl-marker-zoom"><?php _e( 'Max zoom level', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php _e( 'If this zoom level is reached or exceeded, then all markers are moved out of the marker cluster and shown as individual markers.', 'wpsl' ); ?></span></span></label>
408
+ <?php echo $wpsl_admin->settings_page->show_cluster_options( 'cluster_zoom' ); ?>
409
+ </p>
410
+ <p>
411
+ <label for="wpsl-marker-cluster-size"><?php _e( 'Cluster size', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'The grid size of a cluster in pixels. %s A larger number will result in a lower amount of clusters and also make the algorithm run faster.', 'wpsl' ), '<br><br>' ); ?></span></span></label>
412
+ <?php echo $wpsl_admin->settings_page->show_cluster_options( 'cluster_size' ); ?>
413
+ </p>
414
+ </div>
415
+ <p class="submit">
416
+ <input type="submit" value="<?php _e( 'Save Changes', 'wpsl' ); ?>" class="button-primary">
417
+ </p>
418
+ </div>
419
+ </div>
420
+ </div>
421
+ </div>
422
+
423
+ <div class="postbox-container">
424
+ <div class="metabox-holder">
425
+ <div id="wpsl-store-editor-settings" class="postbox">
426
+ <h3 class="hndle"><span><?php _e( 'Store Editor', 'wpsl' ); ?></span></h3>
427
+ <div class="inside">
428
+ <p>
429
+ <label for="wpsl-editor-country"><?php _e( 'Default country', 'wpsl' ); ?>:</label>
430
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'editor_country', '' ) ); ?>" name="wpsl_editor[default_country]" class="textinput" id="wpsl-editor-country">
431
+ </p>
432
+ <p>
433
+ <label for="wpsl-editor-map-type"><?php _e( 'Map type for the location preview', 'wpsl' ); ?>:</label>
434
+ <?php echo $wpsl_admin->settings_page->create_dropdown( 'editor_map_types' ); ?>
435
+ </p>
436
+ <p>
437
+ <label for="wpsl-editor-hide-hours"><?php _e( 'Hide the opening hours?', 'wpsl' ); ?></label>
438
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['hide_hours'], true ); ?> name="wpsl_editor[hide_hours]" id="wpsl-editor-hide-hours" class="wpsl-has-conditional-option">
439
+ </p>
440
+ <div class="wpsl-conditional-option" <?php if ( $wpsl_settings['hide_hours'] ) { echo 'style="display:none"'; } ?>>
441
+ <?php if ( get_option( 'wpsl_legacy_support' ) ) { // Is only set for users who upgraded from 1.x ?>
442
+ <p>
443
+ <label for="wpsl-editor-hour-input"><?php _e( 'Opening hours input type', 'wpsl' ); ?>:</label>
444
+ <?php echo $wpsl_admin->settings_page->create_dropdown( 'hour_input' ); ?>
445
+ </p>
446
+ <p class="wpsl-hour-notice <?php if ( $wpsl_settings['editor_hour_input'] !== 'dropdown' ) { echo 'style="display:none"'; } ?>">
447
+ <em><?php echo sprintf( __( 'Opening hours created in version 1.x %sare not%s automatically converted to the new dropdown format.', 'wpsl' ), '<strong>', '</strong>' ); ?></em>
448
+ </p>
449
+ <div class="wpsl-textarea-hours" <?php if ( $wpsl_settings['editor_hour_input'] !== 'textarea' ) { echo 'style="display:none"'; } ?>>
450
+ <p class="wpsl-default-hours"><strong><?php _e( 'The default opening hours', 'wpsl' ); ?></strong></p>
451
+ <textarea rows="5" cols="5" name="wpsl_editor[textarea]" id="wpsl-textarea-hours"><?php if ( isset( $wpsl_settings['editor_hours']['textarea'] ) ) { echo esc_textarea( stripslashes( $wpsl_settings['editor_hours']['textarea'] ) ); } ?></textarea>
452
+ </div>
453
+ <?php } ?>
454
+ <div class="wpsl-dropdown-hours" <?php if ( $wpsl_settings['editor_hour_input'] !== 'dropdown' ) { echo 'style="display:none"'; } ?>>
455
+ <p>
456
+ <label for="wpsl-editor-hour-format"><?php _e( 'Opening hours format', 'wpsl' ); ?>:</label>
457
+ <?php echo $wpsl_admin->settings_page->show_opening_hours_format(); ?>
458
+ </p>
459
+ <p class="wpsl-default-hours"><strong><?php _e( 'The default opening hours', 'wpsl' ); ?></strong></p>
460
+ <?php echo $wpsl_admin->metaboxes->opening_hours( 'settings' ); ?>
461
+ </div>
462
+ </div>
463
+ <p><em><?php _e( 'The default country and opening hours are only used when a new store is created. So changing the default values will have no effect on existing store locations.', 'wpsl' ); ?></em></p>
464
+
465
+ <p class="submit">
466
+ <input type="submit" value="<?php _e( 'Save Changes', 'wpsl' ); ?>" class="button-primary">
467
+ </p>
468
+ </div>
469
+ </div>
470
+ </div>
471
+ </div>
472
+
473
+ <div class="postbox-container">
474
+ <div class="metabox-holder">
475
+ <div id="wpsl-permalink-settings" class="postbox">
476
+ <h3 class="hndle"><span><?php _e( 'Permalink', 'wpsl' ); ?></span></h3>
477
+ <div class="inside">
478
+ <p>
479
+ <label for="wpsl-permalinks-active"><?php _e( 'Enable permalink?', 'wpsl' ); ?></label>
480
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['permalinks'], true ); ?> name="wpsl_permalinks[active]" id="wpsl-permalinks-active" class="wpsl-has-conditional-option">
481
+ </p>
482
+ <div class="wpsl-conditional-option" <?php if ( !$wpsl_settings['permalinks'] ) { echo 'style="display:none;"'; } ?>>
483
+ <p>
484
+ <label for="wpsl-permalink-remove-front"><?php _e( 'Remove the front base from the permalink structure?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'The front base is set on the %spermalink settings%s page in the "Custom structure" field. %s If a front base is set ( for example /blog/ ), then enabling this option will remove it from the store locator permalinks.', 'wpsl' ), '<a href="https://codex.wordpress.org/Settings_Permalinks_Screen#Customize_Permalink_Structure" target="_blank">', '</a>', '<br><br>' ); ?></span></span></label>
485
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['permalink_remove_front'], true ); ?> name="wpsl_permalinks[remove_front]" id="wpsl-permalink-remove-front">
486
+ </p>
487
+ <p>
488
+ <label for="wpsl-permalinks-slug"><?php _e( 'Store slug', 'wpsl' ); ?>:</label>
489
+ <input type="text" value="<?php echo esc_attr( $wpsl_settings['permalink_slug'] ); ?>" name="wpsl_permalinks[slug]" class="textinput" id="wpsl-permalinks-slug">
490
+ </p>
491
+ <p>
492
+ <label for="wpsl-category-slug"><?php _e( 'Category slug', 'wpsl' ); ?>:</label>
493
+ <input type="text" value="<?php echo esc_attr( $wpsl_settings['category_slug'] ); ?>" name="wpsl_permalinks[category_slug]" class="textinput" id="wpsl-category-slug">
494
+ </p>
495
+ <em><?php echo sprintf( __( 'The permalink slugs %smust be unique%s on your site.', 'wpsl' ), '<strong>', '</strong>' ); ?></em>
496
+ </div>
497
+ <p class="submit">
498
+ <input type="submit" value="<?php _e( 'Save Changes', 'wpsl' ); ?>" class="button-primary">
499
+ </p>
500
+ </div>
501
+ </div>
502
+ </div>
503
+ </div>
504
+
505
+ <div class="postbox-container">
506
+ <div class="metabox-holder">
507
+ <div id="wpsl-label-settings" class="postbox">
508
+ <h3 class="hndle"><span><?php _e( 'Labels', 'wpsl' ); ?></span></h3>
509
+ <div class="inside">
510
+ <?php
511
+ /*
512
+ * Show a msg to make sure that when a WPML compatible plugin
513
+ * is active users use the 'String Translations' page to change the labels,
514
+ * instead of the 'Label' section.
515
+ */
516
+ if ( $wpsl->i18n->wpml_exists() ) {
517
+ echo '<p>' . sprintf( __( '%sWarning!%s %sWPML%s, or a plugin using the WPML API is active.', 'wpsl' ), '<strong>', '</strong>', '<a href="https://wpml.org/">', '</a>' ) . '</p>';
518
+ echo '<p>' . __( 'Please use the "String Translations" section in the used multilingual plugin to change the labels. Changing them here will have no effect as long as the multilingual plugin remains active.', 'wpsl' ) . '</p>';
519
+ }
520
+ ?>
521
+ <p>
522
+ <label for="wpsl-search"><?php _e( 'Your location', 'wpsl' ); ?>:</label>
523
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'search_label', __( 'Your location', 'wpsl' ) ) ); ?>" name="wpsl_label[search]" class="textinput" id="wpsl-search">
524
+ </p>
525
+ <p>
526
+ <label for="wpsl-search-radius"><?php _e( 'Search radius', 'wpsl' ); ?>:</label>
527
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'radius_label', __( 'Search radius', 'wpsl' ) ) ); ?>" name="wpsl_label[radius]" class="textinput" id="wpsl-search-radius">
528
+ </p>
529
+ <p>
530
+ <label for="wpsl-no-results"><?php _e( 'No results found', 'wpsl' ); ?>:</label>
531
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'no_results_label', __( 'No results found', 'wpsl' ) ) ); ?>" name="wpsl_label[no_results]" class="textinput" id="wpsl-no-results">
532
+ </p>
533
+ <p>
534
+ <label for="wpsl-search-btn"><?php _e( 'Search', 'wpsl' ); ?>:</label>
535
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'search_btn_label', __( 'Search', 'wpsl' ) ) ); ?>" name="wpsl_label[search_btn]" class="textinput" id="wpsl-search-btn">
536
+ </p>
537
+ <p>
538
+ <label for="wpsl-preloader"><?php _e( 'Searching (preloader text)', 'wpsl' ); ?>:</label>
539
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'preloader_label', __( 'Searching...', 'wpsl' ) ) ); ?>" name="wpsl_label[preloader]" class="textinput" id="wpsl-preloader">
540
+ </p>
541
+ <p>
542
+ <label for="wpsl-results"><?php _e( 'Results', 'wpsl' ); ?>:</label>
543
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'results_label', __( 'Results', 'wpsl' ) ) ); ?>" name="wpsl_label[results]" class="textinput" id="wpsl-results">
544
+ </p>
545
+ <p>
546
+ <label for="wpsl-category"><?php _e( 'Category filter', 'wpsl' ); ?>:</label>
547
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'category_label', __( 'Category', 'wpsl' ) ) ); ?>" name="wpsl_label[category]" class="textinput" id="wpsl-category">
548
+ </p>
549
+ <p>
550
+ <label for="wpsl-category-default"><?php _e( 'Category first item', 'wpsl' ); ?>:</label>
551
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'category_default_label', __( 'Any', 'wpsl' ) ) ); ?>" name="wpsl_label[category_default]" class="textinput" id="wpsl-category-default">
552
+ </p>
553
+ <p>
554
+ <label for="wpsl-more-info"><?php _e( 'More info', 'wpsl' ); ?>:</label>
555
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'more_label', __( 'More info', 'wpsl' ) ) ); ?>" name="wpsl_label[more]" class="textinput" id="wpsl-more-info">
556
+ </p>
557
+ <p>
558
+ <label for="wpsl-phone"><?php _e( 'Phone', 'wpsl' ); ?>:</label>
559
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'phone_label', __( 'Phone', 'wpsl' ) ) ); ?>" name="wpsl_label[phone]" class="textinput" id="wpsl-phone">
560
+ </p>
561
+ <p>
562
+ <label for="wpsl-fax"><?php _e( 'Fax', 'wpsl' ); ?>:</label>
563
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'fax_label', __( 'Fax', 'wpsl' ) ) ); ?>" name="wpsl_label[fax]" class="textinput" id="wpsl-fax">
564
+ </p>
565
+ <p>
566
+ <label for="wpsl-email"><?php _e( 'Email', 'wpsl' ); ?>:</label>
567
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'email_label', __( 'Email', 'wpsl' ) ) ); ?>" name="wpsl_label[email]" class="textinput" id="wpsl-email">
568
+ </p>
569
+ <p>
570
+ <label for="wpsl-url"><?php _e( 'Url', 'wpsl' ); ?>:</label>
571
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'url_label', __( 'Url', 'wpsl' ) ) ); ?>" name="wpsl_label[url]" class="textinput" id="wpsl-url">
572
+ </p>
573
+ <p>
574
+ <label for="wpsl-hours"><?php _e( 'Hours', 'wpsl' ); ?>:</label>
575
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'hours_label', __( 'Hours', 'wpsl' ) ) ); ?>" name="wpsl_label[hours]" class="textinput" id="wpsl-hours">
576
+ </p>
577
+ <p>
578
+ <label for="wpsl-start"><?php _e( 'Start location', 'wpsl' ); ?>:</label>
579
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'start_label', __( 'Start location', 'wpsl' ) ) ); ?>" name="wpsl_label[start]" class="textinput" id="wpsl-start">
580
+ </p>
581
+ <p>
582
+ <label for="wpsl-directions"><?php _e( 'Get directions', 'wpsl' ); ?>:</label>
583
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'directions_label', __( 'Directions', 'wpsl' ) ) ); ?>" name="wpsl_label[directions]" class="textinput" id="wpsl-directions">
584
+ </p>
585
+ <p>
586
+ <label for="wpsl-no-directions"><?php _e( 'No directions found', 'wpsl' ); ?>:</label>
587
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'no_directions_label', __( 'No route could be found between the origin and destination', 'wpsl' ) ) ); ?>" name="wpsl_label[no_directions]" class="textinput" id="wpsl-no-directions">
588
+ </p>
589
+ <p>
590
+ <label for="wpsl-back"><?php _e( 'Back', 'wpsl' ); ?>:</label>
591
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'back_label', __( 'Back', 'wpsl' ) ) ); ?>" name="wpsl_label[back]" class="textinput" id="wpsl-back">
592
+ </p>
593
+ <p>
594
+ <label for="wpsl-street-view"><?php _e( 'Street view', 'wpsl' ); ?>:</label>
595
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'street_view_label', __( 'Street view', 'wpsl' ) ) ); ?>" name="wpsl_label[street_view]" class="textinput" id="wpsl-street-view">
596
+ </p>
597
+ <p>
598
+ <label for="wpsl-zoom-here"><?php _e( 'Zoom here', 'wpsl' ); ?>:</label>
599
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'zoom_here_label', __( 'Zoom here', 'wpsl' ) ) ); ?>" name="wpsl_label[zoom_here]" class="textinput" id="wpsl-zoom-here">
600
+ </p>
601
+ <p>
602
+ <label for="wpsl-error"><?php _e( 'General error', 'wpsl' ); ?>:</label>
603
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'error_label', __( 'Something went wrong, please try again!', 'wpsl' ) ) ); ?>" name="wpsl_label[error]" class="textinput" id="wpsl-error">
604
+ </p>
605
+ <p>
606
+ <label for="wpsl-limit"><?php _e( 'Query limit error', 'wpsl' ); ?>:<span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'You can raise the %susage limit%s by obtaining an API %skey%s, and fill in the "API key" field at the top of this page.', 'wpsl' ), '<a href="https://developers.google.com/maps/documentation/javascript/usage#usage_limits" target="_blank">', '</a>' ,'<a href="https://developers.google.com/maps/documentation/javascript/tutorial#api_key" target="_blank">', '</a>' ); ?></span></span></label>
607
+ <input type="text" value="<?php echo esc_attr( $wpsl->i18n->get_translation( 'limit_label', __( 'API usage limit reached', 'wpsl' ) ) ); ?>" name="wpsl_label[limit]" class="textinput" id="wpsl-limit">
608
+ </p>
609
+ <p class="submit">
610
+ <input type="submit" value="<?php _e( 'Save Changes', 'wpsl' ); ?>" class="button-primary">
611
+ </p>
612
+ </div>
613
+ </div>
614
+ </div>
615
+ </div>
616
+
617
+ <div class="postbox-container">
618
+ <div class="metabox-holder">
619
+ <div id="wpsl-tools" class="postbox">
620
+ <h3 class="hndle"><span><?php _e( 'Tools', 'wpsl' ); ?></span></h3>
621
+ <div class="inside">
622
+ <p>
623
+ <label for="wpsl-debug"><?php _e( 'Enable store locator debug?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'This disables the WPSL transient cache. %sThe transient cache is only used if the %sLoad locations on page load%s option is enabled.', 'wpsl' ), '<br><br>', '<em>', '</em>' ); ?></span></span></label>
624
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['debug'], true ); ?> name="wpsl_tools[debug]" id="wpsl-debug">
625
+ </p>
626
+ <p>
627
+ <label for="wpsl-deregister-gmaps"><?php _e( 'Enable compatibility mode?', 'wpsl' ); ?><span class="wpsl-info"><span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'If the %sbrowser console%s shows the error below, then enabling this option should fix it. %s %sYou have included the Google Maps API multiple times on this page. This may cause unexpected errors.%s %s This error can in some situations break the store locator map.', 'wpsl' ), '<a href="https://codex.wordpress.org/Using_Your_Browser_to_Diagnose_JavaScript_Errors#Step_3:_Diagnosis">', '</a>', '<br><br>', '<em>', '</em>', '<br><br>' ); ?></span></span></label>
628
+ <input type="checkbox" value="" <?php checked( $wpsl_settings['deregister_gmaps'], true ); ?> name="wpsl_tools[deregister_gmaps]" id="wpsl-deregister-gmaps">
629
+ </p>
630
+ <p>
631
+ <label for="wpsl-transient"><?php _e( 'WPSL transients', 'wpsl' ); ?></label>
632
+ <a class="button" href="<?php echo wp_nonce_url( admin_url( "edit.php?post_type=wpsl_stores&page=wpsl_settings&action=clear_wpsl_transients" ), 'clear_transients' ); ?>"><?php _e( 'Clear store locator transient cache', 'wpsl' ); ?></a>
633
+ </p>
634
+ <?php
635
+ $borlabs_exists = function_exists( 'BorlabsCookieHelper' );
636
+
637
+ /**
638
+ * Make sure the blocked content type for the store locator exists
639
+ * in the Borlabs Cookie plugins. If not, then it's created.
640
+ */
641
+ if ( $borlabs_exists ) {
642
+ $borlabs = New WPSL_Borlabs_Cookie();
643
+ $borlabs->maybe_enable_bct();
644
+ }
645
+ ?>
646
+ <p>
647
+ <label for="wpsl-delay-loading"><?php _e( 'GDPR - Only load Google Maps after the user agrees to it?', 'wpsl' ); ?>
648
+ <span class="wpsl-info <?php if ( !$borlabs_exists ) { echo 'wpsl-warning'; } ?>">
649
+ <?php if ( !$borlabs_exists ) { ?>
650
+ <span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'This option requires the %sBorlabs Cookie%s plugin.', 'wpsl' ), '<a target="_new" href="https://borlabs.io/borlabs-cookie/">', '</a>' ); ?></span>
651
+ <?php } else { ?>
652
+ <span class="wpsl-info-text wpsl-hide"><?php echo sprintf( __( 'Make sure to wrap the Borlabs Cookie %sshortcode%s around the WPSL shortcode.', 'wpsl' ), '<a href="https://wpstorelocator.co/document/the-general-data-protection-regulation/#borlabs">', '</a>' ); ?></span>
653
+ <?php }?>
654
+ </span>
655
+ </label>
656
+ <input <?php if ( !$borlabs_exists ) { echo 'disabled="disabled"'; } ?> type="checkbox" value="" <?php checked( $wpsl_settings['delay_loading'], true ); ?> name="wpsl_tools[delay_loading]" id="wpsl-delay-loading">
657
+ </p>
658
+ <p>
659
+ <label for="wpsl-show-geocode-response"><?php _e( 'Show the Geocode API response for a location search', 'wpsl' ); ?></label>
660
+ <a id="wpsl-show-geocode-response" class="button" href="#"><?php _e( 'Input location details', 'wpsl' ); ?></a>
661
+ </p>
662
+ <p class="submit">
663
+ <input type="submit" value="<?php _e( 'Save Changes', 'wpsl' ); ?>" class="button-primary">
664
+ </p>
665
+ </div>
666
+ </div>
667
+ </div>
668
+ </div>
669
+
670
+ <?php settings_fields( 'wpsl_settings' ); ?>
671
+ </form>
672
+ </div>
673
+
674
+ <?php
675
+ } else {
676
+ do_action( 'wpsl_settings_section', $current_tab );
677
+ }
678
+ ?>
679
+ </div>
680
+ <div id="wpsl-geocode-test" class="wpsl-hide" title="<?php _e( 'Geocode API Response', 'wpsl' ); ?>">
681
+ <div class="wpsl-geocode-warning" style="display: none;">
682
+ <p><strong><?php _e( 'Note', 'wpsl' ); ?>: </strong></p>
683
+ </div>
684
+
685
+ <input id="wpsl-geocode-input" type="text" placeholder="<?php _e( 'Location details', 'wpsl' ); ?>" >
686
+ <input id="wpsl-geocode-submit" type="submit" name="<?php _e( 'Search', 'wpsl' ); ?>" />
687
+ <p class="wpsl-geocode-api-notice" style="display: none;">
688
+ <strong><?php _e( 'API Status', 'wpsl' ); ?>: </strong>
689
+ <span></span>
690
+ </p>
691
+ <div id="wpsl-geocode-tabs" style="width: auto;">
692
+ <ul>
693
+ <li><a href="#wpsl-geocode-preview"><?php _e( 'Map Preview', 'wpsl' ); ?></a></li>
694
+ <li><a href="#wpsl-geocode-response"><?php _e( 'API Response', 'wpsl' ); ?></a></li>
695
+ </ul>
696
+ <div id="wpsl-geocode-preview" style="width:auto;height:300px;"></div>
697
+ <div id="wpsl-geocode-response">
698
+ <textarea readonly="readonly" cols="50" rows="25"></textarea>
699
+ </div>
700
+ </div>
701
+ </div>
admin/upgrade.php CHANGED
@@ -1,702 +1,708 @@
1
- <?php
2
- add_action( 'admin_init', 'wpsl_check_upgrade' );
3
- add_action( 'admin_init', 'wpsl_cpt_update_state' );
4
-
5
- /**
6
- * If the db doesn't hold the current version, run the upgrade procedure
7
- *
8
- * @since 1.2
9
- * @return void
10
- */
11
- function wpsl_check_upgrade() {
12
-
13
- global $wpsl_settings;
14
-
15
- $current_version = get_option( 'wpsl_version' );
16
-
17
- if ( version_compare( $current_version, WPSL_VERSION_NUM, '===' ) )
18
- return;
19
-
20
- if ( version_compare( $current_version, '1.1', '<' ) ) {
21
- if ( is_array( $wpsl_settings ) ) {
22
- if ( empty( $wpsl_settings['reset_map'] ) ) {
23
- $wpsl_settings['reset_map'] = 0;
24
- }
25
-
26
- if ( empty( $wpsl_settings['auto_load'] ) ) {
27
- $wpsl_settings['auto_load'] = 1;
28
- }
29
-
30
- if ( empty( $wpsl_settings['new_window'] ) ) {
31
- $wpsl_settings['new_window'] = 0;
32
- }
33
-
34
- update_option( 'wpsl_settings', $wpsl_settings );
35
- }
36
- }
37
-
38
- if ( version_compare( $current_version, '1.2', '<' ) ) {
39
- if ( is_array( $wpsl_settings ) ) {
40
- if ( empty( $wpsl_settings['store_below'] ) ) {
41
- $wpsl_settings['store_below'] = 0;
42
- }
43
-
44
- if ( empty( $wpsl_settings['direction_redirect'] ) ) {
45
- $wpsl_settings['direction_redirect'] = 0;
46
- }
47
-
48
- update_option( 'wpsl_settings', $wpsl_settings );
49
- }
50
- }
51
-
52
- if ( version_compare( $current_version, '1.2.11', '<' ) ) {
53
- if ( is_array( $wpsl_settings ) ) {
54
- if ( empty( $wpsl_settings['more_info'] ) ) {
55
- $wpsl_settings['more_info'] = 0;
56
- }
57
-
58
- if ( empty( $wpsl_settings['more_label'] ) ) {
59
- $wpsl_settings['more_label'] = __( 'More info', 'wpsl' );
60
- }
61
-
62
- if ( empty( $wpsl_settings['mouse_focus'] ) ) {
63
- $wpsl_settings['mouse_focus'] = 0;
64
- }
65
-
66
- update_option( 'wpsl_settings', $wpsl_settings );
67
- }
68
- }
69
-
70
- if ( version_compare( $current_version, '1.2.12', '<' ) ) {
71
- if ( is_array( $wpsl_settings ) ) {
72
- if ( empty( $wpsl_settings['more_info_location'] ) ) {
73
- $wpsl_settings['more_info_location'] = __( 'info window', 'wpsl' );
74
- }
75
-
76
- if ( empty( $wpsl_settings['back_label'] ) ) {
77
- $wpsl_settings['back_label'] = __( 'Back', 'wpsl' );
78
- }
79
-
80
- if ( empty( $wpsl_settings['reset_label'] ) ) {
81
- $wpsl_settings['reset_label'] = __( 'Reset', 'wpsl' );
82
- }
83
-
84
- if ( empty( $wpsl_settings['store_below_scroll'] ) ) {
85
- $wpsl_settings['store_below_scroll'] = 0;
86
- }
87
-
88
- update_option( 'wpsl_settings', $wpsl_settings );
89
- }
90
- }
91
-
92
- if ( version_compare( $current_version, '1.2.20', '<' ) ) {
93
-
94
- global $wpdb;
95
-
96
- $wpsl_table = $wpdb->prefix . 'wpsl_stores';
97
-
98
- // Rename the street field to address.
99
- $wpdb->query( "ALTER TABLE $wpsl_table CHANGE street address VARCHAR(255)" );
100
-
101
- // Add the second address field.
102
- $wpdb->query( "ALTER TABLE $wpsl_table ADD address2 VARCHAR(255) NULL AFTER address" );
103
-
104
- if ( is_array( $wpsl_settings ) ) {
105
- if ( empty( $wpsl_settings['store_url'] ) ) {
106
- $wpsl_settings['store_url'] = 0;
107
- }
108
-
109
- if ( empty( $wpsl_settings['phone_url'] ) ) {
110
- $wpsl_settings['phone_url'] = 0;
111
- }
112
-
113
- if ( empty( $wpsl_settings['marker_clusters'] ) ) {
114
- $wpsl_settings['marker_clusters'] = 0;
115
- }
116
-
117
- if ( empty( $wpsl_settings['cluster_zoom'] ) ) {
118
- $wpsl_settings['cluster_zoom'] = 0;
119
- }
120
-
121
- if ( empty( $wpsl_settings['cluster_size'] ) ) {
122
- $wpsl_settings['cluster_size'] = 0;
123
- }
124
-
125
- if ( empty( $wpsl_settings['template_id'] ) ) {
126
- $wpsl_settings['template_id'] = ( $wpsl_settings['store_below'] ) ? 1 : 0;
127
- unset( $wpsl_settings['store_below'] );
128
- }
129
-
130
- if ( empty( $wpsl_settings['marker_streetview'] ) ) {
131
- $wpsl_settings['marker_streetview'] = 0;
132
- }
133
-
134
- if ( empty( $wpsl_settings['marker_zoom_to'] ) ) {
135
- $wpsl_settings['marker_zoom_to'] = 0;
136
- }
137
-
138
- if ( !isset( $wpsl_settings['editor_country'] ) ) {
139
- $wpsl_settings['editor_country'] = '';
140
- }
141
-
142
- if ( empty( $wpsl_settings['street_view_label'] ) ) {
143
- $wpsl_settings['street_view_label'] = __( 'Street view', 'wpsl' );
144
- }
145
-
146
- if ( empty( $wpsl_settings['zoom_here_label'] ) ) {
147
- $wpsl_settings['zoom_here_label'] = __( 'Zoom here', 'wpsl' );
148
- }
149
-
150
- if ( empty( $wpsl_settings['no_directions_label'] ) ) {
151
- $wpsl_settings['no_directions_label'] = __( 'No route could be found between the origin and destination', 'wpsl' );
152
- }
153
-
154
- update_option( 'wpsl_settings', $wpsl_settings );
155
- }
156
- }
157
-
158
- if ( version_compare( $current_version, '2.0', '<' ) ) {
159
-
160
- global $wpdb;
161
-
162
- $wpsl_table = $wpdb->prefix . 'wpsl_stores';
163
-
164
- if ( is_array( $wpsl_settings ) ) {
165
- if ( empty( $wpsl_settings['radius_dropdown'] ) ) {
166
- $wpsl_settings['radius_dropdown'] = 1;
167
- }
168
-
169
- if ( empty( $wpsl_settings['permalinks'] ) ) {
170
- $wpsl_settings['permalinks'] = 0;
171
- }
172
-
173
- if ( empty( $wpsl_settings['permalink_slug'] ) ) {
174
- $wpsl_settings['permalink_slug'] = __( 'stores', 'wpsl' );
175
- }
176
-
177
- if ( empty( $wpsl_settings['category_slug'] ) ) {
178
- $wpsl_settings['category_slug'] = __( 'store-category', 'wpsl' );
179
- }
180
-
181
- if ( empty( $wpsl_settings['editor_hours'] ) ) {
182
- $wpsl_settings['editor_hours'] = wpsl_default_opening_hours();
183
- }
184
-
185
- if ( empty( $wpsl_settings['editor_hour_format'] ) ) {
186
- $wpsl_settings['editor_hour_format'] = 12;
187
- }
188
-
189
- if ( empty( $wpsl_settings['editor_map_type'] ) ) {
190
- $wpsl_settings['editor_map_type'] = 'roadmap';
191
- }
192
-
193
- if ( empty( $wpsl_settings['infowindow_style'] ) ) {
194
- $wpsl_settings['infowindow_style'] = 'default';
195
- }
196
-
197
- if ( empty( $wpsl_settings['email_label'] ) ) {
198
- $wpsl_settings['email_label'] = __( 'Email', 'wpsl' );
199
- }
200
-
201
- if ( empty( $wpsl_settings['url_label'] ) ) {
202
- $wpsl_settings['url_label'] = __( 'Url', 'wpsl' );
203
- }
204
-
205
- if ( empty( $wpsl_settings['category_label'] ) ) {
206
- $wpsl_settings['category_label'] = __( 'Category filter', 'wpsl' );
207
- }
208
-
209
- if ( empty( $wpsl_settings['show_credits'] ) ) {
210
- $wpsl_settings['show_credits'] = 0;
211
- }
212
-
213
- if ( empty( $wpsl_settings['autoload_limit'] ) ) {
214
- $wpsl_settings['autoload_limit'] = 50;
215
- }
216
-
217
- if ( empty( $wpsl_settings['scrollwheel'] ) ) {
218
- $wpsl_settings['scrollwheel'] = 1;
219
- }
220
-
221
- if ( empty( $wpsl_settings['type_control'] ) ) {
222
- $wpsl_settings['type_control'] = 0;
223
- }
224
-
225
- if ( empty( $wpsl_settings['hide_hours'] ) ) {
226
- $wpsl_settings['hide_hours'] = 0;
227
- }
228
-
229
- // Either correct the existing map style format from the 2.0 beta or set it to empty.
230
- if ( isset( $wpsl_settings['map_style'] ) && is_array( $wpsl_settings['map_style'] ) && isset( $wpsl_settings['map_style']['id'] ) ) {
231
- switch( $wpsl_settings['map_style']['id'] ) {
232
- case 'custom':
233
- $map_style = $wpsl_settings['map_style']['custom_json'];
234
- break;
235
- case 'default':
236
- $map_style = '';
237
- break;
238
- default:
239
- $map_style = $wpsl_settings['map_style']['theme_json'];
240
- break;
241
- }
242
-
243
- $wpsl_settings['map_style'] = $map_style;
244
- } else {
245
- $wpsl_settings['map_style'] = '';
246
- }
247
-
248
- if ( empty( $wpsl_settings['autoload'] ) ) {
249
- $wpsl_settings['autoload'] = $wpsl_settings['auto_load'];
250
- unset( $wpsl_settings['auto_load'] );
251
- }
252
-
253
- if ( empty( $wpsl_settings['address_format'] ) ) {
254
- $wpsl_settings['address_format'] = 'city_state_zip';
255
- }
256
-
257
- if ( empty( $wpsl_settings['auto_zoom_level'] ) ) {
258
- $wpsl_settings['auto_zoom_level'] = 15;
259
- }
260
-
261
- if ( empty( $wpsl_settings['hide_distance'] ) ) {
262
- $wpsl_settings['hide_distance'] = 0;
263
- }
264
-
265
- if ( empty( $wpsl_settings['debug'] ) ) {
266
- $wpsl_settings['debug'] = 0;
267
- }
268
-
269
- if ( empty( $wpsl_settings['category_dropdown'] ) ) {
270
- $wpsl_settings['category_dropdown'] = 0;
271
- }
272
-
273
- /*
274
- * Replace marker_bounce with marker_effect to better reflect what the option contains.
275
- *
276
- * If a user hovers over the result list then either the corresponding marker will bounce,
277
- * the info window will open, or nothing will happen.
278
- *
279
- * The default behaviour is that the marker will bounce.
280
- */
281
- if ( empty( $wpsl_settings['marker_effect'] ) ) {
282
- $wpsl_settings['marker_effect'] = ( $wpsl_settings['marker_bounce'] ) ? 'bounce' : 'ignore';
283
- unset( $wpsl_settings['marker_bounce'] );
284
- }
285
-
286
- /*
287
- * The default input for the opening hours is set to textarea for current users,
288
- * for new users it will be set to dropdown ( easier to format in a table output and to use with schema.org in the future ).
289
- */
290
- if ( empty( $wpsl_settings['editor_hour_input'] ) ) {
291
- $wpsl_settings['editor_hour_input'] = 'textarea';
292
- }
293
-
294
- // Rename store_below_scroll to listing_below_no_scroll, it better reflects what it does.
295
- if ( empty( $wpsl_settings['listing_below_no_scroll'] ) && isset( $wpsl_settings['store_below_scroll'] ) ) {
296
- $wpsl_settings['listing_below_no_scroll'] = $wpsl_settings['store_below_scroll'];
297
- unset( $wpsl_settings['store_below_scroll'] );
298
- }
299
-
300
- // Change the template ids from number based to name based.
301
- if ( is_numeric( $wpsl_settings['template_id'] ) ) {
302
- $wpsl_settings['template_id'] = ( !$wpsl_settings['template_id'] ) ? 'default' : 'below_map';
303
- }
304
-
305
- $replace_data = array(
306
- 'max_results' => $wpsl_settings['max_results'],
307
- 'search_radius' => $wpsl_settings['search_radius']
308
- );
309
-
310
- /*
311
- * Replace the () with [], this fixes an issue with the mod_security module that is installed on some servers.
312
- * It triggerd a 'Possible SQL injection attack' warning probably because of the int,(int) format of the data.
313
- */
314
- foreach ( $replace_data as $index => $option_value ) {
315
- $wpsl_settings[$index] = str_replace( array( '(', ')' ), array( '[', ']' ), $option_value );
316
- }
317
-
318
- // The reset button now uses an icon instead of text, so no need for the label anymore.
319
- unset( $wpsl_settings['reset_label'] );
320
-
321
- update_option( 'wpsl_settings', $wpsl_settings );
322
-
323
- /*
324
- * Users upgrading from 1.x will be given the choice between the textarea or
325
- * dropdowns for the opening hours.
326
- *
327
- * New users don't get that choice, they will only get the dropdowns.
328
- *
329
- * The wpsl_legacy_support option is used to determine if we need to show both options.
330
- */
331
- update_option( 'wpsl_legacy_support', 1 );
332
-
333
- // Add the WPSL roles and caps.
334
- wpsl_add_roles();
335
- wpsl_add_caps();
336
-
337
- // If there is a wpsl_stores table, then we need to convert all the locations to the 'wpsl_stores' custom post type.
338
- if ( $wpdb->get_var( "SHOW TABLES LIKE '$wpsl_table'" ) && version_compare( $current_version, '1.9', '<' ) ) {
339
- if ( wpsl_remaining_cpt_count() ) {
340
- update_option( 'wpsl_convert_cpt', 'in_progress' );
341
- }
342
- }
343
- }
344
- }
345
-
346
- /*
347
- * Both map options are no longer supported in 3.22 of the Google Maps API.
348
- * See: https://developers.google.com/maps/articles/v322-controls-diff
349
- */
350
- if ( version_compare( $current_version, '2.0.3', '<' ) ) {
351
- unset( $wpsl_settings['control_style'] );
352
- unset( $wpsl_settings['pan_controls'] );
353
-
354
- update_option( 'wpsl_settings', $wpsl_settings );
355
- }
356
-
357
- if ( version_compare( $current_version, '2.1.0', '<' ) ) {
358
- if ( !isset( $wpsl_settings['api_geocode_component'] ) ) {
359
- $wpsl_settings['api_geocode_component'] = 0;
360
- }
361
-
362
- update_option( 'wpsl_settings', $wpsl_settings );
363
- }
364
-
365
- if ( version_compare( $current_version, '2.2', '<' ) ) {
366
- $wpsl_settings['autocomplete'] = 0;
367
- $wpsl_settings['category_default_label'] = __( 'Any', 'wpsl' );
368
-
369
- // Rename the 'zoom_name' and 'zoom_latlng' to 'start_name' and 'start_latlng'.
370
- if ( isset( $wpsl_settings['zoom_name'] ) ) {
371
- $wpsl_settings['start_name'] = $wpsl_settings['zoom_name'];
372
- unset( $wpsl_settings['zoom_name'] );
373
- }
374
-
375
- if ( isset( $wpsl_settings['zoom_latlng'] ) ) {
376
- $wpsl_settings['start_latlng'] = $wpsl_settings['zoom_latlng'];
377
- unset( $wpsl_settings['zoom_latlng'] );
378
- }
379
-
380
- if ( isset( $wpsl_settings['category_dropdown'] ) ) {
381
- $wpsl_settings['category_filter'] = $wpsl_settings['category_dropdown'];
382
- unset( $wpsl_settings['category_dropdown'] );
383
- }
384
-
385
- // We now have separate browser and server key fields, and assume the existing key is a server key.
386
- if ( isset( $wpsl_settings['api_key'] ) ) {
387
- $wpsl_settings['api_server_key'] = $wpsl_settings['api_key'];
388
- unset( $wpsl_settings['api_key'] );
389
- }
390
-
391
- $wpsl_settings['api_browser_key'] = '';
392
- $wpsl_settings['category_filter_type'] = 'dropdown';
393
- $wpsl_settings['hide_country'] = 0;
394
- $wpsl_settings['show_contact_details'] = 0;
395
-
396
- update_option( 'wpsl_settings', $wpsl_settings );
397
- }
398
-
399
- if ( version_compare( $current_version, '2.2.4', '<' ) ) {
400
- $wpsl_settings['deregister_gmaps'] = 0;
401
-
402
- update_option( 'wpsl_settings', $wpsl_settings );
403
- }
404
-
405
- if ( version_compare( $current_version, '2.2.9', '<' ) ) {
406
- $wpsl_settings['run_fitbounds'] = 1;
407
-
408
- update_option( 'wpsl_settings', $wpsl_settings );
409
- }
410
-
411
- if ( version_compare( $current_version, '2.2.13', '<' ) ) {
412
- $wpsl_settings['clickable_contact_details'] = 0;
413
-
414
- update_option( 'wpsl_settings', $wpsl_settings );
415
- }
416
-
417
- if ( version_compare( $current_version, '2.2.20', '<' ) ) {
418
- $wpsl_settings['force_postalcode'] = 0;
419
- $wpsl_settings['permalink_remove_front'] = 0;
420
-
421
- update_option( 'wpsl_settings', $wpsl_settings );
422
- }
423
-
424
- update_option( 'wpsl_version', WPSL_VERSION_NUM );
425
- }
426
-
427
- /**
428
- * Check if we need to show the notice that tells users that the store locations
429
- * need to be converted to custom post types before the update from 1.x to 2.x is complete.
430
- *
431
- * @since 2.0
432
- * @return void
433
- */
434
- function wpsl_cpt_update_state() {
435
-
436
- global $wpsl_admin;
437
-
438
- $conversion_state = get_option( 'wpsl_convert_cpt' );
439
-
440
- if ( $conversion_state == 'in_progress' ) {
441
- if ( ( !defined( 'DOING_AJAX' ) || !DOING_AJAX ) ) {
442
- $remaining = wpsl_remaining_cpt_count();
443
- $wpsl_admin->notices->save( 'error', sprintf( __( 'Because you updated WP Store Locator from version 1.x, the %s current store locations need to be %sconverted%s to custom post types.', 'wpsl' ), "<span class='wpsl-cpt-remaining'>" . $remaining . "</span>", "<a href='#' id='wpsl-cpt-dialog'>", "</a>" ) );
444
-
445
- add_action( 'admin_footer', 'wpsl_cpt_dialog_html' );
446
- }
447
-
448
- add_action( 'admin_enqueue_scripts', 'wpsl_convert_cpt_js' );
449
- add_action( 'wp_ajax_convert_cpt', 'wpsl_convert_cpt' );
450
- add_action( 'wp_ajax_convert_cpt_count', 'wpsl_convert_cpt_count' );
451
- }
452
- }
453
-
454
- /**
455
- * Include the js file that handles the ajax request to
456
- * start converting the 1.x store locations to custom post types.
457
- *
458
- * @since 2.0
459
- * @return void
460
- */
461
- function wpsl_convert_cpt_js() {
462
-
463
- $cpt_js_l10n = array(
464
- 'timeout' => sprintf( __( 'The script converting the locations timed out. %s You can click the "Start Converting" button again to restart the script. %s If there are thousands of store locations left to convert and you keep seeing this message, then you can try to contact your host and ask if they can increase the maximum execution time. %s The plugin tried to disable the maximum execution time, but if you are reading this then that failed.', 'wpsl' ), '<br><br>', '<br><br>', '<br><br>' ),
465
- 'securityFail' => __( 'Security check failed, reload the page and try again.', 'wpsl' )
466
- );
467
-
468
- wp_enqueue_script( 'jquery-ui-dialog' );
469
- wp_enqueue_script( 'wpsl-queue', plugins_url( '/js/ajax-queue.js', __FILE__ ), array( 'jquery' ), false );
470
- wp_enqueue_script( 'wpsl-cpt-js', plugins_url( '/js/wpsl-cpt-upgrade.js', __FILE__ ), array( 'jquery' ), false );
471
- wp_localize_script( 'wpsl-cpt-js', 'wpslCptConversion', $cpt_js_l10n );
472
- }
473
-
474
- /**
475
- * The html for the lightbox
476
- *
477
- * @since 2.0
478
- * @return void
479
- */
480
- function wpsl_cpt_dialog_html() {
481
-
482
- ?>
483
- <div id="wpsl-cpt-lightbox" style="display:none;">
484
- <span class="tb-close-icon"></span>
485
- <p class="wpsl-cpt-remaining"><?php _e( 'Store locations to convert:', 'wpsl' ); echo '<span></span>'; ?></p>
486
- <div class="wslp-cpt-fix-wrap">
487
- <input id="wpsl-start-cpt-conversion" class="button-primary" type="submit" value="<?php _e( 'Start Converting', 'wpsl' ); ?>" >
488
- <img class="wpsl-preloader" alt="preloader" src="<?php echo WPSL_URL . 'img/ajax-loader.gif'; ?>" />
489
- </div>
490
- <input type="hidden" name="wpsl-cpt-fix-nonce" value="<?php echo wp_create_nonce( 'wpsl-cpt-fix' ); ?>" />
491
- <input type="hidden" name="wpsl-cpt-conversion-count" value="<?php echo wp_create_nonce( 'wpsl-cpt-count' ); ?>" />
492
- </div>
493
- <div id="wpsl-cpt-overlay" style="display:none;"></div>
494
- <style>
495
- .wslp-cpt-fix-wrap {
496
- float:left;
497
- clear:both;
498
- width:100%;
499
- margin:0 0 15px 0;
500
- }
501
-
502
- #wpsl-cpt-lightbox .wpsl-cpt-remaining span {
503
- margin-left:5px;
504
- }
505
-
506
- #wpsl-start-cpt-conversion {
507
- float:left;
508
- }
509
-
510
- .wslp-cpt-fix-wrap .wpsl-preloader,
511
- .wslp-cpt-fix-wrap span {
512
- float:left;
513
- margin:8px 0 0 10px;
514
- }
515
-
516
- .wslp-cpt-fix-wrap .wpsl-preloader {
517
- display: none;
518
- }
519
-
520
- #wpsl-cpt-lightbox {
521
- position:fixed;
522
- width:450px;
523
- left:50%;
524
- right:50%;
525
- top:3.8em;
526
- padding:15px;
527
- background:none repeat scroll 0 0 #fff;
528
- border-radius:3px;
529
- margin-left:-225px;
530
- z-index: 9999;
531
- }
532
-
533
- #wpsl-cpt-overlay {
534
- position:fixed;
535
- right:0;
536
- top:0;
537
- z-index:9998;
538
- background:none repeat scroll 0 0 #000;
539
- bottom:0;
540
- left:0;
541
- opacity:0.5;
542
- }
543
-
544
- .tb-close-icon {
545
- color: #666;
546
- text-align: center;
547
- line-height: 29px;
548
- width: 29px;
549
- height: 29px;
550
- position: absolute;
551
- top: 0;
552
- right: 0;
553
- }
554
-
555
- .tb-close-icon:before {
556
- content: '\f158';
557
- font: normal 20px/29px 'dashicons';
558
- speak: none;
559
- -webkit-font-smoothing: antialiased;
560
- -moz-osx-font-smoothing: grayscale;
561
- }
562
-
563
- .tb-close-icon:hover {
564
- color: #999 !important;
565
- cursor: pointer;
566
- }
567
- </style>
568
- <?php
569
- }
570
-
571
- /**
572
- * Handle the ajax call to start converting the
573
- * store locations to custom post types.
574
- *
575
- * @since 2.0
576
- * @return void|string json on completion
577
- */
578
- function wpsl_convert_cpt() {
579
-
580
- if ( !current_user_can( 'manage_options' ) )
581
- die( '-1' );
582
- check_ajax_referer( 'wpsl-cpt-fix' );
583
-
584
- // Start the cpt coversion.
585
- wpsl_cpt_conversion();
586
-
587
- exit();
588
- }
589
-
590
- /**
591
- * Get the amount of locations that still need to be converted.
592
- *
593
- * @since 2.0
594
- * @return string json The amount of locations that still need to be converted
595
- */
596
- function wpsl_convert_cpt_count() {
597
-
598
- if ( !current_user_can( 'manage_options' ) )
599
- die( '-1' );
600
- check_ajax_referer( 'wpsl-cpt-count' );
601
-
602
- $remaining_count = wpsl_remaining_cpt_count();
603
-
604
- $response['success'] = true;
605
-
606
- if ( $remaining_count ) {
607
- $response['count'] = $remaining_count;
608
- } else {
609
- $response['url'] = sprintf( __( 'All the store locations are now converted to custom post types. %s You can view them on the %sAll Stores%s page.', 'wpsl' ), '<br><br>', '<a href="' . admin_url( 'edit.php?post_type=wpsl_stores' ) . '">', '</a>' );
610
-
611
- delete_option( 'wpsl_convert_cpt' );
612
- }
613
-
614
- wp_send_json( $response );
615
-
616
- exit();
617
- }
618
-
619
- /**
620
- * Return the difference between the number of existing wpsl custom post types,
621
- * and the number of records in the old wpsl_stores database.
622
- *
623
- * @since 2.0
624
- * @return int|boolean $remaining The amount of locations that still need to be converted
625
- */
626
- function wpsl_remaining_cpt_count() {
627
-
628
- global $wpdb;
629
-
630
- $table = $wpdb->prefix . 'wpsl_stores';
631
- $count = wp_count_posts( 'wpsl_stores' );
632
-
633
- if ( isset( $count->publish ) && isset( $count->draft ) ) {
634
- $cpt_count = $count->publish + $count->draft;
635
- } else {
636
- $cpt_count = 0;
637
- }
638
-
639
- $db_count = $wpdb->get_var( "SELECT COUNT(wpsl_id) FROM $table" );
640
- $difference = $db_count - $cpt_count;
641
-
642
- /*
643
- * This prevents users who used the 2.0 beta, and later added
644
- * more stores from seeing the upgrade notice again.
645
- */
646
- $remaining = ( $difference < 0 ) ? false : $difference;
647
-
648
- return $remaining;
649
- }
650
-
651
- /**
652
- * Convert the existing locations to custom post types.
653
- *
654
- * @since 2.0
655
- * @return void|boolean True if the conversion is completed
656
- */
657
- function wpsl_cpt_conversion() {
658
-
659
- global $wpdb;
660
-
661
- // Try to disable the time limit to prevent timeouts.
662
- @set_time_limit( 0 );
663
-
664
- $meta_keys = array( 'address', 'address2', 'city', 'state', 'zip', 'country', 'country_iso', 'lat', 'lng', 'phone', 'fax', 'url', 'email', 'hours' );
665
- $offset = wpsl_remaining_cpt_count();
666
- $wpsl_table = $wpdb->prefix . 'wpsl_stores';
667
- $stores = $wpdb->get_results( "(SELECT * FROM $wpsl_table ORDER BY wpsl_id DESC LIMIT $offset) ORDER BY wpsl_id ASC" );
668
-
669
- foreach ( $stores as $store ) {
670
-
671
- // Make sure we set the correct post status.
672
- if ( $store->active ) {
673
- $post_status = 'publish';
674
- } else {
675
- $post_status = 'draft';
676
- }
677
-
678
- $post = array (
679
- 'post_type' => 'wpsl_stores',
680
- 'post_status' => $post_status,
681
- 'post_title' => $store->store,
682
- 'post_content' => $store->description
683
- );
684
-
685
- $post_id = wp_insert_post( $post );
686
-
687
- if ( $post_id ) {
688
-
689
- // Save the data from the wpsl_stores db table as post meta data.
690
- foreach ( $meta_keys as $meta_key ) {
691
- if ( isset( $store->{$meta_key} ) && !empty( $store->{$meta_key} ) ) {
692
- update_post_meta( $post_id, 'wpsl_' . $meta_key, $store->{$meta_key} );
693
- }
694
- }
695
-
696
- // If we have a thumb ID set the post thumbnail for the inserted post.
697
- if ( $store->thumb_id ) {
698
- set_post_thumbnail( $post_id, $store->thumb_id );
699
- }
700
- }
701
- }
 
 
 
 
 
 
702
  }
1
+ <?php
2
+ add_action( 'admin_init', 'wpsl_check_upgrade' );
3
+ add_action( 'admin_init', 'wpsl_cpt_update_state' );
4
+
5
+ /**
6
+ * If the db doesn't hold the current version, run the upgrade procedure
7
+ *
8
+ * @since 1.2
9
+ * @return void
10
+ */
11
+ function wpsl_check_upgrade() {
12
+
13
+ global $wpsl_settings;
14
+
15
+ $current_version = get_option( 'wpsl_version' );
16
+
17
+ if ( version_compare( $current_version, WPSL_VERSION_NUM, '===' ) )
18
+ return;
19
+
20
+ if ( version_compare( $current_version, '1.1', '<' ) ) {
21
+ if ( is_array( $wpsl_settings ) ) {
22
+ if ( empty( $wpsl_settings['reset_map'] ) ) {
23
+ $wpsl_settings['reset_map'] = 0;
24
+ }
25
+
26
+ if ( empty( $wpsl_settings['auto_load'] ) ) {
27
+ $wpsl_settings['auto_load'] = 1;
28
+ }
29
+
30
+ if ( empty( $wpsl_settings['new_window'] ) ) {
31
+ $wpsl_settings['new_window'] = 0;
32
+ }
33
+
34
+ update_option( 'wpsl_settings', $wpsl_settings );
35
+ }
36
+ }
37
+
38
+ if ( version_compare( $current_version, '1.2', '<' ) ) {
39
+ if ( is_array( $wpsl_settings ) ) {
40
+ if ( empty( $wpsl_settings['store_below'] ) ) {
41
+ $wpsl_settings['store_below'] = 0;
42
+ }
43
+
44
+ if ( empty( $wpsl_settings['direction_redirect'] ) ) {
45
+ $wpsl_settings['direction_redirect'] = 0;
46
+ }
47
+
48
+ update_option( 'wpsl_settings', $wpsl_settings );
49
+ }
50
+ }
51
+
52
+ if ( version_compare( $current_version, '1.2.11', '<' ) ) {
53
+ if ( is_array( $wpsl_settings ) ) {
54
+ if ( empty( $wpsl_settings['more_info'] ) ) {
55
+ $wpsl_settings['more_info'] = 0;
56
+ }
57
+
58
+ if ( empty( $wpsl_settings['more_label'] ) ) {
59
+ $wpsl_settings['more_label'] = __( 'More info', 'wpsl' );
60
+ }
61
+
62
+ if ( empty( $wpsl_settings['mouse_focus'] ) ) {
63
+ $wpsl_settings['mouse_focus'] = 0;
64
+ }
65
+
66
+ update_option( 'wpsl_settings', $wpsl_settings );
67
+ }
68
+ }
69
+
70
+ if ( version_compare( $current_version, '1.2.12', '<' ) ) {
71
+ if ( is_array( $wpsl_settings ) ) {
72
+ if ( empty( $wpsl_settings['more_info_location'] ) ) {
73
+ $wpsl_settings['more_info_location'] = __( 'info window', 'wpsl' );
74
+ }
75
+
76
+ if ( empty( $wpsl_settings['back_label'] ) ) {
77
+ $wpsl_settings['back_label'] = __( 'Back', 'wpsl' );
78
+ }
79
+
80
+ if ( empty( $wpsl_settings['reset_label'] ) ) {
81
+ $wpsl_settings['reset_label'] = __( 'Reset', 'wpsl' );
82
+ }
83
+
84
+ if ( empty( $wpsl_settings['store_below_scroll'] ) ) {
85
+ $wpsl_settings['store_below_scroll'] = 0;
86
+ }
87
+
88
+ update_option( 'wpsl_settings', $wpsl_settings );
89
+ }
90
+ }
91
+
92
+ if ( version_compare( $current_version, '1.2.20', '<' ) ) {
93
+
94
+ global $wpdb;
95
+
96
+ $wpsl_table = $wpdb->prefix . 'wpsl_stores';
97
+
98
+ // Rename the street field to address.
99
+ $wpdb->query( "ALTER TABLE $wpsl_table CHANGE street address VARCHAR(255)" );
100
+
101
+ // Add the second address field.
102
+ $wpdb->query( "ALTER TABLE $wpsl_table ADD address2 VARCHAR(255) NULL AFTER address" );
103
+
104
+ if ( is_array( $wpsl_settings ) ) {
105
+ if ( empty( $wpsl_settings['store_url'] ) ) {
106
+ $wpsl_settings['store_url'] = 0;
107
+ }
108
+
109
+ if ( empty( $wpsl_settings['phone_url'] ) ) {
110
+ $wpsl_settings['phone_url'] = 0;
111
+ }
112
+
113
+ if ( empty( $wpsl_settings['marker_clusters'] ) ) {
114
+ $wpsl_settings['marker_clusters'] = 0;
115
+ }
116
+
117
+ if ( empty( $wpsl_settings['cluster_zoom'] ) ) {
118
+ $wpsl_settings['cluster_zoom'] = 0;
119
+ }
120
+
121
+ if ( empty( $wpsl_settings['cluster_size'] ) ) {
122
+ $wpsl_settings['cluster_size'] = 0;
123
+ }
124
+
125
+ if ( empty( $wpsl_settings['template_id'] ) ) {
126
+ $wpsl_settings['template_id'] = ( $wpsl_settings['store_below'] ) ? 1 : 0;
127
+ unset( $wpsl_settings['store_below'] );
128
+ }
129
+
130
+ if ( empty( $wpsl_settings['marker_streetview'] ) ) {
131
+ $wpsl_settings['marker_streetview'] = 0;
132
+ }
133
+
134
+ if ( empty( $wpsl_settings['marker_zoom_to'] ) ) {
135
+ $wpsl_settings['marker_zoom_to'] = 0;
136
+ }
137
+
138
+ if ( !isset( $wpsl_settings['editor_country'] ) ) {
139
+ $wpsl_settings['editor_country'] = '';
140
+ }
141
+
142
+ if ( empty( $wpsl_settings['street_view_label'] ) ) {
143
+ $wpsl_settings['street_view_label'] = __( 'Street view', 'wpsl' );
144
+ }
145
+
146
+ if ( empty( $wpsl_settings['zoom_here_label'] ) ) {
147
+ $wpsl_settings['zoom_here_label'] = __( 'Zoom here', 'wpsl' );
148
+ }
149
+
150
+ if ( empty( $wpsl_settings['no_directions_label'] ) ) {
151
+ $wpsl_settings['no_directions_label'] = __( 'No route could be found between the origin and destination', 'wpsl' );
152
+ }
153
+
154
+ update_option( 'wpsl_settings', $wpsl_settings );
155
+ }
156
+ }
157
+
158
+ if ( version_compare( $current_version, '2.0', '<' ) ) {
159
+
160
+ global $wpdb;
161
+
162
+ $wpsl_table = $wpdb->prefix . 'wpsl_stores';
163
+
164
+ if ( is_array( $wpsl_settings ) ) {
165
+ if ( empty( $wpsl_settings['radius_dropdown'] ) ) {
166
+ $wpsl_settings['radius_dropdown'] = 1;
167
+ }
168
+
169
+ if ( empty( $wpsl_settings['permalinks'] ) ) {
170
+ $wpsl_settings['permalinks'] = 0;
171
+ }
172
+
173
+ if ( empty( $wpsl_settings['permalink_slug'] ) ) {
174
+ $wpsl_settings['permalink_slug'] = __( 'stores', 'wpsl' );
175
+ }
176
+
177
+ if ( empty( $wpsl_settings['category_slug'] ) ) {
178
+ $wpsl_settings['category_slug'] = __( 'store-category', 'wpsl' );
179
+ }
180
+
181
+ if ( empty( $wpsl_settings['editor_hours'] ) ) {
182
+ $wpsl_settings['editor_hours'] = wpsl_default_opening_hours();
183
+ }
184
+
185
+ if ( empty( $wpsl_settings['editor_hour_format'] ) ) {
186
+ $wpsl_settings['editor_hour_format'] = 12;
187
+ }
188
+
189
+ if ( empty( $wpsl_settings['editor_map_type'] ) ) {
190
+ $wpsl_settings['editor_map_type'] = 'roadmap';
191
+ }
192
+
193
+ if ( empty( $wpsl_settings['infowindow_style'] ) ) {
194
+ $wpsl_settings['infowindow_style'] = 'default';
195
+ }
196
+
197
+ if ( empty( $wpsl_settings['email_label'] ) ) {
198
+ $wpsl_settings['email_label'] = __( 'Email', 'wpsl' );
199
+ }
200
+
201
+ if ( empty( $wpsl_settings['url_label'] ) ) {
202
+ $wpsl_settings['url_label'] = __( 'Url', 'wpsl' );
203
+ }
204
+
205
+ if ( empty( $wpsl_settings['category_label'] ) ) {
206
+ $wpsl_settings['category_label'] = __( 'Category filter', 'wpsl' );
207
+ }
208
+
209
+ if ( empty( $wpsl_settings['show_credits'] ) ) {
210
+ $wpsl_settings['show_credits'] = 0;
211
+ }
212
+
213
+ if ( empty( $wpsl_settings['autoload_limit'] ) ) {
214
+ $wpsl_settings['autoload_limit'] = 50;
215
+ }
216
+
217
+ if ( empty( $wpsl_settings['scrollwheel'] ) ) {
218
+ $wpsl_settings['scrollwheel'] = 1;
219
+ }
220
+
221
+ if ( empty( $wpsl_settings['type_control'] ) ) {
222
+ $wpsl_settings['type_control'] = 0;
223
+ }
224
+
225
+ if ( empty( $wpsl_settings['hide_hours'] ) ) {
226
+ $wpsl_settings['hide_hours'] = 0;
227
+ }
228
+
229
+ // Either correct the existing map style format from the 2.0 beta or set it to empty.
230
+ if ( isset( $wpsl_settings['map_style'] ) && is_array( $wpsl_settings['map_style'] ) && isset( $wpsl_settings['map_style']['id'] ) ) {
231
+ switch( $wpsl_settings['map_style']['id'] ) {
232
+ case 'custom':
233
+ $map_style = $wpsl_settings['map_style']['custom_json'];
234
+ break;
235
+ case 'default':
236
+ $map_style = '';
237
+ break;
238
+ default:
239
+ $map_style = $wpsl_settings['map_style']['theme_json'];
240
+ break;
241
+ }
242
+
243
+ $wpsl_settings['map_style'] = $map_style;
244
+ } else {
245
+ $wpsl_settings['map_style'] = '';
246
+ }
247
+
248
+ if ( empty( $wpsl_settings['autoload'] ) ) {
249
+ $wpsl_settings['autoload'] = $wpsl_settings['auto_load'];
250
+ unset( $wpsl_settings['auto_load'] );
251
+ }
252
+
253
+ if ( empty( $wpsl_settings['address_format'] ) ) {
254
+ $wpsl_settings['address_format'] = 'city_state_zip';
255
+ }
256
+
257
+ if ( empty( $wpsl_settings['auto_zoom_level'] ) ) {
258
+ $wpsl_settings['auto_zoom_level'] = 15;
259
+ }
260
+
261
+ if ( empty( $wpsl_settings['hide_distance'] ) ) {
262
+ $wpsl_settings['hide_distance'] = 0;
263
+ }
264
+
265
+ if ( empty( $wpsl_settings['debug'] ) ) {
266
+ $wpsl_settings['debug'] = 0;
267
+ }
268
+
269
+ if ( empty( $wpsl_settings['category_dropdown'] ) ) {
270
+ $wpsl_settings['category_dropdown'] = 0;
271
+ }
272
+
273
+ /*
274
+ * Replace marker_bounce with marker_effect to better reflect what the option contains.
275
+ *
276
+ * If a user hovers over the result list then either the corresponding marker will bounce,
277
+ * the info window will open, or nothing will happen.
278
+ *
279
+ * The default behaviour is that the marker will bounce.
280
+ */
281
+ if ( empty( $wpsl_settings['marker_effect'] ) ) {
282
+ $wpsl_settings['marker_effect'] = ( $wpsl_settings['marker_bounce'] ) ? 'bounce' : 'ignore';
283
+ unset( $wpsl_settings['marker_bounce'] );
284
+ }
285
+
286
+ /*
287
+ * The default input for the opening hours is set to textarea for current users,
288
+ * for new users it will be set to dropdown ( easier to format in a table output and to use with schema.org in the future ).
289
+ */
290
+ if ( empty( $wpsl_settings['editor_hour_input'] ) ) {
291
+ $wpsl_settings['editor_hour_input'] = 'textarea';
292
+ }
293
+
294
+ // Rename store_below_scroll to listing_below_no_scroll, it better reflects what it does.
295
+ if ( empty( $wpsl_settings['listing_below_no_scroll'] ) && isset( $wpsl_settings['store_below_scroll'] ) ) {
296
+ $wpsl_settings['listing_below_no_scroll'] = $wpsl_settings['store_below_scroll'];
297
+ unset( $wpsl_settings['store_below_scroll'] );
298
+ }
299
+
300
+ // Change the template ids from number based to name based.
301
+ if ( is_numeric( $wpsl_settings['template_id'] ) ) {
302
+ $wpsl_settings['template_id'] = ( !$wpsl_settings['template_id'] ) ? 'default' : 'below_map';
303
+ }
304
+
305
+ $replace_data = array(
306
+ 'max_results' => $wpsl_settings['max_results'],
307
+ 'search_radius' => $wpsl_settings['search_radius']
308
+ );
309
+
310
+ /*
311
+ * Replace the () with [], this fixes an issue with the mod_security module that is installed on some servers.
312
+ * It triggerd a 'Possible SQL injection attack' warning probably because of the int,(int) format of the data.
313
+ */
314
+ foreach ( $replace_data as $index => $option_value ) {
315
+ $wpsl_settings[$index] = str_replace( array( '(', ')' ), array( '[', ']' ), $option_value );
316
+ }
317
+
318
+ // The reset button now uses an icon instead of text, so no need for the label anymore.
319
+ unset( $wpsl_settings['reset_label'] );
320
+
321
+ update_option( 'wpsl_settings', $wpsl_settings );
322
+
323
+ /*
324
+ * Users upgrading from 1.x will be given the choice between the textarea or
325
+ * dropdowns for the opening hours.
326
+ *
327
+ * New users don't get that choice, they will only get the dropdowns.
328
+ *
329
+ * The wpsl_legacy_support option is used to determine if we need to show both options.
330
+ */
331
+ update_option( 'wpsl_legacy_support', 1 );
332
+
333
+ // Add the WPSL roles and caps.
334
+ wpsl_add_roles();
335
+ wpsl_add_caps();
336
+
337
+ // If there is a wpsl_stores table, then we need to convert all the locations to the 'wpsl_stores' custom post type.
338
+ if ( $wpdb->get_var( "SHOW TABLES LIKE '$wpsl_table'" ) && version_compare( $current_version, '1.9', '<' ) ) {
339
+ if ( wpsl_remaining_cpt_count() ) {
340
+ update_option( 'wpsl_convert_cpt', 'in_progress' );
341
+ }
342
+ }
343
+ }
344
+ }
345
+
346
+ /*
347
+ * Both map options are no longer supported in 3.22 of the Google Maps API.
348
+ * See: https://developers.google.com/maps/articles/v322-controls-diff
349
+ */
350
+ if ( version_compare( $current_version, '2.0.3', '<' ) ) {
351
+ unset( $wpsl_settings['control_style'] );
352
+ unset( $wpsl_settings['pan_controls'] );
353
+
354
+ update_option( 'wpsl_settings', $wpsl_settings );
355
+ }
356
+
357
+ if ( version_compare( $current_version, '2.1.0', '<' ) ) {
358
+ if ( !isset( $wpsl_settings['api_geocode_component'] ) ) {
359
+ $wpsl_settings['api_geocode_component'] = 0;
360
+ }
361
+
362
+ update_option( 'wpsl_settings', $wpsl_settings );
363
+ }
364
+
365
+ if ( version_compare( $current_version, '2.2', '<' ) ) {
366
+ $wpsl_settings['autocomplete'] = 0;
367
+ $wpsl_settings['category_default_label'] = __( 'Any', 'wpsl' );
368
+
369
+ // Rename the 'zoom_name' and 'zoom_latlng' to 'start_name' and 'start_latlng'.
370
+ if ( isset( $wpsl_settings['zoom_name'] ) ) {
371
+ $wpsl_settings['start_name'] = $wpsl_settings['zoom_name'];
372
+ unset( $wpsl_settings['zoom_name'] );
373
+ }
374
+
375
+ if ( isset( $wpsl_settings['zoom_latlng'] ) ) {
376
+ $wpsl_settings['start_latlng'] = $wpsl_settings['zoom_latlng'];
377
+ unset( $wpsl_settings['zoom_latlng'] );
378
+ }
379
+
380
+ if ( isset( $wpsl_settings['category_dropdown'] ) ) {
381
+ $wpsl_settings['category_filter'] = $wpsl_settings['category_dropdown'];
382
+ unset( $wpsl_settings['category_dropdown'] );
383
+ }
384
+
385
+ // We now have separate browser and server key fields, and assume the existing key is a server key.
386
+ if ( isset( $wpsl_settings['api_key'] ) ) {
387
+ $wpsl_settings['api_server_key'] = $wpsl_settings['api_key'];
388
+ unset( $wpsl_settings['api_key'] );
389
+ }
390
+
391
+ $wpsl_settings['api_browser_key'] = '';
392
+ $wpsl_settings['category_filter_type'] = 'dropdown';
393
+ $wpsl_settings['hide_country'] = 0;
394
+ $wpsl_settings['show_contact_details'] = 0;
395
+
396
+ update_option( 'wpsl_settings', $wpsl_settings );
397
+ }
398
+
399
+ if ( version_compare( $current_version, '2.2.4', '<' ) ) {
400
+ $wpsl_settings['deregister_gmaps'] = 0;
401
+
402
+ update_option( 'wpsl_settings', $wpsl_settings );
403
+ }
404
+
405
+ if ( version_compare( $current_version, '2.2.9', '<' ) ) {
406
+ $wpsl_settings['run_fitbounds'] = 1;
407
+
408
+ update_option( 'wpsl_settings', $wpsl_settings );
409
+ }
410
+
411
+ if ( version_compare( $current_version, '2.2.13', '<' ) ) {
412
+ $wpsl_settings['clickable_contact_details'] = 0;
413
+
414
+ update_option( 'wpsl_settings', $wpsl_settings );
415
+ }
416
+
417
+ if ( version_compare( $current_version, '2.2.20', '<' ) ) {
418
+ $wpsl_settings['force_postalcode'] = 0;
419
+ $wpsl_settings['permalink_remove_front'] = 0;
420
+
421
+ update_option( 'wpsl_settings', $wpsl_settings );
422
+ }
423
+
424
+ if ( version_compare( $current_version, '2.2.22', '<' ) ) {
425
+ $wpsl_settings['delay_loading'] = 0;
426
+
427
+ update_option( 'wpsl_settings', $wpsl_settings );
428
+ }
429
+
430
+ update_option( 'wpsl_version', WPSL_VERSION_NUM );
431
+ }
432
+
433
+ /**
434
+ * Check if we need to show the notice that tells users that the store locations
435
+ * need to be converted to custom post types before the update from 1.x to 2.x is complete.
436
+ *
437
+ * @since 2.0
438
+ * @return void
439
+ */
440
+ function wpsl_cpt_update_state() {
441
+
442
+ global $wpsl_admin;
443
+
444
+ $conversion_state = get_option( 'wpsl_convert_cpt' );
445
+
446
+ if ( $conversion_state == 'in_progress' ) {
447
+ if ( ( !defined( 'DOING_AJAX' ) || !DOING_AJAX ) ) {
448
+ $remaining = wpsl_remaining_cpt_count();
449
+ $wpsl_admin->notices->save( 'error', sprintf( __( 'Because you updated WP Store Locator from version 1.x, the %s current store locations need to be %sconverted%s to custom post types.', 'wpsl' ), "<span class='wpsl-cpt-remaining'>" . $remaining . "</span>", "<a href='#' id='wpsl-cpt-dialog'>", "</a>" ) );
450
+
451
+ add_action( 'admin_footer', 'wpsl_cpt_dialog_html' );
452
+ }
453
+
454
+ add_action( 'admin_enqueue_scripts', 'wpsl_convert_cpt_js' );
455
+ add_action( 'wp_ajax_convert_cpt', 'wpsl_convert_cpt' );
456
+ add_action( 'wp_ajax_convert_cpt_count', 'wpsl_convert_cpt_count' );
457
+ }
458
+ }
459
+
460
+ /**
461
+ * Include the js file that handles the ajax request to
462
+ * start converting the 1.x store locations to custom post types.
463
+ *
464
+ * @since 2.0
465
+ * @return void
466
+ */
467
+ function wpsl_convert_cpt_js() {
468
+
469
+ $cpt_js_l10n = array(
470
+ 'timeout' => sprintf( __( 'The script converting the locations timed out. %s You can click the "Start Converting" button again to restart the script. %s If there are thousands of store locations left to convert and you keep seeing this message, then you can try to contact your host and ask if they can increase the maximum execution time. %s The plugin tried to disable the maximum execution time, but if you are reading this then that failed.', 'wpsl' ), '<br><br>', '<br><br>', '<br><br>' ),
471
+ 'securityFail' => __( 'Security check failed, reload the page and try again.', 'wpsl' )
472
+ );
473
+
474
+ wp_enqueue_script( 'jquery-ui-dialog' );
475
+ wp_enqueue_script( 'wpsl-queue', plugins_url( '/js/ajax-queue.js', __FILE__ ), array( 'jquery' ), false );
476
+ wp_enqueue_script( 'wpsl-cpt-js', plugins_url( '/js/wpsl-cpt-upgrade.js', __FILE__ ), array( 'jquery' ), false );
477
+ wp_localize_script( 'wpsl-cpt-js', 'wpslCptConversion', $cpt_js_l10n );
478
+ }
479
+
480
+ /**
481
+ * The html for the lightbox
482
+ *
483
+ * @since 2.0
484
+ * @return void
485
+ */
486
+ function wpsl_cpt_dialog_html() {
487
+
488
+ ?>
489
+ <div id="wpsl-cpt-lightbox" style="display:none;">
490
+ <span class="tb-close-icon"></span>
491
+ <p class="wpsl-cpt-remaining"><?php _e( 'Store locations to convert:', 'wpsl' ); echo '<span></span>'; ?></p>
492
+ <div class="wslp-cpt-fix-wrap">
493
+ <input id="wpsl-start-cpt-conversion" class="button-primary" type="submit" value="<?php _e( 'Start Converting', 'wpsl' ); ?>" >
494
+ <img class="wpsl-preloader" alt="preloader" src="<?php echo WPSL_URL . 'img/ajax-loader.gif'; ?>" />
495
+ </div>
496
+ <input type="hidden" name="wpsl-cpt-fix-nonce" value="<?php echo wp_create_nonce( 'wpsl-cpt-fix' ); ?>" />
497
+ <input type="hidden" name="wpsl-cpt-conversion-count" value="<?php echo wp_create_nonce( 'wpsl-cpt-count' ); ?>" />
498
+ </div>
499
+ <div id="wpsl-cpt-overlay" style="display:none;"></div>
500
+ <style>
501
+ .wslp-cpt-fix-wrap {
502
+ float:left;
503
+ clear:both;
504
+ width:100%;
505
+ margin:0 0 15px 0;
506
+ }
507
+
508
+ #wpsl-cpt-lightbox .wpsl-cpt-remaining span {
509
+ margin-left:5px;
510
+ }
511
+
512
+ #wpsl-start-cpt-conversion {
513
+ float:left;
514
+ }
515
+
516
+ .wslp-cpt-fix-wrap .wpsl-preloader,
517
+ .wslp-cpt-fix-wrap span {
518
+ float:left;
519
+ margin:8px 0 0 10px;
520
+ }
521
+
522
+ .wslp-cpt-fix-wrap .wpsl-preloader {
523
+ display: none;
524
+ }
525
+
526
+ #wpsl-cpt-lightbox {
527
+ position:fixed;
528
+ width:450px;
529
+ left:50%;
530
+ right:50%;
531
+ top:3.8em;
532
+ padding:15px;
533
+ background:none repeat scroll 0 0 #fff;
534
+ border-radius:3px;
535
+ margin-left:-225px;
536
+ z-index: 9999;
537
+ }
538
+
539
+ #wpsl-cpt-overlay {
540
+ position:fixed;
541
+ right:0;
542
+ top:0;
543
+ z-index:9998;
544
+ background:none repeat scroll 0 0 #000;
545
+ bottom:0;
546
+ left:0;
547
+ opacity:0.5;
548
+ }
549
+
550
+ .tb-close-icon {
551
+ color: #666;
552
+ text-align: center;
553
+ line-height: 29px;
554
+ width: 29px;
555
+ height: 29px;
556
+ position: absolute;
557
+ top: 0;
558
+ right: 0;
559
+ }
560
+
561
+ .tb-close-icon:before {
562
+ content: '\f158';
563
+ font: normal 20px/29px 'dashicons';
564
+ speak: none;
565
+ -webkit-font-smoothing: antialiased;
566
+ -moz-osx-font-smoothing: grayscale;
567
+ }
568
+
569
+ .tb-close-icon:hover {
570
+ color: #999 !important;
571
+ cursor: pointer;
572
+ }
573
+ </style>
574
+ <?php
575
+ }
576
+
577
+ /**
578
+ * Handle the ajax call to start converting the
579
+ * store locations to custom post types.
580
+ *
581
+ * @since 2.0
582
+ * @return void|string json on completion
583
+ */
584
+ function wpsl_convert_cpt() {
585
+
586
+ if ( !current_user_can( 'manage_options' ) )
587
+ die( '-1' );
588
+ check_ajax_referer( 'wpsl-cpt-fix' );
589
+
590
+ // Start the cpt coversion.
591
+ wpsl_cpt_conversion();
592
+
593
+ exit();
594
+ }
595
+
596
+ /**
597
+ * Get the amount of locations that still need to be converted.
598
+ *
599
+ * @since 2.0
600
+ * @return string json The amount of locations that still need to be converted
601
+ */
602
+ function wpsl_convert_cpt_count() {
603
+
604
+ if ( !current_user_can( 'manage_options' ) )
605
+ die( '-1' );
606
+ check_ajax_referer( 'wpsl-cpt-count' );
607
+
608
+ $remaining_count = wpsl_remaining_cpt_count();
609
+
610
+ $response['success'] = true;
611
+
612
+ if ( $remaining_count ) {
613
+ $response['count'] = $remaining_count;
614
+ } else {
615
+ $response['url'] = sprintf( __( 'All the store locations are now converted to custom post types. %s You can view them on the %sAll Stores%s page.', 'wpsl' ), '<br><br>', '<a href="' . admin_url( 'edit.php?post_type=wpsl_stores' ) . '">', '</a>' );
616
+
617
+ delete_option( 'wpsl_convert_cpt' );
618
+ }
619
+
620
+ wp_send_json( $response );
621
+
622
+ exit();
623
+ }
624
+
625
+ /**
626
+ * Return the difference between the number of existing wpsl custom post types,
627
+ * and the number of records in the old wpsl_stores database.
628
+ *
629
+ * @since 2.0
630
+ * @return int|boolean $remaining The amount of locations that still need to be converted
631
+ */
632
+ function wpsl_remaining_cpt_count() {
633
+
634
+ global $wpdb;
635
+
636
+ $table = $wpdb->prefix . 'wpsl_stores';
637
+ $count = wp_count_posts( 'wpsl_stores' );
638
+
639
+ if ( isset( $count->publish ) && isset( $count->draft ) ) {
640
+ $cpt_count = $count->publish + $count->draft;
641
+ } else {
642
+ $cpt_count = 0;
643
+ }
644
+
645
+ $db_count = $wpdb->get_var( "SELECT COUNT(wpsl_id) FROM $table" );
646
+ $difference = $db_count - $cpt_count;
647
+
648
+ /*
649
+ * This prevents users who used the 2.0 beta, and later added
650
+ * more stores from seeing the upgrade notice again.
651
+ */
652
+ $remaining = ( $difference < 0 ) ? false : $difference;
653
+
654
+ return $remaining;
655
+ }
656
+
657
+ /**
658
+ * Convert the existing locations to custom post types.
659
+ *
660
+ * @since 2.0
661
+ * @return void|boolean True if the conversion is completed
662
+ */
663
+ function wpsl_cpt_conversion() {
664
+
665
+ global $wpdb;
666
+
667
+ // Try to disable the time limit to prevent timeouts.
668
+ @set_time_limit( 0 );
669
+
670
+ $meta_keys = array( 'address', 'address2', 'city', 'state', 'zip', 'country', 'country_iso', 'lat', 'lng', 'phone', 'fax', 'url', 'email', 'hours' );
671
+ $offset = wpsl_remaining_cpt_count();
672
+ $wpsl_table = $wpdb->prefix . 'wpsl_stores';
673
+ $stores = $wpdb->get_results( "(SELECT * FROM $wpsl_table ORDER BY wpsl_id DESC LIMIT $offset) ORDER BY wpsl_id ASC" );
674
+
675
+ foreach ( $stores as $store ) {
676
+
677
+ // Make sure we set the correct post status.
678
+ if ( $store->active ) {
679
+ $post_status = 'publish';
680
+ } else {
681
+ $post_status = 'draft';
682
+ }
683
+
684
+ $post = array (
685
+ 'post_type' => 'wpsl_stores',
686
+ 'post_status' => $post_status,
687
+ 'post_title' => $store->store,
688
+ 'post_content' => $store->description
689
+ );
690
+
691
+ $post_id = wp_insert_post( $post );
692
+
693
+ if ( $post_id ) {
694
+
695
+ // Save the data from the wpsl_stores db table as post meta data.
696
+ foreach ( $meta_keys as $meta_key ) {
697
+ if ( isset( $store->{$meta_key} ) && !empty( $store->{$meta_key} ) ) {
698
+ update_post_meta( $post_id, 'wpsl_' . $meta_key, $store->{$meta_key} );
699
+ }
700
+ }
701
+
702
+ // If we have a thumb ID set the post thumbnail for the inserted post.
703
+ if ( $store->thumb_id ) {
704
+ set_post_thumbnail( $post_id, $store->thumb_id );
705
+ }
706
+ }
707
+ }
708
  }
css/styles.css CHANGED
@@ -1,1072 +1,1072 @@
1
- @font-face {
2
- font-family: 'wpsl-fontello';
3
- src: url('../font/fontello.eot?28897909');
4
- src: url('../font/fontello.eot?28897909#iefix') format('embedded-opentype'),
5
- url('../font/fontello.woff?28897909') format('woff'),
6
- url('../font/fontello.ttf?28897909') format('truetype'),
7
- url('../font/fontello.svg?28897909#fontello') format('svg');
8
- font-weight: normal;
9
- font-style: normal;
10
- }
11
-
12
- #wpsl-gmap {
13
- float:right;
14
- width:66.5%;
15
- height:350px;
16
- margin-bottom:0;
17
- }
18
-
19
- .wpsl-store-below #wpsl-gmap {
20
- float:none;
21
- width:100%;
22
- }
23
-
24
- .wpsl-gmap-canvas {
25
- width:100%;
26
- height:300px;
27
- margin-bottom:20px;
28
- }
29
-
30
- #wpsl-reset-map:hover {
31
- cursor: pointer;
32
- }
33
-
34
- /*
35
- Some themes set a box-shadow or max-width for all image /
36
- div elements, we disable it to prevent it from messing up the map
37
-
38
- The .gv-iv- class is used in streetview, and they should not be included.
39
- */
40
- #wpsl-gmap div:not[class^="gv-iv"],
41
- #wpsl-gmap img,
42
- .wpsl-gmap-canvas div:not[class^="gv-iv"],
43
- .wpsl-gmap-canvas img {
44
- box-shadow: none !important;
45
- max-width: none !important;
46
- background: none;
47
- }
48
-
49
- #wpsl-gmap img,
50
- .wpsl-gmap-canvas img {
51
- display: inline;
52
- opacity: 1 !important;
53
- max-height: none !important;
54
- }
55
-
56
- /*
57
- Fix a problem where the background color used
58
- in street view mode doesn't cover the control area.
59
- */
60
- #wpsl-gmap * {
61
- box-sizing: content-box !important;
62
- -webkit-box-sizing: content-box !important;
63
- -moz-box-sizing: content-box !important;
64
- }
65
-
66
- #wpsl-gmap div.gm-iv-marker,
67
- .wpsl-gmap-canvas div.gm-iv-marker {
68
- backgroud-image: inherit;
69
- }
70
-
71
- #wpsl-wrap {
72
- position: relative;
73
- width: 100%;
74
- overflow: hidden;
75
- clear: both;
76
- margin-bottom: 20px;
77
- }
78
-
79
- #wpsl-search-wrap {
80
- float: left;
81
- width: 100%;
82
- }
83
-
84
- #wpsl-search-wrap form {
85
- margin: 0;
86
- padding: 0;
87
- border: none;
88
- outline: none;
89
- }
90
-
91
- /* Map Controls */
92
- #wpsl-gmap #wpsl-map-controls {
93
- position: absolute;
94
- height: 40px;
95
- right: 10px;
96
- bottom: 24px;
97
- border-radius: 2px;
98
- z-index: 3;
99
- font-size: 14px;
100
- white-space: nowrap;
101
- overflow: hidden;
102
- box-shadow: rgba(0, 0, 0, 0.3) 0px 1px 4px -1px;
103
- }
104
-
105
- #wpsl-gmap #wpsl-map-controls.wpsl-street-view-exists {
106
- right: 60px;
107
- }
108
-
109
- #wpsl-map-controls .wpsl-direction-preloader {
110
- margin: 5px 5px 0 5px;
111
- }
112
-
113
- #wpsl-map-controls div {
114
- float: left;
115
- background: #fff;
116
- border-radius: 2px;
117
- }
118
-
119
- #wpsl-map-controls div:hover {
120
- cursor: pointer;
121
- }
122
-
123
- #wpsl-wrap [class^="wpsl-icon-"],
124
- #wpsl-wrap [class*=" wpsl-icon-"] {
125
- position: relative;
126
- float: left;
127
- padding: 12px 13px;
128
- display: inline-block;
129
- font-family: "wpsl-fontello";
130
- font-style: normal;
131
- font-weight: normal;
132
- font-size: 1.3em;
133
- color: #737373;
134
- speak: none;
135
- text-decoration: inherit;
136
- text-align: center;
137
- font-variant: normal;
138
- text-transform: none;
139
- line-height: 1em;
140
- -webkit-font-smoothing: antialiased;
141
- -moz-osx-font-smoothing: grayscale;
142
- }
143
-
144
- /*
145
- * Make sure the CSS from a theme doesn't set a different font family, or font size
146
- * for the font icons. Otherwise the icons either don't show, or are to large.
147
- */
148
- #wpsl-map-controls span {
149
- font-family: inherit;
150
- font-size: inherit;
151
- }
152
-
153
- #wpsl-wrap .wpsl-icon-reset {
154
- border-radius: 2px 0 0 2px;
155
- z-index: 2;
156
- padding-right: 4px;
157
- }
158
-
159
- /* Fix the padding for the icon fonts in IE 8-11 :( */
160
- #wpsl-wrap .wpsl-ie .wpsl-icon-reset {
161
- margin-right: -1px;
162
- }
163
-
164
- #wpsl-wrap .wpsl-icon-direction {
165
- z-index: 1;
166
- }
167
-
168
- #wpsl-map-controls.wpsl-reset-exists .wpsl-icon-direction {
169
- border-radius: 0 2px 2px 0;
170
- }
171
-
172
- #wpsl-wrap .wpsl-active-icon,
173
- #wpsl-wrap [class^="wpsl-icon-"]:hover,
174
- #wpsl-wrap [class*=" wpsl-icon-"]:hover {
175
- color: #000;
176
- }
177
-
178
- #wpsl-wrap [class^="wpsl-icon-"]:active,
179
- #wpsl-wrap [class*=" wpsl-icon-"]:focus {
180
- outline: 0;
181
- }
182
-
183
- #wpsl-wrap .wpsl-in-progress:hover,
184
- #wpsl-wrap .wpsl-in-progress {
185
- color: #c6c6c6;
186
- }
187
-
188
- /* Map reset button */
189
- #wpsl-gmap #wpsl-reset-map {
190
- position: absolute;
191
- display: none;
192
- right: 37px;
193
- top: 37px;
194
- padding: 6px 14px;
195
- background: #fff !important;
196
- background-clip: padding-box;
197
- border: 1px solid rgba(0, 0, 0, 0.15);
198
- border-radius: 2px;
199
- z-index: 3;
200
- }
201
-
202
- #wpsl-reset-map:hover {
203
- cursor: pointer;
204
- }
205
-
206
- /* Possible fix for vertical text issue in IE9? */
207
- .gm-style-cc {
208
- word-wrap:normal;
209
- }
210
-
211
- #wpsl-search-wrap .wpsl-input,
212
- #wpsl-search-wrap .wpsl-select-wrap {
213
- display:table;
214
- }
215
-
216
- #wpsl-search-wrap .wpsl-input label,
217
- #wpsl-search-wrap .wpsl-input input,
218
- #wpsl-search-wrap #wpsl-radius,
219
- #wpsl-search-wrap #wpsl-results,
220
- #wpsl-search-btn {
221
- display:table-cell;
222
- }
223
-
224
- #wpsl-search-wrap label {
225
- margin-bottom:0;
226
- }
227
-
228
- #wpsl-search-input {
229
- width: 179px;
230
- height: auto;
231
- padding: 7px 12px;
232
- font-size: 100%;
233
- margin: 0;
234
- }
235
-
236
- #wpsl-search-wrap input,
237
- #wpsl-search-btn {
238
- border: 1px solid #d2d2d2;
239
- border-radius: 3px;
240
- }
241
-
242
- #wpsl-search-btn {
243
- padding: 7px 10px;
244
- line-height: 1.428571429;
245
- font-weight: normal;
246
- color: #7c7c7c;
247
- background-color: #e6e6e6;
248
- background-repeat: repeat-x;
249
- background-image: -moz-linear-gradient(top, #f4f4f4, #e6e6e6);
250
- background-image: -ms-linear-gradient(top, #f4f4f4, #e6e6e6);
251
- background-image: -webkit-linear-gradient(top, #f4f4f4, #e6e6e6);
252
- background-image: -o-linear-gradient(top, #f4f4f4, #e6e6e6);
253
- background-image: linear-gradient(top, #f4f4f4, #e6e6e6);
254
- box-shadow: 0 1px 2px rgba(64, 64, 64, 0.1);
255
- text-transform: none !important;
256
- }
257
-
258
- #wpsl-search-input.wpsl-error {
259
- border:1px solid #bd0028 !important;
260
- }
261
-
262
- .wpsl-search {
263
- margin-bottom:12px;
264
- padding:12px 12px 0 12px;
265
- background:#f4f3f3;
266
- }
267
-
268
- .wpsl-search.wpsl-checkboxes-enabled {
269
- padding: 12px;
270
- }
271
-
272
- /* Result list */
273
- .wpsl-back {
274
- display: inline-block;
275
- }
276
-
277
- #wpsl-result-list {
278
- width:33%;
279
- margin-right:0.5%;
280
- }
281
-
282
- .wpsl-store-below #wpsl-result-list {
283
- width:100%;
284
- margin:12px 0 0 0;
285
- }
286
-
287
- #wpsl-stores,
288
- #wpsl-direction-details {
289
- height:350px;
290
- overflow-y:auto;
291
- }
292
-
293
- .wpsl-hide,
294
- #wpsl-direction-details {
295
- display:none;
296
- }
297
-
298
- #wpsl-result-list p {
299
- padding-left:10px;
300
- }
301
- .wpsl-store-below #wpsl-result-list p {
302
- padding-left: 0;
303
- }
304
-
305
- #wpsl-result-list a {
306
- outline:none;
307
- }
308
-
309
- .wpsl-direction-before {
310
- margin: 14px 0 21px 0;
311
- padding-left: 10px;
312
- }
313
-
314
- .wpsl-store-below .wpsl-direction-before {
315
- padding-left: 0;
316
- }
317
-
318
- .wpsl-direction-before div {
319
- margin-top: 10px;
320
- }
321
-
322
- #wpsl-wrap #wpsl-result-list li {
323
- padding: 10px;
324
- border-bottom: 1px dotted #ccc;
325
- margin-left: 0;
326
- overflow: hidden;
327
- list-style: none outside none !important;
328
- text-indent: 0;
329
- }
330
-
331
- #wpsl-wrap #wpsl-result-list li li {
332
- padding: 0;
333
- border-bottom: 0;
334
- margin-left: 14px;
335
- overflow: visible;
336
- }
337
-
338
- #wpsl-wrap #wpsl-result-list ul li {
339
- list-style: none !important;
340
- }
341
-
342
- #wpsl-wrap #wpsl-result-list ol li {
343
- list-style: decimal !important;
344
- }
345
-
346
- #wpsl-wrap.wpsl-store-below #wpsl-result-list li {
347
- padding: 10px 10px 10px 0;
348
- }
349
-
350
- #wpsl-result-list li p {
351
- padding-left: 0;
352
- margin: 0 0 20px 0;
353
- }
354
-
355
- .wpsl-store-details.wpsl-store-listing {
356
- position: relative;
357
- padding-right: 20px;
358
- }
359
-
360
- .wpsl-store-details.wpsl-store-listing:before,
361
- .wpsl-store-details.wpsl-store-listing.wpsl-active-details:before {
362
- position: absolute;
363
- content: '';
364
- bottom:6px;
365
- right:0;
366
- border-top: 5px solid #000000;
367
- border-left: 6px solid rgba(0, 0, 0, 0);
368
- border-right: 6px solid rgba(0, 0, 0, 0);
369
- }
370
-
371
- .wpsl-store-details.wpsl-store-listing.wpsl-active-details:before {
372
- border-bottom: 5px solid #000000;
373
- border-top:none;
374
- border-left: 6px solid rgba(0, 0, 0, 0);
375
- border-right: 6px solid rgba(0, 0, 0, 0);
376
- }
377
-
378
- #wpsl-stores .wpsl-store-thumb {
379
- float:right;
380
- border-radius:3px;
381
- margin:7px 0 0 10px;
382
- padding:0;
383
- border:none;
384
- }
385
-
386
- .wpsl-direction-index {
387
- float:left;
388
- width:8%;
389
- margin:0 5% 0 0;
390
- }
391
-
392
- .wpsl-direction-txt {
393
- float:left;
394
- width:62%;
395
- }
396
-
397
- .wpsl-direction-distance {
398
- float:left;
399
- width:20%;
400
- margin:0 0 0 5%;
401
- }
402
-
403
- .wpsl-direction-txt span {
404
- display:block;
405
- margin-top:10px;
406
- }
407
-
408
- .wpsl-street,
409
- .wpsl-country {
410
- display: block;
411
- border-bottom: none !important;
412
- }
413
-
414
- .wpsl-directions {
415
- display: table;
416
- border-bottom: none !important;
417
- }
418
-
419
- /* Preloader */
420
- #wpsl-wrap #wpsl-result-list li.wpsl-preloader {
421
- position: relative;
422
- border-bottom: none;
423
- padding: 10px 10px 10px 35px;
424
- }
425
-
426
- .wpsl-preloader img {
427
- position: absolute;
428
- left: 10px;
429
- top: 50%;
430
- margin-top: -8px;
431
- box-shadow:none !important;
432
- border:none !important;
433
- }
434
-
435
- .wpsl-preloader span {
436
- float: left;
437
- margin: -5px 0 0 11px;
438
- }
439
-
440
- #wpsl-search-wrap div,
441
- #wpsl-search-btn {
442
- margin-right: 10px;
443
- float: left;
444
- }
445
-
446
- #wpsl-search-wrap .wpsl-select-wrap {
447
- position: relative;
448
- z-index: 2;
449
- margin-right: 0;
450
- }
451
-
452
- #wpsl-search-wrap .wpsl-input-field {
453
- position: relative;
454
- }
455
-
456
- #wpsl-radius, #wpsl-results {
457
- float: left;
458
- margin-right: 15px;
459
- }
460
-
461
- #wpsl-category {
462
- position: relative;
463
- z-index: 1;
464
- clear: both;
465
- }
466
-
467
- #wpsl-search-wrap .wpsl-dropdown div {
468
- position: absolute;
469
- float: none;
470
- margin: -1px 0 0 0;
471
- top: 100%;
472
- left: -1px;
473
- right: -1px;
474
- border: 1px solid #ccc;
475
- background: #fff;
476
- border-top: 1px solid #eee;
477
- border-radius: 0 0 3px 3px;
478
- opacity: 0;
479
- overflow: hidden;
480
- -webkit-transition: all 150ms ease-in-out;
481
- -moz-transition: all 150ms ease-in-out;
482
- -ms-transition: all 150ms ease-in-out;
483
- transition: all 150ms ease-in-out;
484
- }
485
-
486
- #wpsl-search-wrap .wpsl-dropdown.wpsl-active div {
487
- opacity: 1;
488
- }
489
-
490
- #wpsl-search-wrap .wpsl-input label {
491
- margin-right:0;
492
- }
493
-
494
- #wpsl-radius, #wpsl-results {
495
- display:inline;
496
- }
497
-
498
- #wpsl-radius {
499
- margin-right:10px;
500
- }
501
- #wpsl-search-btn:hover {
502
- cursor: pointer;
503
- }
504
-
505
- #wpsl-search-wrap select,
506
- #wpsl-search select {
507
- display:none;
508
- }
509
-
510
- #wpsl-search-wrap div label {
511
- float:left;
512
- margin-right:10px;
513
- line-height: 32px;
514
- }
515
-
516
- #wpsl-results label {
517
- width: auto;
518
- }
519
-
520
- #wpsl-result-list ul {
521
- list-style: none;
522
- margin: 0;
523
- padding: 0;
524
- }
525
- .wpsl-direction-details {
526
- display: none;
527
- }
528
-
529
- /* Infowindow */
530
- #wpsl-gmap .wpsl-info-window,
531
- .wpsl-gmap-canvas .wpsl-info-window {
532
- max-width:225px;
533
- }
534
-
535
- .wpsl-more-info-listings span,
536
- .wpsl-info-window span {
537
- display:block;
538
- }
539
-
540
- .wpsl-info-window .wpsl-no-margin {
541
- margin:0;
542
- }
543
-
544
- /* More info details in the store listings */
545
- .wpsl-more-info-listings {
546
- display:none;
547
- }
548
-
549
- /* Fix for Google Voice breaking the phone numbers */
550
- .wpsl-info-window span span {
551
- display:inline !important;
552
- }
553
-
554
- #wpsl-wrap .wpsl-info-window p {
555
- margin: 0 0 10px 0;
556
- }
557
-
558
- .wpsl-store-hours {
559
- margin-top:10px;
560
- }
561
-
562
- .wpsl-store-hours strong {
563
- display:block;
564
- }
565
-
566
- #wpsl-gmap .wpsl-info-actions {
567
- display:block;
568
- margin:10px 0 !important;
569
- }
570
-
571
- .wpsl-info-actions a {
572
- float:left;
573
- margin-right: 7px;
574
- }
575
-
576
- .wpsl-info-actions .wpsl-zoom-here {
577
- margin-right:0;
578
- }
579
-
580
- /* --- dropdowns --- */
581
- .wpsl-dropdown {
582
- position: relative;
583
- width: 90px;
584
- border: 1px solid #ccc;
585
- cursor: pointer;
586
- background: #fff;
587
- border-radius: 3px;
588
- -webkit-user-select: none;
589
- -moz-user-select: none;
590
- user-select: none;
591
- margin-right: 0 !important;
592
- z-index: 2;
593
- }
594
-
595
- #wpsl-results .wpsl-dropdown {
596
- width: 70px;
597
- }
598
-
599
- .wpsl-dropdown ul {
600
- position: absolute;
601
- left: 0;
602
- width: 100%;
603
- height: 100%;
604
- padding: 0 !important;
605
- margin: 0 !important;
606
- list-style: none;
607
- overflow: hidden;
608
- }
609
-
610
- .wpsl-dropdown:hover {
611
- box-shadow: 0 0 5px rgba( 0, 0, 0, 0.15 );
612
- }
613
-
614
- .wpsl-dropdown .wpsl-selected-item,
615
- .wpsl-dropdown li {
616
- position: relative;
617
- display: block;
618
- line-height: normal;
619
- color: #000;
620
- overflow: hidden;
621
- }
622
-
623
- #wpsl-radius .wpsl-dropdown .wpsl-selected-item,
624
- #wpsl-radius .wpsl-dropdown li,
625
- #wpsl-results .wpsl-dropdown .wpsl-selected-item,
626
- #wpsl-results .wpsl-dropdown li {
627
- white-space: nowrap;
628
- }
629
-
630
- .wpsl-selected-item:after {
631
- position: absolute;
632
- content: "";
633
- right: 12px;
634
- top: 50%;
635
- margin-top: -4px;
636
- border: 6px solid transparent;
637
- border-top: 8px solid #000;
638
- }
639
-
640
- .wpsl-active .wpsl-selected-item:after {
641
- margin-top: -10px;
642
- border: 6px solid transparent;
643
- border-bottom: 8px solid #000;
644
- }
645
-
646
- .wpsl-dropdown li:hover {
647
- background: #f8f9f8;
648
- position: relative;
649
- z-index: 3;
650
- color: #000;
651
- }
652
-
653
- .wpsl-dropdown .wpsl-selected-item,
654
- .wpsl-dropdown li,
655
- .wpsl-selected-item {
656
- list-style: none;
657
- padding: 9px 12px !important;
658
- margin:0 !important;
659
- }
660
-
661
- .wpsl-selected-dropdown {
662
- font-weight: bold;
663
- }
664
-
665
- .wpsl-clearfix:before,
666
- .wpsl-clearfix:after {
667
- content: " ";
668
- display: table;
669
- }
670
-
671
- .wpsl-clearfix:after {
672
- clear: both;
673
- }
674
-
675
- #wpsl-wrap .wpsl-selected-item {
676
- position: static;
677
- padding-right: 35px !important;
678
- }
679
-
680
- #wpsl-category,
681
- .wpsl-input,
682
- .wpsl-select-wrap {
683
- position: relative;
684
- margin-bottom: 10px;
685
- }
686
-
687
- #wpsl-search-wrap .wpsl-scroll-required div {
688
- overflow-y: scroll;
689
- }
690
-
691
- .wpsl-scroll-required ul {
692
- overflow: visible;
693
- }
694
-
695
- .wpsl-provided-by {
696
- float: right;
697
- padding: 5px 0;
698
- text-align: right;
699
- font-size: 12px;
700
- width: 100%;
701
- }
702
-
703
- #wpsl-wrap .wpsl-results-only label {
704
- width: auto;
705
- }
706
-
707
- /* wpsl custom post type pages */
708
- .wpsl-locations-details,
709
- .wpsl-location-address,
710
- .wpsl-contact-details {
711
- margin-bottom: 15px;
712
- }
713
-
714
- .wpsl-contact-details {
715
- clear: both;
716
- }
717
-
718
- table.wpsl-opening-hours td {
719
- vertical-align: top;
720
- padding: 0 15px 0 0;
721
- text-align: left;
722
- }
723
-
724
- table.wpsl-opening-hours time {
725
- display:block;
726
- }
727
-
728
- table.wpsl-opening-hours {
729
- width:auto !important;
730
- font-size:100% !important;
731
- }
732
-
733
- table.wpsl-opening-hours,
734
- table.wpsl-opening-hours td {
735
- border:none !important;
736
- }
737
-
738
- /* Custom Infobox */
739
- .wpsl-gmap-canvas .wpsl-infobox {
740
- min-width:155px;
741
- max-width:350px !important;
742
- padding:10px;
743
- border-radius:4px;
744
- font-size:13px;
745
- font-weight:300;
746
- border:1px solid #ccc;
747
- background:#fff !important;
748
- }
749
-
750
- .wpsl-gmap-canvas .wpsl-infobox:after,
751
- .wpsl-gmap-canvas .wpsl-infobox:before {
752
- position:absolute;
753
- content:"";
754
- left:40px;
755
- bottom:-11px;
756
- }
757
-
758
- .wpsl-gmap-canvas .wpsl-infobox:after {
759
- border-left:11px solid transparent;
760
- border-right:11px solid transparent;
761
- border-top:11px solid #fff;
762
- }
763
-
764
- .wpsl-gmap-canvas .wpsl-infobox:before {
765
- border-left:13px solid transparent;
766
- border-right:13px solid transparent;
767
- border-top:13px solid #ccc;
768
- bottom:-13px;
769
- left:38px;
770
- }
771
-
772
- #wpsl-checkbox-filter,
773
- .wpsl-custom-checkboxes {
774
- display: block;
775
- float: left;
776
- margin: 5px 0 15px;
777
- padding: 0;
778
- width: 100%;
779
- }
780
-
781
- #wpsl-checkbox-filter li,
782
- .wpsl-custom-checkboxes li {
783
- float: left;
784
- list-style: none;
785
- margin: 0 1% 0 0;
786
- }
787
-
788
- #wpsl-checkbox-filter.wpsl-checkbox-1-columns li,
789
- .wpsl-custom-checkboxes.wpsl-checkbox-1-columns li {
790
- width: 99%;
791
- }
792
-
793
- #wpsl-checkbox-filter.wpsl-checkbox-2-columns li,
794
- .wpsl-custom-checkboxes.wpsl-checkbox-2-columns li {
795
- width: 49%;
796
- }
797
-
798
- #wpsl-checkbox-filter.wpsl-checkbox-3-columns li,
799
- .wpsl-custom-checkboxes.wpsl-checkbox-3-columns li {
800
- width: 32%;
801
- }
802
-
803
- #wpsl-checkbox-filter.wpsl-checkbox-4-columns li,
804
- .wpsl-custom-checkboxes.wpsl-checkbox-4-columns li {
805
- width: 24%;
806
- }
807
-
808
- #wpsl-checkbox-filter input,
809
- .wpsl-custom-checkboxes input {
810
- margin-right: 5px;
811
- }
812
-
813
- #wpsl-result-list .wpsl-contact-details span {
814
- display: block !important;
815
- }
816
-
817
- /*
818
- Hide the select2 ( https://select2.org/ ) dropdowns to
819
- prevent duplicate dropdowns from showing up in the search bar.
820
- */
821
- #wpsl-search-wrap .select2 {
822
- display: none !important;
823
- }
824
-
825
- /*
826
- Make a few adjustments for themes that use RTL languages
827
- */
828
- .rtl #wpsl-result-list {
829
- float: left;
830
- }
831
-
832
- .rtl #wpsl-checkbox-filter input,
833
- .rtl .wpsl-custom-checkboxes input {
834
- margin-right: 0;
835
- margin-left: 5px;
836
- }
837
-
838
- .rtl .wpsl-info-actions a {
839
- float: right;
840
- margin: 0 0 0 7px;
841
- }
842
-
843
- .rtl #wpsl-gmap .wpsl-info-window {
844
- padding-right: 22px;
845
- }
846
-
847
- .rtl #wpsl-wrap #wpsl-result-list li.wpsl-preloader {
848
- padding: 10px 35px 10px 0;
849
- }
850
-
851
- .rtl .wpsl-preloader img {
852
- left: 0;
853
- right: 10px;
854
- }
855
-
856
- /* Only used when the TwentyNinteen theme is active */
857
- .wpsl-twentynineteen .wpsl-input {
858
- width: 100%;
859
- }
860
-
861
- .wpsl-twentynineteen #wpsl-search-input {
862
- line-height: 1.3em;
863
- }
864
-
865
- .wpsl-twentynineteen #wpsl-search-wrap label {
866
- margin-top: 6px;
867
- }
868
-
869
- .wpsl-twentynineteen .wpsl-dropdown {
870
- width: 116px;
871
- }
872
-
873
- #wpsl-results .wpsl-dropdown {
874
- width: 81px;
875
- }
876
-
877
- @media (max-width: 825px) {
878
- #wpsl-search-input {
879
- width: 348px;
880
- }
881
-
882
- .wpsl-results-only #wpsl-search-wrap .wpsl-dropdown {
883
- width: 70px;
884
- }
885
-
886
- #wpsl-search-wrap .wpsl-input {
887
- width: 100%;
888
- margin-bottom: 10px;
889
- }
890
-
891
- .wpsl-input label,
892
- #wpsl-radius label,
893
- #wpsl-category label,
894
- .wpsl-cat-results-filter #wpsl-search-wrap .wpsl-input,
895
- .wpsl-no-filters #wpsl-search-wrap .wpsl-input,
896
- .wpsl-results-only #wpsl-search-wrap .wpsl-input {
897
- width: auto;
898
- }
899
- }
900
-
901
- @media (max-width: 720px) {
902
- #wpsl-search-wrap .wpsl-dropdown {
903
- width: 114px;
904
- }
905
- }
906
-
907
- @media (max-width: 675px) {
908
- #wpsl-search-wrap #wpsl-search-btn {
909
- float: left;
910
- margin: 0 5px 0 0;
911
- }
912
-
913
- .wpsl-results-only #wpsl-search-wrap .wpsl-input,
914
- .wpsl-dropdown {
915
- width: 100%;
916
- }
917
-
918
- .wpsl-search {
919
- padding: 2%;
920
- }
921
-
922
- .wpsl-input {
923
- margin-right: 0;
924
- }
925
-
926
- #wpsl-result-list,
927
- #wpsl-gmap {
928
- width:49.75%;
929
- }
930
-
931
- #wpsl-result-list,
932
- #wpsl-gmap {
933
- float: none;
934
- width: 100%;
935
- }
936
-
937
- .wpsl-direction-before {
938
- padding-left: 0;
939
- }
940
-
941
- #wpsl-gmap {
942
- margin-bottom: 15px;
943
- }
944
-
945
- .wpsl-cat-results-filter .wpsl-select-wrap,
946
- .wpsl-filter .wpsl-select-wrap,
947
- #wpsl-result-list {
948
- margin-bottom: 10px;
949
- }
950
-
951
- #wpsl-result-list p,
952
- #wpsl-wrap #wpsl-result-list li {
953
- padding-left: 0;
954
- }
955
-
956
- #wpsl-wrap #wpsl-result-list li.wpsl-preloader {
957
- padding-left: 25px;
958
- }
959
-
960
- .wpsl-preloader img {
961
- left: 0;
962
- }
963
-
964
- #wpsl-stores.wpsl-not-loaded {
965
- height: 25px;
966
- }
967
-
968
- #wpsl-reset-map {
969
- top: 25px;
970
- }
971
-
972
- #wpsl-gmap {
973
- margin-top: 10px;
974
- }
975
-
976
- .wpsl-no-filters #wpsl-search-wrap .wpsl-input,
977
- #wpsl-category, .wpsl-input, .wpsl-select-wrap,
978
- .wpsl-input, #wpsl-search-btn {
979
- margin-bottom: 0;
980
- }
981
-
982
- #wpsl-stores.wpsl-no-autoload {
983
- height: auto !important;
984
- }
985
-
986
- #wpsl-checkbox-filter.wpsl-checkbox-3-columns li,
987
- #wpsl-checkbox-filter.wpsl-checkbox-4-columns li {
988
- width: 49%;
989
- }
990
- }
991
-
992
- @media (max-width: 570px) {
993
- #wpsl-search-wrap #wpsl-search-btn {
994
- margin-bottom: 5px;
995
- }
996
-
997
- .wpsl-search {
998
- padding: 4%;
999
- }
1000
-
1001
- #wpsl-search-input {
1002
- width: 98% !important;
1003
- }
1004
-
1005
- .wpsl-cat-results-filter #wpsl-search-wrap .wpsl-input,
1006
- .wpsl-cat-results-filter #wpsl-search-input,
1007
- .wpsl-no-results #wpsl-search-input,
1008
- .wpsl-results-only #wpsl-search-input {
1009
- width: 100% !important;
1010
- }
1011
-
1012
- .wpsl-search-btn-wrap {
1013
- margin-top: 15px;
1014
- }
1015
-
1016
- .wpsl-checkboxes-enabled .wpsl-search-btn-wrap {
1017
- margin-top: 0;
1018
- }
1019
-
1020
- #wpsl-search-wrap div,
1021
- #wpsl-search-btn {
1022
- margin-right: 0;
1023
- }
1024
-
1025
- #wpsl-search-wrap div label {
1026
- display: block;
1027
- width: 100%;
1028
- }
1029
-
1030
- #wpsl-results {
1031
- width:auto;
1032
- }
1033
-
1034
- .wpsl-select-wrap {
1035
- width: 100%;
1036
- }
1037
-
1038
- #wpsl-radius,
1039
- #wpsl-results {
1040
- width: 50%;
1041
- }
1042
-
1043
- #wpsl-radius {
1044
- margin-right: 4%;
1045
- }
1046
-
1047
- #wpsl-search-wrap .wpsl-dropdown {
1048
- width: 96% !important;
1049
- }
1050
-
1051
- .wpsl-search-btn-wrap {
1052
- clear: both;
1053
- }
1054
-
1055
- .wpsl-no-filters #wpsl-search-wrap .wpsl-input,
1056
- .wpsl-no-filters #wpsl-search-input {
1057
- width: 100% !important;
1058
- }
1059
- }
1060
-
1061
- @media (max-width: 420px) {
1062
- #wpsl-checkbox-filter li {
1063
- margin: 0;
1064
- }
1065
-
1066
- #wpsl-checkbox-filter.wpsl-checkbox-1-columns li,
1067
- #wpsl-checkbox-filter.wpsl-checkbox-2-columns li,
1068
- #wpsl-checkbox-filter.wpsl-checkbox-3-columns li,
1069
- #wpsl-checkbox-filter.wpsl-checkbox-4-columns li {
1070
- width: 100%;
1071
- }
1072
  }
1
+ @font-face {
2
+ font-family: 'wpsl-fontello';
3
+ src: url('../font/fontello.eot?28897909');
4
+ src: url('../font/fontello.eot?28897909#iefix') format('embedded-opentype'),
5
+ url('../font/fontello.woff?28897909') format('woff'),
6
+ url('../font/fontello.ttf?28897909') format('truetype'),
7
+ url('../font/fontello.svg?28897909#fontello') format('svg');
8
+ font-weight: normal;
9
+ font-style: normal;
10
+ }
11
+
12
+ #wpsl-gmap {
13
+ float:right;
14
+ width:66.5%;
15
+ height:350px;
16
+ margin-bottom:0;
17
+ }
18
+
19
+ .wpsl-store-below #wpsl-gmap {
20
+ float:none;
21
+ width:100%;
22
+ }
23
+
24
+ .wpsl-gmap-canvas {
25
+ width:100%;
26
+ height:300px;
27
+ margin-bottom:20px;
28
+ }
29
+
30
+ #wpsl-reset-map:hover {
31
+ cursor: pointer;
32
+ }
33
+
34
+ /*
35
+ Some themes set a box-shadow or max-width for all image /
36
+ div elements, we disable it to prevent it from messing up the map
37
+
38
+ The .gv-iv- class is used in streetview, and they should not be included.
39
+ */
40
+ #wpsl-gmap div:not[class^="gv-iv"],
41
+ #wpsl-gmap img,
42
+ .wpsl-gmap-canvas div:not[class^="gv-iv"],
43
+ .wpsl-gmap-canvas img {
44
+ box-shadow: none !important;
45
+ max-width: none !important;
46
+ background: none;
47
+ }
48
+
49
+ #wpsl-gmap img,
50
+ .wpsl-gmap-canvas img {
51
+ display: inline;
52
+ opacity: 1 !important;
53
+ max-height: none !important;
54
+ }
55
+
56
+ /*
57
+ Fix a problem where the background color used
58
+ in street view mode doesn't cover the control area.
59
+ */
60
+ #wpsl-gmap * {
61
+ box-sizing: content-box !important;
62
+ -webkit-box-sizing: content-box !important;
63
+ -moz-box-sizing: content-box !important;
64
+ }
65
+
66
+ #wpsl-gmap div.gm-iv-marker,
67
+ .wpsl-gmap-canvas div.gm-iv-marker {
68
+ backgroud-image: inherit;
69
+ }
70
+
71
+ #wpsl-wrap {
72
+ position: relative;
73
+ width: 100%;
74
+ overflow: hidden;
75
+ clear: both;
76
+ margin-bottom: 20px;
77
+ }
78
+
79
+ #wpsl-search-wrap {
80
+ float: left;
81
+ width: 100%;
82
+ }
83
+
84
+ #wpsl-search-wrap form {
85
+ margin: 0;
86
+ padding: 0;
87
+ border: none;
88
+ outline: none;
89
+ }
90
+
91
+ /* Map Controls */
92
+ #wpsl-gmap #wpsl-map-controls {
93
+ position: absolute;
94
+ height: 40px;
95
+ right: 10px;
96
+ bottom: 24px;
97
+ border-radius: 2px;
98
+ z-index: 3;
99
+ font-size: 14px;
100
+ white-space: nowrap;
101
+ overflow: hidden;
102
+ box-shadow: rgba(0, 0, 0, 0.3) 0px 1px 4px -1px;
103
+ }
104
+
105
+ #wpsl-gmap #wpsl-map-controls.wpsl-street-view-exists {
106
+ right: 60px;
107
+ }
108
+
109
+ #wpsl-map-controls .wpsl-direction-preloader {
110
+ margin: 5px 5px 0 5px;
111
+ }
112
+
113
+ #wpsl-map-controls div {
114
+ float: left;
115
+ background: #fff;
116
+ border-radius: 2px;
117
+ }
118
+
119
+ #wpsl-map-controls div:hover {
120
+ cursor: pointer;
121
+ }
122
+
123
+ #wpsl-wrap [class^="wpsl-icon-"],
124
+ #wpsl-wrap [class*=" wpsl-icon-"] {
125
+ position: relative;
126
+ float: left;
127
+ padding: 12px 13px;
128
+ display: inline-block;
129
+ font-family: "wpsl-fontello";
130
+ font-style: normal;
131
+ font-weight: normal;
132
+ font-size: 1.3em;
133
+ color: #737373;
134
+ speak: none;
135
+ text-decoration: inherit;
136
+ text-align: center;
137
+ font-variant: normal;
138
+ text-transform: none;
139
+ line-height: 1em;
140
+ -webkit-font-smoothing: antialiased;
141
+ -moz-osx-font-smoothing: grayscale;
142
+ }
143
+
144
+ /*
145
+ * Make sure the CSS from a theme doesn't set a different font family, or font size
146
+ * for the font icons. Otherwise the icons either don't show, or are to large.
147
+ */
148
+ #wpsl-map-controls span {
149
+ font-family: inherit;
150
+ font-size: inherit;
151
+ }
152
+
153
+ #wpsl-wrap .wpsl-icon-reset {
154
+ border-radius: 2px 0 0 2px;
155
+ z-index: 2;
156
+ padding-right: 4px;
157
+ }
158
+
159
+ /* Fix the padding for the icon fonts in IE 8-11 :( */
160
+ #wpsl-wrap .wpsl-ie .wpsl-icon-reset {
161
+ margin-right: -1px;
162
+ }
163
+
164
+ #wpsl-wrap .wpsl-icon-direction {
165
+ z-index: 1;
166
+ }
167
+
168
+ #wpsl-map-controls.wpsl-reset-exists .wpsl-icon-direction {
169
+ border-radius: 0 2px 2px 0;
170
+ }
171
+
172
+ #wpsl-wrap .wpsl-active-icon,
173
+ #wpsl-wrap [class^="wpsl-icon-"]:hover,
174
+ #wpsl-wrap [class*=" wpsl-icon-"]:hover {
175
+ color: #000;
176
+ }
177
+
178
+ #wpsl-wrap [class^="wpsl-icon-"]:active,
179
+ #wpsl-wrap [class*=" wpsl-icon-"]:focus {
180
+ outline: 0;
181
+ }
182
+
183
+ #wpsl-wrap .wpsl-in-progress:hover,
184
+ #wpsl-wrap .wpsl-in-progress {
185
+ color: #c6c6c6;
186
+ }
187
+
188
+ /* Map reset button */
189
+ #wpsl-gmap #wpsl-reset-map {
190
+ position: absolute;
191
+ display: none;
192
+ right: 37px;
193
+ top: 37px;
194
+ padding: 6px 14px;
195
+ background: #fff !important;
196
+ background-clip: padding-box;
197
+ border: 1px solid rgba(0, 0, 0, 0.15);
198
+ border-radius: 2px;
199
+ z-index: 3;
200
+ }
201
+
202
+ #wpsl-reset-map:hover {
203
+ cursor: pointer;
204
+ }
205
+
206
+ /* Possible fix for vertical text issue in IE9? */
207
+ .gm-style-cc {
208
+ word-wrap:normal;
209
+ }
210
+
211
+ #wpsl-search-wrap .wpsl-input,
212
+ #wpsl-search-wrap .wpsl-select-wrap {
213
+ display:table;
214
+ }
215
+
216
+ #wpsl-search-wrap .wpsl-input label,
217
+ #wpsl-search-wrap .wpsl-input input,
218
+ #wpsl-search-wrap #wpsl-radius,
219
+ #wpsl-search-wrap #wpsl-results,
220
+ #wpsl-search-btn {
221
+ display:table-cell;
222
+ }
223
+
224
+ #wpsl-search-wrap label {
225
+ margin-bottom:0;
226
+ }
227
+
228
+ #wpsl-search-input {
229
+ width: 179px;
230
+ height: auto;
231
+ padding: 7px 12px;
232
+ font-size: 100%;
233
+ margin: 0;
234
+ }
235
+
236
+ #wpsl-search-wrap input,
237
+ #wpsl-search-btn {
238
+ border: 1px solid #d2d2d2;
239
+ border-radius: 3px;
240
+ }
241
+
242
+ #wpsl-search-btn {
243
+ padding: 7px 10px;
244
+ line-height: 1.428571429;
245
+ font-weight: normal;
246
+ color: #7c7c7c;
247
+ background-color: #e6e6e6;
248
+ background-repeat: repeat-x;
249
+ background-image: -moz-linear-gradient(top, #f4f4f4, #e6e6e6);
250
+ background-image: -ms-linear-gradient(top, #f4f4f4, #e6e6e6);
251
+ background-image: -webkit-linear-gradient(top, #f4f4f4, #e6e6e6);
252
+ background-image: -o-linear-gradient(top, #f4f4f4, #e6e6e6);
253
+ background-image: linear-gradient(top, #f4f4f4, #e6e6e6);
254
+ box-shadow: 0 1px 2px rgba(64, 64, 64, 0.1);
255
+ text-transform: none !important;
256
+ }
257
+
258
+ #wpsl-search-input.wpsl-error {
259
+ border:1px solid #bd0028 !important;
260
+ }
261
+
262
+ .wpsl-search {
263
+ margin-bottom:12px;
264
+ padding:12px 12px 0 12px;
265
+ background:#f4f3f3;
266
+ }
267
+
268
+ .wpsl-search.wpsl-checkboxes-enabled {
269
+ padding: 12px;
270
+ }
271
+
272
+ /* Result list */
273
+ .wpsl-back {
274
+ display: inline-block;
275
+ }
276
+
277
+ #wpsl-result-list {
278
+ width:33%;
279
+ margin-right:0.5%;
280
+ }
281
+
282
+ .wpsl-store-below #wpsl-result-list {
283
+ width:100%;
284
+ margin:12px 0 0 0;
285
+ }
286
+
287
+ #wpsl-stores,
288
+ #wpsl-direction-details {
289
+ height:350px;
290
+ overflow-y:auto;
291
+ }
292
+
293
+ .wpsl-hide,
294
+ #wpsl-direction-details {
295
+ display:none;
296
+ }
297
+
298
+ #wpsl-result-list p {
299
+ padding-left:10px;
300
+ }
301
+ .wpsl-store-below #wpsl-result-list p {
302
+ padding-left: 0;
303
+ }
304
+
305
+ #wpsl-result-list a {
306
+ outline:none;
307
+ }
308
+
309
+ .wpsl-direction-before {
310
+ margin: 14px 0 21px 0;
311
+ padding-left: 10px;
312
+ }
313
+
314
+ .wpsl-store-below .wpsl-direction-before {
315
+ padding-left: 0;
316
+ }
317
+
318
+ .wpsl-direction-before div {
319
+ margin-top: 10px;
320
+ }
321
+
322
+ #wpsl-wrap #wpsl-result-list li {
323
+ padding: 10px;
324
+ border-bottom: 1px dotted #ccc;
325
+ margin-left: 0;
326
+ overflow: hidden;
327
+ list-style: none outside none !important;
328
+ text-indent: 0;
329
+ }
330
+
331
+ #wpsl-wrap #wpsl-result-list li li {
332
+ padding: 0;
333
+ border-bottom: 0;
334
+ margin-left: 14px;
335
+ overflow: visible;
336
+ }
337
+
338
+ #wpsl-wrap #wpsl-result-list ul li {
339
+ list-style: none !important;
340
+ }
341
+
342
+ #wpsl-wrap #wpsl-result-list ol li {
343
+ list-style: decimal !important;
344
+ }
345
+
346
+ #wpsl-wrap.wpsl-store-below #wpsl-result-list li {
347
+ padding: 10px 10px 10px 0;
348
+ }
349
+
350
+ #wpsl-result-list li p {
351
+ padding-left: 0;
352
+ margin: 0 0 20px 0;
353
+ }
354
+
355
+ .wpsl-store-details.wpsl-store-listing {
356
+ position: relative;
357
+ padding-right: 20px;
358
+ }
359
+
360
+ .wpsl-store-details.wpsl-store-listing:before,
361
+ .wpsl-store-details.wpsl-store-listing.wpsl-active-details:before {
362
+ position: absolute;
363
+ content: '';
364
+ bottom:6px;
365
+ right:0;
366
+ border-top: 5px solid #000000;
367
+ border-left: 6px solid rgba(0, 0, 0, 0);
368
+ border-right: 6px solid rgba(0, 0, 0, 0);
369
+ }
370
+
371
+ .wpsl-store-details.wpsl-store-listing.wpsl-active-details:before {
372
+ border-bottom: 5px solid #000000;
373
+ border-top:none;
374
+ border-left: 6px solid rgba(0, 0, 0, 0);
375
+ border-right: 6px solid rgba(0, 0, 0, 0);
376
+ }
377
+
378
+ #wpsl-stores .wpsl-store-thumb {
379
+ float:right;
380
+ border-radius:3px;
381
+ margin:7px 0 0 10px;
382
+ padding:0;
383
+ border:none;
384
+ }
385
+
386
+ .wpsl-direction-index {
387
+ float:left;
388
+ width:8%;
389
+ margin:0 5% 0 0;
390
+ }
391
+
392
+ .wpsl-direction-txt {
393
+ float:left;
394
+ width:62%;
395
+ }
396
+
397
+ .wpsl-direction-distance {
398
+ float:left;
399
+ width:20%;
400
+ margin:0 0 0 5%;
401
+ }
402
+
403
+ .wpsl-direction-txt span {
404
+ display:block;
405
+ margin-top:10px;
406
+ }
407
+
408
+ .wpsl-street,
409
+ .wpsl-country {
410
+ display: block;
411
+ border-bottom: none !important;
412
+ }
413
+
414
+ .wpsl-directions {
415
+ display: table;
416
+ border-bottom: none !important;
417
+ }
418
+
419
+ /* Preloader */
420
+ #wpsl-wrap #wpsl-result-list li.wpsl-preloader {
421
+ position: relative;
422
+ border-bottom: none;
423
+ padding: 10px 10px 10px 35px;
424
+ }
425
+
426
+ .wpsl-preloader img {
427
+ position: absolute;
428
+ left: 10px;
429
+ top: 50%;
430
+ margin-top: -8px;
431
+ box-shadow:none !important;
432
+ border:none !important;
433
+ }
434
+
435
+ .wpsl-preloader span {
436
+ float: left;
437
+ margin: -5px 0 0 11px;
438
+ }
439
+
440
+ #wpsl-search-wrap div,
441
+ #wpsl-search-btn {
442
+ margin-right: 10px;
443
+ float: left;
444
+ }
445
+
446
+ #wpsl-search-wrap .wpsl-select-wrap {
447
+ position: relative;
448
+ z-index: 2;
449
+ margin-right: 0;
450
+ }
451
+
452
+ #wpsl-search-wrap .wpsl-input-field {
453
+ position: relative;
454
+ }
455
+
456
+ #wpsl-radius, #wpsl-results {
457
+ float: left;
458
+ margin-right: 15px;
459
+ }
460
+
461
+ #wpsl-category {
462
+ position: relative;
463
+ z-index: 1;
464
+ clear: both;
465
+ }
466
+
467
+ #wpsl-search-wrap .wpsl-dropdown div {
468
+ position: absolute;
469
+ float: none;
470
+ margin: -1px 0 0 0;
471
+ top: 100%;
472
+ left: -1px;
473
+ right: -1px;
474
+ border: 1px solid #ccc;
475
+ background: #fff;
476
+ border-top: 1px solid #eee;
477
+ border-radius: 0 0 3px 3px;
478
+ opacity: 0;
479
+ overflow: hidden;
480
+ -webkit-transition: all 150ms ease-in-out;
481
+ -moz-transition: all 150ms ease-in-out;
482
+ -ms-transition: all 150ms ease-in-out;
483
+ transition: all 150ms ease-in-out;
484
+ }
485
+
486
+ #wpsl-search-wrap .wpsl-dropdown.wpsl-active div {
487
+ opacity: 1;
488
+ }
489
+
490
+ #wpsl-search-wrap .wpsl-input label {
491
+ margin-right:0;
492
+ }
493
+
494
+ #wpsl-radius, #wpsl-results {
495
+ display:inline;
496
+ }
497
+
498
+ #wpsl-radius {
499
+ margin-right:10px;
500
+ }
501
+ #wpsl-search-btn:hover {
502
+ cursor: pointer;
503
+ }
504
+
505
+ #wpsl-search-wrap select,
506
+ #wpsl-search select {
507
+ display:none;
508
+ }
509
+
510
+ #wpsl-search-wrap div label {
511
+ float:left;
512
+ margin-right:10px;
513
+ line-height: 32px;
514
+ }
515
+
516
+ #wpsl-results label {
517
+ width: auto;
518
+ }
519
+
520
+ #wpsl-result-list ul {
521
+ list-style: none;
522
+ margin: 0;
523
+ padding: 0;
524
+ }
525
+ .wpsl-direction-details {
526
+ display: none;
527
+ }
528
+
529
+ /* Infowindow */
530
+ #wpsl-gmap .wpsl-info-window,
531
+ .wpsl-gmap-canvas .wpsl-info-window {
532
+ max-width:225px;
533
+ }
534
+
535
+ .wpsl-more-info-listings span,
536
+ .wpsl-info-window span {
537
+ display:block;
538
+ }
539
+
540
+ .wpsl-info-window .wpsl-no-margin {
541
+ margin:0;
542
+ }
543
+
544
+ /* More info details in the store listings */
545
+ .wpsl-more-info-listings {
546
+ display:none;
547
+ }
548
+
549
+ /* Fix for Google Voice breaking the phone numbers */
550
+ .wpsl-info-window span span {
551
+ display:inline !important;
552
+ }
553
+
554
+ #wpsl-wrap .wpsl-info-window p {
555
+ margin: 0 0 10px 0;
556
+ }
557
+
558
+ .wpsl-store-hours {
559
+ margin-top:10px;
560
+ }
561
+
562
+ .wpsl-store-hours strong {
563
+ display:block;
564
+ }
565
+
566
+ #wpsl-gmap .wpsl-info-actions {
567
+ display:block;
568
+ margin:10px 0 !important;
569
+ }
570
+
571
+ .wpsl-info-actions a {
572
+ float:left;
573
+ margin-right: 7px;
574
+ }
575
+
576
+ .wpsl-info-actions .wpsl-zoom-here {
577
+ margin-right:0;
578
+ }
579
+
580
+ /* --- dropdowns --- */
581
+ .wpsl-dropdown {
582
+ position: relative;
583
+ width: 90px;
584
+ border: 1px solid #ccc;
585
+ cursor: pointer;
586
+ background: #fff;
587
+ border-radius: 3px;
588
+ -webkit-user-select: none;
589
+ -moz-user-select: none;
590
+ user-select: none;
591
+ margin-right: 0 !important;
592
+ z-index: 2;
593
+ }
594
+
595
+ #wpsl-results .wpsl-dropdown {
596
+ width: 70px;
597
+ }
598
+
599
+ .wpsl-dropdown ul {
600
+ position: absolute;
601
+ left: 0;
602
+ width: 100%;
603
+ height: 100%;
604
+ padding: 0 !important;
605
+ margin: 0 !important;
606
+ list-style: none;
607
+ overflow: hidden;
608
+ }
609
+
610
+ .wpsl-dropdown:hover {
611
+ box-shadow: 0 0 5px rgba( 0, 0, 0, 0.15 );
612
+ }
613
+
614
+ .wpsl-dropdown .wpsl-selected-item,
615
+ .wpsl-dropdown li {
616
+ position: relative;
617
+ display: block;
618
+ line-height: normal;
619
+ color: #000;
620
+ overflow: hidden;
621
+ }
622
+
623
+ #wpsl-radius .wpsl-dropdown .wpsl-selected-item,
624
+ #wpsl-radius .wpsl-dropdown li,
625
+ #wpsl-results .wpsl-dropdown .wpsl-selected-item,
626
+ #wpsl-results .wpsl-dropdown li {
627
+ white-space: nowrap;
628
+ }
629
+
630
+ .wpsl-selected-item:after {
631
+ position: absolute;
632
+ content: "";
633
+ right: 12px;
634
+ top: 50%;
635
+ margin-top: -4px;
636
+ border: 6px solid transparent;
637
+ border-top: 8px solid #000;
638
+ }
639
+
640
+ .wpsl-active .wpsl-selected-item:after {
641
+ margin-top: -10px;
642
+ border: 6px solid transparent;
643
+ border-bottom: 8px solid #000;
644
+ }
645
+
646
+ .wpsl-dropdown li:hover {
647
+ background: #f8f9f8;
648
+ position: relative;
649
+ z-index: 3;
650
+ color: #000;
651
+ }
652
+
653
+ .wpsl-dropdown .wpsl-selected-item,
654
+ .wpsl-dropdown li,
655
+ .wpsl-selected-item {
656
+ list-style: none;
657
+ padding: 9px 12px !important;
658
+ margin:0 !important;
659
+ }
660
+
661
+ .wpsl-selected-dropdown {
662
+ font-weight: bold;
663
+ }
664
+
665
+ .wpsl-clearfix:before,
666
+ .wpsl-clearfix:after {
667
+ content: " ";
668
+ display: table;
669
+ }
670
+
671
+ .wpsl-clearfix:after {
672
+ clear: both;
673
+ }
674
+
675
+ #wpsl-wrap .wpsl-selected-item {
676
+ position: static;
677
+ padding-right: 35px !important;
678
+ }
679
+
680
+ #wpsl-category,
681
+ .wpsl-input,
682
+ .wpsl-select-wrap {
683
+ position: relative;
684
+ margin-bottom: 10px;
685
+ }
686
+
687
+ #wpsl-search-wrap .wpsl-scroll-required div {
688
+ overflow-y: scroll;
689
+ }
690
+
691
+ .wpsl-scroll-required ul {
692
+ overflow: visible;
693
+ }
694
+
695
+ .wpsl-provided-by {
696
+ float: right;
697
+ padding: 5px 0;
698
+ text-align: right;
699
+ font-size: 12px;
700
+ width: 100%;
701
+ }
702
+
703
+ #wpsl-wrap .wpsl-results-only label {
704
+ width: auto;
705
+ }
706
+
707
+ /* wpsl custom post type pages */
708
+ .wpsl-locations-details,
709
+ .wpsl-location-address,
710
+ .wpsl-contact-details {
711
+ margin-bottom: 15px;
712
+ }
713
+
714
+ .wpsl-contact-details {
715
+ clear: both;
716
+ }
717
+
718
+ table.wpsl-opening-hours td {
719
+ vertical-align: top;
720
+ padding: 0 15px 0 0;
721
+ text-align: left;
722
+ }
723
+
724
+ table.wpsl-opening-hours time {
725
+ display:block;
726
+ }
727
+
728
+ table.wpsl-opening-hours {
729
+ width:auto !important;
730
+ font-size:100% !important;
731
+ }
732
+
733
+ table.wpsl-opening-hours,
734
+ table.wpsl-opening-hours td {
735
+ border:none !important;
736
+ }
737
+
738
+ /* Custom Infobox */
739
+ .wpsl-gmap-canvas .wpsl-infobox {
740
+ min-width:155px;
741
+ max-width:350px !important;
742
+ padding:10px;
743
+ border-radius:4px;
744
+ font-size:13px;
745
+ font-weight:300;
746
+ border:1px solid #ccc;
747
+ background:#fff !important;
748
+ }
749
+
750
+ .wpsl-gmap-canvas .wpsl-infobox:after,
751
+ .wpsl-gmap-canvas .wpsl-infobox:before {
752
+ position:absolute;
753
+ content:"";
754
+ left:40px;
755
+ bottom:-11px;
756
+ }
757
+
758
+ .wpsl-gmap-canvas .wpsl-infobox:after {
759
+ border-left:11px solid transparent;
760
+ border-right:11px solid transparent;
761
+ border-top:11px solid #fff;
762
+ }
763
+
764
+ .wpsl-gmap-canvas .wpsl-infobox:before {
765
+ border-left:13px solid transparent;
766
+ border-right:13px solid transparent;
767
+ border-top:13px solid #ccc;
768
+ bottom:-13px;
769
+ left:38px;
770
+ }
771
+
772
+ #wpsl-checkbox-filter,
773
+ .wpsl-custom-checkboxes {
774
+ display: block;
775
+ float: left;
776
+ margin: 5px 0 15px;
777
+ padding: 0;
778
+ width: 100%;
779
+ }
780
+
781
+ #wpsl-checkbox-filter li,
782
+ .wpsl-custom-checkboxes li {
783
+ float: left;
784
+ list-style: none;
785
+ margin: 0 1% 0 0;
786
+ }
787
+
788
+ #wpsl-checkbox-filter.wpsl-checkbox-1-columns li,
789
+ .wpsl-custom-checkboxes.wpsl-checkbox-1-columns li {
790
+ width: 99%;
791
+ }
792
+
793
+ #wpsl-checkbox-filter.wpsl-checkbox-2-columns li,
794
+ .wpsl-custom-checkboxes.wpsl-checkbox-2-columns li {
795
+ width: 49%;
796
+ }
797
+
798
+ #wpsl-checkbox-filter.wpsl-checkbox-3-columns li,
799
+ .wpsl-custom-checkboxes.wpsl-checkbox-3-columns li {
800
+ width: 32%;
801
+ }
802
+
803
+ #wpsl-checkbox-filter.wpsl-checkbox-4-columns li,
804
+ .wpsl-custom-checkboxes.wpsl-checkbox-4-columns li {
805
+ width: 24%;
806
+ }
807
+
808
+ #wpsl-checkbox-filter input,
809
+ .wpsl-custom-checkboxes input {
810
+ margin-right: 5px;
811
+ }
812
+
813
+ #wpsl-result-list .wpsl-contact-details span {
814
+ display: block !important;
815
+ }
816
+
817
+ /*
818
+ Hide the select2 ( https://select2.org/ ) dropdowns to
819
+ prevent duplicate dropdowns from showing up in the search bar.
820
+ */
821
+ #wpsl-search-wrap .select2 {
822
+ display: none !important;
823
+ }
824
+
825
+ /*
826
+ Make a few adjustments for themes that use RTL languages
827
+ */
828
+ .rtl #wpsl-result-list {
829
+ float: left;
830
+ }
831
+
832
+ .rtl #wpsl-checkbox-filter input,
833
+ .rtl .wpsl-custom-checkboxes input {
834
+ margin-right: 0;
835
+ margin-left: 5px;
836
+ }
837
+
838
+ .rtl .wpsl-info-actions a {
839
+ float: right;
840
+ margin: 0 0 0 7px;
841
+ }
842
+
843
+ .rtl #wpsl-gmap .wpsl-info-window {
844
+ padding-right: 22px;
845
+ }
846
+
847
+ .rtl #wpsl-wrap #wpsl-result-list li.wpsl-preloader {
848
+ padding: 10px 35px 10px 0;
849
+ }
850
+
851
+ .rtl .wpsl-preloader img {
852
+ left: 0;
853
+ right: 10px;
854
+ }
855
+
856
+ /* Only used when the TwentyNinteen theme is active */
857
+ .wpsl-twentynineteen .wpsl-input {
858
+ width: 100%;
859
+ }
860
+
861
+ .wpsl-twentynineteen #wpsl-search-input {
862
+ line-height: 1.3em;
863
+ }
864
+
865
+ .wpsl-twentynineteen #wpsl-search-wrap label {
866
+ margin-top: 6px;
867
+ }
868
+
869
+ .wpsl-twentynineteen .wpsl-dropdown {
870
+ width: 116px;
871
+ }
872
+
873
+ #wpsl-results .wpsl-dropdown {
874
+ width: 81px;
875
+ }
876
+
877
+ @media (max-width: 825px) {
878
+ #wpsl-search-input {
879
+ width: 348px;
880
+ }
881
+
882
+ .wpsl-results-only #wpsl-search-wrap .wpsl-dropdown {
883
+ width: 70px;
884
+ }
885
+
886
+ #wpsl-search-wrap .wpsl-input {
887
+ width: 100%;
888
+ margin-bottom: 10px;
889
+ }
890
+
891
+ .wpsl-input label,
892
+ #wpsl-radius label,
893
+ #wpsl-category label,
894
+ .wpsl-cat-results-filter #wpsl-search-wrap .wpsl-input,
895
+ .wpsl-no-filters #wpsl-search-wrap .wpsl-input,
896
+ .wpsl-results-only #wpsl-search-wrap .wpsl-input {
897
+ width: auto;
898
+ }
899
+ }
900
+
901
+ @media (max-width: 720px) {
902
+ #wpsl-search-wrap .wpsl-dropdown {
903
+ width: 114px;
904
+ }
905
+ }
906
+
907
+ @media (max-width: 675px) {
908
+ #wpsl-search-wrap #wpsl-search-btn {
909
+ float: left;
910
+ margin: 0 5px 0 0;
911
+ }
912
+
913
+ .wpsl-results-only #wpsl-search-wrap .wpsl-input,
914
+ .wpsl-dropdown {
915
+ width: 100%;
916
+ }
917
+
918
+ .wpsl-search {
919
+ padding: 2%;
920
+ }
921
+
922
+ .wpsl-input {
923
+ margin-right: 0;
924
+ }
925
+
926
+ #wpsl-result-list,
927
+ #wpsl-gmap {
928
+ width:49.75%;
929
+ }
930
+
931
+ #wpsl-result-list,
932
+ #wpsl-gmap {
933
+ float: none;
934
+ width: 100%;
935
+ }
936
+
937
+ .wpsl-direction-before {
938
+ padding-left: 0;
939
+ }
940
+
941
+ #wpsl-gmap {
942
+ margin-bottom: 15px;
943
+ }
944
+
945
+ .wpsl-cat-results-filter .wpsl-select-wrap,
946
+ .wpsl-filter .wpsl-select-wrap,
947
+ #wpsl-result-list {
948
+ margin-bottom: 10px;
949
+ }
950
+
951
+ #wpsl-result-list p,
952
+ #wpsl-wrap #wpsl-result-list li {
953
+ padding-left: 0;
954
+ }
955
+
956
+ #wpsl-wrap #wpsl-result-list li.wpsl-preloader {
957
+ padding-left: 25px;
958
+ }
959
+
960
+ .wpsl-preloader img {
961
+ left: 0;
962
+ }
963
+
964
+ #wpsl-stores.wpsl-not-loaded {
965
+ height: 25px;
966
+ }
967
+
968
+ #wpsl-reset-map {
969
+ top: 25px;
970
+ }
971
+
972
+ #wpsl-gmap {
973
+ margin-top: 10px;
974
+ }
975
+
976
+ .wpsl-no-filters #wpsl-search-wrap .wpsl-input,
977
+ #wpsl-category, .wpsl-input, .wpsl-select-wrap,
978
+ .wpsl-input, #wpsl-search-btn {
979
+ margin-bottom: 0;
980
+ }
981
+
982
+ #wpsl-stores.wpsl-no-autoload {
983
+ height: auto !important;
984
+ }
985
+
986
+ #wpsl-checkbox-filter.wpsl-checkbox-3-columns li,
987
+ #wpsl-checkbox-filter.wpsl-checkbox-4-columns li {
988
+ width: 49%;
989
+ }
990
+ }
991
+
992
+ @media (max-width: 570px) {
993
+ #wpsl-search-wrap #wpsl-search-btn {
994
+ margin-bottom: 5px;
995
+ }
996
+
997
+ .wpsl-search {
998
+ padding: 4%;
999
+ }
1000
+
1001
+ #wpsl-search-input {
1002
+ width: 98% !important;
1003
+ }
1004
+
1005
+ .wpsl-cat-results-filter #wpsl-search-wrap .wpsl-input,
1006
+ .wpsl-cat-results-filter #wpsl-search-input,
1007
+ .wpsl-no-results #wpsl-search-input,
1008
+ .wpsl-results-only #wpsl-search-input {
1009
+ width: 100% !important;
1010
+ }
1011
+
1012
+ .wpsl-search-btn-wrap {
1013
+ margin-top: 15px;
1014
+ }
1015
+
1016
+ .wpsl-checkboxes-enabled .wpsl-search-btn-wrap {
1017
+ margin-top: 0;
1018
+ }
1019
+
1020
+ #wpsl-search-wrap div,
1021
+ #wpsl-search-btn {
1022
+ margin-right: 0;
1023
+ }
1024
+
1025
+ #wpsl-search-wrap div label {
1026
+ display: block;
1027
+ width: 100%;
1028
+ }
1029
+
1030
+ #wpsl-results {
1031
+ width:auto;
1032
+ }
1033
+
1034
+ .wpsl-select-wrap {
1035
+ width: 100%;
1036
+ }
1037
+
1038
+ #wpsl-radius,
1039
+ #wpsl-results {
1040
+ width: 50%;
1041
+ }
1042
+
1043
+ #wpsl-radius {
1044
+ margin-right: 4%;
1045
+ }
1046
+
1047
+ #wpsl-search-wrap .wpsl-dropdown {
1048
+ width: 96% !important;
1049
+ }
1050
+
1051
+ .wpsl-search-btn-wrap {
1052
+ clear: both;
1053
+ }
1054
+
1055
+ .wpsl-no-filters #wpsl-search-wrap .wpsl-input,
1056
+ .wpsl-no-filters #wpsl-search-input {
1057
+ width: 100% !important;
1058
+ }
1059
+ }
1060
+
1061
+ @media (max-width: 420px) {
1062
+ #wpsl-checkbox-filter li {
1063
+ margin: 0;
1064
+ }
1065
+
1066
+ #wpsl-checkbox-filter.wpsl-checkbox-1-columns li,
1067
+ #wpsl-checkbox-filter.wpsl-checkbox-2-columns li,
1068
+ #wpsl-checkbox-filter.wpsl-checkbox-3-columns li,
1069
+ #wpsl-checkbox-filter.wpsl-checkbox-4-columns li {
1070
+ width: 100%;
1071
+ }
1072
  }
css/styles.min.css CHANGED
@@ -1 +1 @@
1
- #wpsl-wrap,.wpsl-gmap-canvas{margin-bottom:20px;width:100%}#wpsl-result-list a,#wpsl-wrap [class*=" wpsl-icon-"]:focus,#wpsl-wrap [class^=wpsl-icon-]:active{outline:0}#wpsl-map-controls div:hover,#wpsl-reset-map:hover,#wpsl-search-btn:hover,.wpsl-dropdown{cursor:pointer}#wpsl-wrap,.wpsl-clearfix:after,.wpsl-contact-details{clear:both}@font-face{font-family:wpsl-fontello;src:url(../font/fontello.eot?28897909);src:url(../font/fontello.eot?28897909#iefix) format('embedded-opentype'),url(../font/fontello.woff?28897909) format('woff'),url(../font/fontello.ttf?28897909) format('truetype'),url(../font/fontello.svg?28897909#fontello) format('svg');font-weight:400;font-style:normal}#wpsl-gmap{float:right;width:66.5%;height:350px;margin-bottom:0}.wpsl-store-below #wpsl-gmap{float:none;width:100%}.wpsl-gmap-canvas{height:300px}#wpsl-gmap div:not[class^=gv-iv],#wpsl-gmap img,.wpsl-gmap-canvas div:not[class^=gv-iv],.wpsl-gmap-canvas img{box-shadow:none!important;max-width:none!important;background:0 0}#wpsl-gmap img,.wpsl-gmap-canvas img{display:inline;opacity:1!important;max-height:none!important}#wpsl-gmap *{box-sizing:content-box!important;-webkit-box-sizing:content-box!important;-moz-box-sizing:content-box!important}#wpsl-gmap div.gm-iv-marker,.wpsl-gmap-canvas div.gm-iv-marker{backgroud-image:inherit}#wpsl-wrap{position:relative;overflow:hidden}#wpsl-search-wrap{float:left;width:100%}#wpsl-search-wrap form{margin:0;padding:0;border:none;outline:0}#wpsl-gmap #wpsl-map-controls{position:absolute;height:40px;right:10px;bottom:24px;border-radius:2px;z-index:3;font-size:14px;white-space:nowrap;overflow:hidden;box-shadow:rgba(0,0,0,.3) 0 1px 4px -1px}#wpsl-gmap #wpsl-map-controls.wpsl-street-view-exists{right:60px}#wpsl-map-controls .wpsl-direction-preloader{margin:5px 5px 0}#wpsl-map-controls div{float:left;background:#fff;border-radius:2px}#wpsl-wrap [class*=" wpsl-icon-"],#wpsl-wrap [class^=wpsl-icon-]{position:relative;float:left;padding:12px 13px;display:inline-block;font-family:wpsl-fontello;font-style:normal;font-weight:400;font-size:1.3em;color:#737373;speak:none;text-decoration:inherit;text-align:center;font-variant:normal;text-transform:none;line-height:1em;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#wpsl-map-controls span{font-family:inherit;font-size:inherit}#wpsl-wrap .wpsl-icon-reset{border-radius:2px 0 0 2px;z-index:2;padding-right:4px}#wpsl-wrap .wpsl-ie .wpsl-icon-reset{margin-right:-1px}#wpsl-wrap .wpsl-icon-direction{z-index:1}#wpsl-map-controls.wpsl-reset-exists .wpsl-icon-direction{border-radius:0 2px 2px 0}#wpsl-wrap .wpsl-active-icon,#wpsl-wrap [class*=" wpsl-icon-"]:hover,#wpsl-wrap [class^=wpsl-icon-]:hover{color:#000}#wpsl-wrap .wpsl-in-progress,#wpsl-wrap .wpsl-in-progress:hover{color:#c6c6c6}#wpsl-gmap #wpsl-reset-map{position:absolute;display:none;right:37px;top:37px;padding:6px 14px;background:#fff!important;border:1px solid rgba(0,0,0,.15);border-radius:2px;z-index:3}.gm-style-cc{word-wrap:normal}#wpsl-search-wrap .wpsl-input,#wpsl-search-wrap .wpsl-select-wrap{display:table}#wpsl-search-btn,#wpsl-search-wrap #wpsl-radius,#wpsl-search-wrap #wpsl-results,#wpsl-search-wrap .wpsl-input input,#wpsl-search-wrap .wpsl-input label{display:table-cell}#wpsl-search-wrap label{margin-bottom:0}#wpsl-search-input{width:179px;height:auto;padding:7px 12px;font-size:100%;margin:0}#wpsl-search-btn,#wpsl-search-wrap input{border:1px solid #d2d2d2;border-radius:3px}#wpsl-search-btn{padding:7px 10px;line-height:1.428571429;font-weight:400;color:#7c7c7c;background-color:#e6e6e6;background-repeat:repeat-x;background-image:-moz-linear-gradient(top,#f4f4f4,#e6e6e6);background-image:-ms-linear-gradient(top,#f4f4f4,#e6e6e6);background-image:-webkit-linear-gradient(top,#f4f4f4,#e6e6e6);background-image:-o-linear-gradient(top,#f4f4f4,#e6e6e6);background-image:linear-gradient(top,#f4f4f4,#e6e6e6);box-shadow:0 1px 2px rgba(64,64,64,.1);text-transform:none!important}#wpsl-search-input.wpsl-error{border:1px solid #bd0028!important}.wpsl-search{margin-bottom:12px;padding:12px 12px 0;background:#f4f3f3}.wpsl-search.wpsl-checkboxes-enabled{padding:12px}.wpsl-back{display:inline-block}#wpsl-result-list{width:33%;margin-right:.5%}.wpsl-store-below #wpsl-result-list{width:100%;margin:12px 0 0}#wpsl-direction-details,#wpsl-stores{height:350px;overflow-y:auto}#wpsl-direction-details,.wpsl-hide{display:none}#wpsl-result-list p{padding-left:10px}.wpsl-store-below #wpsl-result-list p{padding-left:0}.wpsl-direction-before{margin:14px 0 21px;padding-left:10px}.wpsl-store-below .wpsl-direction-before{padding-left:0}.wpsl-direction-before div{margin-top:10px}#wpsl-wrap #wpsl-result-list li{padding:10px;border-bottom:1px dotted #ccc;margin-left:0;overflow:hidden;list-style:none!important;text-indent:0}#wpsl-wrap #wpsl-result-list li li{padding:0;border-bottom:0;margin-left:14px;overflow:visible}#wpsl-wrap #wpsl-result-list ul li{list-style:none!important}#wpsl-wrap #wpsl-result-list ol li{list-style:decimal!important}#wpsl-wrap.wpsl-store-below #wpsl-result-list li{padding:10px 10px 10px 0}#wpsl-result-list li p{padding-left:0;margin:0 0 20px}.wpsl-store-details.wpsl-store-listing{position:relative;padding-right:20px}.wpsl-store-details.wpsl-store-listing.wpsl-active-details:before,.wpsl-store-details.wpsl-store-listing:before{position:absolute;content:'';bottom:6px;right:0;border-top:5px solid #000;border-left:6px solid transparent;border-right:6px solid transparent}.wpsl-store-details.wpsl-store-listing.wpsl-active-details:before{border-bottom:5px solid #000;border-top:none;border-left:6px solid transparent;border-right:6px solid transparent}#wpsl-stores .wpsl-store-thumb{float:right;border-radius:3px;margin:7px 0 0 10px;padding:0;border:none}.wpsl-direction-index{float:left;width:8%;margin:0 5% 0 0}.wpsl-direction-txt{float:left;width:62%}.wpsl-direction-distance{float:left;width:20%;margin:0 0 0 5%}.wpsl-direction-txt span{display:block;margin-top:10px}.wpsl-country,.wpsl-street{display:block;border-bottom:none!important}.wpsl-directions{display:table;border-bottom:none!important}#wpsl-wrap #wpsl-result-list li.wpsl-preloader{position:relative;border-bottom:none;padding:10px 10px 10px 35px}.wpsl-preloader img{position:absolute;left:10px;top:50%;margin-top:-8px;box-shadow:none!important;border:none!important}.wpsl-preloader span{float:left;margin:-5px 0 0 11px}#wpsl-search-btn,#wpsl-search-wrap div{margin-right:10px;float:left}#wpsl-search-wrap .wpsl-select-wrap{position:relative;z-index:2;margin-right:0}#wpsl-search-wrap .wpsl-input-field{position:relative}#wpsl-radius,#wpsl-results{float:left;margin-right:15px;display:inline}#wpsl-category{z-index:1;clear:both}#wpsl-search-wrap .wpsl-dropdown div{position:absolute;float:none;margin:-1px 0 0;top:100%;left:-1px;right:-1px;border:1px solid #ccc;background:#fff;border-top:1px solid #eee;border-radius:0 0 3px 3px;opacity:0;overflow:hidden;-webkit-transition:all 150ms ease-in-out;-moz-transition:all 150ms ease-in-out;-ms-transition:all 150ms ease-in-out;transition:all 150ms ease-in-out}#wpsl-search-wrap .wpsl-dropdown.wpsl-active div{opacity:1}#wpsl-search-wrap .wpsl-input label{margin-right:0}#wpsl-radius{margin-right:10px}#wpsl-search select,#wpsl-search-wrap select,.wpsl-direction-details{display:none}#wpsl-search-wrap div label{float:left;margin-right:10px;line-height:32px}#wpsl-results label{width:auto}#wpsl-result-list ul{list-style:none;margin:0;padding:0}#wpsl-gmap .wpsl-info-window,.wpsl-gmap-canvas .wpsl-info-window{max-width:225px}.wpsl-info-window span,.wpsl-more-info-listings span{display:block}.wpsl-info-window .wpsl-no-margin{margin:0}.wpsl-more-info-listings{display:none}.wpsl-info-window span span{display:inline!important}#wpsl-wrap .wpsl-info-window p{margin:0 0 10px}.wpsl-store-hours{margin-top:10px}.wpsl-store-hours strong{display:block}#wpsl-gmap .wpsl-info-actions{display:block;margin:10px 0!important}.wpsl-info-actions a{float:left;margin-right:7px}.wpsl-info-actions .wpsl-zoom-here{margin-right:0}.wpsl-dropdown{position:relative;width:90px;border:1px solid #ccc;background:#fff;border-radius:3px;-webkit-user-select:none;-moz-user-select:none;user-select:none;margin-right:0!important;z-index:2}.wpsl-dropdown ul{position:absolute;left:0;width:100%;height:100%;padding:0!important;margin:0!important;list-style:none;overflow:hidden}.wpsl-dropdown:hover{box-shadow:0 0 5px rgba(0,0,0,.15)}.wpsl-dropdown .wpsl-selected-item,.wpsl-dropdown li{position:relative;display:block;line-height:normal;color:#000;overflow:hidden}#wpsl-radius .wpsl-dropdown .wpsl-selected-item,#wpsl-radius .wpsl-dropdown li,#wpsl-results .wpsl-dropdown .wpsl-selected-item,#wpsl-results .wpsl-dropdown li{white-space:nowrap}.wpsl-selected-item:after{position:absolute;content:"";right:12px;top:50%;margin-top:-4px;border:6px solid transparent;border-top:8px solid #000}.wpsl-active .wpsl-selected-item:after{margin-top:-10px;border:6px solid transparent;border-bottom:8px solid #000}.wpsl-dropdown li:hover{background:#f8f9f8;position:relative;z-index:3;color:#000}.wpsl-dropdown .wpsl-selected-item,.wpsl-dropdown li,.wpsl-selected-item{list-style:none;padding:9px 12px!important;margin:0!important}.wpsl-selected-dropdown{font-weight:700}.wpsl-clearfix:after,.wpsl-clearfix:before{content:" ";display:table}#wpsl-wrap .wpsl-selected-item{position:static;padding-right:35px!important}#wpsl-category,.wpsl-input,.wpsl-select-wrap{position:relative;margin-bottom:10px}#wpsl-search-wrap .wpsl-scroll-required div{overflow-y:scroll}.wpsl-scroll-required ul{overflow:visible}.wpsl-provided-by{float:right;padding:5px 0;text-align:right;font-size:12px;width:100%}#wpsl-wrap .wpsl-results-only label{width:auto}.wpsl-contact-details,.wpsl-location-address,.wpsl-locations-details{margin-bottom:15px}table.wpsl-opening-hours td{vertical-align:top;padding:0 15px 0 0;text-align:left}table.wpsl-opening-hours time{display:block}table.wpsl-opening-hours{width:auto!important;font-size:100%!important}table.wpsl-opening-hours,table.wpsl-opening-hours td{border:none!important}.wpsl-gmap-canvas .wpsl-infobox{min-width:155px;max-width:350px!important;padding:10px;border-radius:4px;font-size:13px;font-weight:300;border:1px solid #ccc;background:#fff!important}.wpsl-gmap-canvas .wpsl-infobox:after,.wpsl-gmap-canvas .wpsl-infobox:before{position:absolute;content:"";left:40px;bottom:-11px}.wpsl-gmap-canvas .wpsl-infobox:after{border-left:11px solid transparent;border-right:11px solid transparent;border-top:11px solid #fff}.wpsl-gmap-canvas .wpsl-infobox:before{border-left:13px solid transparent;border-right:13px solid transparent;border-top:13px solid #ccc;bottom:-13px;left:38px}#wpsl-checkbox-filter,.wpsl-custom-checkboxes{display:block;float:left;margin:5px 0 15px;padding:0;width:100%}#wpsl-checkbox-filter li,.wpsl-custom-checkboxes li{float:left;list-style:none;margin:0 1% 0 0}#wpsl-checkbox-filter.wpsl-checkbox-1-columns li,.wpsl-custom-checkboxes.wpsl-checkbox-1-columns li{width:99%}#wpsl-checkbox-filter.wpsl-checkbox-2-columns li,.wpsl-custom-checkboxes.wpsl-checkbox-2-columns li{width:49%}#wpsl-checkbox-filter.wpsl-checkbox-3-columns li,.wpsl-custom-checkboxes.wpsl-checkbox-3-columns li{width:32%}#wpsl-checkbox-filter.wpsl-checkbox-4-columns li,.wpsl-custom-checkboxes.wpsl-checkbox-4-columns li{width:24%}#wpsl-checkbox-filter input,.wpsl-custom-checkboxes input{margin-right:5px}#wpsl-result-list .wpsl-contact-details span{display:block!important}#wpsl-search-wrap .select2{display:none!important}.rtl #wpsl-result-list{float:left}.rtl #wpsl-checkbox-filter input,.rtl .wpsl-custom-checkboxes input{margin-right:0;margin-left:5px}.rtl .wpsl-info-actions a{float:right;margin:0 0 0 7px}.rtl #wpsl-gmap .wpsl-info-window{padding-right:22px}.rtl #wpsl-wrap #wpsl-result-list li.wpsl-preloader{padding:10px 35px 10px 0}.rtl .wpsl-preloader img{left:0;right:10px}.wpsl-twentynineteen .wpsl-input{width:100%}.wpsl-twentynineteen #wpsl-search-input{line-height:1.3em}.wpsl-twentynineteen #wpsl-search-wrap label{margin-top:6px}.wpsl-twentynineteen .wpsl-dropdown{width:116px}#wpsl-results .wpsl-dropdown{width:81px}@media (max-width:825px){#wpsl-search-input{width:348px}.wpsl-results-only #wpsl-search-wrap .wpsl-dropdown{width:70px}#wpsl-search-wrap .wpsl-input{width:100%;margin-bottom:10px}#wpsl-category label,#wpsl-radius label,.wpsl-cat-results-filter #wpsl-search-wrap .wpsl-input,.wpsl-input label,.wpsl-no-filters #wpsl-search-wrap .wpsl-input,.wpsl-results-only #wpsl-search-wrap .wpsl-input{width:auto}}@media (max-width:720px){#wpsl-search-wrap .wpsl-dropdown{width:114px}}@media (max-width:675px){#wpsl-search-wrap #wpsl-search-btn{float:left;margin:0 5px 0 0}.wpsl-dropdown,.wpsl-results-only #wpsl-search-wrap .wpsl-input{width:100%}.wpsl-search{padding:2%}#wpsl-result-list p,#wpsl-wrap #wpsl-result-list li,.wpsl-direction-before{padding-left:0}.wpsl-input{margin-right:0}#wpsl-gmap,#wpsl-result-list{float:none;width:100%}#wpsl-gmap{margin-bottom:15px;margin-top:10px}#wpsl-result-list,.wpsl-cat-results-filter .wpsl-select-wrap,.wpsl-filter .wpsl-select-wrap{margin-bottom:10px}#wpsl-wrap #wpsl-result-list li.wpsl-preloader{padding-left:25px}.wpsl-preloader img{left:0}#wpsl-stores.wpsl-not-loaded{height:25px}#wpsl-reset-map{top:25px}#wpsl-category,#wpsl-search-btn,.wpsl-input,.wpsl-no-filters #wpsl-search-wrap .wpsl-input,.wpsl-select-wrap{margin-bottom:0}#wpsl-stores.wpsl-no-autoload{height:auto!important}#wpsl-checkbox-filter.wpsl-checkbox-3-columns li,#wpsl-checkbox-filter.wpsl-checkbox-4-columns li{width:49%}}@media (max-width:570px){#wpsl-search-wrap #wpsl-search-btn{margin-bottom:5px}.wpsl-search{padding:4%}#wpsl-search-input{width:98%!important}.wpsl-cat-results-filter #wpsl-search-input,.wpsl-cat-results-filter #wpsl-search-wrap .wpsl-input,.wpsl-no-results #wpsl-search-input,.wpsl-results-only #wpsl-search-input{width:100%!important}.wpsl-search-btn-wrap{margin-top:15px;clear:both}.wpsl-checkboxes-enabled .wpsl-search-btn-wrap{margin-top:0}#wpsl-search-btn,#wpsl-search-wrap div{margin-right:0}#wpsl-search-wrap div label{display:block;width:100%}.wpsl-select-wrap{width:100%}#wpsl-radius,#wpsl-results{width:50%}#wpsl-radius{margin-right:4%}#wpsl-search-wrap .wpsl-dropdown{width:96%!important}.wpsl-no-filters #wpsl-search-input,.wpsl-no-filters #wpsl-search-wrap .wpsl-input{width:100%!important}}@media (max-width:420px){#wpsl-checkbox-filter li{margin:0}#wpsl-checkbox-filter.wpsl-checkbox-1-columns li,#wpsl-checkbox-filter.wpsl-checkbox-2-columns li,#wpsl-checkbox-filter.wpsl-checkbox-3-columns li,#wpsl-checkbox-filter.wpsl-checkbox-4-columns li{width:100%}}
1
+ @font-face{font-family:wpsl-fontello;src:url(../font/fontello.eot?28897909);src:url(../font/fontello.eot?28897909#iefix) format('embedded-opentype'),url(../font/fontello.woff?28897909) format('woff'),url(../font/fontello.ttf?28897909) format('truetype'),url(../font/fontello.svg?28897909#fontello) format('svg');font-weight:400;font-style:normal}#wpsl-gmap{float:right;width:66.5%;height:350px;margin-bottom:0}.wpsl-store-below #wpsl-gmap{float:none;width:100%}.wpsl-gmap-canvas{width:100%;height:300px;margin-bottom:20px}#wpsl-reset-map:hover{cursor:pointer}#wpsl-gmap div:not[class^=gv-iv],#wpsl-gmap img,.wpsl-gmap-canvas div:not[class^=gv-iv],.wpsl-gmap-canvas img{box-shadow:none!important;max-width:none!important;background:0 0}#wpsl-gmap img,.wpsl-gmap-canvas img{display:inline;opacity:1!important;max-height:none!important}#wpsl-gmap *{box-sizing:content-box!important;-webkit-box-sizing:content-box!important;-moz-box-sizing:content-box!important}#wpsl-gmap div.gm-iv-marker,.wpsl-gmap-canvas div.gm-iv-marker{backgroud-image:inherit}#wpsl-wrap{position:relative;width:100%;overflow:hidden;clear:both;margin-bottom:20px}#wpsl-search-wrap{float:left;width:100%}#wpsl-search-wrap form{margin:0;padding:0;border:none;outline:0}#wpsl-gmap #wpsl-map-controls{position:absolute;height:40px;right:10px;bottom:24px;border-radius:2px;z-index:3;font-size:14px;white-space:nowrap;overflow:hidden;box-shadow:rgba(0,0,0,.3) 0 1px 4px -1px}#wpsl-gmap #wpsl-map-controls.wpsl-street-view-exists{right:60px}#wpsl-map-controls .wpsl-direction-preloader{margin:5px 5px 0 5px}#wpsl-map-controls div{float:left;background:#fff;border-radius:2px}#wpsl-map-controls div:hover{cursor:pointer}#wpsl-wrap [class*=" wpsl-icon-"],#wpsl-wrap [class^=wpsl-icon-]{position:relative;float:left;padding:12px 13px;display:inline-block;font-family:wpsl-fontello;font-style:normal;font-weight:400;font-size:1.3em;color:#737373;speak:none;text-decoration:inherit;text-align:center;font-variant:normal;text-transform:none;line-height:1em;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#wpsl-map-controls span{font-family:inherit;font-size:inherit}#wpsl-wrap .wpsl-icon-reset{border-radius:2px 0 0 2px;z-index:2;padding-right:4px}#wpsl-wrap .wpsl-ie .wpsl-icon-reset{margin-right:-1px}#wpsl-wrap .wpsl-icon-direction{z-index:1}#wpsl-map-controls.wpsl-reset-exists .wpsl-icon-direction{border-radius:0 2px 2px 0}#wpsl-wrap .wpsl-active-icon,#wpsl-wrap [class*=" wpsl-icon-"]:hover,#wpsl-wrap [class^=wpsl-icon-]:hover{color:#000}#wpsl-wrap [class*=" wpsl-icon-"]:focus,#wpsl-wrap [class^=wpsl-icon-]:active{outline:0}#wpsl-wrap .wpsl-in-progress,#wpsl-wrap .wpsl-in-progress:hover{color:#c6c6c6}#wpsl-gmap #wpsl-reset-map{position:absolute;display:none;right:37px;top:37px;padding:6px 14px;background:#fff!important;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:2px;z-index:3}#wpsl-reset-map:hover{cursor:pointer}.gm-style-cc{word-wrap:normal}#wpsl-search-wrap .wpsl-input,#wpsl-search-wrap .wpsl-select-wrap{display:table}#wpsl-search-btn,#wpsl-search-wrap #wpsl-radius,#wpsl-search-wrap #wpsl-results,#wpsl-search-wrap .wpsl-input input,#wpsl-search-wrap .wpsl-input label{display:table-cell}#wpsl-search-wrap label{margin-bottom:0}#wpsl-search-input{width:179px;height:auto;padding:7px 12px;font-size:100%;margin:0}#wpsl-search-btn,#wpsl-search-wrap input{border:1px solid #d2d2d2;border-radius:3px}#wpsl-search-btn{padding:7px 10px;line-height:1.428571429;font-weight:400;color:#7c7c7c;background-color:#e6e6e6;background-repeat:repeat-x;background-image:-moz-linear-gradient(top,#f4f4f4,#e6e6e6);background-image:-ms-linear-gradient(top,#f4f4f4,#e6e6e6);background-image:-webkit-linear-gradient(top,#f4f4f4,#e6e6e6);background-image:-o-linear-gradient(top,#f4f4f4,#e6e6e6);background-image:linear-gradient(top,#f4f4f4,#e6e6e6);box-shadow:0 1px 2px rgba(64,64,64,.1);text-transform:none!important}#wpsl-search-input.wpsl-error{border:1px solid #bd0028!important}.wpsl-search{margin-bottom:12px;padding:12px 12px 0 12px;background:#f4f3f3}.wpsl-search.wpsl-checkboxes-enabled{padding:12px}.wpsl-back{display:inline-block}#wpsl-result-list{width:33%;margin-right:.5%}.wpsl-store-below #wpsl-result-list{width:100%;margin:12px 0 0 0}#wpsl-direction-details,#wpsl-stores{height:350px;overflow-y:auto}#wpsl-direction-details,.wpsl-hide{display:none}#wpsl-result-list p{padding-left:10px}.wpsl-store-below #wpsl-result-list p{padding-left:0}#wpsl-result-list a{outline:0}.wpsl-direction-before{margin:14px 0 21px 0;padding-left:10px}.wpsl-store-below .wpsl-direction-before{padding-left:0}.wpsl-direction-before div{margin-top:10px}#wpsl-wrap #wpsl-result-list li{padding:10px;border-bottom:1px dotted #ccc;margin-left:0;overflow:hidden;list-style:none outside none!important;text-indent:0}#wpsl-wrap #wpsl-result-list li li{padding:0;border-bottom:0;margin-left:14px;overflow:visible}#wpsl-wrap #wpsl-result-list ul li{list-style:none!important}#wpsl-wrap #wpsl-result-list ol li{list-style:decimal!important}#wpsl-wrap.wpsl-store-below #wpsl-result-list li{padding:10px 10px 10px 0}#wpsl-result-list li p{padding-left:0;margin:0 0 20px 0}.wpsl-store-details.wpsl-store-listing{position:relative;padding-right:20px}.wpsl-store-details.wpsl-store-listing.wpsl-active-details:before,.wpsl-store-details.wpsl-store-listing:before{position:absolute;content:'';bottom:6px;right:0;border-top:5px solid #000;border-left:6px solid transparent;border-right:6px solid transparent}.wpsl-store-details.wpsl-store-listing.wpsl-active-details:before{border-bottom:5px solid #000;border-top:none;border-left:6px solid transparent;border-right:6px solid transparent}#wpsl-stores .wpsl-store-thumb{float:right;border-radius:3px;margin:7px 0 0 10px;padding:0;border:none}.wpsl-direction-index{float:left;width:8%;margin:0 5% 0 0}.wpsl-direction-txt{float:left;width:62%}.wpsl-direction-distance{float:left;width:20%;margin:0 0 0 5%}.wpsl-direction-txt span{display:block;margin-top:10px}.wpsl-country,.wpsl-street{display:block;border-bottom:none!important}.wpsl-directions{display:table;border-bottom:none!important}#wpsl-wrap #wpsl-result-list li.wpsl-preloader{position:relative;border-bottom:none;padding:10px 10px 10px 35px}.wpsl-preloader img{position:absolute;left:10px;top:50%;margin-top:-8px;box-shadow:none!important;border:none!important}.wpsl-preloader span{float:left;margin:-5px 0 0 11px}#wpsl-search-btn,#wpsl-search-wrap div{margin-right:10px;float:left}#wpsl-search-wrap .wpsl-select-wrap{position:relative;z-index:2;margin-right:0}#wpsl-search-wrap .wpsl-input-field{position:relative}#wpsl-radius,#wpsl-results{float:left;margin-right:15px}#wpsl-category{position:relative;z-index:1;clear:both}#wpsl-search-wrap .wpsl-dropdown div{position:absolute;float:none;margin:-1px 0 0 0;top:100%;left:-1px;right:-1px;border:1px solid #ccc;background:#fff;border-top:1px solid #eee;border-radius:0 0 3px 3px;opacity:0;overflow:hidden;-webkit-transition:all 150ms ease-in-out;-moz-transition:all 150ms ease-in-out;-ms-transition:all 150ms ease-in-out;transition:all 150ms ease-in-out}#wpsl-search-wrap .wpsl-dropdown.wpsl-active div{opacity:1}#wpsl-search-wrap .wpsl-input label{margin-right:0}#wpsl-radius,#wpsl-results{display:inline}#wpsl-radius{margin-right:10px}#wpsl-search-btn:hover{cursor:pointer}#wpsl-search select,#wpsl-search-wrap select{display:none}#wpsl-search-wrap div label{float:left;margin-right:10px;line-height:32px}#wpsl-results label{width:auto}#wpsl-result-list ul{list-style:none;margin:0;padding:0}.wpsl-direction-details{display:none}#wpsl-gmap .wpsl-info-window,.wpsl-gmap-canvas .wpsl-info-window{max-width:225px}.wpsl-info-window span,.wpsl-more-info-listings span{display:block}.wpsl-info-window .wpsl-no-margin{margin:0}.wpsl-more-info-listings{display:none}.wpsl-info-window span span{display:inline!important}#wpsl-wrap .wpsl-info-window p{margin:0 0 10px 0}.wpsl-store-hours{margin-top:10px}.wpsl-store-hours strong{display:block}#wpsl-gmap .wpsl-info-actions{display:block;margin:10px 0!important}.wpsl-info-actions a{float:left;margin-right:7px}.wpsl-info-actions .wpsl-zoom-here{margin-right:0}.wpsl-dropdown{position:relative;width:90px;border:1px solid #ccc;cursor:pointer;background:#fff;border-radius:3px;-webkit-user-select:none;-moz-user-select:none;user-select:none;margin-right:0!important;z-index:2}#wpsl-results .wpsl-dropdown{width:70px}.wpsl-dropdown ul{position:absolute;left:0;width:100%;height:100%;padding:0!important;margin:0!important;list-style:none;overflow:hidden}.wpsl-dropdown:hover{box-shadow:0 0 5px rgba(0,0,0,.15)}.wpsl-dropdown .wpsl-selected-item,.wpsl-dropdown li{position:relative;display:block;line-height:normal;color:#000;overflow:hidden}#wpsl-radius .wpsl-dropdown .wpsl-selected-item,#wpsl-radius .wpsl-dropdown li,#wpsl-results .wpsl-dropdown .wpsl-selected-item,#wpsl-results .wpsl-dropdown li{white-space:nowrap}.wpsl-selected-item:after{position:absolute;content:"";right:12px;top:50%;margin-top:-4px;border:6px solid transparent;border-top:8px solid #000}.wpsl-active .wpsl-selected-item:after{margin-top:-10px;border:6px solid transparent;border-bottom:8px solid #000}.wpsl-dropdown li:hover{background:#f8f9f8;position:relative;z-index:3;color:#000}.wpsl-dropdown .wpsl-selected-item,.wpsl-dropdown li,.wpsl-selected-item{list-style:none;padding:9px 12px!important;margin:0!important}.wpsl-selected-dropdown{font-weight:700}.wpsl-clearfix:after,.wpsl-clearfix:before{content:" ";display:table}.wpsl-clearfix:after{clear:both}#wpsl-wrap .wpsl-selected-item{position:static;padding-right:35px!important}#wpsl-category,.wpsl-input,.wpsl-select-wrap{position:relative;margin-bottom:10px}#wpsl-search-wrap .wpsl-scroll-required div{overflow-y:scroll}.wpsl-scroll-required ul{overflow:visible}.wpsl-provided-by{float:right;padding:5px 0;text-align:right;font-size:12px;width:100%}#wpsl-wrap .wpsl-results-only label{width:auto}.wpsl-contact-details,.wpsl-location-address,.wpsl-locations-details{margin-bottom:15px}.wpsl-contact-details{clear:both}table.wpsl-opening-hours td{vertical-align:top;padding:0 15px 0 0;text-align:left}table.wpsl-opening-hours time{display:block}table.wpsl-opening-hours{width:auto!important;font-size:100%!important}table.wpsl-opening-hours,table.wpsl-opening-hours td{border:none!important}.wpsl-gmap-canvas .wpsl-infobox{min-width:155px;max-width:350px!important;padding:10px;border-radius:4px;font-size:13px;font-weight:300;border:1px solid #ccc;background:#fff!important}.wpsl-gmap-canvas .wpsl-infobox:after,.wpsl-gmap-canvas .wpsl-infobox:before{position:absolute;content:"";left:40px;bottom:-11px}.wpsl-gmap-canvas .wpsl-infobox:after{border-left:11px solid transparent;border-right:11px solid transparent;border-top:11px solid #fff}.wpsl-gmap-canvas .wpsl-infobox:before{border-left:13px solid transparent;border-right:13px solid transparent;border-top:13px solid #ccc;bottom:-13px;left:38px}#wpsl-checkbox-filter,.wpsl-custom-checkboxes{display:block;float:left;margin:5px 0 15px;padding:0;width:100%}#wpsl-checkbox-filter li,.wpsl-custom-checkboxes li{float:left;list-style:none;margin:0 1% 0 0}#wpsl-checkbox-filter.wpsl-checkbox-1-columns li,.wpsl-custom-checkboxes.wpsl-checkbox-1-columns li{width:99%}#wpsl-checkbox-filter.wpsl-checkbox-2-columns li,.wpsl-custom-checkboxes.wpsl-checkbox-2-columns li{width:49%}#wpsl-checkbox-filter.wpsl-checkbox-3-columns li,.wpsl-custom-checkboxes.wpsl-checkbox-3-columns li{width:32%}#wpsl-checkbox-filter.wpsl-checkbox-4-columns li,.wpsl-custom-checkboxes.wpsl-checkbox-4-columns li{width:24%}#wpsl-checkbox-filter input,.wpsl-custom-checkboxes input{margin-right:5px}#wpsl-result-list .wpsl-contact-details span{display:block!important}#wpsl-search-wrap .select2{display:none!important}.rtl #wpsl-result-list{float:left}.rtl #wpsl-checkbox-filter input,.rtl .wpsl-custom-checkboxes input{margin-right:0;margin-left:5px}.rtl .wpsl-info-actions a{float:right;margin:0 0 0 7px}.rtl #wpsl-gmap .wpsl-info-window{padding-right:22px}.rtl #wpsl-wrap #wpsl-result-list li.wpsl-preloader{padding:10px 35px 10px 0}.rtl .wpsl-preloader img{left:0;right:10px}.wpsl-twentynineteen .wpsl-input{width:100%}.wpsl-twentynineteen #wpsl-search-input{line-height:1.3em}.wpsl-twentynineteen #wpsl-search-wrap label{margin-top:6px}.wpsl-twentynineteen .wpsl-dropdown{width:116px}#wpsl-results .wpsl-dropdown{width:81px}@media (max-width:825px){#wpsl-search-input{width:348px}.wpsl-results-only #wpsl-search-wrap .wpsl-dropdown{width:70px}#wpsl-search-wrap .wpsl-input{width:100%;margin-bottom:10px}#wpsl-category label,#wpsl-radius label,.wpsl-cat-results-filter #wpsl-search-wrap .wpsl-input,.wpsl-input label,.wpsl-no-filters #wpsl-search-wrap .wpsl-input,.wpsl-results-only #wpsl-search-wrap .wpsl-input{width:auto}}@media (max-width:720px){#wpsl-search-wrap .wpsl-dropdown{width:114px}}@media (max-width:675px){#wpsl-search-wrap #wpsl-search-btn{float:left;margin:0 5px 0 0}.wpsl-dropdown,.wpsl-results-only #wpsl-search-wrap .wpsl-input{width:100%}.wpsl-search{padding:2%}.wpsl-input{margin-right:0}#wpsl-gmap,#wpsl-result-list{width:49.75%}#wpsl-gmap,#wpsl-result-list{float:none;width:100%}.wpsl-direction-before{padding-left:0}#wpsl-gmap{margin-bottom:15px}#wpsl-result-list,.wpsl-cat-results-filter .wpsl-select-wrap,.wpsl-filter .wpsl-select-wrap{margin-bottom:10px}#wpsl-result-list p,#wpsl-wrap #wpsl-result-list li{padding-left:0}#wpsl-wrap #wpsl-result-list li.wpsl-preloader{padding-left:25px}.wpsl-preloader img{left:0}#wpsl-stores.wpsl-not-loaded{height:25px}#wpsl-reset-map{top:25px}#wpsl-gmap{margin-top:10px}#wpsl-category,#wpsl-search-btn,.wpsl-input,.wpsl-no-filters #wpsl-search-wrap .wpsl-input,.wpsl-select-wrap{margin-bottom:0}#wpsl-stores.wpsl-no-autoload{height:auto!important}#wpsl-checkbox-filter.wpsl-checkbox-3-columns li,#wpsl-checkbox-filter.wpsl-checkbox-4-columns li{width:49%}}@media (max-width:570px){#wpsl-search-wrap #wpsl-search-btn{margin-bottom:5px}.wpsl-search{padding:4%}#wpsl-search-input{width:98%!important}.wpsl-cat-results-filter #wpsl-search-input,.wpsl-cat-results-filter #wpsl-search-wrap .wpsl-input,.wpsl-no-results #wpsl-search-input,.wpsl-results-only #wpsl-search-input{width:100%!important}.wpsl-search-btn-wrap{margin-top:15px}.wpsl-checkboxes-enabled .wpsl-search-btn-wrap{margin-top:0}#wpsl-search-btn,#wpsl-search-wrap div{margin-right:0}#wpsl-search-wrap div label{display:block;width:100%}#wpsl-results{width:auto}.wpsl-select-wrap{width:100%}#wpsl-radius,#wpsl-results{width:50%}#wpsl-radius{margin-right:4%}#wpsl-search-wrap .wpsl-dropdown{width:96%!important}.wpsl-search-btn-wrap{clear:both}.wpsl-no-filters #wpsl-search-input,.wpsl-no-filters #wpsl-search-wrap .wpsl-input{width:100%!important}}@media (max-width:420px){#wpsl-checkbox-filter li{margin:0}#wpsl-checkbox-filter.wpsl-checkbox-1-columns li,#wpsl-checkbox-filter.wpsl-checkbox-2-columns li,#wpsl-checkbox-filter.wpsl-checkbox-3-columns li,#wpsl-checkbox-filter.wpsl-checkbox-4-columns li{width:100%}}
frontend/class-frontend.php CHANGED
@@ -1,1905 +1,1926 @@
1
- <?php
2
- /**
3
- * Frontend class
4
- *
5
- * @author Tijmen Smit
6
- * @since 1.0.0
7
- */
8
-
9
- if ( !defined( 'ABSPATH' ) ) exit;
10
-
11
- if ( !class_exists( 'WPSL_Frontend' ) ) {
12
-
13
- /**
14
- * Handle the frontend of the store locator
15
- *
16
- * @since 1.0.0
17
- */
18
- class WPSL_Frontend {
19
-
20
- /**
21
- * Keep track which scripts we need to load
22
- *
23
- * @since 2.0.0
24
- */
25
- private $load_scripts = array();
26
-
27
- /**
28
- * Keep track of the amount of maps on the page
29
- *
30
- * @since 2.0.0
31
- */
32
- private static $map_count = 0;
33
-
34
- /*
35
- * Holds the shortcode atts for the [wpsl] shortcode.
36
- *
37
- * Used to overwrite the settings just before
38
- * they are send to wp_localize_script.
39
- *
40
- * @since 2.1.1
41
- */
42
- public $sl_shortcode_atts;
43
-
44
- private $store_map_data = array();
45
-
46
-
47
- /**
48
- * Class constructor
49
- */
50
- public function __construct() {
51
-
52
- $this->includes();
53
-
54
- add_action( 'wp_ajax_store_search', array( $this, 'store_search' ) );
55
- add_action( 'wp_ajax_nopriv_store_search', array( $this, 'store_search' ) );
56
- add_action( 'wp_enqueue_scripts', array( $this, 'add_frontend_styles' ) );
57
- add_action( 'wp_footer', array( $this, 'add_frontend_scripts' ) );
58
-
59
- add_filter( 'the_content', array( $this, 'cpt_template' ) );
60
-
61
- add_shortcode( 'wpsl', array( $this, 'show_store_locator' ) );
62
- add_shortcode( 'wpsl_address', array( $this, 'show_store_address' ) );
63
- add_shortcode( 'wpsl_hours', array( $this, 'show_opening_hours' ) );
64
- add_shortcode( 'wpsl_map', array( $this, 'show_store_map' ) );
65
- }
66
-
67
- /**
68
- * Include the required front-end files.
69
- *
70
- * @since 2.0.0
71
- * @return void
72
- */
73
- public function includes() {
74
- require_once( WPSL_PLUGIN_DIR . 'frontend/underscore-functions.php' );
75
- }
76
-
77
- /**
78
- * Handle the Ajax search on the frontend.
79
- *
80
- * @since 1.0.0
81
- * @return json A list of store locations that are located within the selected search radius
82
- */
83
- public function store_search() {
84
-
85
- global $wpsl_settings;
86
-
87
- /*
88
- * Check if auto loading the locations on page load is enabled.
89
- *
90
- * If so then we save the store data in a transient to prevent a long loading time
91
- * in case a large amount of locations need to be displayed.
92
- *
93
- * The SQL query that selects nearby locations doesn't take that long,
94
- * but collecting all the store meta data in get_store_meta_data() for hunderds,
95
- * or thousands of stores can make it really slow.
96
- */
97
- if ( $wpsl_settings['autoload'] && isset( $_GET['autoload'] ) && $_GET['autoload'] && !$wpsl_settings['debug'] && !isset( $_GET['skip_cache'] ) ) {
98
- $transient_name = $this->create_transient_name();
99
-
100
- if ( false === ( $store_data = get_transient( 'wpsl_autoload_' . $transient_name ) ) ) {
101
- $store_data = $this->find_nearby_locations();
102
-
103
- if ( $store_data ) {
104
- set_transient( 'wpsl_autoload_' . $transient_name, $store_data, 0 );
105
- }
106
- }
107
- } else {
108
- $store_data = $this->find_nearby_locations();
109
- }
110
-
111
- do_action( 'wpsl_store_search' );
112
-
113
- wp_send_json( $store_data );
114
-
115
- exit();
116
- }
117
-
118
- /**
119
- * Create the name used in the wpsl autoload transient.
120
- *
121
- * @since 2.1.1
122
- * @return string $transient_name The transient name.
123
- */
124
- public function create_transient_name() {
125
-
126
- global $wpsl, $wpsl_settings;
127
-
128
- $name_section = array();
129
-
130
- // Include the set autoload limit.
131
- if ( $wpsl_settings['autoload'] && $wpsl_settings['autoload_limit'] ) {
132
- $name_section[] = absint( $wpsl_settings['autoload_limit'] );
133
- }
134
-
135
- /*
136
- * Check if we need to include the cat id(s) in the transient name.
137
- *
138
- * This can only happen if the user used the
139
- * 'category' attr on the wpsl shortcode.
140
- */
141
- if ( isset( $_GET['filter'] ) && $_GET['filter'] ) {
142
- $name_section[] = absint( str_replace( ',', '', $_GET['filter'] ) );
143
- }
144
-
145
- // Include the lat value from the start location.
146
- if ( isset( $_GET['lat'] ) && $_GET['lat'] ) {
147
- $name_section[] = absint( str_replace( '.', '', $_GET['lat'] ) );
148
- }
149
-
150
- /*
151
- * If a multilingual plugin ( WPML or qTranslate X ) is active then we have
152
- * to make sure each language has his own unique transient. We do this by
153
- * including the lang code in the transient name.
154
- *
155
- * Otherwise if the language is for example set to German on page load,
156
- * and the user switches to Spanish, then he would get the incorrect
157
- * permalink structure ( /de/.. instead or /es/.. ) and translated
158
- * store details.
159
- */
160
- $lang_code = $wpsl->i18n->check_multilingual_code();
161
-
162
- if ( $lang_code ) {
163
- $name_section[] = $lang_code;
164
- }
165
-
166
- $transient_name = implode( '_', $name_section );
167
-
168
- /*
169
- * If the distance unit filter ( wpsl_distance_unit ) is used to change the km / mi unit based on
170
- * the location of the IP, then we include the km / mi in the transient name. This is done to
171
- * prevent users from seeing the wrong distances from the cached data.
172
- *
173
- * This way one data set can include the distance in km, and the other one the distance in miles.
174
- */
175
- if ( has_filter( 'wpsl_distance_unit' ) ) {
176
- $transient_name = $transient_name . '_' . wpsl_get_distance_unit();
177
- }
178
-
179
- return $transient_name;
180
- }
181
-
182
- /**
183
- * Find store locations that are located within the selected search radius.
184
- *
185
- * This happens by calculating the distance between the
186
- * latlng of the searched location, and the latlng from
187
- * the stores in the db.
188
- *
189
- * @since 2.0.0
190
- * @param array $args The arguments to use in the SQL query, only used by add-ons
191
- * @return void|array $store_data The list of stores that fall within the selected range.
192
- */
193
- public function find_nearby_locations( $args = array() ) {
194
-
195
- global $wpdb, $wpsl, $wpsl_settings;
196
-
197
- $store_data = array();
198
-
199
- /*
200
- * Set the correct earth radius in either km or miles.
201
- * We need this to calculate the distance between two coordinates.
202
- */
203
- $placeholder_values[] = ( wpsl_get_distance_unit() == 'km' ) ? 6371 : 3959;
204
-
205
- // The placeholder values for the prepared statement in the SQL query.
206
- if ( empty( $args ) ) {
207
- $args = $_GET;
208
- }
209
-
210
- array_push( $placeholder_values, $args['lat'], $args['lng'], $args['lat'] );
211
-
212
- // Check if we need to filter the results by category.
213
- if ( isset( $args['filter'] ) && $args['filter'] ) {
214
- $filter_ids = array_map( 'absint', explode( ',', $args['filter'] ) );
215
- $cat_filter = "INNER JOIN $wpdb->term_relationships AS term_rel ON posts.ID = term_rel.object_id
216
- INNER JOIN $wpdb->term_taxonomy AS term_tax ON term_rel.term_taxonomy_id = term_tax.term_taxonomy_id
217
- AND term_tax.taxonomy = 'wpsl_store_category'
218
- AND term_tax.term_id IN (" . implode( ',', $filter_ids ) . ")";
219
- } else {
220
- $cat_filter = '';
221
- }
222
-
223
- /*
224
- * If WPML is active we include 'GROUP BY lat' in the sql query
225
- * to prevent duplicate locations from showing up in the results.
226
- *
227
- * This is a problem when a store location for example
228
- * exists in 4 different languages. They would all fall within
229
- * the selected radius, but we only need one store ID for the 'icl_object_id'
230
- * function to get the correct store ID for the current language.
231
- */
232
- if ( $wpsl->i18n->wpml_exists() ) {
233
- $group_by = 'GROUP BY lat';
234
- } else {
235
- $group_by = 'GROUP BY posts.ID';
236
- }
237
-
238
- /*
239
- * If autoload is enabled we need to check if there is a limit to the
240
- * amount of locations we need to show.
241
- *
242
- * Otherwise include the radius and max results limit in the sql query.
243
- */
244
- if ( isset( $args['autoload'] ) && $args['autoload'] ) {
245
- $limit = '';
246
-
247
- if ( $wpsl_settings['autoload_limit'] ) {
248
- $limit = 'LIMIT %d';
249
- $placeholder_values[] = $wpsl_settings['autoload_limit'];
250
- }
251
-
252
- $sql_sort = 'ORDER BY distance '. $limit;
253
- } else {
254
- array_push( $placeholder_values, $this->check_store_filter( $args, 'search_radius' ), $this->check_store_filter( $args, 'max_results' ) );
255
- $sql_sort = 'HAVING distance < %d ORDER BY distance LIMIT 0, %d';
256
- }
257
-
258
- $placeholder_values = apply_filters( 'wpsl_sql_placeholder_values', $placeholder_values );
259
-
260
- /*
261
- * The sql that will check which store locations fall within
262
- * the selected radius based on the lat and lng values.
263
- */
264
- $sql = apply_filters( 'wpsl_sql',
265
- "SELECT post_lat.meta_value AS lat,
266
- post_lng.meta_value AS lng,
267
- posts.ID,
268
- ( %d * acos( cos( radians( %s ) ) * cos( radians( post_lat.meta_value ) ) * cos( radians( post_lng.meta_value ) - radians( %s ) ) + sin( radians( %s ) ) * sin( radians( post_lat.meta_value ) ) ) )
269
- AS distance
270
- FROM $wpdb->posts AS posts
271
- INNER JOIN $wpdb->postmeta AS post_lat ON post_lat.post_id = posts.ID AND post_lat.meta_key = 'wpsl_lat'
272
- INNER JOIN $wpdb->postmeta AS post_lng ON post_lng.post_id = posts.ID AND post_lng.meta_key = 'wpsl_lng'
273
- $cat_filter
274
- WHERE posts.post_type = 'wpsl_stores'
275
- AND posts.post_status = 'publish' $group_by $sql_sort"
276
- );
277
-
278
- $stores = $wpdb->get_results( $wpdb->prepare( $sql, $placeholder_values ) );
279
-
280
- if ( $stores ) {
281
- $store_data = apply_filters( 'wpsl_store_data', $this->get_store_meta_data( $stores ) );
282
- } else {
283
- $store_data = apply_filters( 'wpsl_no_results_sql', '' );
284
- }
285
-
286
- return $store_data;
287
- }
288
-
289
- /**
290
- * Get the post meta data for the selected stores.
291
- *
292
- * @since 2.0.0
293
- * @param object $stores
294
- * @return array $all_stores The stores that fall within the selected range with the post meta data.
295
- */
296
- public function get_store_meta_data( $stores ) {
297
-
298
- global $wpsl_settings, $wpsl;
299
-
300
- $all_stores = array();
301
-
302
- // Get the list of store fields that we need to filter out of the post meta data.
303
- $meta_field_map = $this->frontend_meta_fields();
304
-
305
- foreach ( $stores as $store_key => $store ) {
306
-
307
- // If WPML is active try to get the id of the translated page.
308
- if ( $wpsl->i18n->wpml_exists() ) {
309
- $store->ID = $wpsl->i18n->maybe_get_wpml_id( $store->ID );
310
-
311
- if ( !$store->ID ) {
312
- continue;
313
- }
314
- }
315
-
316
- // Get the post meta data for each store that was within the range of the search radius.
317
- $custom_fields = get_post_custom( $store->ID );
318
-
319
- foreach ( $meta_field_map as $meta_key => $meta_value ) {
320
-
321
- if ( isset( $custom_fields[$meta_key][0] ) ) {
322
- if ( ( isset( $meta_value['type'] ) ) && ( !empty( $meta_value['type'] ) ) ) {
323
- $meta_type = $meta_value['type'];
324
- } else {
325
- $meta_type = '';
326
- }
327
-
328
- // If we need to hide the opening hours, and the current meta type is set to hours we skip it.
329
- if ( $wpsl_settings['hide_hours'] && $meta_type == 'hours' ) {
330
- continue;
331
- }
332
-
333
- // Make sure the data is safe to use on the frontend and in the format we expect it to be.
334
- switch ( $meta_type ) {
335
- case 'numeric':
336
- $meta_data = ( is_numeric( $custom_fields[$meta_key][0] ) ) ? $custom_fields[$meta_key][0] : 0 ;
337
- break;
338
- case 'email':
339
- $meta_data = sanitize_email( $custom_fields[$meta_key][0] );
340
- break;
341
- case 'url':
342
- $meta_data = esc_url( $custom_fields[$meta_key][0] );
343
- break;
344
- case 'hours':
345
- $meta_data = $this->get_opening_hours( $custom_fields[$meta_key][0], apply_filters( 'wpsl_hide_closed_hours', false ) );
346
- break;
347
- case 'wp_editor':
348
- case 'textarea':
349
- $meta_data = wp_kses_post( wpautop( $custom_fields[$meta_key][0] ) );
350
- break;
351
- case 'text':
352
- default:
353
- $meta_data = sanitize_text_field( stripslashes( $custom_fields[$meta_key][0] ) );
354
- break;
355
- }
356
-
357
- $store_meta[$meta_value['name']] = $meta_data;
358
- } else {
359
- $store_meta[$meta_value['name']] = '';
360
- }
361
-
362
- /*
363
- * Include the post content if the "More info" option is enabled on the settings page,
364
- * or if $include_post_content is set to true through the 'wpsl_include_post_content' filter.
365
- */
366
- if ( ( $wpsl_settings['more_info'] && $wpsl_settings['more_info_location'] == 'store listings' ) || apply_filters( 'wpsl_include_post_content', false ) ) {
367
- $page_object = get_post( $store->ID );
368
-
369
- // Check if we need to strip the shortcode from the post content.
370
- if ( apply_filters( 'wpsl_strip_content_shortcode', true ) ) {
371
- $post_content = strip_shortcodes( $page_object->post_content );
372
- } else {
373
- $post_content = $page_object->post_content;
374
- }
375
-
376
- $store_meta['description'] = apply_filters( 'the_content', $post_content );
377
- }
378
-
379
- $store_meta['store'] = get_the_title( $store->ID );
380
- $store_meta['thumb'] = $this->get_store_thumb( $store->ID, $store_meta['store'] );
381
- $store_meta['id'] = $store->ID;
382
-
383
- if ( !$wpsl_settings['hide_distance'] ) {
384
- $store_meta['distance'] = round( $store->distance, 1 );
385
- }
386
-
387
- if ( $wpsl_settings['permalinks'] ) {
388
- $store_meta['permalink'] = get_permalink( $store->ID );
389
- }
390
- }
391
-
392
- $all_stores[] = apply_filters( 'wpsl_store_meta', $store_meta, $store->ID );
393
- }
394
-
395
- return $all_stores;
396
- }
397
-
398
- /**
399
- * The store meta fields that are included in the json output.
400
- *
401
- * The wpsl_ is the name in db, the name value is used as the key in the json output.
402
- *
403
- * The type itself is used to determine how the value should be sanitized.
404
- * Text will go through sanitize_text_field, email through sanitize_email and so on.
405
- *
406
- * If no type is set it will default to sanitize_text_field.
407
- *
408
- * @since 2.0.0
409
- * @return array $store_fields The names of the meta fields used by the store
410
- */
411
- public function frontend_meta_fields() {
412
-
413
- $store_fields = array(
414
- 'wpsl_address' => array(
415
- 'name' => 'address'
416
- ),
417
- 'wpsl_address2' => array(
418
- 'name' => 'address2'
419
- ),
420
- 'wpsl_city' => array(
421
- 'name' => 'city'
422
- ),
423
- 'wpsl_state' => array(
424
- 'name' => 'state'
425
- ),
426
- 'wpsl_zip' => array(
427
- 'name' => 'zip'
428
- ),
429
- 'wpsl_country' => array(
430
- 'name' => 'country'
431
- ),
432
- 'wpsl_lat' => array(
433
- 'name' => 'lat',
434
- 'type' => 'numeric'
435
- ),
436
- 'wpsl_lng' => array(
437
- 'name' => 'lng',
438
- 'type' => 'numeric'
439
- ),
440
- 'wpsl_phone' => array(
441
- 'name' => 'phone'
442
- ),
443
- 'wpsl_fax' => array(
444
- 'name' => 'fax'
445
- ),
446
- 'wpsl_email' => array(
447
- 'name' => 'email',
448
- 'type' => 'email'
449
- ),
450
- 'wpsl_hours' => array(
451
- 'name' => 'hours',
452
- 'type' => 'hours'
453
- ),
454
- 'wpsl_url' => array(
455
- 'name' => 'url',
456
- 'type' => 'url'
457
- )
458
- );
459
-
460
- return apply_filters( 'wpsl_frontend_meta_fields', $store_fields );
461
- }
462
-
463
- /**
464
- * Get the store thumbnail.
465
- *
466
- * @since 2.0.0
467
- * @param string $post_id The post id of the store
468
- * @param string $store_name The name of the store
469
- * @return void|string $thumb The html img tag
470
- */
471
- public function get_store_thumb( $post_id, $store_name ) {
472
-
473
- $attr = array(
474
- 'class' => 'wpsl-store-thumb',
475
- 'alt' => $store_name
476
- );
477
-
478
- $thumb = get_the_post_thumbnail( $post_id, $this->get_store_thumb_size(), apply_filters( 'wpsl_thumb_attr', $attr ) );
479
-
480
- return $thumb;
481
- }
482
-
483
- /**
484
- * Get the store thumbnail size.
485
- *
486
- * @since 2.0.0
487
- * @return array $size The thumb format
488
- */
489
- public function get_store_thumb_size() {
490
-
491
- $size = apply_filters( 'wpsl_thumb_size', array( 45, 45 ) );
492
-
493
- return $size;
494
- }
495
-
496
- /**
497
- * Get the opening hours in the correct format.
498
- *
499
- * Either convert the hour values that are set through
500
- * a dropdown to a table, or wrap the textarea input in a <p>.
501
- *
502
- * Note: The opening hours can only be set in the textarea format by users who upgraded from 1.x.
503
- *
504
- * @since 2.0.0
505
- * @param array|string $hours The opening hours
506
- * @param boolean $hide_closed Hide the days were the location is closed
507
- * @return string $hours The formated opening hours
508
- */
509
- public function get_opening_hours( $hours, $hide_closed ) {
510
-
511
- $hours = maybe_unserialize( $hours );
512
-
513
- /*
514
- * If the hours are set through the dropdown then we create a table for the opening hours.
515
- * Otherwise we output the data entered in the textarea.
516
- */
517
- if ( is_array( $hours ) ) {
518
- $hours = $this->create_opening_hours_tabel( $hours, $hide_closed );
519
- } else {
520
- $hours = wp_kses_post( wpautop( $hours ) );
521
- }
522
-
523
- return $hours;
524
- }
525
-
526
- /**
527
- * Create a table for the opening hours.
528
- *
529
- * @since 2.0.0
530
- * @todo add schema.org support.
531
- * @param array $hours The opening hours
532
- * @param boolean $hide_closed Hide the days where the location is closed
533
- * @return string $hour_table The opening hours sorted in a table
534
- */
535
- public function create_opening_hours_tabel( $hours, $hide_closed ) {
536
-
537
- $opening_days = wpsl_get_weekdays();
538
-
539
- // Make sure that we have actual opening hours, and not every day is empty.
540
- if ( $this->not_always_closed( $hours ) ) {
541
- $hour_table = '<table role="presentation" class="wpsl-opening-hours">';
542
-
543
- foreach ( $opening_days as $index => $day ) {
544
- $i = 0;
545
- $hour_count = count( $hours[$index] );
546
-
547
- // If we need to hide days that are set to closed then skip them.
548
- if ( $hide_closed && !$hour_count ) {
549
- continue;
550
- }
551
-
552
- $hour_table .= '<tr>';
553
- $hour_table .= '<td>' . esc_html( $day ) . '</td>';
554
-
555
- // If we have opening hours we show them, otherwise just show 'Closed'.
556
- if ( $hour_count > 0 ) {
557
- $hour_table .= '<td>';
558
-
559
- while ( $i < $hour_count ) {
560
- $hour = explode( ',', $hours[$index][$i] );
561
- $hour_table .= '<time>' . esc_html( $hour[0] ) . ' - ' . esc_html( $hour[1] ) . '</time>';
562
-
563
- $i++;
564
- }
565
-
566
- $hour_table .= '</td>';
567
- } else {
568
- $hour_table .= '<td>' . __( 'Closed', 'wpsl' ) . '</td>';
569
- }
570
-
571
- $hour_table .= '</tr>';
572
- }
573
-
574
- $hour_table .= '</table>';
575
-
576
- return $hour_table;
577
- }
578
- }
579
-
580
- /**
581
- * Create the wpsl post type output.
582
- *
583
- * If you want to create a custom template you need to
584
- * create a single-wpsl_stores.php file in your theme folder.
585
- * You can see an example here https://wpstorelocator.co/document/create-custom-store-page-template/
586
- *
587
- * @since 2.0.0
588
- * @param string $content
589
- * @return string $content
590
- */
591
- public function cpt_template( $content ) {
592
-
593
- global $wpsl_settings, $post;
594
-
595
- // Prevent duplicate output when the Twenty Nineteen theme is active.
596
- $skip_status = ( get_option( 'template' ) === 'twentynineteen' ) ? true : false;
597
- $skip_cpt_template = apply_filters( 'wpsl_skip_cpt_template', $skip_status );
598
-
599
- if ( isset( $post->post_type ) && $post->post_type == 'wpsl_stores' && is_single() && in_the_loop() && !$skip_cpt_template ) {
600
- array_push( $this->load_scripts, 'wpsl_base' );
601
-
602
- $content .= '[wpsl_map]';
603
- $content .= '[wpsl_address]';
604
-
605
- if ( !$wpsl_settings['hide_hours'] ) {
606
- $content .= '[wpsl_hours]';
607
- }
608
- }
609
-
610
- return $content;
611
- }
612
-
613
- /**
614
- * Handle the [wpsl] shortcode attributes.
615
- *
616
- * @since 2.1.1
617
- * @param array $atts Shortcode attributes
618
- */
619
- public function check_sl_shortcode_atts( $atts ) {
620
-
621
- /*
622
- * Use a custom start location?
623
- *
624
- * If the provided location fails to geocode,
625
- * then the start location from the settings page is used.
626
- */
627
- if ( isset( $atts['start_location'] ) && $atts['start_location'] ) {
628
- $start_latlng = wpsl_check_latlng_transient( $atts['start_location'] );
629
-
630
- if ( isset( $start_latlng ) && $start_latlng ) {
631
- $this->sl_shortcode_atts['js']['startLatlng'] = $start_latlng;
632
- }
633
- }
634
-
635
- if ( isset( $atts['auto_locate'] ) && $atts['auto_locate'] ) {
636
- $this->sl_shortcode_atts['js']['autoLocate'] = ( $atts['auto_locate'] == 'true' ) ? 1 : 0;
637
- }
638
-
639
- // Change the category slugs into category ids.
640
- if ( isset( $atts['category'] ) && $atts['category'] ) {
641
- $term_ids = wpsl_get_term_ids( $atts['category'] );
642
-
643
- if ( $term_ids ) {
644
- $this->sl_shortcode_atts['js']['categoryIds'] = implode( ',', $term_ids );
645
- }
646
- }
647
-
648
- if ( isset( $atts['category_selection'] ) && $atts['category_selection'] ) {
649
- $this->sl_shortcode_atts['category_selection'] = wpsl_get_term_ids( $atts['category_selection'] );
650
- }
651
-
652
- if ( isset( $atts['category_filter_type'] ) && in_array( $atts['category_filter_type'], array( 'dropdown', 'checkboxes' ) ) ) {
653
- $this->sl_shortcode_atts['category_filter_type'] = $atts['category_filter_type'];
654
- }
655
-
656
- if ( isset( $atts['checkbox_columns'] ) && is_numeric( $atts['checkbox_columns'] ) ) {
657
- $this->sl_shortcode_atts['checkbox_columns'] = $atts['checkbox_columns'];
658
- }
659
-
660
- if ( isset( $atts['map_type'] ) && array_key_exists( $atts['map_type'], wpsl_get_map_types() ) ) {
661
- $this->sl_shortcode_atts['js']['mapType'] = $atts['map_type'];
662
- }
663
-
664
- if ( isset( $atts['start_marker'] ) && $atts['start_marker'] ) {
665
- $this->sl_shortcode_atts['js']['startMarker'] = $atts['start_marker'] . '@2x.png';
666
- }
667
-
668
- if ( isset( $atts['store_marker'] ) && $atts['store_marker'] ) {
669
- $this->sl_shortcode_atts['js']['storeMarker'] = $atts['store_marker'] . '@2x.png';
670
- }
671
- }
672
-
673
- /**
674
- * Handle the [wpsl] shortcode.
675
- *
676
- * @since 1.0.0
677
- * @param array $atts Shortcode attributes
678
- * @return string $output The wpsl template
679
- */
680
- public function show_store_locator( $atts ) {
681
-
682
- global $wpsl, $wpsl_settings;
683
-
684
- $atts = shortcode_atts( array(
685
- 'template' => $wpsl_settings['template_id'],
686
- 'start_location' => '',
687
- 'auto_locate' => '',
688
- 'category' => '',
689
- 'category_selection' => '',
690
- 'category_filter_type' => '',
691
- 'checkbox_columns' => '3',
692
- 'map_type' => '',
693
- 'start_marker' => '',
694
- 'store_marker' => ''
695
- ), $atts );
696
-
697
- $this->check_sl_shortcode_atts( $atts );
698
-
699
- // Make sure the required scripts are included for the wpsl shortcode.
700
- array_push( $this->load_scripts, 'wpsl_store_locator' );
701
-
702
- $template_details = $wpsl->templates->get_template_details( $atts['template'] );
703
-
704
- $output = include( $template_details['path'] );
705
-
706
- return $output;
707
- }
708
-
709
- /**
710
- * Handle the [wpsl_address] shortcode.
711
- *
712
- * @since 2.0.0
713
- * @todo add schema.org support.
714
- * @param array $atts Shortcode attributes
715
- * @return void|string $output The store address
716
- */
717
- public function show_store_address( $atts ) {
718
-
719
- global $post, $wpsl_settings, $wpsl;
720
-
721
- $atts = wpsl_bool_check( shortcode_atts( apply_filters( 'wpsl_address_shortcode_defaults', array(
722
- 'id' => '',
723
- 'name' => true,
724
- 'address' => true,
725
- 'address2' => true,
726
- 'city' => true,
727
- 'state' => true,
728
- 'zip' => true,
729
- 'country' => true,
730
- 'phone' => true,
731
- 'fax' => true,
732
- 'email' => true,
733
- 'url' => true,
734
- 'directions' => false,
735
- 'clickable_contact_details' => (bool) $wpsl_settings['clickable_contact_details']
736
- ) ), $atts ) );
737
-
738
- if ( get_post_type() == 'wpsl_stores' ) {
739
- if ( empty( $atts['id'] ) ) {
740
- if ( isset( $post->ID ) ) {
741
- $atts['id'] = $post->ID;
742
- } else {
743
- return;
744
- }
745
- }
746
- } else if ( empty( $atts['id'] ) ) {
747
- return __( 'If you use the [wpsl_address] shortcode outside a store page you need to set the ID attribute.', 'wpsl' );
748
- }
749
-
750
- $content = '<div class="wpsl-locations-details">';
751
-
752
- if ( $atts['name'] && $name = get_the_title( $atts['id'] ) ) {
753
- $content .= '<span><strong>' . esc_html( $name ) . '</strong></span>';
754
- }
755
-
756
- $content .= '<div class="wpsl-location-address">';
757
-
758
- if ( $atts['address'] && $address = get_post_meta( $atts['id'], 'wpsl_address', true ) ) {
759
- $content .= '<span>' . esc_html( $address ) . '</span><br/>';
760
- }
761
-
762
- if ( $atts['address2'] && $address2 = get_post_meta( $atts['id'], 'wpsl_address2', true ) ) {
763
- $content .= '<span>' . esc_html( $address2 ) . '</span><br/>';
764
- }
765
-
766
- $address_format = explode( '_', $wpsl_settings['address_format'] );
767
- $count = count( $address_format );
768
- $i = 1;
769
-
770
- // Loop over the address parts to make sure they are shown in the right order.
771
- foreach ( $address_format as $address_part ) {
772
-
773
- // Make sure the shortcode attribute is set to true for the $address_part, and it's not the 'comma' part.
774
- if ( $address_part != 'comma' && $atts[$address_part] ) {
775
- $post_meta = get_post_meta( $atts['id'], 'wpsl_' . $address_part, true );
776
-
777
- if ( $post_meta ) {
778
-
779
- /*
780
- * Check if the next part of the address is set to 'comma'.
781
- * If so add the, after the current address part, otherwise just show a space
782
- */
783
- if ( isset( $address_format[$i] ) && ( $address_format[$i] == 'comma' ) ) {
784
- $punctuation = ', ';
785
- } else {
786
- $punctuation = ' ';
787
- }
788
-
789
- // If we have reached the last item add a <br /> behind it.
790
- $br = ( $count == $i ) ? '<br />' : '';
791
-
792
- $content .= '<span>' . esc_html( $post_meta ) . $punctuation . '</span>' . $br;
793
- }
794
- }
795
-
796
- $i++;
797
- }
798
-
799
- if ( $atts['country'] && $country = get_post_meta( $atts['id'], 'wpsl_country', true ) ) {
800
- $content .= '<span>' . esc_html( $country ) . '</span>';
801
- }
802
-
803
- $content .= '</div>';
804
-
805
- // If either the phone, fax, email or url is set to true, then add the wrap div for the contact details.
806
- if ( $atts['phone'] || $atts['fax'] || $atts['email'] || $atts['url'] ) {
807
- $phone = get_post_meta( $atts['id'], 'wpsl_phone', true );
808
- $fax = get_post_meta( $atts['id'], 'wpsl_fax', true );
809
- $email = get_post_meta( $atts['id'], 'wpsl_email', true );
810
-
811
- if ( $atts['clickable_contact_details'] ) {
812
- $contact_details = array(
813
- 'phone' => '<a href="tel:' . esc_attr( $phone ) . '">' . esc_html( $phone ) . '</a>',
814
- 'fax' => '<a href="tel:' . esc_attr( $fax ) . '">' . esc_html( $fax ) . '</a>',
815
- 'email' => '<a href="mailto:' . sanitize_email( $email ) . '">' . sanitize_email( $email ) . '</a>'
816
- );
817
- } else {
818
- $contact_details = array(
819
- 'phone' => esc_html( $phone ),
820
- 'fax' => esc_html( $fax ),
821
- 'email' => sanitize_email( $email )
822
- );
823
- }
824
-
825
- $content .= '<div class="wpsl-contact-details">';
826
-
827
- if ( $atts['phone'] && $phone ) {
828
- $content .= esc_html( $wpsl->i18n->get_translation( 'phone_label', __( 'Phone', 'wpsl' ) ) ) . ': <span>' . $contact_details['phone'] . '</span><br/>';
829
- }
830
-
831
- if ( $atts['fax'] && $fax ) {
832
- $content .= esc_html( $wpsl->i18n->get_translation( 'fax_label', __( 'Fax', 'wpsl' ) ) ) . ': <span>' . $contact_details['fax'] . '</span><br/>';
833
- }
834
-
835
- if ( $atts['email'] && $email ) {
836
- $content .= esc_html( $wpsl->i18n->get_translation( 'email_label', __( 'Email', 'wpsl' ) ) ) . ': <span>' . $contact_details['email'] . '</span><br/>';
837
- }
838
-
839
- if ( $atts['url'] && $store_url = get_post_meta( $atts['id'], 'wpsl_url', true ) ) {
840
- $new_window = ( $wpsl_settings['new_window'] ) ? 'target="_blank"' : '' ;
841
- $content .= esc_html( $wpsl->i18n->get_translation( 'url_label', __( 'Url', 'wpsl' ) ) ) . ': <a ' . $new_window . ' href="' . esc_url( $store_url ) . '">' . esc_url( $store_url ) . '</a><br/>';
842
- }
843
-
844
- $content .= '</div>';
845
- }
846
-
847
- if ( $atts['directions'] && $address ) {
848
- if ( $wpsl_settings['new_window'] ) {
849
- $new_window = ' target="_blank"';
850
- } else {
851
- $new_window = '';
852
- }
853
-
854
- $content .= '<div class="wpsl-location-directions">';
855
-
856
- $city = get_post_meta( $atts['id'], 'wpsl_city', true );
857
- $country = get_post_meta( $atts['id'], 'wpsl_country', true );
858
- $destination = $address . ',' . $city . ',' . $country;
859
- $direction_url = "https://maps.google.com/maps?saddr=&daddr=" . urlencode( $destination ) . "&travelmode=" . strtolower( $this->get_directions_travel_mode() );
860
-
861
- $content .= '<p><a ' . $new_window . ' href="' . esc_url( $direction_url ) . '">' . __( 'Directions', 'wpsl' ) . '</a></p>';
862
- $content .= '</div>';
863
- }
864
-
865
- $content .= '</div>';
866
-
867
- return $content;
868
- }
869
-
870
- /**
871
- * Handle the [wpsl_hours] shortcode.
872
- *
873
- * @since 2.0.0
874
- * @param array $atts Shortcode attributes
875
- * @return void|string $output The opening hours
876
- */
877
- public function show_opening_hours( $atts ) {
878
-
879
- global $wpsl_settings, $post;
880
-
881
- // If the hours are set to hidden on the settings page, then respect that and don't continue.
882
- if ( $wpsl_settings['hide_hours'] ) {
883
- return;
884
- }
885
-
886
- $hide_closed = apply_filters( 'wpsl_hide_closed_hours', false );
887
-
888
- $atts = wpsl_bool_check( shortcode_atts( apply_filters( 'wpsl_hour_shortcode_defaults', array(
889
- 'id' => '',
890
- 'hide_closed' => $hide_closed
891
- ) ), $atts ) );
892
-
893
- if ( get_post_type() == 'wpsl_stores' ) {
894
- if ( empty( $atts['id'] ) ) {
895
- if ( isset( $post->ID ) ) {
896
- $atts['id'] = $post->ID;
897
- } else {
898
- return;
899
- }
900
- }
901
- } else if ( empty( $atts['id'] ) ) {
902
- return __( 'If you use the [wpsl_hours] shortcode outside a store page you need to set the ID attribute.', 'wpsl' );
903
- }
904
-
905
- $opening_hours = get_post_meta( $atts['id'], 'wpsl_hours' );
906
-
907
- if ( $opening_hours ) {
908
- $output = $this->get_opening_hours( $opening_hours[0], $atts['hide_closed'] );
909
-
910
- return $output;
911
- }
912
- }
913
-
914
- /**
915
- * Handle the [wpsl_map] shortcode.
916
- *
917
- * @since 2.0.0
918
- * @param array $atts Shortcode attributes
919
- * @return string $output The html for the map
920
- */
921
- public function show_store_map( $atts ) {
922
-
923
- global $wpsl_settings, $post;
924
-
925
- $atts = shortcode_atts( apply_filters( 'wpsl_map_shortcode_defaults', array(
926
- 'id' => '',
927
- 'category' => '',
928
- 'width' => '',
929
- 'height' => $wpsl_settings['height'],
930
- 'zoom' => $wpsl_settings['zoom_level'],
931
- 'map_type' => $wpsl_settings['map_type'],
932
- 'map_type_control' => $wpsl_settings['type_control'],
933
- 'map_style' => '',
934
- 'street_view' => $wpsl_settings['streetview'],
935
- 'scrollwheel' => $wpsl_settings['scrollwheel'],
936
- 'control_position' => $wpsl_settings['control_position']
937
- ) ), $atts );
938
-
939
- array_push( $this->load_scripts, 'wpsl_base' );
940
-
941
- if ( get_post_type() == 'wpsl_stores' ) {
942
- if ( empty( $atts['id'] ) ) {
943
- if ( isset( $post->ID ) ) {
944
- $atts['id'] = $post->ID;
945
- } else {
946
- return;
947
- }
948
- }
949
- } else if ( empty( $atts['id'] ) && empty( $atts['category'] ) ) {
950
- return __( 'If you use the [wpsl_map] shortcode outside a store page, then you need to set the ID or category attribute.', 'wpsl' );
951
- }
952
-
953
- if ( $atts['category'] ) {
954
- $store_ids = get_posts( array(
955
- 'numberposts' => -1,
956
- 'post_type' => 'wpsl_stores',
957
- 'post_status' => 'publish',
958
- 'tax_query' => array(
959
- array(
960
- 'taxonomy' => 'wpsl_store_category',
961
- 'field' => 'slug',
962
- 'terms' => explode( ',', sanitize_text_field( $atts['category'] ) )
963
- ),
964
- ),
965
- 'fields' => 'ids'
966
- ) );
967
- } else {
968
- $store_ids = array_map( 'absint', explode( ',', $atts['id'] ) );
969
- $id_count = count( $store_ids );
970
- }
971
-
972
- /*
973
- * The location url is included if:
974
- *
975
- * - Multiple ids are set.
976
- * - The category attr is set.
977
- * - The shortcode is used on a post type other then 'wpsl_stores'. No point in showing a location
978
- * url to the user that links back to the page they are already on.
979
- */
980
- if ( $atts['category'] || isset( $id_count ) && $id_count > 1 || get_post_type() != 'wpsl_stores' && !empty( $atts['id'] ) ) {
981
- $incl_url = true;
982
- } else {
983
- $incl_url = false;
984
- }
985
-
986
- $store_meta = array();
987
- $i = 0;
988
-
989
- foreach ( $store_ids as $store_id ) {
990
- $lat = get_post_meta( $store_id, 'wpsl_lat', true );
991
- $lng = get_post_meta( $store_id, 'wpsl_lng', true );
992
-
993
- // Make sure the latlng is numeric before collecting the other meta data.
994
- if ( is_numeric( $lat ) && is_numeric( $lng ) ) {
995
- $store_meta[$i] = apply_filters( 'wpsl_cpt_info_window_meta_fields', array(
996
- 'store' => get_the_title( $store_id ),
997
- 'address' => get_post_meta( $store_id, 'wpsl_address', true ),
998
- 'address2' => get_post_meta( $store_id, 'wpsl_address2', true ),
999
- 'city' => get_post_meta( $store_id, 'wpsl_city', true ),
1000
- 'state' => get_post_meta( $store_id, 'wpsl_state', true ),
1001
- 'zip' => get_post_meta( $store_id, 'wpsl_zip', true ),
1002
- 'country' => get_post_meta( $store_id, 'wpsl_country', true )
1003
- ), $store_id );
1004
-
1005
- // Grab the permalink / url if necessary.
1006
- if ( $incl_url ) {
1007
- if ( $wpsl_settings['permalinks'] ) {
1008
- $store_meta[$i]['permalink'] = get_permalink( $store_id );
1009
- } else {
1010
- $store_meta[$i]['url'] = get_post_meta( $store_id, 'wpsl_url', true );
1011
- }
1012
- }
1013
-
1014
- $store_meta[$i]['lat'] = $lat;
1015
- $store_meta[$i]['lng'] = $lng;
1016
- $store_meta[$i]['id'] = $store_id;
1017
-
1018
- $i++;
1019
- }
1020
- }
1021
-
1022
- $output = '<div id="wpsl-base-gmap_' . self::$map_count . '" class="wpsl-gmap-canvas"></div>' . "\r\n";
1023
-
1024
- // Make sure the shortcode attributes are valid.
1025
- $map_styles = $this->check_map_shortcode_atts( $atts );
1026
-
1027
- if ( $map_styles ) {
1028
- if ( isset( $map_styles['css'] ) && !empty( $map_styles['css'] ) ) {
1029
- $output .= '<style>' . $map_styles['css'] . '</style>' . "\r\n";
1030
- unset( $map_styles['css'] );
1031
- }
1032
-
1033
- if ( $map_styles ) {
1034
- $store_data['shortCode'] = $map_styles;
1035
- }
1036
- }
1037
-
1038
- $store_data['locations'] = $store_meta;
1039
-
1040
- $this->store_map_data[self::$map_count] = $store_data;
1041
-
1042
- self::$map_count++;
1043
-
1044
- return $output;
1045
- }
1046
-
1047
- /**
1048
- * Make sure the map style shortcode attributes are valid.
1049
- *
1050
- * The values are send to wp_localize_script in add_frontend_scripts.
1051
- *
1052
- * @since 2.0.0
1053
- * @param array $atts The map style shortcode attributes
1054
- * @return array $map_atts Validated map style shortcode attributes
1055
- */
1056
- public function check_map_shortcode_atts( $atts ) {
1057
-
1058
- $map_atts = array();
1059
-
1060
- if ( isset( $atts['width'] ) && is_numeric( $atts['width'] ) ) {
1061
- $width = 'width:' . $atts['width'] . 'px;';
1062
- } else {
1063
- $width = '';
1064
- }
1065
-
1066
- if ( isset( $atts['height'] ) && is_numeric( $atts['height'] ) ) {
1067
- $height = 'height:' . $atts['height'] . 'px;';
1068
- } else {
1069
- $height = '';
1070
- }
1071
-
1072
- if ( $width || $height ) {
1073
- $map_atts['css'] = '#wpsl-base-gmap_' . self::$map_count . ' {' . $width . $height . '}';
1074
- }
1075
-
1076
- if ( isset( $atts['zoom'] ) && !empty( $atts['zoom'] ) ) {
1077
- $map_atts['zoomLevel'] = wpsl_valid_zoom_level( $atts['zoom'] );
1078
- }
1079
-
1080
- if ( isset( $atts['map_type'] ) && !empty( $atts['map_type'] ) ) {
1081
- $map_atts['mapType'] = wpsl_valid_map_type( $atts['map_type'] );
1082
- }
1083
-
1084
- if ( isset( $atts['map_type_control'] ) ) {
1085
- $map_atts['mapTypeControl'] = $this->shortcode_atts_boolean( $atts['map_type_control'] );
1086
- }
1087
-
1088
- if ( isset( $atts['map_style'] ) && $atts['map_style'] == 'default' ) {
1089
- $map_atts['mapStyle'] = '';
1090
- }
1091
-
1092
- if ( isset( $atts['street_view'] ) ) {
1093
- $map_atts['streetView'] = $this->shortcode_atts_boolean( $atts['street_view'] );
1094
- }
1095
-
1096
- if ( isset( $atts['scrollwheel'] ) ) {
1097
- $map_atts['scrollWheel'] = $this->shortcode_atts_boolean( $atts['scrollwheel'] );
1098
- }
1099
-
1100
- if ( isset( $atts['control_position'] ) && !empty( $atts['control_position'] ) && ( $atts['control_position'] == 'left' || $atts['control_position'] == 'right' ) ) {
1101
- $map_atts['controlPosition'] = $atts['control_position'];
1102
- }
1103
-
1104
- return $map_atts;
1105
- }
1106
-
1107
- /**
1108
- * Set the shortcode attribute to either 1 or 0.
1109
- *
1110
- * @since 2.0.0
1111
- * @param string $att The shortcode attribute val
1112
- * @return int $att_val Either 1 or 0
1113
- */
1114
- public function shortcode_atts_boolean( $att ) {
1115
-
1116
- if ( $att === 'true' || absint( $att ) ) {
1117
- $att_val = 1;
1118
- } else {
1119
- $att_val = 0;
1120
- }
1121
-
1122
- return $att_val;
1123
- }
1124
-
1125
- /**
1126
- * Make sure the filter contains a valid value, otherwise use the default value.
1127
- *
1128
- * @since 2.0.0
1129
- * @param array $args The values used in the SQL query to find nearby locations
1130
- * @param string $filter The name of the filter
1131
- * @return string $filter_value The filter value
1132
- */
1133
- public function check_store_filter( $args, $filter ) {
1134
-
1135
- if ( isset( $args[$filter] ) && absint( $args[$filter] ) && $this->check_allowed_filter_value( $args, $filter ) ) {
1136
- $filter_value = $args[$filter];
1137
- } else {
1138
- $filter_value = $this->get_default_filter_value( $filter );
1139
- }
1140
-
1141
- return $filter_value;
1142
- }
1143
-
1144
- /**
1145
- * Make sure the used filter value isn't bigger
1146
- * then the value that's set on the settings page.
1147
- *
1148
- * @since 2.2.9
1149
- * @param array $args The values used in the SQL query to find nearby locations
1150
- * @param string $filter The name of the filter
1151
- * @return bool $allowed True if the value is equal or smaller then the value from the settings page
1152
- */
1153
- public function check_allowed_filter_value( $args, $filter ) {
1154
-
1155
- global $wpsl_settings;
1156
-
1157
- $allowed = false;
1158
-
1159
- $max_filter_val = max( explode(',', str_replace( array( '[',']' ), '', $wpsl_settings[$filter] ) ) );
1160
-
1161
- if ( (int) $args[$filter] <= (int) $max_filter_val ) {
1162
- $allowed = true;
1163
- }
1164
-
1165
- return $allowed;
1166
- }
1167
-
1168
- /**
1169
- * Get the default selected value for a dropdown.
1170
- *
1171
- * @since 1.0.0
1172
- * @param string $type The request list type
1173
- * @return string $response The default list value
1174
- */
1175
- public function get_default_filter_value( $type ) {
1176
-
1177
- $settings = get_option( 'wpsl_settings' );
1178
- $list_values = explode( ',', $settings[$type] );
1179
-
1180
- foreach ( $list_values as $k => $list_value ) {
1181
-
1182
- // The default radius has a [] wrapped around it, so we check for that and filter out the [].
1183
- if ( strpos( $list_value, '[' ) !== false ) {
1184
- $response = filter_var( $list_value, FILTER_SANITIZE_NUMBER_INT );
1185
- break;
1186
- }
1187
- }
1188
-
1189
- return $response;
1190
- }
1191
-
1192
- /**
1193
- * Check if we have a opening day that has an value, if not they are all set to closed.
1194
- *
1195
- * @since 2.0.0
1196
- * @param array $opening_hours The opening hours
1197
- * @return boolean True if a day is found that isn't empty
1198
- */
1199
- public function not_always_closed( $opening_hours ) {
1200
-
1201
- foreach ( $opening_hours as $hours => $hour ) {
1202
- if ( !empty( $hour ) ) {
1203
- return true;
1204
- }
1205
- }
1206
- }
1207
-
1208
- /**
1209
- * Create the css rules based on the height / max-width that is set on the settings page.
1210
- *
1211
- * @since 1.0.0
1212
- * @return string $css The custom css rules
1213
- */
1214
- public function get_custom_css() {
1215
-
1216
- global $wpsl_settings;
1217
-
1218
- $thumb_size = $this->get_store_thumb_size();
1219
-
1220
- $css = '<style>' . "\r\n";
1221
-
1222
- if ( isset( $thumb_size[0] ) && is_numeric( $thumb_size[0] ) && isset( $thumb_size[1] ) && is_numeric( $thumb_size[1] ) ) {
1223
- $css .= "\t" . "#wpsl-stores .wpsl-store-thumb {height:" . esc_attr( $thumb_size[0] ) . "px !important; width:" . esc_attr( $thumb_size[1] ) . "px !important;}" . "\r\n";
1224
- }
1225
-
1226
- if ( $wpsl_settings['template_id'] == 'below_map' && $wpsl_settings['listing_below_no_scroll'] ) {
1227
- $css .= "\t" . "#wpsl-gmap {height:" . esc_attr( $wpsl_settings['height'] ) . "px !important;}" . "\r\n";
1228
- $css .= "\t" . "#wpsl-stores, #wpsl-direction-details {height:auto !important;}";
1229
- } else {
1230
- $css .= "\t" . "#wpsl-stores, #wpsl-direction-details, #wpsl-gmap {height:" . esc_attr( $wpsl_settings['height'] ) . "px !important;}" . "\r\n";
1231
- }
1232
-
1233
- /*
1234
- * If the category dropdowns are enabled then we make it
1235
- * the same width as the search input field.
1236
- */
1237
- if ( $wpsl_settings['category_filter'] && $wpsl_settings['category_filter_type'] == 'dropdown' || isset( $this->sl_shortcode_atts['category_filter_type'] ) && $this->sl_shortcode_atts['category_filter_type'] == 'dropdown' ) {
1238
- $cat_elem = ',#wpsl-category .wpsl-dropdown';
1239
- } else {
1240
- $cat_elem = '';
1241
- }
1242
-
1243
- $css .= "\t" . "#wpsl-gmap .wpsl-info-window {max-width:" . esc_attr( $wpsl_settings['infowindow_width'] ) . "px !important;}" . "\r\n";
1244
- $css .= "\t" . ".wpsl-input label, #wpsl-radius label, #wpsl-category label {width:" . esc_attr( $wpsl_settings['label_width'] ) . "px;}" . "\r\n";
1245
- $css .= "\t" . "#wpsl-search-input " . $cat_elem . " {width:" . esc_attr( $wpsl_settings['search_width'] ) . "px;}" . "\r\n";
1246
- $css .= '</style>' . "\r\n";
1247
-
1248
- return $css;
1249
- }
1250
-
1251
- /**
1252
- * Collect the CSS classes that are placed on the outer store locator div.
1253
- *
1254
- * @since 2.0.0
1255
- * @return string $classes The custom CSS rules
1256
- */
1257
- public function get_css_classes() {
1258
-
1259
- global $wpsl_settings;
1260
-
1261
- $classes = array();
1262
-
1263
- if ( $wpsl_settings['category_filter'] && $wpsl_settings['results_dropdown'] && !$wpsl_settings['radius_dropdown'] ) {
1264
- $classes[] = 'wpsl-cat-results-filter';
1265
- } else if ( $wpsl_settings['category_filter'] && ( $wpsl_settings['results_dropdown'] || $wpsl_settings['radius_dropdown'] ) ) {
1266
- $classes[] = 'wpsl-filter';
1267
- }
1268
- // checkboxes class toevoegen?
1269
- if ( !$wpsl_settings['category_filter'] && !$wpsl_settings['results_dropdown'] && !$wpsl_settings['radius_dropdown'] ) {
1270
- $classes[] = 'wpsl-no-filters';
1271
- }
1272
-
1273
- if ( $wpsl_settings['category_filter'] && $wpsl_settings['category_filter_type'] == 'checkboxes' ) {
1274
- $classes[] = 'wpsl-checkboxes-enabled';
1275
- }
1276
-
1277
- if ( $wpsl_settings['results_dropdown'] && !$wpsl_settings['category_filter'] && !$wpsl_settings['radius_dropdown'] ) {
1278
- $classes[] = 'wpsl-results-only';
1279
- }
1280
-
1281
- // Adjust the styling of the store locator for the default WP 5.0 theme.
1282
- if ( get_option( 'template' ) === 'twentynineteen' ) {
1283
- $classes[] = 'wpsl-twentynineteen';
1284
- }
1285
-
1286
- $classes = apply_filters( 'wpsl_template_css_classes', $classes );
1287
-
1288
- if ( !empty( $classes ) ) {
1289
- return join( ' ', $classes );
1290
- }
1291
- }
1292
-
1293
- /**
1294
- * Create a dropdown list holding the search radius or
1295
- * max search results options.
1296
- *
1297
- * @since 1.0.0
1298
- * @param string $list_type The name of the list we need to load data for
1299
- * @return string $dropdown_list A list with the available options for the dropdown list
1300
- */
1301
- public function get_dropdown_list( $list_type ) {
1302
-
1303
- global $wpsl_settings;
1304
-
1305
- $dropdown_list = '';
1306
- $settings = explode( ',', $wpsl_settings[$list_type] );
1307
-
1308
- // Only show the distance unit if we are dealing with the search radius.
1309
- if ( $list_type == 'search_radius' ) {
1310
- $distance_unit = ' '. esc_attr( wpsl_get_distance_unit() );
1311
- } else {
1312
- $distance_unit = '';
1313
- }
1314
-
1315
- foreach ( $settings as $index => $setting_value ) {
1316
-
1317
- // The default radius has a [] wrapped around it, so we check for that and filter out the [].
1318
- if ( strpos( $setting_value, '[' ) !== false ) {
1319
- $setting_value = filter_var( $setting_value, FILTER_SANITIZE_NUMBER_INT );
1320
- $selected = 'selected="selected" ';
1321
- } else {
1322
- $selected = '';
1323
- }
1324
-
1325
- $dropdown_list .= '<option ' . $selected . 'value="'. absint( $setting_value ) .'">'. absint( $setting_value ) . $distance_unit .'</option>';
1326
- }
1327
-
1328
- return $dropdown_list;
1329
- }
1330
-
1331
- /**
1332
- * Check if we need to use a dropdown or checkboxes
1333
- * to filter the search results by categories.
1334
- *
1335
- * @since 2.2.10
1336
- * @return bool $use_filter
1337
- */
1338
- public function use_category_filter() {
1339
-
1340
- global $wpsl_settings;
1341
-
1342
- $use_filter = false;
1343
-
1344
- // Is a filter type set through the shortcode, or is the filter option enable on the settings page?
1345
- if ( isset( $this->sl_shortcode_atts['category_filter_type'] ) || $wpsl_settings['category_filter'] ) {
1346
- $use_filter = true;
1347
- }
1348
-
1349
- return $use_filter;
1350
- }
1351
-
1352
- /**
1353
- * Create the category filter.
1354
- *
1355
- * @todo create another func that accepts a meta key param to generate
1356
- * a dropdown with unique values. So for example create_filter( 'restaurant' ) will output a
1357
- * filter with all restaurant types. This can be used in a custom theme template.
1358
- *
1359
- * @since 2.0.0
1360
- * @return string|void $category The HTML for the category dropdown, or nothing if no terms exist.
1361
- */
1362
- public function create_category_filter() {
1363
-
1364
- global $wpsl, $wpsl_settings;
1365
-
1366
- /*
1367
- * If the category attr is set on the wpsl shortcode, then
1368
- * there is no need to ouput an extra category dropdown.
1369
- */
1370
- if ( isset( $this->sl_shortcode_atts['js']['categoryIds'] ) ) {
1371
- return;
1372
- }
1373
-
1374
- $terms = get_terms( 'wpsl_store_category' );
1375
-
1376
- if ( count( $terms ) > 0 ) {
1377
-
1378
- // Either use the shortcode atts filter type or the one from the settings page.
1379
- if ( isset( $this->sl_shortcode_atts['category_filter_type'] ) ) {
1380
- $filter_type = $this->sl_shortcode_atts['category_filter_type'];
1381
- } else {
1382
- $filter_type = $wpsl_settings['category_filter_type'];
1383
- }
1384
-
1385
- // Check if we need to show the filter as checkboxes or a dropdown list
1386
- if ( $filter_type == 'checkboxes' ) {
1387
- if ( isset( $this->sl_shortcode_atts['checkbox_columns'] ) ) {
1388
- $checkbox_columns = absint( $this->sl_shortcode_atts['checkbox_columns'] );
1389
- }
1390
-
1391
- if ( isset( $checkbox_columns ) && $checkbox_columns ) {
1392
- $column_count = $checkbox_columns;
1393
- } else {
1394
- $column_count = 3;
1395
- }
1396
-
1397
- $category = '<ul id="wpsl-checkbox-filter" class="wpsl-checkbox-' . $column_count . '-columns">';
1398
-
1399
- foreach ( $terms as $term ) {
1400
- $category .= '<li>';
1401
- $category .= '<label>';
1402
- $category .= '<input type="checkbox" value="' . esc_attr( $term->term_id ) . '" ' . $this->set_selected_category( $filter_type, $term->term_id ) . ' />';
1403
- $category .= esc_html( $term->name );
1404
- $category .= '</label>';
1405
- $category .= '</li>';
1406
- }
1407
-
1408
- $category .= '</ul>';
1409
- } else {
1410
- $category = '<div id="wpsl-category">' . "\r\n";
1411
- $category .= '<label for="wpsl-category-list">' . esc_html( $wpsl->i18n->get_translation( 'category_label', __( 'Category', 'wpsl' ) ) ) . '</label>' . "\r\n";
1412
-
1413
- $args = apply_filters( 'wpsl_dropdown_category_args', array(
1414
- 'show_option_none' => $wpsl->i18n->get_translation( 'category_default_label', __( 'Any', 'wpsl' ) ),
1415
- 'option_none_value' => '0',
1416
- 'orderby' => 'NAME',
1417
- 'order' => 'ASC',
1418
- 'echo' => 0,
1419
- 'selected' => $this->set_selected_category( $filter_type ),
1420
- 'hierarchical' => 1,
1421
- 'name' => 'wpsl-category',
1422
- 'id' => 'wpsl-category-list',
1423
- 'class' => 'wpsl-dropdown',
1424
- 'taxonomy' => 'wpsl_store_category',
1425
- 'hide_if_empty' => true
1426
- )
1427
- );
1428
-
1429
- $category .= wp_dropdown_categories( $args );
1430
-
1431
- $category .= '</div>' . "\r\n";
1432
- }
1433
-
1434
- return $category;
1435
- }
1436
- }
1437
-
1438
- /**
1439
- * Set the selected category item.
1440
- *
1441
- * @since 2.1.2
1442
- * @param string $filter_type The type of filter being used ( dropdown or checkbox )
1443
- * @param int|string $term_id The term id ( checkbox only )
1444
- * @return string|void $category The ID of the selected option, or checked='checked' if it's for a checkbox
1445
- */
1446
- public function set_selected_category( $filter_type, $term_id = '' ) {
1447
-
1448
- $selected_id = '';
1449
-
1450
- // Check if the ID for the selected cat is either passed through the widget, or shortcode
1451
- if ( isset( $_REQUEST['wpsl-widget-categories'] ) ) {
1452
- $selected_id = absint( $_REQUEST['wpsl-widget-categories'] );
1453
- } else if ( isset( $this->sl_shortcode_atts['category_selection'] ) ) {
1454
-
1455
- /*
1456
- * When the term_id is set, then it's a checkbox.
1457
- *
1458
- * Otherwise select the first value from the provided list since
1459
- * multiple selections are not supported in dropdowns.
1460
- */
1461
- if ( $term_id ) {
1462
-
1463
- // Check if the passed term id exists in the set shortcode value.
1464
- $key = array_search( $term_id, $this->sl_shortcode_atts['category_selection'] );
1465
-
1466
- if ( $key !== false ) {
1467
- $selected_id = $this->sl_shortcode_atts['category_selection'][$key];
1468
- }
1469
- } else {
1470
- $selected_id = $this->sl_shortcode_atts['category_selection'][0];
1471
- }
1472
- }
1473
-
1474
- if ( $selected_id ) {
1475
-
1476
- /*
1477
- * Based on the filter type, either return the ID of the selected category,
1478
- * or check if the checkbox needs to be set to checked="checked".
1479
- */
1480
- if ( $filter_type == 'dropdown' ) {
1481
- return $selected_id;
1482
- } else {
1483
- return checked( $selected_id, $term_id, false );
1484
- }
1485
- }
1486
- }
1487
-
1488
- /**
1489
- * Create a filename with @2x in it for the selected marker color.
1490
- *
1491
- * So when a user selected green.png in the admin panel. The JS on the front-end will end up
1492
- * loading green@2x.png to provide support for retina compatible devices.
1493
- *
1494
- * @since 1.0.0
1495
- * @param string $filename The name of the seleted marker
1496
- * @return string $filename The filename with @2x added to the end
1497
- */
1498
- public function create_retina_filename( $filename ) {
1499
-
1500
- $filename = explode( '.', $filename );
1501
- $filename = $filename[0] . '@2x.' . $filename[1];
1502
-
1503
- return $filename;
1504
- }
1505
-
1506
- /**
1507
- * Get the default values for the max_results and the search_radius dropdown.
1508
- *
1509
- * @since 1.0.2
1510
- * @return array $output The default dropdown values
1511
- */
1512
- public function get_dropdown_defaults() {
1513
-
1514
- global $wpsl_settings;
1515
-
1516
- $required_defaults = array(
1517
- 'max_results',
1518
- 'search_radius'
1519
- );
1520
-
1521
- // Strip out the default values that are wrapped in [].
1522
- foreach ( $required_defaults as $required_default ) {
1523
- preg_match_all( '/\[([0-9]+?)\]/', $wpsl_settings[$required_default], $match, PREG_PATTERN_ORDER );
1524
- $output[$required_default] = ( isset( $match[1][0] ) ) ? $match[1][0] : '25';
1525
- }
1526
-
1527
- return $output;
1528
- }
1529
-
1530
- /**
1531
- * Load the required css styles.
1532
- *
1533
- * @since 2.0.0
1534
- * @return void
1535
- */
1536
- public function add_frontend_styles() {
1537
-
1538
- global $wpsl_settings;
1539
-
1540
- /**
1541
- * Check if we need to deregister other Google Maps scripts loaded
1542
- * by other plugins, or the current theme?
1543
- *
1544
- * This in some cases can break the store locator map.
1545
- */
1546
- if ( $wpsl_settings['deregister_gmaps'] ) {
1547
- wpsl_deregister_other_gmaps();
1548
- }
1549
-
1550
- $min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
1551
-
1552
- wp_enqueue_style( 'wpsl-styles', WPSL_URL . 'css/styles'. $min .'.css', '', WPSL_VERSION_NUM );
1553
- }
1554
-
1555
- /**
1556
- * Get the HTML for the map controls.
1557
- *
1558
- * The '&#xe800;' and '&#xe801;' code is for the icon font from fontello.com
1559
- *
1560
- * @since 2.0.0
1561
- * @return string The HTML for the map controls
1562
- */
1563
- public function get_map_controls() {
1564
-
1565
- global $wpsl_settings, $is_IE;
1566
-
1567
- $classes = array();
1568
-
1569
- if ( $wpsl_settings['reset_map'] ) {
1570
- $reset_button = '<div class="wpsl-icon-reset"><span>&#xe801;</span></div>';
1571
- } else {
1572
- $reset_button = '';
1573
- }
1574
-
1575
- /*
1576
- * IE messes up the top padding for the icon fonts from fontello >_<.
1577
- *
1578
- * Luckily it's the same in all IE version ( 8-11 ),
1579
- * so adjusting the padding just for IE fixes it.
1580
- */
1581
- if ( $is_IE ) {
1582
- $classes[] = 'wpsl-ie';
1583
- }
1584
-
1585
- // If the street view option is enabled, then we need to adjust the right margin for the map control div.
1586
- if ( $wpsl_settings['streetview'] ) {
1587
- $classes[] = 'wpsl-street-view-exists';
1588
- }
1589
-
1590
- if ( !empty( $classes ) ) {
1591
- $class = 'class="' . join( ' ', $classes ) . '"';
1592
- } else {
1593
- $class = '';
1594
- }
1595
-
1596
- $map_controls = '<div id="wpsl-map-controls" ' . $class . '>' . $reset_button . '<div class="wpsl-icon-direction"><span>&#xe800;</span></div></div>';
1597
-
1598
- return apply_filters( 'wpsl_map_controls', $map_controls );
1599
- }
1600
-
1601
- /**
1602
- * The different geolocation errors.
1603
- *
1604
- * They are shown when the Geolocation API returns an error.
1605
- *
1606
- * @since 2.0.0
1607
- * @return array $geolocation_errors
1608
- */
1609
- public function geolocation_errors() {
1610
-
1611
- $geolocation_errors = array(
1612
- 'denied' => __( 'The application does not have permission to use the Geolocation API.', 'wpsl' ),
1613
- 'unavailable' => __( 'Location information is unavailable.', 'wpsl' ),
1614
- 'timeout' => __( 'The geolocation request timed out.', 'wpsl' ),
1615
- 'generalError' => __( 'An unknown error occurred.', 'wpsl' )
1616
- );
1617
-
1618
- return $geolocation_errors;
1619
- }
1620
-
1621
- /**
1622
- * Get the used marker properties.
1623
- *
1624
- * @since 2.1.0
1625
- * @link https://developers.google.com/maps/documentation/javascript/3.exp/reference#Icon
1626
- * @return array $marker_props The marker properties.
1627
- */
1628
- public function get_marker_props() {
1629
-
1630
- $marker_props = array(
1631
- 'scaledSize' => '24,35', // 50% of the normal image to make it work on retina screens.
1632
- 'origin' => '0,0',
1633
- 'anchor' => '12,35'
1634
- );
1635
-
1636
- /*
1637
- * If this is not defined, the url path will default to
1638
- * the url path of the WPSL plugin folder + /img/markers/
1639
- * in the wpsl-gmap.js.
1640
- */
1641
- if ( defined( 'WPSL_MARKER_URI' ) ) {
1642
- $marker_props['url'] = WPSL_MARKER_URI;
1643
- }
1644
-
1645
- return apply_filters( 'wpsl_marker_props', $marker_props );
1646
- }
1647
-
1648
- /**
1649
- * Get the used travel direction mode.
1650
- *
1651
- * @since 2.2.8
1652
- * @return string $travel_mode The used travel mode for the travel direcions
1653
- */
1654
- public function get_directions_travel_mode() {
1655
-
1656
- $default = 'driving';
1657
-
1658
- $travel_mode = apply_filters( 'wpsl_direction_travel_mode', $default );
1659
- $allowed_modes = array( 'driving', 'bicycling', 'transit', 'walking' );
1660
-
1661
- if ( !in_array( $travel_mode, $allowed_modes ) ) {
1662
- $travel_mode = $default;
1663
- }
1664
-
1665
- return strtoupper( $travel_mode );
1666
- }
1667
-
1668
- /**
1669
- * Get the map tab anchors.
1670
- *
1671
- * If the wpsl/wpsl_map shortcode is used in one or more tabs,
1672
- * then a JS fix ( the fixGreyTabMap function ) needs to run
1673
- * to make sure the map doesn't turn grey.
1674
- *
1675
- * For the fix to work need to know the used anchor(s).
1676
- *
1677
- * @since 2.2.10
1678
- * @return string|array $map_tab_anchor One or more anchors used to show the map(s)
1679
- */
1680
- public function get_map_tab_anchor() {
1681
-
1682
- $map_tab_anchor = apply_filters( 'wpsl_map_tab_anchor', 'wpsl-map-tab' );
1683
-
1684
- return $map_tab_anchor;
1685
- }
1686
-
1687
- /**
1688
- * Load the required JS scripts.
1689
- *
1690
- * @since 1.0.0
1691
- * @return void
1692
- */
1693
- public function add_frontend_scripts() {
1694
-
1695
- global $wpsl_settings, $wpsl;
1696
-
1697
- // Only load the required js files on the store locator page or individual store pages.
1698
- if ( empty( $this->load_scripts ) ) {
1699
- return;
1700
- }
1701
-
1702
- $min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
1703
-
1704
- $dropdown_defaults = $this->get_dropdown_defaults();
1705
-
1706
- /**
1707
- * Check if we need to deregister other Google Maps scripts loaded
1708
- * by other plugins, or the current theme?
1709
- *
1710
- * This in some cases can break the store locator map.
1711
- */
1712
- if ( $wpsl_settings['deregister_gmaps'] ) {
1713
- wpsl_deregister_other_gmaps();
1714
- }
1715
-
1716
- wp_enqueue_script( 'wpsl-gmap', ( 'https://maps.google.com/maps/api/js' . wpsl_get_gmap_api_params( 'browser_key' ) . '' ), '', null, true );
1717
-
1718
- $base_settings = array(
1719
- 'storeMarker' => $this->create_retina_filename( $wpsl_settings['store_marker'] ),
1720
- 'mapType' => $wpsl_settings['map_type'],
1721
- 'mapTypeControl' => $wpsl_settings['type_control'],
1722
- 'zoomLevel' => $wpsl_settings['zoom_level'],
1723
- 'startLatlng' => $wpsl_settings['start_latlng'],
1724
- 'autoZoomLevel' => $wpsl_settings['auto_zoom_level'],
1725
- 'scrollWheel' => $wpsl_settings['scrollwheel'],
1726
- 'controlPosition' => $wpsl_settings['control_position'],
1727
- 'url' => WPSL_URL,
1728
- 'markerIconProps' => $this->get_marker_props(),
1729
- 'storeUrl' => $wpsl_settings['store_url'],
1730
- 'maxDropdownHeight' => apply_filters( 'wpsl_max_dropdown_height', 300 ),
1731
- 'enableStyledDropdowns' => apply_filters( 'wpsl_enable_styled_dropdowns', true ),
1732
- 'mapTabAnchor' => $this->get_map_tab_anchor(),
1733
- 'mapTabAnchorReturn' => apply_filters( 'wpsl_map_tab_anchor_return', false ),
1734
- 'gestureHandling' => apply_filters( 'wpsl_gesture_handling', 'auto' ),
1735
- 'directionsTravelMode' => $this->get_directions_travel_mode(),
1736
- 'runFitBounds' => $wpsl_settings['run_fitbounds']
1737
- );
1738
-
1739
- $locator_map_settings = array(
1740
- 'startMarker' => $this->create_retina_filename( $wpsl_settings['start_marker'] ),
1741
- 'markerClusters' => $wpsl_settings['marker_clusters'],
1742
- 'streetView' => $wpsl_settings['streetview'],
1743
- 'autoComplete' => $wpsl_settings['autocomplete'],
1744
- 'autoLocate' => $wpsl_settings['auto_locate'],
1745
- 'autoLoad' => $wpsl_settings['autoload'],
1746
- 'markerEffect' => $wpsl_settings['marker_effect'],
1747
- 'markerStreetView' => $wpsl_settings['marker_streetview'],
1748
- 'markerZoomTo' => $wpsl_settings['marker_zoom_to'],
1749
- 'newWindow' => $wpsl_settings['new_window'],
1750
- 'resetMap' => $wpsl_settings['reset_map'],
1751
- 'directionRedirect' => $wpsl_settings['direction_redirect'],
1752
- 'phoneUrl' => $wpsl_settings['phone_url'],
1753
- 'clickableDetails' => $wpsl_settings['clickable_contact_details'],
1754
- 'moreInfoLocation' => $wpsl_settings['more_info_location'],
1755
- 'mouseFocus' => $wpsl_settings['mouse_focus'],
1756
- 'templateId' => $wpsl_settings['template_id'],
1757
- 'maxResults' => $dropdown_defaults['max_results'],
1758
- 'searchRadius' => $dropdown_defaults['search_radius'],
1759
- 'distanceUnit' => wpsl_get_distance_unit(),
1760
- 'geoLocationTimeout' => apply_filters( 'wpsl_geolocation_timeout', 7500 ),
1761
- 'ajaxurl' => wpsl_get_ajax_url(),
1762
- 'mapControls' => $this->get_map_controls()
1763
- );
1764
-
1765
- /*
1766
- * If no results are found then by default it will just show the
1767
- * "No results found" text. This filter makes it possible to show
1768
- * a custom HTML block instead of the "No results found" text.
1769
- */
1770
- $no_results_msg = apply_filters( 'wpsl_no_results', '' );
1771
-
1772
- if ( $no_results_msg ) {
1773
- $locator_map_settings['noResults'] = $no_results_msg;
1774
- }
1775
-
1776
- /**
1777
- * If enabled, include the component filter settings.
1778
- * @todo see https://developers.google.com/maps/documentation/javascript/releases#327
1779
- * See https://developers.google.com/maps/documentation/javascript/geocoding#ComponentFiltering
1780
- */
1781
- if ( $wpsl_settings['api_region'] && $wpsl_settings['api_geocode_component'] ) {
1782
- $geocode_components = array();
1783
- $geocode_components['country'] = strtoupper( $wpsl_settings['api_region'] );
1784
-
1785
- if ( $wpsl_settings['force_postalcode'] ) {
1786
- $geocode_components['postalCode'] = '';
1787
- }
1788
-
1789
- $locator_map_settings['geocodeComponents'] = apply_filters( 'wpsl_geocode_components', $geocode_components );
1790
- }
1791
-
1792
- /**
1793
- * Reduce the requested data fields with each autocomplete API call.
1794
- *
1795
- * You can see the supported fields here https://developers.google.com/maps/documentation/javascript/reference/places-service#PlaceResult
1796
- * and other possible options to target here https://developers.google.com/maps/documentation/javascript/reference/places-widget#AutocompleteOptions
1797
- */
1798
- if ( $wpsl_settings['autocomplete'] ) {
1799
- $locator_map_settings['autoCompleteOptions'] = apply_filters( 'wpsl_autocomplete_options', array(
1800
- 'fields' => array( 'geometry.location' ),
1801
- 'types' => array( '(regions)' )
1802
- ) );
1803
- }
1804
-
1805
- // If the marker clusters are enabled, include the js file and marker settings.
1806
- if ( $wpsl_settings['marker_clusters'] ) {
1807
- wp_enqueue_script( 'wpsl-cluster', WPSL_URL . 'js/markerclusterer'. $min .'.js', array( 'wpsl-js' ), WPSL_VERSION_NUM, true ); //not minified version is in the /js folder
1808
-
1809
- $base_settings['clusterZoom'] = $wpsl_settings['cluster_zoom'];
1810
- $base_settings['clusterSize'] = $wpsl_settings['cluster_size'];
1811
- $base_settings['clusterImagePath'] = 'https://cdn.rawgit.com/googlemaps/js-marker-clusterer/gh-pages/images/m';
1812
- }
1813
-
1814
- // Check if we need to include the infobox script and settings.
1815
- if ( $wpsl_settings['infowindow_style'] == 'infobox' ) {
1816
- wp_enqueue_script( 'wpsl-infobox', WPSL_URL . 'js/infobox'. $min .'.js', array( 'wpsl-gmap' ), WPSL_VERSION_NUM, true ); // Not minified version is in the /js folder
1817
-
1818
- $base_settings['infoWindowStyle'] = $wpsl_settings['infowindow_style'];
1819
- $base_settings = $this->get_infobox_settings( $base_settings );
1820
- }
1821
-
1822
- // Include the map style.
1823
- if ( !empty( $wpsl_settings['map_style'] ) ) {
1824
- $base_settings['mapStyle'] = strip_tags( stripslashes( json_decode( $wpsl_settings['map_style'] ) ) );
1825
- }
1826
-
1827
- wp_enqueue_script( 'wpsl-js', apply_filters( 'wpsl_gmap_js', WPSL_URL . 'js/wpsl-gmap'. $min .'.js' ), array( 'jquery' ), WPSL_VERSION_NUM, true );
1828
- wp_enqueue_script( 'underscore' );
1829
-
1830
- // Check if we need to include all the settings and labels or just a part of them.
1831
- if ( in_array( 'wpsl_store_locator', $this->load_scripts ) ) {
1832
- $settings = wp_parse_args( $base_settings, $locator_map_settings );
1833
- $template = 'wpsl_store_locator';
1834
- $labels = array(
1835
- 'preloader' => $wpsl->i18n->get_translation( 'preloader_label', __( 'Searching...', 'wpsl' ) ),
1836
- 'noResults' => $wpsl->i18n->get_translation( 'no_results_label', __( 'No results found', 'wpsl' ) ),
1837
- 'moreInfo' => $wpsl->i18n->get_translation( 'more_label', __( 'More info', 'wpsl' ) ),
1838
- 'generalError' => $wpsl->i18n->get_translation( 'error_label', __( 'Something went wrong, please try again!', 'wpsl' ) ),
1839
- 'queryLimit' => $wpsl->i18n->get_translation( 'limit_label', __( 'API usage limit reached', 'wpsl' ) ),
1840
- 'directions' => $wpsl->i18n->get_translation( 'directions_label', __( 'Directions', 'wpsl' ) ),
1841
- 'noDirectionsFound' => $wpsl->i18n->get_translation( 'no_directions_label', __( 'No route could be found between the origin and destination', 'wpsl' ) ),
1842
- 'startPoint' => $wpsl->i18n->get_translation( 'start_label', __( 'Start location', 'wpsl' ) ),
1843
- 'back' => $wpsl->i18n->get_translation( 'back_label', __( 'Back', 'wpsl' ) ),
1844
- 'streetView' => $wpsl->i18n->get_translation( 'street_view_label', __( 'Street view', 'wpsl' ) ),
1845
- 'zoomHere' => $wpsl->i18n->get_translation( 'zoom_here_label', __( 'Zoom here', 'wpsl' ) )
1846
- );
1847
-
1848
- wp_localize_script( 'wpsl-js', 'wpslLabels', $labels );
1849
- wp_localize_script( 'wpsl-js', 'wpslGeolocationErrors', $this->geolocation_errors() );
1850
- } else {
1851
- $template = '';
1852
- $settings = $base_settings;
1853
- }
1854
-
1855
- // Check if we need to overwrite JS settings that are set through the [wpsl] shortcode.
1856
- if ( $this->sl_shortcode_atts && isset( $this->sl_shortcode_atts['js'] ) ) {
1857
- foreach ( $this->sl_shortcode_atts['js'] as $shortcode_key => $shortcode_val ) {
1858
- $settings[$shortcode_key] = $shortcode_val;
1859
- }
1860
- }
1861
-
1862
- wp_localize_script( 'wpsl-js', 'wpslSettings', apply_filters( 'wpsl_js_settings', $settings ) );
1863
-
1864
- wpsl_create_underscore_templates( $template );
1865
-
1866
- if ( !empty( $this->store_map_data ) ) {
1867
- $i = 0;
1868
-
1869
- foreach ( $this->store_map_data as $map ) {
1870
- wp_localize_script( 'wpsl-js', 'wpslMap_' . $i, $map );
1871
-
1872
- $i++;
1873
- }
1874
- }
1875
- }
1876
-
1877
- /**
1878
- * Get the infobox settings.
1879
- *
1880
- * @since 2.0.0
1881
- * @see http://google-maps-utility-library-v3.googlecode.com/svn/trunk/infobox/docs/reference.html
1882
- * @param array $settings The plugin settings used on the front-end in js
1883
- * @return array $settings The plugin settings including the infobox settings
1884
- */
1885
- public function get_infobox_settings( $settings ) {
1886
-
1887
- $infobox_settings = apply_filters( 'wpsl_infobox_settings', array(
1888
- 'infoBoxClass' => 'wpsl-infobox',
1889
- 'infoBoxCloseMargin' => '2px', // The margin can be written in css style, so 2px 2px 4px 2px for top, right, bottom, left
1890
- 'infoBoxCloseUrl' => '//www.google.com/intl/en_us/mapfiles/close.gif',
1891
- 'infoBoxClearance' => '40,40',
1892
- 'infoBoxDisableAutoPan' => 0,
1893
- 'infoBoxEnableEventPropagation' => 0,
1894
- 'infoBoxPixelOffset' => '-52,-45',
1895
- 'infoBoxZindex' => 1500
1896
- ) );
1897
-
1898
- foreach ( $infobox_settings as $infobox_key => $infobox_setting ) {
1899
- $settings[$infobox_key] = $infobox_setting;
1900
- }
1901
-
1902
- return $settings;
1903
- }
1904
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1905
  }
1
+ <?php
2
+ /**
3
+ * Frontend class
4
+ *
5
+ * @author Tijmen Smit
6
+ * @since 1.0.0
7
+ */
8
+
9
+ if ( !defined( 'ABSPATH' ) ) exit;
10
+
11
+ if ( !class_exists( 'WPSL_Frontend' ) ) {
12
+
13
+ /**
14
+ * Handle the frontend of the store locator
15
+ *
16
+ * @since 1.0.0
17
+ */
18
+ class WPSL_Frontend {
19
+
20
+ /**
21
+ * Keep track which scripts we need to load
22
+ *
23
+ * @since 2.0.0
24
+ */
25
+ private $load_scripts = array();
26
+
27
+ /**
28
+ * Keep track of the amount of maps on the page
29
+ *
30
+ * @since 2.0.0
31
+ */
32
+ private static $map_count = 0;
33
+
34
+ /*
35
+ * Holds the shortcode atts for the [wpsl] shortcode.
36
+ *
37
+ * Used to overwrite the settings just before
38
+ * they are send to wp_localize_script.
39
+ *
40
+ * @since 2.1.1
41
+ */
42
+ public $sl_shortcode_atts;
43
+
44
+ private $store_map_data = array();
45
+
46
+
47
+ /**
48
+ * Class constructor
49
+ */
50
+ public function __construct() {
51
+
52
+ $this->includes();
53
+
54
+ if ( function_exists( 'BorlabsCookieHelper' ) ) {
55
+ add_action( 'init', array( $this, 'borlabs_cookie' ) );
56
+ }
57
+
58
+ add_action( 'wp_ajax_store_search', array( $this, 'store_search' ) );
59
+ add_action( 'wp_ajax_nopriv_store_search', array( $this, 'store_search' ) );
60
+ add_action( 'wp_enqueue_scripts', array( $this, 'add_frontend_styles' ) );
61
+ add_action( 'wp_footer', array( $this, 'add_frontend_scripts' ) );
62
+
63
+ add_filter( 'the_content', array( $this, 'cpt_template' ) );
64
+
65
+ add_shortcode( 'wpsl', array( $this, 'show_store_locator' ) );
66
+ add_shortcode( 'wpsl_address', array( $this, 'show_store_address' ) );
67
+ add_shortcode( 'wpsl_hours', array( $this, 'show_opening_hours' ) );
68
+ add_shortcode( 'wpsl_map', array( $this, 'show_store_map' ) );
69
+ }
70
+
71
+ /**
72
+ * Include the required front-end files.
73
+ *
74
+ * @since 2.0.0
75
+ * @return void
76
+ */
77
+ public function includes() {
78
+ require_once( WPSL_PLUGIN_DIR . 'frontend/underscore-functions.php' );
79
+ }
80
+
81
+
82
+ /**
83
+ * Include the required file for the borlabs cookie plugin to work.
84
+ *
85
+ * @since 2.2.22
86
+ * @return void
87
+ */
88
+ public function borlabs_cookie() {
89
+ require_once( WPSL_PLUGIN_DIR . 'inc/class-borlabs-cookie.php' );
90
+ }
91
+
92
+ /**
93
+ * Handle the Ajax search on the frontend.
94
+ *
95
+ * @since 1.0.0
96
+ * @return json A list of store locations that are located within the selected search radius
97
+ */
98
+ public function store_search() {
99
+
100
+ global $wpsl_settings;
101
+
102
+ /*
103
+ * Check if auto loading the locations on page load is enabled.
104
+ *
105
+ * If so then we save the store data in a transient to prevent a long loading time
106
+ * in case a large amount of locations need to be displayed.
107
+ *
108
+ * The SQL query that selects nearby locations doesn't take that long,
109
+ * but collecting all the store meta data in get_store_meta_data() for hunderds,
110
+ * or thousands of stores can make it really slow.
111
+ */
112
+ if ( $wpsl_settings['autoload'] && isset( $_GET['autoload'] ) && $_GET['autoload'] && !$wpsl_settings['debug'] && !isset( $_GET['skip_cache'] ) ) {
113
+ $transient_name = $this->create_transient_name();
114
+
115
+ if ( false === ( $store_data = get_transient( 'wpsl_autoload_' . $transient_name ) ) ) {
116
+ $store_data = $this->find_nearby_locations();
117
+
118
+ if ( $store_data ) {
119
+ set_transient( 'wpsl_autoload_' . $transient_name, $store_data, 0 );
120
+ }
121
+ }
122
+ } else {
123
+ $store_data = $this->find_nearby_locations();
124
+ }
125
+
126
+ do_action( 'wpsl_store_search' );
127
+
128
+ wp_send_json( $store_data );
129
+
130
+ exit();
131
+ }
132
+
133
+ /**
134
+ * Create the name used in the wpsl autoload transient.
135
+ *
136
+ * @since 2.1.1
137
+ * @return string $transient_name The transient name.
138
+ */
139
+ public function create_transient_name() {
140
+
141
+ global $wpsl, $wpsl_settings;
142
+
143
+ $name_section = array();
144
+
145
+ // Include the set autoload limit.
146
+ if ( $wpsl_settings['autoload'] && $wpsl_settings['autoload_limit'] ) {
147
+ $name_section[] = absint( $wpsl_settings['autoload_limit'] );
148
+ }
149
+
150
+ /*
151
+ * Check if we need to include the cat id(s) in the transient name.
152
+ *
153
+ * This can only happen if the user used the
154
+ * 'category' attr on the wpsl shortcode.
155
+ */
156
+ if ( isset( $_GET['filter'] ) && $_GET['filter'] ) {
157
+ $name_section[] = absint( str_replace( ',', '', $_GET['filter'] ) );
158
+ }
159
+
160
+ // Include the lat value from the start location.
161
+ if ( isset( $_GET['lat'] ) && $_GET['lat'] ) {
162
+ $name_section[] = absint( str_replace( '.', '', $_GET['lat'] ) );
163
+ }
164
+
165
+ /*
166
+ * If a multilingual plugin ( WPML or qTranslate X ) is active then we have
167
+ * to make sure each language has his own unique transient. We do this by
168
+ * including the lang code in the transient name.
169
+ *
170
+ * Otherwise if the language is for example set to German on page load,
171
+ * and the user switches to Spanish, then he would get the incorrect
172
+ * permalink structure ( /de/.. instead or /es/.. ) and translated
173
+ * store details.
174
+ */
175
+ $lang_code = $wpsl->i18n->check_multilingual_code();
176
+
177
+ if ( $lang_code ) {
178
+ $name_section[] = $lang_code;
179
+ }
180
+
181
+ $transient_name = implode( '_', $name_section );
182
+
183
+ /*
184
+ * If the distance unit filter ( wpsl_distance_unit ) is used to change the km / mi unit based on
185
+ * the location of the IP, then we include the km / mi in the transient name. This is done to
186
+ * prevent users from seeing the wrong distances from the cached data.
187
+ *
188
+ * This way one data set can include the distance in km, and the other one the distance in miles.
189
+ */
190
+ if ( has_filter( 'wpsl_distance_unit' ) ) {
191
+ $transient_name = $transient_name . '_' . wpsl_get_distance_unit();
192
+ }
193
+
194
+ return $transient_name;
195
+ }
196
+
197
+ /**
198
+ * Find store locations that are located within the selected search radius.
199
+ *
200
+ * This happens by calculating the distance between the
201
+ * latlng of the searched location, and the latlng from
202
+ * the stores in the db.
203
+ *
204
+ * @since 2.0.0
205
+ * @param array $args The arguments to use in the SQL query, only used by add-ons
206
+ * @return void|array $store_data The list of stores that fall within the selected range.
207
+ */
208
+ public function find_nearby_locations( $args = array() ) {
209
+
210
+ global $wpdb, $wpsl, $wpsl_settings;
211
+
212
+ $store_data = array();
213
+
214
+ /*
215
+ * Set the correct earth radius in either km or miles.
216
+ * We need this to calculate the distance between two coordinates.
217
+ */
218
+ $placeholder_values[] = ( wpsl_get_distance_unit() == 'km' ) ? 6371 : 3959;
219
+
220
+ // The placeholder values for the prepared statement in the SQL query.
221
+ if ( empty( $args ) ) {
222
+ $args = $_GET;
223
+ }
224
+
225
+ array_push( $placeholder_values, $args['lat'], $args['lng'], $args['lat'] );
226
+
227
+ // Check if we need to filter the results by category.
228
+ if ( isset( $args['filter'] ) && $args['filter'] ) {
229
+ $filter_ids = array_map( 'absint', explode( ',', $args['filter'] ) );
230
+ $cat_filter = "INNER JOIN $wpdb->term_relationships AS term_rel ON posts.ID = term_rel.object_id
231
+ INNER JOIN $wpdb->term_taxonomy AS term_tax ON term_rel.term_taxonomy_id = term_tax.term_taxonomy_id
232
+ AND term_tax.taxonomy = 'wpsl_store_category'
233
+ AND term_tax.term_id IN (" . implode( ',', $filter_ids ) . ")";
234
+ } else {
235
+ $cat_filter = '';
236
+ }
237
+
238
+ /*
239
+ * If WPML is active we include 'GROUP BY lat' in the sql query
240
+ * to prevent duplicate locations from showing up in the results.
241
+ *
242
+ * This is a problem when a store location for example
243
+ * exists in 4 different languages. They would all fall within
244
+ * the selected radius, but we only need one store ID for the 'icl_object_id'
245
+ * function to get the correct store ID for the current language.
246
+ */
247
+ if ( $wpsl->i18n->wpml_exists() ) {
248
+ $group_by = 'GROUP BY lat';
249
+ } else {
250
+ $group_by = 'GROUP BY posts.ID';
251
+ }
252
+
253
+ /*
254
+ * If autoload is enabled we need to check if there is a limit to the
255
+ * amount of locations we need to show.
256
+ *
257
+ * Otherwise include the radius and max results limit in the sql query.
258
+ */
259
+ if ( isset( $args['autoload'] ) && $args['autoload'] ) {
260
+ $limit = '';
261
+
262
+ if ( $wpsl_settings['autoload_limit'] ) {
263
+ $limit = 'LIMIT %d';
264
+ $placeholder_values[] = $wpsl_settings['autoload_limit'];
265
+ }
266
+
267
+ $sql_sort = 'ORDER BY distance '. $limit;
268
+ } else {
269
+ array_push( $placeholder_values, $this->check_store_filter( $args, 'search_radius' ), $this->check_store_filter( $args, 'max_results' ) );
270
+ $sql_sort = 'HAVING distance < %d ORDER BY distance LIMIT 0, %d';
271
+ }
272
+
273
+ $placeholder_values = apply_filters( 'wpsl_sql_placeholder_values', $placeholder_values );
274
+
275
+ /*
276
+ * The sql that will check which store locations fall within
277
+ * the selected radius based on the lat and lng values.
278
+ */
279
+ $sql = apply_filters( 'wpsl_sql',
280
+ "SELECT post_lat.meta_value AS lat,
281
+ post_lng.meta_value AS lng,
282
+ posts.ID,
283
+ ( %d * acos( cos( radians( %s ) ) * cos( radians( post_lat.meta_value ) ) * cos( radians( post_lng.meta_value ) - radians( %s ) ) + sin( radians( %s ) ) * sin( radians( post_lat.meta_value ) ) ) )
284
+ AS distance
285
+ FROM $wpdb->posts AS posts
286
+ INNER JOIN $wpdb->postmeta AS post_lat ON post_lat.post_id = posts.ID AND post_lat.meta_key = 'wpsl_lat'
287
+ INNER JOIN $wpdb->postmeta AS post_lng ON post_lng.post_id = posts.ID AND post_lng.meta_key = 'wpsl_lng'
288
+ $cat_filter
289
+ WHERE posts.post_type = 'wpsl_stores'
290
+ AND posts.post_status = 'publish' $group_by $sql_sort"
291
+ );
292
+
293
+ $stores = $wpdb->get_results( $wpdb->prepare( $sql, $placeholder_values ) );
294
+
295
+ if ( $stores ) {
296
+ $store_data = apply_filters( 'wpsl_store_data', $this->get_store_meta_data( $stores ) );
297
+ } else {
298
+ $store_data = apply_filters( 'wpsl_no_results_sql', '' );
299
+ }
300
+
301
+ return $store_data;
302
+ }
303
+
304
+ /**
305
+ * Get the post meta data for the selected stores.
306
+ *
307
+ * @since 2.0.0
308
+ * @param object $stores
309
+ * @return array $all_stores The stores that fall within the selected range with the post meta data.
310
+ */
311
+ public function get_store_meta_data( $stores ) {
312
+
313
+ global $wpsl_settings, $wpsl;
314
+
315
+ $all_stores = array();
316
+
317
+ // Get the list of store fields that we need to filter out of the post meta data.
318
+ $meta_field_map = $this->frontend_meta_fields();
319
+
320
+ foreach ( $stores as $store_key => $store ) {
321
+
322
+ // If WPML is active try to get the id of the translated page.
323
+ if ( $wpsl->i18n->wpml_exists() ) {
324
+ $store->ID = $wpsl->i18n->maybe_get_wpml_id( $store->ID );
325
+
326
+ if ( !$store->ID ) {
327
+ continue;
328
+ }
329
+ }
330
+
331
+ // Get the post meta data for each store that was within the range of the search radius.
332
+ $custom_fields = get_post_custom( $store->ID );
333
+
334
+ foreach ( $meta_field_map as $meta_key => $meta_value ) {
335
+
336
+ if ( isset( $custom_fields[$meta_key][0] ) ) {
337
+ if ( ( isset( $meta_value['type'] ) ) && ( !empty( $meta_value['type'] ) ) ) {
338
+ $meta_type = $meta_value['type'];
339
+ } else {
340
+ $meta_type = '';
341
+ }
342
+
343
+ // If we need to hide the opening hours, and the current meta type is set to hours we skip it.
344
+ if ( $wpsl_settings['hide_hours'] && $meta_type == 'hours' ) {
345
+ continue;
346
+ }
347
+
348
+ // Make sure the data is safe to use on the frontend and in the format we expect it to be.
349
+ switch ( $meta_type ) {
350
+ case 'numeric':
351
+ $meta_data = ( is_numeric( $custom_fields[$meta_key][0] ) ) ? $custom_fields[$meta_key][0] : 0 ;
352
+ break;
353
+ case 'email':
354
+ $meta_data = sanitize_email( $custom_fields[$meta_key][0] );
355
+ break;
356
+ case 'url':
357
+ $meta_data = esc_url( $custom_fields[$meta_key][0] );
358
+ break;
359
+ case 'hours':
360
+ $meta_data = $this->get_opening_hours( $custom_fields[$meta_key][0], apply_filters( 'wpsl_hide_closed_hours', false ) );
361
+ break;
362
+ case 'wp_editor':
363
+ case 'textarea':
364
+ $meta_data = wp_kses_post( wpautop( $custom_fields[$meta_key][0] ) );
365
+ break;
366
+ case 'text':
367
+ default:
368
+ $meta_data = sanitize_text_field( stripslashes( $custom_fields[$meta_key][0] ) );
369
+ break;
370
+ }
371
+
372
+ $store_meta[$meta_value['name']] = $meta_data;
373
+ } else {
374
+ $store_meta[$meta_value['name']] = '';
375
+ }
376
+
377
+ /*
378
+ * Include the post content if the "More info" option is enabled on the settings page,
379
+ * or if $include_post_content is set to true through the 'wpsl_include_post_content' filter.
380
+ */
381
+ if ( ( $wpsl_settings['more_info'] && $wpsl_settings['more_info_location'] == 'store listings' ) || apply_filters( 'wpsl_include_post_content', false ) ) {
382
+ $page_object = get_post( $store->ID );
383
+
384
+ // Check if we need to strip the shortcode from the post content.
385
+ if ( apply_filters( 'wpsl_strip_content_shortcode', true ) ) {
386
+ $post_content = strip_shortcodes( $page_object->post_content );
387
+ } else {
388
+ $post_content = $page_object->post_content;
389
+ }
390
+
391
+ $store_meta['description'] = apply_filters( 'the_content', $post_content );
392
+ }
393
+
394
+ $store_meta['store'] = get_the_title( $store->ID );
395
+ $store_meta['thumb'] = $this->get_store_thumb( $store->ID, $store_meta['store'] );
396
+ $store_meta['id'] = $store->ID;
397
+
398
+ if ( !$wpsl_settings['hide_distance'] ) {
399
+ $store_meta['distance'] = round( $store->distance, 1 );
400
+ }
401
+
402
+ if ( $wpsl_settings['permalinks'] ) {
403
+ $store_meta['permalink'] = get_permalink( $store->ID );
404
+ }
405
+ }
406
+
407
+ $all_stores[] = apply_filters( 'wpsl_store_meta', $store_meta, $store->ID );
408
+ }
409
+
410
+ return $all_stores;
411
+ }
412
+
413
+ /**
414
+ * The store meta fields that are included in the json output.
415
+ *
416
+ * The wpsl_ is the name in db, the name value is used as the key in the json output.
417
+ *
418
+ * The type itself is used to determine how the value should be sanitized.
419
+ * Text will go through sanitize_text_field, email through sanitize_email and so on.
420
+ *
421
+ * If no type is set it will default to sanitize_text_field.
422
+ *
423
+ * @since 2.0.0
424
+ * @return array $store_fields The names of the meta fields used by the store
425
+ */
426
+ public function frontend_meta_fields() {
427
+
428
+ $store_fields = array(
429
+ 'wpsl_address' => array(
430
+ 'name' => 'address'
431
+ ),
432
+ 'wpsl_address2' => array(
433
+ 'name' => 'address2'
434
+ ),
435
+ 'wpsl_city' => array(
436
+ 'name' => 'city'
437
+ ),
438
+ 'wpsl_state' => array(
439
+ 'name' => 'state'
440
+ ),
441
+ 'wpsl_zip' => array(
442
+ 'name' => 'zip'
443
+ ),
444
+ 'wpsl_country' => array(
445
+ 'name' => 'country'
446
+ ),
447
+ 'wpsl_lat' => array(
448
+ 'name' => 'lat',
449
+ 'type' => 'numeric'
450
+ ),
451
+ 'wpsl_lng' => array(
452
+ 'name' => 'lng',
453
+ 'type' => 'numeric'
454
+ ),
455
+ 'wpsl_phone' => array(
456
+ 'name' => 'phone'
457
+ ),
458
+ 'wpsl_fax' => array(
459
+ 'name' => 'fax'
460
+ ),
461
+ 'wpsl_email' => array(
462
+ 'name' => 'email',
463
+ 'type' => 'email'
464
+ ),
465
+ 'wpsl_hours' => array(
466
+ 'name' => 'hours',
467
+ 'type' => 'hours'
468
+ ),
469
+ 'wpsl_url' => array(
470
+ 'name' => 'url',
471
+ 'type' => 'url'
472
+ )
473
+ );
474
+
475
+ return apply_filters( 'wpsl_frontend_meta_fields', $store_fields );
476
+ }
477
+
478
+ /**
479
+ * Get the store thumbnail.
480
+ *
481
+ * @since 2.0.0
482
+ * @param string $post_id The post id of the store
483
+ * @param string $store_name The name of the store
484
+ * @return void|string $thumb The html img tag
485
+ */
486
+ public function get_store_thumb( $post_id, $store_name ) {
487
+
488
+ $attr = array(
489
+ 'class' => 'wpsl-store-thumb',
490
+ 'alt' => $store_name
491
+ );
492
+
493
+ $thumb = get_the_post_thumbnail( $post_id, $this->get_store_thumb_size(), apply_filters( 'wpsl_thumb_attr', $attr ) );
494
+
495
+ return $thumb;
496
+ }
497
+
498
+ /**
499
+ * Get the store thumbnail size.
500
+ *
501
+ * @since 2.0.0
502
+ * @return array $size The thumb format
503
+ */
504
+ public function get_store_thumb_size() {
505
+
506
+ $size = apply_filters( 'wpsl_thumb_size', array( 45, 45 ) );
507
+
508
+ return $size;
509
+ }
510
+
511
+ /**
512
+ * Get the opening hours in the correct format.
513
+ *
514
+ * Either convert the hour values that are set through
515
+ * a dropdown to a table, or wrap the textarea input in a <p>.
516
+ *
517
+ * Note: The opening hours can only be set in the textarea format by users who upgraded from 1.x.
518
+ *
519
+ * @since 2.0.0
520
+ * @param array|string $hours The opening hours
521
+ * @param boolean $hide_closed Hide the days were the location is closed
522
+ * @return string $hours The formated opening hours
523
+ */
524
+ public function get_opening_hours( $hours, $hide_closed ) {
525
+
526
+ $hours = maybe_unserialize( $hours );
527
+
528
+ /*
529
+ * If the hours are set through the dropdown then we create a table for the opening hours.
530
+ * Otherwise we output the data entered in the textarea.
531
+ */
532
+ if ( is_array( $hours ) ) {
533
+ $hours = $this->create_opening_hours_tabel( $hours, $hide_closed );
534
+ } else {
535
+ $hours = wp_kses_post( wpautop( $hours ) );
536
+ }
537
+
538
+ return $hours;
539
+ }
540
+
541
+ /**
542
+ * Create a table for the opening hours.
543
+ *
544
+ * @since 2.0.0
545
+ * @todo add schema.org support.
546
+ * @param array $hours The opening hours
547
+ * @param boolean $hide_closed Hide the days where the location is closed
548
+ * @return string $hour_table The opening hours sorted in a table
549
+ */
550
+ public function create_opening_hours_tabel( $hours, $hide_closed ) {
551
+
552
+ $opening_days = wpsl_get_weekdays();
553
+
554
+ // Make sure that we have actual opening hours, and not every day is empty.
555
+ if ( $this->not_always_closed( $hours ) ) {
556
+ $hour_table = '<table role="presentation" class="wpsl-opening-hours">';
557
+
558
+ foreach ( $opening_days as $index => $day ) {
559
+ $i = 0;
560
+ $hour_count = count( $hours[$index] );
561
+
562
+ // If we need to hide days that are set to closed then skip them.
563
+ if ( $hide_closed && !$hour_count ) {
564
+ continue;
565
+ }
566
+
567
+ $hour_table .= '<tr>';
568
+ $hour_table .= '<td>' . esc_html( $day ) . '</td>';
569
+
570
+ // If we have opening hours we show them, otherwise just show 'Closed'.
571
+ if ( $hour_count > 0 ) {
572
+ $hour_table .= '<td>';
573
+
574
+ while ( $i < $hour_count ) {
575
+ $hour = explode( ',', $hours[$index][$i] );
576
+ $hour_table .= '<time>' . esc_html( $hour[0] ) . ' - ' . esc_html( $hour[1] ) . '</time>';
577
+
578
+ $i++;
579
+ }
580
+
581
+ $hour_table .= '</td>';
582
+ } else {
583
+ $hour_table .= '<td>' . __( 'Closed', 'wpsl' ) . '</td>';
584
+ }
585
+
586
+ $hour_table .= '</tr>';
587
+ }
588
+
589
+ $hour_table .= '</table>';
590
+
591
+ return $hour_table;
592
+ }
593
+ }
594
+
595
+ /**
596
+ * Create the wpsl post type output.
597
+ *
598
+ * If you want to create a custom template you need to
599
+ * create a single-wpsl_stores.php file in your theme folder.
600
+ * You can see an example here https://wpstorelocator.co/document/create-custom-store-page-template/
601
+ *
602
+ * @since 2.0.0
603
+ * @param string $content
604
+ * @return string $content
605
+ */
606
+ public function cpt_template( $content ) {
607
+
608
+ global $wpsl_settings, $post;
609
+
610
+ // Prevent duplicate output when the Twenty Nineteen theme is active.
611
+ $skip_status = ( get_option( 'template' ) === 'twentynineteen' ) ? true : false;
612
+ $skip_cpt_template = apply_filters( 'wpsl_skip_cpt_template', $skip_status );
613
+
614
+ if ( isset( $post->post_type ) && $post->post_type == 'wpsl_stores' && is_single() && in_the_loop() && !$skip_cpt_template ) {
615
+ array_push( $this->load_scripts, 'wpsl_base' );
616
+
617
+ $content .= '[wpsl_map]';
618
+ $content .= '[wpsl_address]';
619
+
620
+ if ( !$wpsl_settings['hide_hours'] ) {
621
+ $content .= '[wpsl_hours]';
622
+ }
623
+ }
624
+
625
+ return $content;
626
+ }
627
+
628
+ /**
629
+ * Handle the [wpsl] shortcode attributes.
630
+ *
631
+ * @since 2.1.1
632
+ * @param array $atts Shortcode attributes
633
+ */
634
+ public function check_sl_shortcode_atts( $atts ) {
635
+
636
+ /*
637
+ * Use a custom start location?
638
+ *
639
+ * If the provided location fails to geocode,
640
+ * then the start location from the settings page is used.
641
+ */
642
+ if ( isset( $atts['start_location'] ) && $atts['start_location'] ) {
643
+ $start_latlng = wpsl_check_latlng_transient( $atts['start_location'] );
644
+
645
+ if ( isset( $start_latlng ) && $start_latlng ) {
646
+ $this->sl_shortcode_atts['js']['startLatlng'] = $start_latlng;
647
+ }
648
+ }
649
+
650
+ if ( isset( $atts['auto_locate'] ) && $atts['auto_locate'] ) {
651
+ $this->sl_shortcode_atts['js']['autoLocate'] = ( $atts['auto_locate'] == 'true' ) ? 1 : 0;
652
+ }
653
+
654
+ // Change the category slugs into category ids.
655
+ if ( isset( $atts['category'] ) && $atts['category'] ) {
656
+ $term_ids = wpsl_get_term_ids( $atts['category'] );
657
+
658
+ if ( $term_ids ) {
659
+ $this->sl_shortcode_atts['js']['categoryIds'] = implode( ',', $term_ids );
660
+ }
661
+ }
662
+
663
+ if ( isset( $atts['category_selection'] ) && $atts['category_selection'] ) {
664
+ $this->sl_shortcode_atts['category_selection'] = wpsl_get_term_ids( $atts['category_selection'] );
665
+ }
666
+
667
+ if ( isset( $atts['category_filter_type'] ) && in_array( $atts['category_filter_type'], array( 'dropdown', 'checkboxes' ) ) ) {
668
+ $this->sl_shortcode_atts['category_filter_type'] = $atts['category_filter_type'];
669
+ }
670
+
671
+ if ( isset( $atts['checkbox_columns'] ) && is_numeric( $atts['checkbox_columns'] ) ) {
672
+ $this->sl_shortcode_atts['checkbox_columns'] = $atts['checkbox_columns'];
673
+ }
674
+
675
+ if ( isset( $atts['map_type'] ) && array_key_exists( $atts['map_type'], wpsl_get_map_types() ) ) {
676
+ $this->sl_shortcode_atts['js']['mapType'] = $atts['map_type'];
677
+ }
678
+
679
+ if ( isset( $atts['start_marker'] ) && $atts['start_marker'] ) {
680
+ $this->sl_shortcode_atts['js']['startMarker'] = $atts['start_marker'] . '@2x.png';
681
+ }
682
+
683
+ if ( isset( $atts['store_marker'] ) && $atts['store_marker'] ) {
684
+ $this->sl_shortcode_atts['js']['storeMarker'] = $atts['store_marker'] . '@2x.png';
685
+ }
686
+ }
687
+
688
+ /**
689
+ * Handle the [wpsl] shortcode.
690
+ *
691
+ * @since 1.0.0
692
+ * @param array $atts Shortcode attributes
693
+ * @return string $output The wpsl template
694
+ */
695
+ public function show_store_locator( $atts ) {
696
+
697
+ global $wpsl, $wpsl_settings;
698
+
699
+ $atts = shortcode_atts( array(
700
+ 'template' => $wpsl_settings['template_id'],
701
+ 'start_location' => '',
702
+ 'auto_locate' => '',
703
+ 'category' => '',
704
+ 'category_selection' => '',
705
+ 'category_filter_type' => '',
706
+ 'checkbox_columns' => '3',
707
+ 'map_type' => '',
708
+ 'start_marker' => '',
709
+ 'store_marker' => ''
710
+ ), $atts );
711
+
712
+ $this->check_sl_shortcode_atts( $atts );
713
+
714
+ // Make sure the required scripts are included for the wpsl shortcode.
715
+ array_push( $this->load_scripts, 'wpsl_store_locator' );
716
+
717
+ $template_details = $wpsl->templates->get_template_details( $atts['template'] );
718
+
719
+ $output = include( $template_details['path'] );
720
+
721
+ return $output;
722
+ }
723
+
724
+ /**
725
+ * Handle the [wpsl_address] shortcode.
726
+ *
727
+ * @since 2.0.0
728
+ * @todo add schema.org support.
729
+ * @param array $atts Shortcode attributes
730
+ * @return void|string $output The store address
731
+ */
732
+ public function show_store_address( $atts ) {
733
+
734
+ global $post, $wpsl_settings, $wpsl;
735
+
736
+ $atts = wpsl_bool_check( shortcode_atts( apply_filters( 'wpsl_address_shortcode_defaults', array(
737
+ 'id' => '',
738
+ 'name' => true,
739
+ 'address' => true,
740
+ 'address2' => true,
741
+ 'city' => true,
742
+ 'state' => true,
743
+ 'zip' => true,
744
+ 'country' => true,
745
+ 'phone' => true,
746
+ 'fax' => true,
747
+ 'email' => true,
748
+ 'url' => true,
749
+ 'directions' => false,
750
+ 'clickable_contact_details' => (bool) $wpsl_settings['clickable_contact_details']
751
+ ) ), $atts ) );
752
+
753
+ if ( get_post_type() == 'wpsl_stores' ) {
754
+ if ( empty( $atts['id'] ) ) {
755
+ if ( isset( $post->ID ) ) {
756
+ $atts['id'] = $post->ID;
757
+ } else {
758
+ return;
759
+ }
760
+ }
761
+ } else if ( empty( $atts['id'] ) ) {
762
+ return __( 'If you use the [wpsl_address] shortcode outside a store page you need to set the ID attribute.', 'wpsl' );
763
+ }
764
+
765
+ $content = '<div class="wpsl-locations-details">';
766
+
767
+ if ( $atts['name'] && $name = get_the_title( $atts['id'] ) ) {
768
+ $content .= '<span><strong>' . esc_html( $name ) . '</strong></span>';
769
+ }
770
+
771
+ $content .= '<div class="wpsl-location-address">';
772
+
773
+ if ( $atts['address'] && $address = get_post_meta( $atts['id'], 'wpsl_address', true ) ) {
774
+ $content .= '<span>' . esc_html( $address ) . '</span><br/>';
775
+ }
776
+
777
+ if ( $atts['address2'] && $address2 = get_post_meta( $atts['id'], 'wpsl_address2', true ) ) {
778
+ $content .= '<span>' . esc_html( $address2 ) . '</span><br/>';
779
+ }
780
+
781
+ $address_format = explode( '_', $wpsl_settings['address_format'] );
782
+ $count = count( $address_format );
783
+ $i = 1;
784
+
785
+ // Loop over the address parts to make sure they are shown in the right order.
786
+ foreach ( $address_format as $address_part ) {
787
+
788
+ // Make sure the shortcode attribute is set to true for the $address_part, and it's not the 'comma' part.
789
+ if ( $address_part != 'comma' && $atts[$address_part] ) {
790
+ $post_meta = get_post_meta( $atts['id'], 'wpsl_' . $address_part, true );
791
+
792
+ if ( $post_meta ) {
793
+
794
+ /*
795
+ * Check if the next part of the address is set to 'comma'.
796
+ * If so add the, after the current address part, otherwise just show a space
797
+ */
798
+ if ( isset( $address_format[$i] ) && ( $address_format[$i] == 'comma' ) ) {
799
+ $punctuation = ', ';
800
+ } else {
801
+ $punctuation = ' ';
802
+ }
803
+
804
+ // If we have reached the last item add a <br /> behind it.
805
+ $br = ( $count == $i ) ? '<br />' : '';
806
+
807
+ $content .= '<span>' . esc_html( $post_meta ) . $punctuation . '</span>' . $br;
808
+ }
809
+ }
810
+
811
+ $i++;
812
+ }
813
+
814
+ if ( $atts['country'] && $country = get_post_meta( $atts['id'], 'wpsl_country', true ) ) {
815
+ $content .= '<span>' . esc_html( $country ) . '</span>';
816
+ }
817
+
818
+ $content .= '</div>';
819
+
820
+ // If either the phone, fax, email or url is set to true, then add the wrap div for the contact details.
821
+ if ( $atts['phone'] || $atts['fax'] || $atts['email'] || $atts['url'] ) {
822
+ $phone = get_post_meta( $atts['id'], 'wpsl_phone', true );
823
+ $fax = get_post_meta( $atts['id'], 'wpsl_fax', true );
824
+ $email = get_post_meta( $atts['id'], 'wpsl_email', true );
825
+
826
+ if ( $atts['clickable_contact_details'] ) {
827
+ $contact_details = array(
828
+ 'phone' => '<a href="tel:' . esc_attr( $phone ) . '">' . esc_html( $phone ) . '</a>',
829
+ 'fax' => '<a href="tel:' . esc_attr( $fax ) . '">' . esc_html( $fax ) . '</a>',
830
+ 'email' => '<a href="mailto:' . sanitize_email( $email ) . '">' . sanitize_email( $email ) . '</a>'
831
+ );
832
+ } else {
833
+ $contact_details = array(
834
+ 'phone' => esc_html( $phone ),
835
+ 'fax' => esc_html( $fax ),
836
+ 'email' => sanitize_email( $email )
837
+ );
838
+ }
839
+
840
+ $content .= '<div class="wpsl-contact-details">';
841
+
842
+ if ( $atts['phone'] && $phone ) {
843
+ $content .= esc_html( $wpsl->i18n->get_translation( 'phone_label', __( 'Phone', 'wpsl' ) ) ) . ': <span>' . $contact_details['phone'] . '</span><br/>';
844
+ }
845
+
846
+ if ( $atts['fax'] && $fax ) {
847
+ $content .= esc_html( $wpsl->i18n->get_translation( 'fax_label', __( 'Fax', 'wpsl' ) ) ) . ': <span>' . $contact_details['fax'] . '</span><br/>';
848
+ }
849
+
850
+ if ( $atts['email'] && $email ) {
851
+ $content .= esc_html( $wpsl->i18n->get_translation( 'email_label', __( 'Email', 'wpsl' ) ) ) . ': <span>' . $contact_details['email'] . '</span><br/>';
852
+ }
853
+
854
+ if ( $atts['url'] && $store_url = get_post_meta( $atts['id'], 'wpsl_url', true ) ) {
855
+ $new_window = ( $wpsl_settings['new_window'] ) ? 'target="_blank"' : '' ;
856
+ $content .= esc_html( $wpsl->i18n->get_translation( 'url_label', __( 'Url', 'wpsl' ) ) ) . ': <a ' . $new_window . ' href="' . esc_url( $store_url ) . '">' . esc_url( $store_url ) . '</a><br/>';
857
+ }
858
+
859
+ $content .= '</div>';
860
+ }
861
+
862
+ if ( $atts['directions'] && $address ) {
863
+ if ( $wpsl_settings['new_window'] ) {
864
+ $new_window = ' target="_blank"';
865
+ } else {
866
+ $new_window = '';
867
+ }
868
+
869
+ $content .= '<div class="wpsl-location-directions">';
870
+
871
+ $city = get_post_meta( $atts['id'], 'wpsl_city', true );
872
+ $country = get_post_meta( $atts['id'], 'wpsl_country', true );
873
+ $destination = $address . ',' . $city . ',' . $country;
874
+ $direction_url = "https://maps.google.com/maps?saddr=&daddr=" . urlencode( $destination ) . "&travelmode=" . strtolower( $this->get_directions_travel_mode() );
875
+
876
+ $content .= '<p><a ' . $new_window . ' href="' . esc_url( $direction_url ) . '">' . __( 'Directions', 'wpsl' ) . '</a></p>';
877
+ $content .= '</div>';
878
+ }
879
+
880
+ $content .= '</div>';
881
+
882
+ return $content;
883
+ }
884
+
885
+ /**
886
+ * Handle the [wpsl_hours] shortcode.
887
+ *
888
+ * @since 2.0.0
889
+ * @param array $atts Shortcode attributes
890
+ * @return void|string $output The opening hours
891
+ */
892
+ public function show_opening_hours( $atts ) {
893
+
894
+ global $wpsl_settings, $post;
895
+
896
+ // If the hours are set to hidden on the settings page, then respect that and don't continue.
897
+ if ( $wpsl_settings['hide_hours'] ) {
898
+ return;
899
+ }
900
+
901
+ $hide_closed = apply_filters( 'wpsl_hide_closed_hours', false );
902
+
903
+ $atts = wpsl_bool_check( shortcode_atts( apply_filters( 'wpsl_hour_shortcode_defaults', array(
904
+ 'id' => '',
905
+ 'hide_closed' => $hide_closed
906
+ ) ), $atts ) );
907
+
908
+ if ( get_post_type() == 'wpsl_stores' ) {
909
+ if ( empty( $atts['id'] ) ) {
910
+ if ( isset( $post->ID ) ) {
911
+ $atts['id'] = $post->ID;
912
+ } else {
913
+ return;
914
+ }
915
+ }
916
+ } else if ( empty( $atts['id'] ) ) {
917
+ return __( 'If you use the [wpsl_hours] shortcode outside a store page you need to set the ID attribute.', 'wpsl' );
918
+ }
919
+
920
+ $opening_hours = get_post_meta( $atts['id'], 'wpsl_hours' );
921
+
922
+ if ( $opening_hours ) {
923
+ $output = $this->get_opening_hours( $opening_hours[0], $atts['hide_closed'] );
924
+
925
+ return $output;
926
+ }
927
+ }
928
+
929
+ /**
930
+ * Handle the [wpsl_map] shortcode.
931
+ *
932
+ * @since 2.0.0
933
+ * @param array $atts Shortcode attributes
934
+ * @return string $output The html for the map
935
+ */
936
+ public function show_store_map( $atts ) {
937
+
938
+ global $wpsl_settings, $post;
939
+
940
+ $atts = shortcode_atts( apply_filters( 'wpsl_map_shortcode_defaults', array(
941
+ 'id' => '',
942
+ 'category' => '',
943
+ 'width' => '',
944
+ 'height' => $wpsl_settings['height'],
945
+ 'zoom' => $wpsl_settings['zoom_level'],
946
+ 'map_type' => $wpsl_settings['map_type'],
947
+ 'map_type_control' => $wpsl_settings['type_control'],
948
+ 'map_style' => '',
949
+ 'street_view' => $wpsl_settings['streetview'],
950
+ 'scrollwheel' => $wpsl_settings['scrollwheel'],
951
+ 'control_position' => $wpsl_settings['control_position']
952
+ ) ), $atts );
953
+
954
+ array_push( $this->load_scripts, 'wpsl_base' );
955
+
956
+ if ( get_post_type() == 'wpsl_stores' ) {
957
+ if ( empty( $atts['id'] ) ) {
958
+ if ( isset( $post->ID ) ) {
959
+ $atts['id'] = $post->ID;
960
+ } else {
961
+ return;
962
+ }
963
+ }
964
+ } else if ( empty( $atts['id'] ) && empty( $atts['category'] ) ) {
965
+ return __( 'If you use the [wpsl_map] shortcode outside a store page, then you need to set the ID or category attribute.', 'wpsl' );
966
+ }
967
+
968
+ if ( $atts['category'] ) {
969
+ $store_ids = get_posts( array(
970
+ 'numberposts' => -1,
971
+ 'post_type' => 'wpsl_stores',
972
+ 'post_status' => 'publish',
973
+ 'tax_query' => array(
974
+ array(
975
+ 'taxonomy' => 'wpsl_store_category',
976
+ 'field' => 'slug',
977
+ 'terms' => explode( ',', sanitize_text_field( $atts['category'] ) )
978
+ ),
979
+ ),
980
+ 'fields' => 'ids'
981
+ ) );
982
+ } else {
983
+ $store_ids = array_map( 'absint', explode( ',', $atts['id'] ) );
984
+ $id_count = count( $store_ids );
985
+ }
986
+
987
+ /*
988
+ * The location url is included if:
989
+ *
990
+ * - Multiple ids are set.
991
+ * - The category attr is set.
992
+ * - The shortcode is used on a post type other then 'wpsl_stores'. No point in showing a location
993
+ * url to the user that links back to the page they are already on.
994
+ */
995
+ if ( $atts['category'] || isset( $id_count ) && $id_count > 1 || get_post_type() != 'wpsl_stores' && !empty( $atts['id'] ) ) {
996
+ $incl_url = true;
997
+ } else {
998
+ $incl_url = false;
999
+ }
1000
+
1001
+ $store_meta = array();
1002
+ $i = 0;
1003
+
1004
+ foreach ( $store_ids as $store_id ) {
1005
+ $lat = get_post_meta( $store_id, 'wpsl_lat', true );
1006
+ $lng = get_post_meta( $store_id, 'wpsl_lng', true );
1007
+
1008
+ // Make sure the latlng is numeric before collecting the other meta data.
1009
+ if ( is_numeric( $lat ) && is_numeric( $lng ) ) {
1010
+ $store_meta[$i] = apply_filters( 'wpsl_cpt_info_window_meta_fields', array(
1011
+ 'store' => get_the_title( $store_id ),
1012
+ 'address' => get_post_meta( $store_id, 'wpsl_address', true ),
1013
+ 'address2' => get_post_meta( $store_id, 'wpsl_address2', true ),
1014
+ 'city' => get_post_meta( $store_id, 'wpsl_city', true ),
1015
+ 'state' => get_post_meta( $store_id, 'wpsl_state', true ),
1016
+ 'zip' => get_post_meta( $store_id, 'wpsl_zip', true ),
1017
+ 'country' => get_post_meta( $store_id, 'wpsl_country', true )
1018
+ ), $store_id );
1019
+
1020
+ // Grab the permalink / url if necessary.
1021
+ if ( $incl_url ) {
1022
+ if ( $wpsl_settings['permalinks'] ) {
1023
+ $store_meta[$i]['permalink'] = get_permalink( $store_id );
1024
+ } else {
1025
+ $store_meta[$i]['url'] = get_post_meta( $store_id, 'wpsl_url', true );
1026
+ }
1027
+ }
1028
+
1029
+ $store_meta[$i]['lat'] = $lat;
1030
+ $store_meta[$i]['lng'] = $lng;
1031
+ $store_meta[$i]['id'] = $store_id;
1032
+
1033
+ $i++;
1034
+ }
1035
+ }
1036
+
1037
+ $output = '<div id="wpsl-base-gmap_' . self::$map_count . '" class="wpsl-gmap-canvas"></div>' . "\r\n";
1038
+
1039
+ // Make sure the shortcode attributes are valid.
1040
+ $map_styles = $this->check_map_shortcode_atts( $atts );
1041
+
1042
+ if ( $map_styles ) {
1043
+ if ( isset( $map_styles['css'] ) && !empty( $map_styles['css'] ) ) {
1044
+ $output .= '<style>' . $map_styles['css'] . '</style>' . "\r\n";
1045
+ unset( $map_styles['css'] );
1046
+ }
1047
+
1048
+ if ( $map_styles ) {
1049
+ $store_data['shortCode'] = $map_styles;
1050
+ }
1051
+ }
1052
+
1053
+ $store_data['locations'] = $store_meta;
1054
+
1055
+ $this->store_map_data[self::$map_count] = $store_data;
1056
+
1057
+ self::$map_count++;
1058
+
1059
+ return $output;
1060
+ }
1061
+
1062
+ /**
1063
+ * Make sure the map style shortcode attributes are valid.
1064
+ *
1065
+ * The values are send to wp_localize_script in add_frontend_scripts.
1066
+ *
1067
+ * @since 2.0.0
1068
+ * @param array $atts The map style shortcode attributes
1069
+ * @return array $map_atts Validated map style shortcode attributes
1070
+ */
1071
+ public function check_map_shortcode_atts( $atts ) {
1072
+
1073
+ $map_atts = array();
1074
+
1075
+ if ( isset( $atts['width'] ) && is_numeric( $atts['width'] ) ) {
1076
+ $width = 'width:' . $atts['width'] . 'px;';
1077
+ } else {
1078
+ $width = '';
1079
+ }
1080
+
1081
+ if ( isset( $atts['height'] ) && is_numeric( $atts['height'] ) ) {
1082
+ $height = 'height:' . $atts['height'] . 'px;';
1083
+ } else {
1084
+ $height = '';
1085
+ }
1086
+
1087
+ if ( $width || $height ) {
1088
+ $map_atts['css'] = '#wpsl-base-gmap_' . self::$map_count . ' {' . $width . $height . '}';
1089
+ }
1090
+
1091
+ if ( isset( $atts['zoom'] ) && !empty( $atts['zoom'] ) ) {
1092
+ $map_atts['zoomLevel'] = wpsl_valid_zoom_level( $atts['zoom'] );
1093
+ }
1094
+
1095
+ if ( isset( $atts['map_type'] ) && !empty( $atts['map_type'] ) ) {
1096
+ $map_atts['mapType'] = wpsl_valid_map_type( $atts['map_type'] );
1097
+ }
1098
+
1099
+ if ( isset( $atts['map_type_control'] ) ) {
1100
+ $map_atts['mapTypeControl'] = $this->shortcode_atts_boolean( $atts['map_type_control'] );
1101
+ }
1102
+
1103
+ if ( isset( $atts['map_style'] ) && $atts['map_style'] == 'default' ) {
1104
+ $map_atts['mapStyle'] = '';
1105
+ }
1106
+
1107
+ if ( isset( $atts['street_view'] ) ) {
1108
+ $map_atts['streetView'] = $this->shortcode_atts_boolean( $atts['street_view'] );
1109
+ }
1110
+
1111
+ if ( isset( $atts['scrollwheel'] ) ) {
1112
+ $map_atts['scrollWheel'] = $this->shortcode_atts_boolean( $atts['scrollwheel'] );
1113
+ }
1114
+
1115
+ if ( isset( $atts['control_position'] ) && !empty( $atts['control_position'] ) && ( $atts['control_position'] == 'left' || $atts['control_position'] == 'right' ) ) {
1116
+ $map_atts['controlPosition'] = $atts['control_position'];
1117
+ }
1118
+
1119
+ return $map_atts;
1120
+ }
1121
+
1122
+ /**
1123
+ * Set the shortcode attribute to either 1 or 0.
1124
+ *
1125
+ * @since 2.0.0
1126
+ * @param string $att The shortcode attribute val
1127
+ * @return int $att_val Either 1 or 0
1128
+ */
1129
+ public function shortcode_atts_boolean( $att ) {
1130
+
1131
+ if ( $att === 'true' || absint( $att ) ) {
1132
+ $att_val = 1;
1133
+ } else {
1134
+ $att_val = 0;
1135
+ }
1136
+
1137
+ return $att_val;
1138
+ }
1139
+
1140
+ /**
1141
+ * Make sure the filter contains a valid value, otherwise use the default value.
1142
+ *
1143
+ * @since 2.0.0
1144
+ * @param array $args The values used in the SQL query to find nearby locations
1145
+ * @param string $filter The name of the filter
1146
+ * @return string $filter_value The filter value
1147
+ */
1148
+ public function check_store_filter( $args, $filter ) {
1149
+
1150
+ if ( isset( $args[$filter] ) && absint( $args[$filter] ) && $this->check_allowed_filter_value( $args, $filter ) ) {
1151
+ $filter_value = $args[$filter];
1152
+ } else {
1153
+ $filter_value = $this->get_default_filter_value( $filter );
1154
+ }
1155
+
1156
+ return $filter_value;
1157
+ }
1158
+
1159
+ /**
1160
+ * Make sure the used filter value isn't bigger
1161
+ * then the value that's set on the settings page.
1162
+ *
1163
+ * @since 2.2.9
1164
+ * @param array $args The values used in the SQL query to find nearby locations
1165
+ * @param string $filter The name of the filter
1166
+ * @return bool $allowed True if the value is equal or smaller then the value from the settings page
1167
+ */
1168
+ public function check_allowed_filter_value( $args, $filter ) {
1169
+
1170
+ global $wpsl_settings;
1171
+
1172
+ $allowed = false;
1173
+
1174
+ $max_filter_val = max( explode(',', str_replace( array( '[',']' ), '', $wpsl_settings[$filter] ) ) );
1175
+
1176
+ if ( (int) $args[$filter] <= (int) $max_filter_val ) {
1177
+ $allowed = true;
1178
+ }
1179
+
1180
+ return $allowed;
1181
+ }
1182
+
1183
+ /**
1184
+ * Get the default selected value for a dropdown.
1185
+ *
1186
+ * @since 1.0.0
1187
+ * @param string $type The request list type
1188
+ * @return string $response The default list value
1189
+ */
1190
+ public function get_default_filter_value( $type ) {
1191
+
1192
+ $settings = get_option( 'wpsl_settings' );
1193
+ $list_values = explode( ',', $settings[$type] );
1194
+
1195
+ foreach ( $list_values as $k => $list_value ) {
1196
+
1197
+ // The default radius has a [] wrapped around it, so we check for that and filter out the [].
1198
+ if ( strpos( $list_value, '[' ) !== false ) {
1199
+ $response = filter_var( $list_value, FILTER_SANITIZE_NUMBER_INT );
1200
+ break;
1201
+ }
1202
+ }
1203
+
1204
+ return $response;
1205
+ }
1206
+
1207
+ /**
1208
+ * Check if we have a opening day that has an value, if not they are all set to closed.
1209
+ *
1210
+ * @since 2.0.0
1211
+ * @param array $opening_hours The opening hours
1212
+ * @return boolean True if a day is found that isn't empty
1213
+ */
1214
+ public function not_always_closed( $opening_hours ) {
1215
+
1216
+ foreach ( $opening_hours as $hours => $hour ) {
1217
+ if ( !empty( $hour ) ) {
1218
+ return true;
1219
+ }
1220
+ }
1221
+ }
1222
+
1223
+ /**
1224
+ * Create the css rules based on the height / max-width that is set on the settings page.
1225
+ *
1226
+ * @since 1.0.0
1227
+ * @return string $css The custom css rules
1228
+ */
1229
+ public function get_custom_css() {
1230
+
1231
+ global $wpsl_settings;
1232
+
1233
+ $thumb_size = $this->get_store_thumb_size();
1234
+
1235
+ $css = '<style>' . "\r\n";
1236
+
1237
+ if ( isset( $thumb_size[0] ) && is_numeric( $thumb_size[0] ) && isset( $thumb_size[1] ) && is_numeric( $thumb_size[1] ) ) {
1238
+ $css .= "\t" . "#wpsl-stores .wpsl-store-thumb {height:" . esc_attr( $thumb_size[0] ) . "px !important; width:" . esc_attr( $thumb_size[1] ) . "px !important;}" . "\r\n";
1239
+ }
1240
+
1241
+ if ( $wpsl_settings['template_id'] == 'below_map' && $wpsl_settings['listing_below_no_scroll'] ) {
1242
+ $css .= "\t" . "#wpsl-gmap {height:" . esc_attr( $wpsl_settings['height'] ) . "px !important;}" . "\r\n";
1243
+ $css .= "\t" . "#wpsl-stores, #wpsl-direction-details {height:auto !important;}";
1244
+ } else {
1245
+ $css .= "\t" . "#wpsl-stores, #wpsl-direction-details, #wpsl-gmap {height:" . esc_attr( $wpsl_settings['height'] ) . "px !important;}" . "\r\n";
1246
+ }
1247
+
1248
+ /*
1249
+ * If the category dropdowns are enabled then we make it
1250
+ * the same width as the search input field.
1251
+ */
1252
+ if ( $wpsl_settings['category_filter'] && $wpsl_settings['category_filter_type'] == 'dropdown' || isset( $this->sl_shortcode_atts['category_filter_type'] ) && $this->sl_shortcode_atts['category_filter_type'] == 'dropdown' ) {
1253
+ $cat_elem = ',#wpsl-category .wpsl-dropdown';
1254
+ } else {
1255
+ $cat_elem = '';
1256
+ }
1257
+
1258
+ $css .= "\t" . "#wpsl-gmap .wpsl-info-window {max-width:" . esc_attr( $wpsl_settings['infowindow_width'] ) . "px !important;}" . "\r\n";
1259
+ $css .= "\t" . ".wpsl-input label, #wpsl-radius label, #wpsl-category label {width:" . esc_attr( $wpsl_settings['label_width'] ) . "px;}" . "\r\n";
1260
+ $css .= "\t" . "#wpsl-search-input " . $cat_elem . " {width:" . esc_attr( $wpsl_settings['search_width'] ) . "px;}" . "\r\n";
1261
+ $css .= '</style>' . "\r\n";
1262
+
1263
+ return $css;
1264
+ }
1265
+
1266
+ /**
1267
+ * Collect the CSS classes that are placed on the outer store locator div.
1268
+ *
1269
+ * @since 2.0.0
1270
+ * @return string $classes The custom CSS rules
1271
+ */
1272
+ public function get_css_classes() {
1273
+
1274
+ global $wpsl_settings;
1275
+
1276
+ $classes = array();
1277
+
1278
+ if ( $wpsl_settings['category_filter'] && $wpsl_settings['results_dropdown'] && !$wpsl_settings['radius_dropdown'] ) {
1279
+ $classes[] = 'wpsl-cat-results-filter';
1280
+ } else if ( $wpsl_settings['category_filter'] && ( $wpsl_settings['results_dropdown'] || $wpsl_settings['radius_dropdown'] ) ) {
1281
+ $classes[] = 'wpsl-filter';
1282
+ }
1283
+ // checkboxes class toevoegen?
1284
+ if ( !$wpsl_settings['category_filter'] && !$wpsl_settings['results_dropdown'] && !$wpsl_settings['radius_dropdown'] ) {
1285
+ $classes[] = 'wpsl-no-filters';
1286
+ }
1287
+
1288
+ if ( $wpsl_settings['category_filter'] && $wpsl_settings['category_filter_type'] == 'checkboxes' ) {
1289
+ $classes[] = 'wpsl-checkboxes-enabled';
1290
+ }
1291
+
1292
+ if ( $wpsl_settings['results_dropdown'] && !$wpsl_settings['category_filter'] && !$wpsl_settings['radius_dropdown'] ) {
1293
+ $classes[] = 'wpsl-results-only';
1294
+ }
1295
+
1296
+ // Adjust the styling of the store locator for the default WP 5.0 theme.
1297
+ if ( get_option( 'template' ) === 'twentynineteen' ) {
1298
+ $classes[] = 'wpsl-twentynineteen';
1299
+ }
1300
+
1301
+ $classes = apply_filters( 'wpsl_template_css_classes', $classes );
1302
+
1303
+ if ( !empty( $classes ) ) {
1304
+ return join( ' ', $classes );
1305
+ }
1306
+ }
1307
+
1308
+ /**
1309
+ * Create a dropdown list holding the search radius or
1310
+ * max search results options.
1311
+ *
1312
+ * @since 1.0.0
1313
+ * @param string $list_type The name of the list we need to load data for
1314
+ * @return string $dropdown_list A list with the available options for the dropdown list
1315
+ */
1316
+ public function get_dropdown_list( $list_type ) {
1317
+
1318
+ global $wpsl_settings;
1319
+
1320
+ $dropdown_list = '';
1321
+ $settings = explode( ',', $wpsl_settings[$list_type] );
1322
+
1323
+ // Only show the distance unit if we are dealing with the search radius.
1324
+ if ( $list_type == 'search_radius' ) {
1325
+ $distance_unit = ' '. esc_attr( wpsl_get_distance_unit() );
1326
+ } else {
1327
+ $distance_unit = '';
1328
+ }
1329
+
1330
+ foreach ( $settings as $index => $setting_value ) {
1331
+
1332
+ // The default radius has a [] wrapped around it, so we check for that and filter out the [].
1333
+ if ( strpos( $setting_value, '[' ) !== false ) {
1334
+ $setting_value = filter_var( $setting_value, FILTER_SANITIZE_NUMBER_INT );
1335
+ $selected = 'selected="selected" ';
1336
+ } else {
1337
+ $selected = '';
1338
+ }
1339
+
1340
+ $dropdown_list .= '<option ' . $selected . 'value="'. absint( $setting_value ) .'">'. absint( $setting_value ) . $distance_unit .'</option>';
1341
+ }
1342
+
1343
+ return $dropdown_list;
1344
+ }
1345
+
1346
+ /**
1347
+ * Check if we need to use a dropdown or checkboxes
1348
+ * to filter the search results by categories.
1349
+ *
1350
+ * @since 2.2.10
1351
+ * @return bool $use_filter
1352
+ */
1353
+ public function use_category_filter() {
1354
+
1355
+ global $wpsl_settings;
1356
+
1357
+ $use_filter = false;
1358
+
1359
+ // Is a filter type set through the shortcode, or is the filter option enable on the settings page?
1360
+ if ( isset( $this->sl_shortcode_atts['category_filter_type'] ) || $wpsl_settings['category_filter'] ) {
1361
+ $use_filter = true;
1362
+ }
1363
+
1364
+ return $use_filter;
1365
+ }
1366
+
1367
+ /**
1368
+ * Create the category filter.
1369
+ *
1370
+ * @todo create another func that accepts a meta key param to generate
1371
+ * a dropdown with unique values. So for example create_filter( 'restaurant' ) will output a
1372
+ * filter with all restaurant types. This can be used in a custom theme template.
1373
+ *
1374
+ * @since 2.0.0
1375
+ * @return string|void $category The HTML for the category dropdown, or nothing if no terms exist.
1376
+ */
1377
+ public function create_category_filter() {
1378
+
1379
+ global $wpsl, $wpsl_settings;
1380
+
1381
+ /*
1382
+ * If the category attr is set on the wpsl shortcode, then
1383
+ * there is no need to ouput an extra category dropdown.
1384
+ */
1385
+ if ( isset( $this->sl_shortcode_atts['js']['categoryIds'] ) ) {
1386
+ return;
1387
+ }
1388
+
1389
+ $terms = get_terms( 'wpsl_store_category' );
1390
+
1391
+ if ( count( $terms ) > 0 ) {
1392
+
1393
+ // Either use the shortcode atts filter type or the one from the settings page.
1394
+ if ( isset( $this->sl_shortcode_atts['category_filter_type'] ) ) {
1395
+ $filter_type = $this->sl_shortcode_atts['category_filter_type'];
1396
+ } else {
1397
+ $filter_type = $wpsl_settings['category_filter_type'];
1398
+ }
1399
+
1400
+ // Check if we need to show the filter as checkboxes or a dropdown list
1401
+ if ( $filter_type == 'checkboxes' ) {
1402
+ if ( isset( $this->sl_shortcode_atts['checkbox_columns'] ) ) {
1403
+ $checkbox_columns = absint( $this->sl_shortcode_atts['checkbox_columns'] );
1404
+ }
1405
+
1406
+ if ( isset( $checkbox_columns ) && $checkbox_columns ) {
1407
+ $column_count = $checkbox_columns;
1408
+ } else {
1409
+ $column_count = 3;
1410
+ }
1411
+
1412
+ $category = '<ul id="wpsl-checkbox-filter" class="wpsl-checkbox-' . $column_count . '-columns">';
1413
+
1414
+ foreach ( $terms as $term ) {
1415
+ $category .= '<li>';
1416
+ $category .= '<label>';
1417
+ $category .= '<input type="checkbox" value="' . esc_attr( $term->term_id ) . '" ' . $this->set_selected_category( $filter_type, $term->term_id ) . ' />';
1418
+ $category .= esc_html( $term->name );
1419
+ $category .= '</label>';
1420
+ $category .= '</li>';
1421
+ }
1422
+
1423
+ $category .= '</ul>';
1424
+ } else {
1425
+ $category = '<div id="wpsl-category">' . "\r\n";
1426
+ $category .= '<label for="wpsl-category-list">' . esc_html( $wpsl->i18n->get_translation( 'category_label', __( 'Category', 'wpsl' ) ) ) . '</label>' . "\r\n";
1427
+
1428
+ $args = apply_filters( 'wpsl_dropdown_category_args', array(
1429
+ 'show_option_none' => $wpsl->i18n->get_translation( 'category_default_label', __( 'Any', 'wpsl' ) ),
1430
+ 'option_none_value' => '0',
1431
+ 'orderby' => 'NAME',
1432
+ 'order' => 'ASC',
1433
+ 'echo' => 0,
1434
+ 'selected' => $this->set_selected_category( $filter_type ),
1435
+ 'hierarchical' => 1,
1436
+ 'name' => 'wpsl-category',
1437
+ 'id' => 'wpsl-category-list',
1438
+ 'class' => 'wpsl-dropdown',
1439
+ 'taxonomy' => 'wpsl_store_category',
1440
+ 'hide_if_empty' => true
1441
+ )
1442
+ );
1443
+
1444
+ $category .= wp_dropdown_categories( $args );
1445
+
1446
+ $category .= '</div>' . "\r\n";
1447
+ }
1448
+
1449
+ return $category;
1450
+ }
1451
+ }
1452
+
1453
+ /**
1454
+ * Set the selected category item.
1455
+ *
1456
+ * @since 2.1.2
1457
+ * @param string $filter_type The type of filter being used ( dropdown or checkbox )
1458
+ * @param int|string $term_id The term id ( checkbox only )
1459
+ * @return string|void $category The ID of the selected option, or checked='checked' if it's for a checkbox
1460
+ */
1461
+ public function set_selected_category( $filter_type, $term_id = '' ) {
1462
+
1463
+ $selected_id = '';
1464
+
1465
+ // Check if the ID for the selected cat is either passed through the widget, or shortcode
1466
+ if ( isset( $_REQUEST['wpsl-widget-categories'] ) ) {
1467
+ $selected_id = absint( $_REQUEST['wpsl-widget-categories'] );
1468
+ } else if ( isset( $this->sl_shortcode_atts['category_selection'] ) ) {
1469
+
1470
+ /*
1471
+ * When the term_id is set, then it's a checkbox.
1472
+ *
1473
+ * Otherwise select the first value from the provided list since
1474
+ * multiple selections are not supported in dropdowns.
1475
+ */
1476
+ if ( $term_id ) {
1477
+
1478
+ // Check if the passed term id exists in the set shortcode value.
1479
+ $key = array_search( $term_id, $this->sl_shortcode_atts['category_selection'] );
1480
+
1481
+ if ( $key !== false ) {
1482
+ $selected_id = $this->sl_shortcode_atts['category_selection'][$key];
1483
+ }
1484
+ } else {
1485
+ $selected_id = $this->sl_shortcode_atts['category_selection'][0];
1486
+ }
1487
+ }
1488
+
1489
+ if ( $selected_id ) {
1490
+
1491
+ /*
1492
+ * Based on the filter type, either return the ID of the selected category,
1493
+ * or check if the checkbox needs to be set to checked="checked".
1494
+ */
1495
+ if ( $filter_type == 'dropdown' ) {
1496
+ return $selected_id;
1497
+ } else {
1498
+ return checked( $selected_id, $term_id, false );
1499
+ }
1500
+ }
1501
+ }
1502
+
1503
+ /**
1504
+ * Create a filename with @2x in it for the selected marker color.
1505
+ *
1506
+ * So when a user selected green.png in the admin panel. The JS on the front-end will end up
1507
+ * loading green@2x.png to provide support for retina compatible devices.
1508
+ *
1509
+ * @since 1.0.0
1510
+ * @param string $filename The name of the seleted marker
1511
+ * @return string $filename The filename with @2x added to the end
1512
+ */
1513
+ public function create_retina_filename( $filename ) {
1514
+
1515
+ $filename = explode( '.', $filename );
1516
+ $filename = $filename[0] . '@2x.' . $filename[1];
1517
+
1518
+ return $filename;
1519
+ }
1520
+
1521
+ /**
1522
+ * Get the default values for the max_results and the search_radius dropdown.
1523
+ *
1524
+ * @since 1.0.2
1525
+ * @return array $output The default dropdown values
1526
+ */
1527
+ public function get_dropdown_defaults() {
1528
+
1529
+ global $wpsl_settings;
1530
+
1531
+ $required_defaults = array(
1532
+ 'max_results',
1533
+ 'search_radius'
1534
+ );
1535
+
1536
+ // Strip out the default values that are wrapped in [].
1537
+ foreach ( $required_defaults as $required_default ) {
1538
+ preg_match_all( '/\[([0-9]+?)\]/', $wpsl_settings[$required_default], $match, PREG_PATTERN_ORDER );
1539
+ $output[$required_default] = ( isset( $match[1][0] ) ) ? $match[1][0] : '25';
1540
+ }
1541
+
1542
+ return $output;
1543
+ }
1544
+
1545
+ /**
1546
+ * Load the required css styles.
1547
+ *
1548
+ * @since 2.0.0
1549
+ * @return void
1550
+ */
1551
+ public function add_frontend_styles() {
1552
+
1553
+ global $wpsl_settings;
1554
+
1555
+ /**
1556
+ * Check if we need to deregister other Google Maps scripts loaded
1557
+ * by other plugins, or the current theme?
1558
+ *
1559
+ * This in some cases can break the store locator map.
1560
+ */
1561
+ if ( $wpsl_settings['deregister_gmaps'] ) {
1562
+ wpsl_deregister_other_gmaps();
1563
+ }
1564
+
1565
+ $min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
1566
+
1567
+ wp_enqueue_style( 'wpsl-styles', WPSL_URL . 'css/styles'. $min .'.css', '', WPSL_VERSION_NUM );
1568
+ }
1569
+
1570
+ /**
1571
+ * Get the HTML for the map controls.
1572
+ *
1573
+ * The '&#xe800;' and '&#xe801;' code is for the icon font from fontello.com
1574
+ *
1575
+ * @since 2.0.0
1576
+ * @return string The HTML for the map controls
1577
+ */
1578
+ public function get_map_controls() {
1579
+
1580
+ global $wpsl_settings, $is_IE;
1581
+
1582
+ $classes = array();
1583
+
1584
+ if ( $wpsl_settings['reset_map'] ) {
1585
+ $reset_button = '<div class="wpsl-icon-reset"><span>&#xe801;</span></div>';
1586
+ } else {
1587
+ $reset_button = '';
1588
+ }
1589
+
1590
+ /*
1591
+ * IE messes up the top padding for the icon fonts from fontello >_<.
1592
+ *
1593
+ * Luckily it's the same in all IE version ( 8-11 ),
1594
+ * so adjusting the padding just for IE fixes it.
1595
+ */
1596
+ if ( $is_IE ) {
1597
+ $classes[] = 'wpsl-ie';
1598
+ }
1599
+
1600
+ // If the street view option is enabled, then we need to adjust the right margin for the map control div.
1601
+ if ( $wpsl_settings['streetview'] ) {
1602
+ $classes[] = 'wpsl-street-view-exists';
1603
+ }
1604
+
1605
+ if ( !empty( $classes ) ) {
1606
+ $class = 'class="' . join( ' ', $classes ) . '"';
1607
+ } else {
1608
+ $class = '';
1609
+ }
1610
+
1611
+ $map_controls = '<div id="wpsl-map-controls" ' . $class . '>' . $reset_button . '<div class="wpsl-icon-direction"><span>&#xe800;</span></div></div>';
1612
+
1613
+ return apply_filters( 'wpsl_map_controls', $map_controls );
1614
+ }
1615
+
1616
+ /**
1617
+ * The different geolocation errors.
1618
+ *
1619
+ * They are shown when the Geolocation API returns an error.
1620
+ *
1621
+ * @since 2.0.0
1622
+ * @return array $geolocation_errors
1623
+ */
1624
+ public function geolocation_errors() {
1625
+
1626
+ $geolocation_errors = array(
1627
+ 'denied' => __( 'The application does not have permission to use the Geolocation API.', 'wpsl' ),
1628
+ 'unavailable' => __( 'Location information is unavailable.', 'wpsl' ),
1629
+ 'timeout' => __( 'The geolocation request timed out.', 'wpsl' ),
1630
+ 'generalError' => __( 'An unknown error occurred.', 'wpsl' )
1631
+ );
1632
+
1633
+ return $geolocation_errors;
1634
+ }
1635
+
1636
+ /**
1637
+ * Get the used marker properties.
1638
+ *
1639
+ * @since 2.1.0
1640
+ * @link https://developers.google.com/maps/documentation/javascript/3.exp/reference#Icon
1641
+ * @return array $marker_props The marker properties.
1642
+ */
1643
+ public function get_marker_props() {
1644
+
1645
+ $marker_props = array(
1646
+ 'scaledSize' => '24,35', // 50% of the normal image to make it work on retina screens.
1647
+ 'origin' => '0,0',
1648
+ 'anchor' => '12,35'
1649
+ );
1650
+
1651
+ /*
1652
+ * If this is not defined, the url path will default to
1653
+ * the url path of the WPSL plugin folder + /img/markers/
1654
+ * in the wpsl-gmap.js.
1655
+ */
1656
+ if ( defined( 'WPSL_MARKER_URI' ) ) {
1657
+ $marker_props['url'] = WPSL_MARKER_URI;
1658
+ }
1659
+
1660
+ return apply_filters( 'wpsl_marker_props', $marker_props );
1661
+ }
1662
+
1663
+ /**
1664
+ * Get the used travel direction mode.
1665
+ *
1666
+ * @since 2.2.8
1667
+ * @return string $travel_mode The used travel mode for the travel direcions
1668
+ */
1669
+ public function get_directions_travel_mode() {
1670
+
1671
+ $default = 'driving';
1672
+
1673
+ $travel_mode = apply_filters( 'wpsl_direction_travel_mode', $default );
1674
+ $allowed_modes = array( 'driving', 'bicycling', 'transit', 'walking' );
1675
+
1676
+ if ( !in_array( $travel_mode, $allowed_modes ) ) {
1677
+ $travel_mode = $default;
1678
+ }
1679
+
1680
+ return strtoupper( $travel_mode );
1681
+ }
1682
+
1683
+ /**
1684
+ * Get the map tab anchors.
1685
+ *
1686
+ * If the wpsl/wpsl_map shortcode is used in one or more tabs,
1687
+ * then a JS fix ( the fixGreyTabMap function ) needs to run
1688
+ * to make sure the map doesn't turn grey.
1689
+ *
1690
+ * For the fix to work need to know the used anchor(s).
1691
+ *
1692
+ * @since 2.2.10
1693
+ * @return string|array $map_tab_anchor One or more anchors used to show the map(s)
1694
+ */
1695
+ public function get_map_tab_anchor() {
1696
+
1697
+ $map_tab_anchor = apply_filters( 'wpsl_map_tab_anchor', 'wpsl-map-tab' );
1698
+
1699
+ return $map_tab_anchor;
1700
+ }
1701
+
1702
+ /**
1703
+ * Load the required JS scripts.
1704
+ *
1705
+ * @since 1.0.0
1706
+ * @return void
1707
+ */
1708
+ public function add_frontend_scripts() {
1709
+
1710
+ global $wpsl_settings, $wpsl, $post;
1711
+
1712
+ // Only load the required js files on the store locator page or individual store pages.
1713
+ if ( empty( $this->load_scripts ) ) {
1714
+ return;
1715
+ }
1716
+
1717
+ $min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
1718
+
1719
+ $dropdown_defaults = $this->get_dropdown_defaults();
1720
+
1721
+ /**
1722
+ * Check if we need to deregister other Google Maps scripts loaded
1723
+ * by other plugins, or the current theme?
1724
+ *
1725
+ * This in some cases can break the store locator map.
1726
+ */
1727
+ if ( $wpsl_settings['deregister_gmaps'] ) {
1728
+ wpsl_deregister_other_gmaps();
1729
+ }
1730
+
1731
+ if ( !function_exists( 'BorlabsCookieHelper' ) ) {
1732
+ wp_enqueue_script( 'wpsl-gmap', ( 'https://maps.google.com/maps/api/js' . wpsl_get_gmap_api_params( 'browser_key' ) . '' ), '', null, true );
1733
+ } else {
1734
+ if ( !$wpsl_settings['delay_loading'] || !stripos( $post->post_content, '[borlabs_cookie_blocked_content type="wpstorelocator"' ) ) {
1735
+ wp_enqueue_script( 'wpsl-gmap', ( 'https://maps.google.com/maps/api/js' . wpsl_get_gmap_api_params( 'browser_key' ) . '' ), '', null, true );
1736
+ }
1737
+ }
1738
+
1739
+ $base_settings = array(
1740
+ 'storeMarker' => $this->create_retina_filename( $wpsl_settings['store_marker'] ),
1741
+ 'mapType' => $wpsl_settings['map_type'],
1742
+ 'mapTypeControl' => $wpsl_settings['type_control'],
1743
+ 'zoomLevel' => $wpsl_settings['zoom_level'],
1744
+ 'startLatlng' => $wpsl_settings['start_latlng'],
1745
+ 'autoZoomLevel' => $wpsl_settings['auto_zoom_level'],
1746
+ 'scrollWheel' => $wpsl_settings['scrollwheel'],
1747
+ 'controlPosition' => $wpsl_settings['control_position'],
1748
+ 'url' => WPSL_URL,
1749
+ 'markerIconProps' => $this->get_marker_props(),
1750
+ 'storeUrl' => $wpsl_settings['store_url'],
1751
+ 'maxDropdownHeight' => apply_filters( 'wpsl_max_dropdown_height', 300 ),
1752
+ 'enableStyledDropdowns' => apply_filters( 'wpsl_enable_styled_dropdowns', true ),
1753
+ 'mapTabAnchor' => $this->get_map_tab_anchor(),
1754
+ 'mapTabAnchorReturn' => apply_filters( 'wpsl_map_tab_anchor_return', false ),
1755
+ 'gestureHandling' => apply_filters( 'wpsl_gesture_handling', 'auto' ),
1756
+ 'directionsTravelMode' => $this->get_directions_travel_mode(),
1757
+ 'runFitBounds' => $wpsl_settings['run_fitbounds']
1758
+ );
1759
+
1760
+ $locator_map_settings = array(
1761
+ 'startMarker' => $this->create_retina_filename( $wpsl_settings['start_marker'] ),
1762
+ 'markerClusters' => $wpsl_settings['marker_clusters'],
1763
+ 'streetView' => $wpsl_settings['streetview'],
1764
+ 'autoComplete' => $wpsl_settings['autocomplete'],
1765
+ 'autoLocate' => $wpsl_settings['auto_locate'],
1766
+ 'autoLoad' => $wpsl_settings['autoload'],
1767
+ 'markerEffect' => $wpsl_settings['marker_effect'],
1768
+ 'markerStreetView' => $wpsl_settings['marker_streetview'],
1769
+ 'markerZoomTo' => $wpsl_settings['marker_zoom_to'],
1770
+ 'newWindow' => $wpsl_settings['new_window'],
1771
+ 'resetMap' => $wpsl_settings['reset_map'],
1772
+ 'directionRedirect' => $wpsl_settings['direction_redirect'],
1773
+ 'phoneUrl' => $wpsl_settings['phone_url'],
1774
+ 'clickableDetails' => $wpsl_settings['clickable_contact_details'],
1775
+ 'moreInfoLocation' => $wpsl_settings['more_info_location'],
1776
+ 'mouseFocus' => $wpsl_settings['mouse_focus'],
1777
+ 'templateId' => $wpsl_settings['template_id'],
1778
+ 'maxResults' => $dropdown_defaults['max_results'],
1779
+ 'searchRadius' => $dropdown_defaults['search_radius'],
1780
+ 'distanceUnit' => wpsl_get_distance_unit(),
1781
+ 'geoLocationTimeout' => apply_filters( 'wpsl_geolocation_timeout', 7500 ),
1782
+ 'ajaxurl' => wpsl_get_ajax_url(),
1783
+ 'mapControls' => $this->get_map_controls()
1784
+ );
1785
+
1786
+ /*
1787
+ * If no results are found then by default it will just show the
1788
+ * "No results found" text. This filter makes it possible to show
1789
+ * a custom HTML block instead of the "No results found" text.
1790
+ */
1791
+ $no_results_msg = apply_filters( 'wpsl_no_results', '' );
1792
+
1793
+ if ( $no_results_msg ) {
1794
+ $locator_map_settings['noResults'] = $no_results_msg;
1795
+ }
1796
+
1797
+ /**
1798
+ * If enabled, include the component filter settings.
1799
+ * @todo see https://developers.google.com/maps/documentation/javascript/releases#327
1800
+ * See https://developers.google.com/maps/documentation/javascript/geocoding#ComponentFiltering
1801
+ */
1802
+ if ( $wpsl_settings['api_region'] && $wpsl_settings['api_geocode_component'] ) {
1803
+ $geocode_components = array();
1804
+ $geocode_components['country'] = strtoupper( $wpsl_settings['api_region'] );
1805
+
1806
+ if ( $wpsl_settings['force_postalcode'] ) {
1807
+ $geocode_components['postalCode'] = '';
1808
+ }
1809
+
1810
+ $locator_map_settings['geocodeComponents'] = apply_filters( 'wpsl_geocode_components', $geocode_components );
1811
+ }
1812
+
1813
+ /**
1814
+ * Reduce the requested data fields with each autocomplete API call.
1815
+ *
1816
+ * You can see the supported fields here https://developers.google.com/maps/documentation/javascript/reference/places-service#PlaceResult
1817
+ * and other possible options to target here https://developers.google.com/maps/documentation/javascript/reference/places-widget#AutocompleteOptions
1818
+ */
1819
+ if ( $wpsl_settings['autocomplete'] ) {
1820
+ $locator_map_settings['autoCompleteOptions'] = apply_filters( 'wpsl_autocomplete_options', array(
1821
+ 'fields' => array( 'geometry.location' ),
1822
+ 'types' => array( '(regions)' )
1823
+ ) );
1824
+ }
1825
+
1826
+ // If the marker clusters are enabled, include the js file and marker settings.
1827
+ if ( $wpsl_settings['marker_clusters'] ) {
1828
+ wp_enqueue_script( 'wpsl-cluster', WPSL_URL . 'js/markerclusterer'. $min .'.js', array( 'wpsl-js' ), WPSL_VERSION_NUM, true ); //not minified version is in the /js folder
1829
+
1830
+ $base_settings['clusterZoom'] = $wpsl_settings['cluster_zoom'];
1831
+ $base_settings['clusterSize'] = $wpsl_settings['cluster_size'];
1832
+ $base_settings['clusterImagePath'] = 'https://cdn.rawgit.com/googlemaps/js-marker-clusterer/gh-pages/images/m';
1833
+ }
1834
+
1835
+ // Check if we need to include the infobox script and settings.
1836
+ if ( $wpsl_settings['infowindow_style'] == 'infobox' ) {
1837
+ wp_enqueue_script( 'wpsl-infobox', WPSL_URL . 'js/infobox'. $min .'.js', array( 'wpsl-gmap' ), WPSL_VERSION_NUM, true ); // Not minified version is in the /js folder
1838
+
1839
+ $base_settings['infoWindowStyle'] = $wpsl_settings['infowindow_style'];
1840
+ $base_settings = $this->get_infobox_settings( $base_settings );
1841
+ }
1842
+
1843
+ // Include the map style.
1844
+ if ( !empty( $wpsl_settings['map_style'] ) ) {
1845
+ $base_settings['mapStyle'] = strip_tags( stripslashes( json_decode( $wpsl_settings['map_style'] ) ) );
1846
+ }
1847
+
1848
+ wp_enqueue_script( 'wpsl-js', apply_filters( 'wpsl_gmap_js', WPSL_URL . 'js/wpsl-gmap'. $min .'.js' ), array( 'jquery' ), WPSL_VERSION_NUM, true );
1849
+ wp_enqueue_script( 'underscore' );
1850
+
1851
+ // Check if we need to include all the settings and labels or just a part of them.
1852
+ if ( in_array( 'wpsl_store_locator', $this->load_scripts ) ) {
1853
+ $settings = wp_parse_args( $base_settings, $locator_map_settings );
1854
+ $template = 'wpsl_store_locator';
1855
+ $labels = array(
1856
+ 'preloader' => $wpsl->i18n->get_translation( 'preloader_label', __( 'Searching...', 'wpsl' ) ),
1857
+ 'noResults' => $wpsl->i18n->get_translation( 'no_results_label', __( 'No results found', 'wpsl' ) ),
1858
+ 'moreInfo' => $wpsl->i18n->get_translation( 'more_label', __( 'More info', 'wpsl' ) ),
1859
+ 'generalError' => $wpsl->i18n->get_translation( 'error_label', __( 'Something went wrong, please try again!', 'wpsl' ) ),
1860
+ 'queryLimit' => $wpsl->i18n->get_translation( 'limit_label', __( 'API usage limit reached', 'wpsl' ) ),
1861
+ 'directions' => $wpsl->i18n->get_translation( 'directions_label', __( 'Directions', 'wpsl' ) ),
1862
+ 'noDirectionsFound' => $wpsl->i18n->get_translation( 'no_directions_label', __( 'No route could be found between the origin and destination', 'wpsl' ) ),
1863
+ 'startPoint' => $wpsl->i18n->get_translation( 'start_label', __( 'Start location', 'wpsl' ) ),
1864
+ 'back' => $wpsl->i18n->get_translation( 'back_label', __( 'Back', 'wpsl' ) ),
1865
+ 'streetView' => $wpsl->i18n->get_translation( 'street_view_label', __( 'Street view', 'wpsl' ) ),
1866
+ 'zoomHere' => $wpsl->i18n->get_translation( 'zoom_here_label', __( 'Zoom here', 'wpsl' ) )
1867
+ );
1868
+
1869
+ wp_localize_script( 'wpsl-js', 'wpslLabels', $labels );
1870
+ wp_localize_script( 'wpsl-js', 'wpslGeolocationErrors', $this->geolocation_errors() );
1871
+ } else {
1872
+ $template = '';
1873
+ $settings = $base_settings;
1874
+ }
1875
+
1876
+ // Check if we need to overwrite JS settings that are set through the [wpsl] shortcode.
1877
+ if ( $this->sl_shortcode_atts && isset( $this->sl_shortcode_atts['js'] ) ) {
1878
+ foreach ( $this->sl_shortcode_atts['js'] as $shortcode_key => $shortcode_val ) {
1879
+ $settings[$shortcode_key] = $shortcode_val;
1880
+ }
1881
+ }
1882
+
1883
+ wp_localize_script( 'wpsl-js', 'wpslSettings', apply_filters( 'wpsl_js_settings', $settings ) );
1884
+
1885
+ wpsl_create_underscore_templates( $template );
1886
+
1887
+ if ( !empty( $this->store_map_data ) ) {
1888
+ $i = 0;
1889
+
1890
+ foreach ( $this->store_map_data as $map ) {
1891
+ wp_localize_script( 'wpsl-js', 'wpslMap_' . $i, $map );
1892
+
1893
+ $i++;
1894
+ }
1895
+ }
1896
+ }
1897
+
1898
+ /**
1899
+ * Get the infobox settings.
1900
+ *
1901
+ * @since 2.0.0
1902
+ * @see http://google-maps-utility-library-v3.googlecode.com/svn/trunk/infobox/docs/reference.html
1903
+ * @param array $settings The plugin settings used on the front-end in js
1904
+ * @return array $settings The plugin settings including the infobox settings
1905
+ */
1906
+ public function get_infobox_settings( $settings ) {
1907
+
1908
+ $infobox_settings = apply_filters( 'wpsl_infobox_settings', array(
1909
+ 'infoBoxClass' => 'wpsl-infobox',
1910
+ 'infoBoxCloseMargin' => '2px', // The margin can be written in css style, so 2px 2px 4px 2px for top, right, bottom, left
1911
+ 'infoBoxCloseUrl' => '//www.google.com/intl/en_us/mapfiles/close.gif',
1912
+ 'infoBoxClearance' => '40,40',
1913
+ 'infoBoxDisableAutoPan' => 0,
1914
+ 'infoBoxEnableEventPropagation' => 0,
1915
+ 'infoBoxPixelOffset' => '-52,-45',
1916
+ 'infoBoxZindex' => 1500
1917
+ ) );
1918
+
1919
+ foreach ( $infobox_settings as $infobox_key => $infobox_setting ) {
1920
+ $settings[$infobox_key] = $infobox_setting;
1921
+ }
1922
+
1923
+ return $settings;
1924
+ }
1925
+ }
1926
  }
frontend/templates/store-listings-below.php CHANGED
@@ -1,73 +1,73 @@
1
- <?php
2
- global $wpsl_settings, $wpsl;
3
-
4
- $output = $this->get_custom_css();
5
- $autoload_class = ( !$wpsl_settings['autoload'] ) ? 'class="wpsl-not-loaded"' : '';
6
-
7
- $output .= '<div id="wpsl-wrap" class="wpsl-store-below">' . "\r\n";
8
- $output .= "\t" . '<div class="wpsl-search wpsl-clearfix ' . $this->get_css_classes() . '">' . "\r\n";
9
- $output .= "\t\t" . '<div id="wpsl-search-wrap">' . "\r\n";
10
- $output .= "\t\t\t" . '<form autocomplete="off">' . "\r\n";
11
- $output .= "\t\t\t" . '<div class="wpsl-input">' . "\r\n";
12
- $output .= "\t\t\t\t" . '<div><label for="wpsl-search-input">' . esc_html( $wpsl->i18n->get_translation( 'search_label', __( 'Your location', 'wpsl' ) ) ) . '</label></div>' . "\r\n";
13
- $output .= "\t\t\t\t" . '<input id="wpsl-search-input" type="text" value="' . apply_filters( 'wpsl_search_input', '' ) . '" name="wpsl-search-input" placeholder="" aria-required="true" />' . "\r\n";
14
- $output .= "\t\t\t" . '</div>' . "\r\n";
15
-
16
- if ( $wpsl_settings['radius_dropdown'] || $wpsl_settings['results_dropdown'] ) {
17
- $output .= "\t\t\t" . '<div class="wpsl-select-wrap">' . "\r\n";
18
-
19
- if ( $wpsl_settings['radius_dropdown'] ) {
20
- $output .= "\t\t\t\t" . '<div id="wpsl-radius">' . "\r\n";
21
- $output .= "\t\t\t\t\t" . '<label for="wpsl-radius-dropdown">' . esc_html( $wpsl->i18n->get_translation( 'radius_label', __( 'Search radius', 'wpsl' ) ) ) . '</label>' . "\r\n";
22
- $output .= "\t\t\t\t\t" . '<select id="wpsl-radius-dropdown" class="wpsl-dropdown" name="wpsl-radius">' . "\r\n";
23
- $output .= "\t\t\t\t\t\t" . $this->get_dropdown_list( 'search_radius' ) . "\r\n";
24
- $output .= "\t\t\t\t\t" . '</select>' . "\r\n";
25
- $output .= "\t\t\t\t" . '</div>' . "\r\n";
26
- }
27
-
28
- if ( $wpsl_settings['results_dropdown'] ) {
29
- $output .= "\t\t\t\t" . '<div id="wpsl-results">' . "\r\n";
30
- $output .= "\t\t\t\t\t" . '<label for="wpsl-results-dropdown">' . esc_html( $wpsl->i18n->get_translation( 'results_label', __( 'Results', 'wpsl' ) ) ) . '</label>' . "\r\n";
31
- $output .= "\t\t\t\t\t" . '<select id="wpsl-results-dropdown" class="wpsl-dropdown" name="wpsl-results">' . "\r\n";
32
- $output .= "\t\t\t\t\t\t" . $this->get_dropdown_list( 'max_results' ) . "\r\n";
33
- $output .= "\t\t\t\t\t" . '</select>' . "\r\n";
34
- $output .= "\t\t\t\t" . '</div>' . "\r\n";
35
- }
36
-
37
- $output .= "\t\t\t" . '</div>' . "\r\n";
38
- }
39
-
40
- if ( $this->use_category_filter() ) {
41
- $output .= $this->create_category_filter();
42
- }
43
-
44
- $output .= "\t\t\t\t" . '<div class="wpsl-search-btn-wrap"><input id="wpsl-search-btn" type="submit" value="' . esc_attr( $wpsl->i18n->get_translation( 'search_btn_label', __( 'Search', 'wpsl' ) ) ) . '"></div>' . "\r\n";
45
-
46
- $output .= "\t\t" . '</form>' . "\r\n";
47
- $output .= "\t\t" . '</div>' . "\r\n";
48
- $output .= "\t" . '</div>' . "\r\n";
49
-
50
- if ( $wpsl_settings['reset_map'] ) {
51
- $output .= "\t" . '<div class="wpsl-gmap-wrap">' . "\r\n";
52
- $output .= "\t\t" . '<div id="wpsl-gmap" class="wpsl-gmap-canvas"></div>' . "\r\n";
53
- $output .= "\t" . '</div>' . "\r\n";
54
- } else {
55
- $output .= "\t" . '<div id="wpsl-gmap" class="wpsl-gmap-canvas"></div>' . "\r\n";
56
- }
57
-
58
- $output .= "\t" . '<div id="wpsl-result-list">' . "\r\n";
59
- $output .= "\t\t" . '<div id="wpsl-stores" '. $autoload_class .'>' . "\r\n";
60
- $output .= "\t\t\t" . '<ul></ul>' . "\r\n";
61
- $output .= "\t\t" . '</div>' . "\r\n";
62
- $output .= "\t\t" . '<div id="wpsl-direction-details">' . "\r\n";
63
- $output .= "\t\t\t" . '<ul></ul>' . "\r\n";
64
- $output .= "\t\t" . '</div>' . "\r\n";
65
- $output .= "\t" . '</div>' . "\r\n";
66
-
67
- if ( $wpsl_settings['show_credits'] ) {
68
- $output .= "\t" . '<div class="wpsl-provided-by">'. sprintf( __( "Search provided by %sWP Store Locator%s", "wpsl" ), "<a target='_blank' href='https://wpstorelocator.co'>", "</a>" ) .'</div>' . "\r\n";
69
- }
70
-
71
- $output .= '</div>' . "\r\n";
72
-
73
  return $output;
1
+ <?php
2
+ global $wpsl_settings, $wpsl;
3
+
4
+ $output = $this->get_custom_css();
5
+ $autoload_class = ( !$wpsl_settings['autoload'] ) ? 'class="wpsl-not-loaded"' : '';
6
+
7
+ $output .= '<div id="wpsl-wrap" class="wpsl-store-below">' . "\r\n";
8
+ $output .= "\t" . '<div class="wpsl-search wpsl-clearfix ' . $this->get_css_classes() . '">' . "\r\n";
9
+ $output .= "\t\t" . '<div id="wpsl-search-wrap">' . "\r\n";
10
+ $output .= "\t\t\t" . '<form autocomplete="off">' . "\r\n";
11
+ $output .= "\t\t\t" . '<div class="wpsl-input">' . "\r\n";
12
+ $output .= "\t\t\t\t" . '<div><label for="wpsl-search-input">' . esc_html( $wpsl->i18n->get_translation( 'search_label', __( 'Your location', 'wpsl' ) ) ) . '</label></div>' . "\r\n";
13
+ $output .= "\t\t\t\t" . '<input id="wpsl-search-input" type="text" value="' . apply_filters( 'wpsl_search_input', '' ) . '" name="wpsl-search-input" placeholder="" aria-required="true" />' . "\r\n";
14
+ $output .= "\t\t\t" . '</div>' . "\r\n";
15
+
16
+ if ( $wpsl_settings['radius_dropdown'] || $wpsl_settings['results_dropdown'] ) {
17
+ $output .= "\t\t\t" . '<div class="wpsl-select-wrap">' . "\r\n";
18
+
19
+ if ( $wpsl_settings['radius_dropdown'] ) {
20
+ $output .= "\t\t\t\t" . '<div id="wpsl-radius">' . "\r\n";
21
+ $output .= "\t\t\t\t\t" . '<label for="wpsl-radius-dropdown">' . esc_html( $wpsl->i18n->get_translation( 'radius_label', __( 'Search radius', 'wpsl' ) ) ) . '</label>' . "\r\n";
22
+ $output .= "\t\t\t\t\t" . '<select id="wpsl-radius-dropdown" class="wpsl-dropdown" name="wpsl-radius">' . "\r\n";
23
+ $output .= "\t\t\t\t\t\t" . $this->get_dropdown_list( 'search_radius' ) . "\r\n";
24
+ $output .= "\t\t\t\t\t" . '</select>' . "\r\n";
25
+ $output .= "\t\t\t\t" . '</div>' . "\r\n";
26
+ }
27
+
28
+ if ( $wpsl_settings['results_dropdown'] ) {
29
+ $output .= "\t\t\t\t" . '<div id="wpsl-results">' . "\r\n";
30
+ $output .= "\t\t\t\t\t" . '<label for="wpsl-results-dropdown">' . esc_html( $wpsl->i18n->get_translation( 'results_label', __( 'Results', 'wpsl' ) ) ) . '</label>' . "\r\n";
31
+ $output .= "\t\t\t\t\t" . '<select id="wpsl-results-dropdown" class="wpsl-dropdown" name="wpsl-results">' . "\r\n";
32
+ $output .= "\t\t\t\t\t\t" . $this->get_dropdown_list( 'max_results' ) . "\r\n";
33
+ $output .= "\t\t\t\t\t" . '</select>' . "\r\n";
34
+ $output .= "\t\t\t\t" . '</div>' . "\r\n";
35
+ }
36
+
37
+ $output .= "\t\t\t" . '</div>' . "\r\n";
38
+ }
39
+
40
+ if ( $this->use_category_filter() ) {
41
+ $output .= $this->create_category_filter();
42
+ }
43
+
44
+ $output .= "\t\t\t\t" . '<div class="wpsl-search-btn-wrap"><input id="wpsl-search-btn" type="submit" value="' . esc_attr( $wpsl->i18n->get_translation( 'search_btn_label', __( 'Search', 'wpsl' ) ) ) . '"></div>' . "\r\n";
45
+
46
+ $output .= "\t\t" . '</form>' . "\r\n";
47
+ $output .= "\t\t" . '</div>' . "\r\n";
48
+ $output .= "\t" . '</div>' . "\r\n";
49
+
50
+ if ( $wpsl_settings['reset_map'] ) {
51
+ $output .= "\t" . '<div class="wpsl-gmap-wrap">' . "\r\n";
52
+ $output .= "\t\t" . '<div id="wpsl-gmap" class="wpsl-gmap-canvas"></div>' . "\r\n";
53
+ $output .= "\t" . '</div>' . "\r\n";
54
+ } else {
55
+ $output .= "\t" . '<div id="wpsl-gmap" class="wpsl-gmap-canvas"></div>' . "\r\n";
56
+ }
57
+
58
+ $output .= "\t" . '<div id="wpsl-result-list">' . "\r\n";
59
+ $output .= "\t\t" . '<div id="wpsl-stores" '. $autoload_class .'>' . "\r\n";
60
+ $output .= "\t\t\t" . '<ul></ul>' . "\r\n";
61
+ $output .= "\t\t" . '</div>' . "\r\n";
62
+ $output .= "\t\t" . '<div id="wpsl-direction-details">' . "\r\n";
63
+ $output .= "\t\t\t" . '<ul></ul>' . "\r\n";
64
+ $output .= "\t\t" . '</div>' . "\r\n";
65
+ $output .= "\t" . '</div>' . "\r\n";
66
+
67
+ if ( $wpsl_settings['show_credits'] ) {
68
+ $output .= "\t" . '<div class="wpsl-provided-by">'. sprintf( __( "Search provided by %sWP Store Locator%s", "wpsl" ), "<a target='_blank' href='https://wpstorelocator.co'>", "</a>" ) .'</div>' . "\r\n";
69
+ }
70
+
71
+ $output .= '</div>' . "\r\n";
72
+
73
  return $output;
frontend/underscore-functions.php CHANGED
@@ -1,267 +1,267 @@
1
- <?php
2
- /**
3
- * Create the store data templates.
4
- *
5
- * The templates are created in JS with _.template, see http://underscorejs.org/#template
6
- *
7
- * @since 2.0.0
8
- * @param string $template The type of template we need to create
9
- * @return void
10
- */
11
- function wpsl_create_underscore_templates( $template ) {
12
-
13
- global $wpsl_settings, $wpsl;
14
-
15
- if ( $template == 'wpsl_store_locator' ) {
16
- ?>
17
- <script id="wpsl-info-window-template" type="text/template">
18
- <?php
19
- $info_window_template = '<div data-store-id="<%= id %>" class="wpsl-info-window">' . "\r\n";
20
- $info_window_template .= "\t\t" . '<p>' . "\r\n";
21
- $info_window_template .= "\t\t\t" . wpsl_store_header_template() . "\r\n"; // Check which header format we use
22
- $info_window_template .= "\t\t\t" . '<span><%= address %></span>' . "\r\n";
23
- $info_window_template .= "\t\t\t" . '<% if ( address2 ) { %>' . "\r\n";
24
- $info_window_template .= "\t\t\t" . '<span><%= address2 %></span>' . "\r\n";
25
- $info_window_template .= "\t\t\t" . '<% } %>' . "\r\n";
26
- $info_window_template .= "\t\t\t" . '<span>' . wpsl_address_format_placeholders() . '</span>' . "\r\n"; // Use the correct address format
27
- $info_window_template .= "\t\t" . '</p>' . "\r\n";
28
- $info_window_template .= "\t\t" . '<% if ( phone ) { %>' . "\r\n";
29
- $info_window_template .= "\t\t" . '<span><strong>' . esc_html( $wpsl->i18n->get_translation( 'phone_label', __( 'Phone', 'wpsl' ) ) ) . '</strong>: <%= formatPhoneNumber( phone ) %></span>' . "\r\n";
30
- $info_window_template .= "\t\t" . '<% } %>' . "\r\n";
31
- $info_window_template .= "\t\t" . '<% if ( fax ) { %>' . "\r\n";
32
- $info_window_template .= "\t\t" . '<span><strong>' . esc_html( $wpsl->i18n->get_translation( 'fax_label', __( 'Fax', 'wpsl' ) ) ) . '</strong>: <%= fax %></span>' . "\r\n";
33
- $info_window_template .= "\t\t" . '<% } %>' . "\r\n";
34
- $info_window_template .= "\t\t" . '<% if ( email ) { %>' . "\r\n";
35
- $info_window_template .= "\t\t" . '<span><strong>' . esc_html( $wpsl->i18n->get_translation( 'email_label', __( 'Email', 'wpsl' ) ) ) . '</strong>: <%= formatEmail( email ) %></span>' . "\r\n";
36
- $info_window_template .= "\t\t" . '<% } %>' . "\r\n";
37
- $info_window_template .= "\t\t" . '<%= createInfoWindowActions( id ) %>' . "\r\n";
38
- $info_window_template .= "\t" . '</div>';
39
-
40
- echo apply_filters( 'wpsl_info_window_template', $info_window_template . "\n" );
41
- ?>
42
- </script>
43
- <script id="wpsl-listing-template" type="text/template">
44
- <?php
45
- $listing_template = '<li data-store-id="<%= id %>">' . "\r\n";
46
- $listing_template .= "\t\t" . '<div class="wpsl-store-location">' . "\r\n";
47
- $listing_template .= "\t\t\t" . '<p><%= thumb %>' . "\r\n";
48
- $listing_template .= "\t\t\t\t" . wpsl_store_header_template( 'listing' ) . "\r\n"; // Check which header format we use
49
- $listing_template .= "\t\t\t\t" . '<span class="wpsl-street"><%= address %></span>' . "\r\n";
50
- $listing_template .= "\t\t\t\t" . '<% if ( address2 ) { %>' . "\r\n";
51
- $listing_template .= "\t\t\t\t" . '<span class="wpsl-street"><%= address2 %></span>' . "\r\n";
52
- $listing_template .= "\t\t\t\t" . '<% } %>' . "\r\n";
53
- $listing_template .= "\t\t\t\t" . '<span>' . wpsl_address_format_placeholders() . '</span>' . "\r\n"; // Use the correct address format
54
-
55
- if ( !$wpsl_settings['hide_country'] ) {
56
- $listing_template .= "\t\t\t\t" . '<span class="wpsl-country"><%= country %></span>' . "\r\n";
57
- }
58
-
59
- $listing_template .= "\t\t\t" . '</p>' . "\r\n";
60
-
61
- // Show the phone, fax or email data if they exist.
62
- if ( $wpsl_settings['show_contact_details'] ) {
63
- $listing_template .= "\t\t\t" . '<p class="wpsl-contact-details">' . "\r\n";
64
- $listing_template .= "\t\t\t" . '<% if ( phone ) { %>' . "\r\n";
65
- $listing_template .= "\t\t\t" . '<span><strong>' . esc_html( $wpsl->i18n->get_translation( 'phone_label', __( 'Phone', 'wpsl' ) ) ) . '</strong>: <%= formatPhoneNumber( phone ) %></span>' . "\r\n";
66
- $listing_template .= "\t\t\t" . '<% } %>' . "\r\n";
67
- $listing_template .= "\t\t\t" . '<% if ( fax ) { %>' . "\r\n";
68
- $listing_template .= "\t\t\t" . '<span><strong>' . esc_html( $wpsl->i18n->get_translation( 'fax_label', __( 'Fax', 'wpsl' ) ) ) . '</strong>: <%= fax %></span>' . "\r\n";
69
- $listing_template .= "\t\t\t" . '<% } %>' . "\r\n";
70
- $listing_template .= "\t\t\t" . '<% if ( email ) { %>' . "\r\n";
71
- $listing_template .= "\t\t\t" . '<span><strong>' . esc_html( $wpsl->i18n->get_translation( 'email_label', __( 'Email', 'wpsl' ) ) ) . '</strong>: <%= formatEmail( email ) %></span>' . "\r\n";
72
- $listing_template .= "\t\t\t" . '<% } %>' . "\r\n";
73
- $listing_template .= "\t\t\t" . '</p>' . "\r\n";
74
- }
75
-
76
- $listing_template .= "\t\t\t" . wpsl_more_info_template() . "\r\n"; // Check if we need to show the 'More Info' link and info
77
- $listing_template .= "\t\t" . '</div>' . "\r\n";
78
- $listing_template .= "\t\t" . '<div class="wpsl-direction-wrap">' . "\r\n";
79
-
80
- if ( !$wpsl_settings['hide_distance'] ) {
81
- $listing_template .= "\t\t\t" . '<%= distance %> ' . esc_html( wpsl_get_distance_unit() ) . '' . "\r\n";
82
- }
83
-
84
- $listing_template .= "\t\t\t" . '<%= createDirectionUrl() %>' . "\r\n";
85
- $listing_template .= "\t\t" . '</div>' . "\r\n";
86
- $listing_template .= "\t" . '</li>';
87
-
88
- echo apply_filters( 'wpsl_listing_template', $listing_template . "\n" );
89
- ?>
90
- </script>
91
- <?php
92
- } else {
93
- ?>
94
- <script id="wpsl-cpt-info-window-template" type="text/template">
95
- <?php
96
- $cpt_info_window_template = '<div class="wpsl-info-window">' . "\r\n";
97
- $cpt_info_window_template .= "\t\t" . '<p class="wpsl-no-margin">' . "\r\n";
98
- $cpt_info_window_template .= "\t\t\t" . wpsl_store_header_template( 'wpsl_map' ) . "\r\n";
99
- $cpt_info_window_template .= "\t\t\t" . '<span><%= address %></span>' . "\r\n";
100
- $cpt_info_window_template .= "\t\t\t" . '<% if ( address2 ) { %>' . "\r\n";
101
- $cpt_info_window_template .= "\t\t\t" . '<span><%= address2 %></span>' . "\r\n";
102
- $cpt_info_window_template .= "\t\t\t" . '<% } %>' . "\r\n";
103
- $cpt_info_window_template .= "\t\t\t" . '<span>' . wpsl_address_format_placeholders() . '</span>' . "\r\n"; // Use the correct address format
104
-
105
- if ( !$wpsl_settings['hide_country'] ) {
106
- $cpt_info_window_template .= "\t\t\t" . '<span class="wpsl-country"><%= country %></span>' . "\r\n";
107
- }
108
-
109
- $cpt_info_window_template .= "\t\t" . '</p>' . "\r\n";
110
- $cpt_info_window_template .= "\t" . '</div>';
111
-
112
- echo apply_filters( 'wpsl_cpt_info_window_template', $cpt_info_window_template . "\n" );
113
- ?>
114
- </script>
115
- <?php
116
- }
117
- }
118
-
119
- /**
120
- * Create the more info template.
121
- *
122
- * @since 2.0.0
123
- * @return string $more_info_template The template that is used to show the "More info" content
124
- */
125
- function wpsl_more_info_template() {
126
-
127
- global $wpsl_settings, $wpsl;
128
-
129
- if ( $wpsl_settings['more_info'] ) {
130
- $more_info_url = '#';
131
-
132
- if ( $wpsl_settings['template_id'] == 'default' && $wpsl_settings['more_info_location'] == 'info window' ) {
133
- $more_info_url = '#wpsl-search-wrap';
134
- }
135
-
136
- if ( $wpsl_settings['more_info_location'] == 'store listings' ) {
137
- $more_info_template = '<% if ( !_.isEmpty( phone ) || !_.isEmpty( fax ) || !_.isEmpty( email ) ) { %>' . "\r\n";
138
- $more_info_template .= "\t\t\t" . '<p><a class="wpsl-store-details wpsl-store-listing" href="#wpsl-id-<%= id %>">' . esc_html( $wpsl->i18n->get_translation( 'more_label', __( 'More info', 'wpsl' ) ) ) . '</a></p>' . "\r\n";
139
- $more_info_template .= "\t\t\t" . '<div id="wpsl-id-<%= id %>" class="wpsl-more-info-listings">' . "\r\n";
140
- $more_info_template .= "\t\t\t\t" . '<% if ( description ) { %>' . "\r\n";
141
- $more_info_template .= "\t\t\t\t" . '<%= description %>' . "\r\n";
142
- $more_info_template .= "\t\t\t\t" . '<% } %>' . "\r\n";
143
-
144
- if ( !$wpsl_settings['show_contact_details'] ) {
145
- $more_info_template .= "\t\t\t\t" . '<p>' . "\r\n";
146
- $more_info_template .= "\t\t\t\t" . '<% if ( phone ) { %>' . "\r\n";
147
- $more_info_template .= "\t\t\t\t" . '<span><strong>' . esc_html( $wpsl->i18n->get_translation( 'phone_label', __( 'Phone', 'wpsl' ) ) ) . '</strong>: <%= formatPhoneNumber( phone ) %></span>' . "\r\n";
148
- $more_info_template .= "\t\t\t\t" . '<% } %>' . "\r\n";
149
- $more_info_template .= "\t\t\t\t" . '<% if ( fax ) { %>' . "\r\n";
150
- $more_info_template .= "\t\t\t\t" . '<span><strong>' . esc_html( $wpsl->i18n->get_translation( 'fax_label', __( 'Fax', 'wpsl' ) ) ) . '</strong>: <%= fax %></span>' . "\r\n";
151
- $more_info_template .= "\t\t\t\t" . '<% } %>' . "\r\n";
152
- $more_info_template .= "\t\t\t\t" . '<% if ( email ) { %>' . "\r\n";
153
- $more_info_template .= "\t\t\t\t" . '<span><strong>' . esc_html( $wpsl->i18n->get_translation( 'email_label', __( 'Email', 'wpsl' ) ) ) . '</strong>: <%= formatEmail( email ) %></span>' . "\r\n";
154
- $more_info_template .= "\t\t\t\t" . '<% } %>' . "\r\n";
155
- $more_info_template .= "\t\t\t\t" . '</p>' . "\r\n";
156
- }
157
-
158
- if ( !$wpsl_settings['hide_hours'] ) {
159
- $more_info_template .= "\t\t\t\t" . '<% if ( hours ) { %>' . "\r\n";
160
- $more_info_template .= "\t\t\t\t" . '<div class="wpsl-store-hours"><strong>' . esc_html( $wpsl->i18n->get_translation( 'hours_label', __( 'Hours', 'wpsl' ) ) ) . '</strong><%= hours %></div>' . "\r\n";
161
- $more_info_template .= "\t\t\t\t" . '<% } %>' . "\r\n";
162
- }
163
-
164
- $more_info_template .= "\t\t\t" . '</div>' . "\r\n";
165
- $more_info_template .= "\t\t\t" . '<% } %>';
166
-
167
- } else {
168
- $more_info_template = '<p><a class="wpsl-store-details" href="' . $more_info_url . '">' . esc_html( $wpsl->i18n->get_translation( 'more_label', __( 'More info', 'wpsl' ) ) ) . '</a></p>';
169
- }
170
-
171
- return apply_filters( 'wpsl_more_info_template', $more_info_template );
172
- }
173
- }
174
-
175
- /**
176
- * Create the store header template.
177
- *
178
- * @since 2.0.0
179
- * @param string $location The location where the header is shown ( info_window / listing / wpsl_map shortcode )
180
- * @return string $header_template The template for the store header
181
- */
182
- function wpsl_store_header_template( $location = 'info_window' ) {
183
-
184
- global $wpsl_settings;
185
-
186
- if ( $wpsl_settings['new_window'] ) {
187
- $new_window = ' target="_blank"';
188
- } else {
189
- $new_window = '';
190
- }
191
-
192
- /*
193
- * To keep the code readable in the HTML source we ( unfortunately ) need to adjust the
194
- * amount of tabs in front of it based on the location were it is shown.
195
- */
196
- if ( $location == 'listing') {
197
- $tab = "\t\t\t\t";
198
- } else {
199
- $tab = "\t\t\t";
200
- }
201
-
202
- if ( $wpsl_settings['permalinks'] ) {
203
-
204
- /**
205
- * It's possible the permalinks are enabled, but not included in the location data on
206
- * pages where the [wpsl_map] shortcode is used.
207
- *
208
- * So we need to check for undefined, which isn't necessary in all other cases.
209
- */
210
- if ( $location == 'wpsl_map') {
211
- $header_template = '<% if ( typeof permalink !== "undefined" ) { %>' . "\r\n";
212
- $header_template .= $tab . '<strong><a' . $new_window . ' href="<%= permalink %>"><%= store %></a></strong>' . "\r\n";
213
- $header_template .= $tab . '<% } else { %>' . "\r\n";
214
- $header_template .= $tab . '<strong><%= store %></strong>' . "\r\n";
215
- $header_template .= $tab . '<% } %>';
216
- } else {
217
- $header_template = '<strong><a' . $new_window . ' href="<%= permalink %>"><%= store %></a></strong>';
218
- }
219
- } else {
220
- $header_template = '<% if ( wpslSettings.storeUrl == 1 && url ) { %>' . "\r\n";
221
- $header_template .= $tab . '<strong><a' . $new_window . ' href="<%= url %>"><%= store %></a></strong>' . "\r\n";
222
- $header_template .= $tab . '<% } else { %>' . "\r\n";
223
- $header_template .= $tab . '<strong><%= store %></strong>' . "\r\n";
224
- $header_template .= $tab . '<% } %>';
225
- }
226
-
227
- return apply_filters( 'wpsl_store_header_template', $header_template );
228
- }
229
-
230
- /**
231
- * Create the address placeholders based on the structure defined on the settings page.
232
- *
233
- * @since 2.0.0
234
- * @return string $address_placeholders A list of address placeholders in the correct order
235
- */
236
- function wpsl_address_format_placeholders() {
237
-
238
- global $wpsl_settings;
239
-
240
- $address_format = explode( '_', $wpsl_settings['address_format'] );
241
- $placeholders = '';
242
- $part_count = count( $address_format ) - 1;
243
- $i = 0;
244
-
245
- foreach ( $address_format as $address_part ) {
246
- if ( $address_part != 'comma' ) {
247
-
248
- /*
249
- * Don't add a space after the placeholder if the next part
250
- * is going to be a comma or if it is the last part.
251
- */
252
- if ( $i == $part_count || $address_format[$i + 1] == 'comma' ) {
253
- $space = '';
254
- } else {
255
- $space = ' ';
256
- }
257
-
258
- $placeholders .= '<%= ' . $address_part . ' %>' . $space;
259
- } else {
260
- $placeholders .= ', ';
261
- }
262
-
263
- $i++;
264
- }
265
-
266
- return $placeholders;
267
  }
1
+ <?php
2
+ /**
3
+ * Create the store data templates.
4
+ *
5
+ * The templates are created in JS with _.template, see http://underscorejs.org/#template
6
+ *
7
+ * @since 2.0.0
8
+ * @param string $template The type of template we need to create
9
+ * @return void
10
+ */
11
+ function wpsl_create_underscore_templates( $template ) {
12
+
13
+ global $wpsl_settings, $wpsl;
14
+
15
+ if ( $template == 'wpsl_store_locator' ) {
16
+ ?>
17
+ <script id="wpsl-info-window-template" type="text/template">
18
+ <?php
19
+ $info_window_template = '<div data-store-id="<%= id %>" class="wpsl-info-window">' . "\r\n";
20
+ $info_window_template .= "\t\t" . '<p>' . "\r\n";
21
+ $info_window_template .= "\t\t\t" . wpsl_store_header_template() . "\r\n"; // Check which header format we use
22
+ $info_window_template .= "\t\t\t" . '<span><%= address %></span>' . "\r\n";
23
+ $info_window_template .= "\t\t\t" . '<% if ( address2 ) { %>' . "\r\n";
24
+ $info_window_template .= "\t\t\t" . '<span><%= address2 %></span>' . "\r\n";
25
+ $info_window_template .= "\t\t\t" . '<% } %>' . "\r\n";
26
+ $info_window_template .= "\t\t\t" . '<span>' . wpsl_address_format_placeholders() . '</span>' . "\r\n"; // Use the correct address format
27
+ $info_window_template .= "\t\t" . '</p>' . "\r\n";
28
+ $info_window_template .= "\t\t" . '<% if ( phone ) { %>' . "\r\n";
29
+ $info_window_template .= "\t\t" . '<span><strong>' . esc_html( $wpsl->i18n->get_translation( 'phone_label', __( 'Phone', 'wpsl' ) ) ) . '</strong>: <%= formatPhoneNumber( phone ) %></span>' . "\r\n";
30
+ $info_window_template .= "\t\t" . '<% } %>' . "\r\n";
31
+ $info_window_template .= "\t\t" . '<% if ( fax ) { %>' . "\r\n";
32
+ $info_window_template .= "\t\t" . '<span><strong>' . esc_html( $wpsl->i18n->get_translation( 'fax_label', __( 'Fax', 'wpsl' ) ) ) . '</strong>: <%= fax %></span>' . "\r\n";
33
+ $info_window_template .= "\t\t" . '<% } %>' . "\r\n";
34
+ $info_window_template .= "\t\t" . '<% if ( email ) { %>' . "\r\n";
35
+ $info_window_template .= "\t\t" . '<span><strong>' . esc_html( $wpsl->i18n->get_translation( 'email_label', __( 'Email', 'wpsl' ) ) ) . '</strong>: <%= formatEmail( email ) %></span>' . "\r\n";
36
+ $info_window_template .= "\t\t" . '<% } %>' . "\r\n";
37
+ $info_window_template .= "\t\t" . '<%= createInfoWindowActions( id ) %>' . "\r\n";
38
+ $info_window_template .= "\t" . '</div>';
39
+
40
+ echo apply_filters( 'wpsl_info_window_template', $info_window_template . "\n" );
41
+ ?>
42
+ </script>
43
+ <script id="wpsl-listing-template" type="text/template">
44
+ <?php
45
+ $listing_template = '<li data-store-id="<%= id %>">' . "\r\n";
46
+ $listing_template .= "\t\t" . '<div class="wpsl-store-location">' . "\r\n";
47
+ $listing_template .= "\t\t\t" . '<p><%= thumb %>' . "\r\n";
48
+ $listing_template .= "\t\t\t\t" . wpsl_store_header_template( 'listing' ) . "\r\n"; // Check which header format we use
49
+ $listing_template .= "\t\t\t\t" . '<span class="wpsl-street"><%= address %></span>' . "\r\n";
50
+ $listing_template .= "\t\t\t\t" . '<% if ( address2 ) { %>' . "\r\n";
51
+ $listing_template .= "\t\t\t\t" . '<span class="wpsl-street"><%= address2 %></span>' . "\r\n";
52
+ $listing_template .= "\t\t\t\t" . '<% } %>' . "\r\n";
53
+ $listing_template .= "\t\t\t\t" . '<span>' . wpsl_address_format_placeholders() . '</span>' . "\r\n"; // Use the correct address format
54
+
55
+ if ( !$wpsl_settings['hide_country'] ) {
56
+ $listing_template .= "\t\t\t\t" . '<span class="wpsl-country"><%= country %></span>' . "\r\n";
57
+ }
58
+
59
+ $listing_template .= "\t\t\t" . '</p>' . "\r\n";
60
+
61
+ // Show the phone, fax or email data if they exist.
62
+ if ( $wpsl_settings['show_contact_details'] ) {
63
+ $listing_template .= "\t\t\t" . '<p class="wpsl-contact-details">' . "\r\n";
64
+ $listing_template .= "\t\t\t" . '<% if ( phone ) { %>' . "\r\n";
65
+ $listing_template .= "\t\t\t" . '<span><strong>' . esc_html( $wpsl->i18n->get_translation( 'phone_label', __( 'Phone', 'wpsl' ) ) ) . '</strong>: <%= formatPhoneNumber( phone ) %></span>' . "\r\n";
66
+ $listing_template .= "\t\t\t" . '<% } %>' . "\r\n";
67
+ $listing_template .= "\t\t\t" . '<% if ( fax ) { %>' . "\r\n";
68
+ $listing_template .= "\t\t\t" . '<span><strong>' . esc_html( $wpsl->i18n->get_translation( 'fax_label', __( 'Fax', 'wpsl' ) ) ) . '</strong>: <%= fax %></span>' . "\r\n";
69
+ $listing_template .= "\t\t\t" . '<% } %>' . "\r\n";
70
+ $listing_template .= "\t\t\t" . '<% if ( email ) { %>' . "\r\n";
71
+ $listing_template .= "\t\t\t" . '<span><strong>' . esc_html( $wpsl->i18n->get_translation( 'email_label', __( 'Email', 'wpsl' ) ) ) . '</strong>: <%= formatEmail( email ) %></span>' . "\r\n";
72
+ $listing_template .= "\t\t\t" . '<% } %>' . "\r\n";
73
+ $listing_template .= "\t\t\t" . '</p>' . "\r\n";
74
+ }
75
+
76
+ $listing_template .= "\t\t\t" . wpsl_more_info_template() . "\r\n"; // Check if we need to show the 'More Info' link and info
77
+ $listing_template .= "\t\t" . '</div>' . "\r\n";
78
+ $listing_template .= "\t\t" . '<div class="wpsl-direction-wrap">' . "\r\n";
79
+
80
+ if ( !$wpsl_settings['hide_distance'] ) {
81
+ $listing_template .= "\t\t\t" . '<%= distance %> ' . esc_html( wpsl_get_distance_unit() ) . '' . "\r\n";
82
+ }
83
+
84
+ $listing_template .= "\t\t\t" . '<%= createDirectionUrl() %>' . "\r\n";
85
+ $listing_template .= "\t\t" . '</div>' . "\r\n";
86
+ $listing_template .= "\t" . '</li>';
87
+
88
+ echo apply_filters( 'wpsl_listing_template', $listing_template . "\n" );
89
+ ?>
90
+ </script>
91
+ <?php
92
+ } else {
93
+ ?>
94
+ <script id="wpsl-cpt-info-window-template" type="text/template">
95
+ <?php
96
+ $cpt_info_window_template = '<div class="wpsl-info-window">' . "\r\n";
97
+ $cpt_info_window_template .= "\t\t" . '<p class="wpsl-no-margin">' . "\r\n";
98
+ $cpt_info_window_template .= "\t\t\t" . wpsl_store_header_template( 'wpsl_map' ) . "\r\n";
99
+ $cpt_info_window_template .= "\t\t\t" . '<span><%= address %></span>' . "\r\n";
100
+ $cpt_info_window_template .= "\t\t\t" . '<% if ( address2 ) { %>' . "\r\n";
101
+ $cpt_info_window_template .= "\t\t\t" . '<span><%= address2 %></span>' . "\r\n";
102
+ $cpt_info_window_template .= "\t\t\t" . '<% } %>' . "\r\n";
103
+ $cpt_info_window_template .= "\t\t\t" . '<span>' . wpsl_address_format_placeholders() . '</span>' . "\r\n"; // Use the correct address format
104
+
105
+ if ( !$wpsl_settings['hide_country'] ) {
106
+ $cpt_info_window_template .= "\t\t\t" . '<span class="wpsl-country"><%= country %></span>' . "\r\n";
107
+ }
108
+
109
+ $cpt_info_window_template .= "\t\t" . '</p>' . "\r\n";
110
+ $cpt_info_window_template .= "\t" . '</div>';
111
+
112
+ echo apply_filters( 'wpsl_cpt_info_window_template', $cpt_info_window_template . "\n" );
113
+ ?>
114
+ </script>
115
+ <?php
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Create the more info template.
121
+ *
122
+ * @since 2.0.0
123
+ * @return string $more_info_template The template that is used to show the "More info" content
124
+ */
125
+ function wpsl_more_info_template() {
126
+
127
+ global $wpsl_settings, $wpsl;
128
+
129
+ if ( $wpsl_settings['more_info'] ) {
130
+ $more_info_url = '#';
131
+
132
+ if ( $wpsl_settings['template_id'] == 'default' && $wpsl_settings['more_info_location'] == 'info window' ) {
133
+ $more_info_url = '#wpsl-search-wrap';
134
+ }
135
+
136
+ if ( $wpsl_settings['more_info_location'] == 'store listings' ) {
137
+ $more_info_template = '<% if ( !_.isEmpty( phone ) || !_.isEmpty( fax ) || !_.isEmpty( email ) ) { %>' . "\r\n";
138
+ $more_info_template .= "\t\t\t" . '<p><a class="wpsl-store-details wpsl-store-listing" href="#wpsl-id-<%= id %>">' . esc_html( $wpsl->i18n->get_translation( 'more_label', __( 'More info', 'wpsl' ) ) ) . '</a></p>' . "\r\n";
139
+ $more_info_template .= "\t\t\t" . '<div id="wpsl-id-<%= id %>" class="wpsl-more-info-listings">' . "\r\n";
140
+ $more_info_template .= "\t\t\t\t" . '<% if ( description ) { %>' . "\r\n";
141
+ $more_info_template .= "\t\t\t\t" . '<%= description %>' . "\r\n";
142
+ $more_info_template .= "\t\t\t\t" . '<% } %>' . "\r\n";
143
+
144
+ if ( !$wpsl_settings['show_contact_details'] ) {
145
+ $more_info_template .= "\t\t\t\t" . '<p>' . "\r\n";
146
+ $more_info_template .= "\t\t\t\t" . '<% if ( phone ) { %>' . "\r\n";
147
+ $more_info_template .= "\t\t\t\t" . '<span><strong>' . esc_html( $wpsl->i18n->get_translation( 'phone_label', __( 'Phone', 'wpsl' ) ) ) . '</strong>: <%= formatPhoneNumber( phone ) %></span>' . "\r\n";
148
+ $more_info_template .= "\t\t\t\t" . '<% } %>' . "\r\n";
149
+ $more_info_template .= "\t\t\t\t" . '<% if ( fax ) { %>' . "\r\n";
150
+ $more_info_template .= "\t\t\t\t" . '<span><strong>' . esc_html( $wpsl->i18n->get_translation( 'fax_label', __( 'Fax', 'wpsl' ) ) ) . '</strong>: <%= fax %></span>' . "\r\n";
151
+ $more_info_template .= "\t\t\t\t" . '<% } %>' . "\r\n";
152
+ $more_info_template .= "\t\t\t\t" . '<% if ( email ) { %>' . "\r\n";
153
+ $more_info_template .= "\t\t\t\t" . '<span><strong>' . esc_html( $wpsl->i18n->get_translation( 'email_label', __( 'Email', 'wpsl' ) ) ) . '</strong>: <%= formatEmail( email ) %></span>' . "\r\n";
154
+ $more_info_template .= "\t\t\t\t" . '<% } %>' . "\r\n";
155
+ $more_info_template .= "\t\t\t\t" . '</p>' . "\r\n";
156
+ }
157
+
158
+ if ( !$wpsl_settings['hide_hours'] ) {
159
+ $more_info_template .= "\t\t\t\t" . '<% if ( hours ) { %>' . "\r\n";
160
+ $more_info_template .= "\t\t\t\t" . '<div class="wpsl-store-hours"><strong>' . esc_html( $wpsl->i18n->get_translation( 'hours_label', __( 'Hours', 'wpsl' ) ) ) . '</strong><%= hours %></div>' . "\r\n";
161
+ $more_info_template .= "\t\t\t\t" . '<% } %>' . "\r\n";
162
+ }
163
+
164
+ $more_info_template .= "\t\t\t" . '</div>' . "\r\n";
165
+ $more_info_template .= "\t\t\t" . '<% } %>';
166
+
167
+ } else {
168
+ $more_info_template = '<p><a class="wpsl-store-details" href="' . $more_info_url . '">' . esc_html( $wpsl->i18n->get_translation( 'more_label', __( 'More info', 'wpsl' ) ) ) . '</a></p>';
169
+ }
170
+
171
+ return apply_filters( 'wpsl_more_info_template', $more_info_template );
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Create the store header template.
177
+ *
178
+ * @since 2.0.0
179
+ * @param string $location The location where the header is shown ( info_window / listing / wpsl_map shortcode )
180
+ * @return string $header_template The template for the store header
181
+ */
182
+ function wpsl_store_header_template( $location = 'info_window' ) {
183
+
184
+ global $wpsl_settings;
185
+
186
+ if ( $wpsl_settings['new_window'] ) {
187
+ $new_window = ' target="_blank"';
188
+ } else {
189
+ $new_window = '';
190
+ }
191
+
192
+ /*
193
+ * To keep the code readable in the HTML source we ( unfortunately ) need to adjust the
194
+ * amount of tabs in front of it based on the location were it is shown.
195
+ */
196
+ if ( $location == 'listing') {
197
+ $tab = "\t\t\t\t";
198
+ } else {
199
+ $tab = "\t\t\t";
200
+ }
201
+
202
+ if ( $wpsl_settings['permalinks'] ) {
203
+
204
+ /**
205
+ * It's possible the permalinks are enabled, but not included in the location data on
206
+ * pages where the [wpsl_map] shortcode is used.
207
+ *
208
+ * So we need to check for undefined, which isn't necessary in all other cases.
209
+ */
210
+ if ( $location == 'wpsl_map') {
211
+ $header_template = '<% if ( typeof permalink !== "undefined" ) { %>' . "\r\n";
212
+ $header_template .= $tab . '<strong><a' . $new_window . ' href="<%= permalink %>"><%= store %></a></strong>' . "\r\n";
213
+ $header_template .= $tab . '<% } else { %>' . "\r\n";
214
+ $header_template .= $tab . '<strong><%= store %></strong>' . "\r\n";
215
+ $header_template .= $tab . '<% } %>';
216
+ } else {
217
+ $header_template = '<strong><a' . $new_window . ' href="<%= permalink %>"><%= store %></a></strong>';
218
+ }
219
+ } else {
220
+ $header_template = '<% if ( wpslSettings.storeUrl == 1 && url ) { %>' . "\r\n";
221
+ $header_template .= $tab . '<strong><a' . $new_window . ' href="<%= url %>"><%= store %></a></strong>' . "\r\n";
222
+ $header_template .= $tab . '<% } else { %>' . "\r\n";
223
+ $header_template .= $tab . '<strong><%= store %></strong>' . "\r\n";
224
+ $header_template .= $tab . '<% } %>';
225
+ }
226
+
227
+ return apply_filters( 'wpsl_store_header_template', $header_template );
228
+ }
229
+
230
+ /**
231
+ * Create the address placeholders based on the structure defined on the settings page.
232
+ *
233
+ * @since 2.0.0
234
+ * @return string $address_placeholders A list of address placeholders in the correct order
235
+ */
236
+ function wpsl_address_format_placeholders() {
237
+
238
+ global $wpsl_settings;
239
+
240
+ $address_format = explode( '_', $wpsl_settings['address_format'] );
241
+ $placeholders = '';
242
+ $part_count = count( $address_format ) - 1;
243
+ $i = 0;
244
+
245
+ foreach ( $address_format as $address_part ) {
246
+ if ( $address_part != 'comma' ) {
247
+
248
+ /*
249
+ * Don't add a space after the placeholder if the next part
250
+ * is going to be a comma or if it is the last part.
251
+ */
252
+ if ( $i == $part_count || $address_format[$i + 1] == 'comma' ) {
253
+ $space = '';
254
+ } else {
255
+ $space = ' ';
256
+ }
257
+
258
+ $placeholders .= '<%= ' . $address_part . ' %>' . $space;
259
+ } else {
260
+ $placeholders .= ', ';
261
+ }
262
+
263
+ $i++;
264
+ }
265
+
266
+ return $placeholders;
267
  }
inc/class-borlabs-cookie.php ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WPSL / Borlabs Cookie class
4
+ *
5
+ * @author Tijmen Smit
6
+ * @since 2.2.22
7
+ */
8
+
9
+ if ( !defined( 'ABSPATH' ) ) exit;
10
+
11
+ if ( !class_exists( 'WPSL_Borlabs_Cookie' ) ) {
12
+
13
+ class WPSL_Borlabs_Cookie {
14
+
15
+ /**
16
+ * Class constructor
17
+ */
18
+ public function __construct() {
19
+
20
+ if ( !is_admin() ) {
21
+ add_filter( 'borlabsCookie/bct/modify_content/wpstorelocator', array( $this, 'update_content_blocker' ), 10, 2 );
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Check if the 'wpstorelocator' blocked content type exists.
27
+ * If this is not the case, then we create it.
28
+ *
29
+ * @since 2.2.22
30
+ * @return void
31
+ */
32
+ public function maybe_enable_bct() {
33
+
34
+ $wpsl_bct_data = BorlabsCookieHelper()->getBlockedContentTypeDataByTypeId( 'wpstorelocator' );
35
+
36
+ if ( !$wpsl_bct_data ) {
37
+ $this->enable();
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Add support for the delayed loading of the
43
+ * Google Maps library in the Borlabs Cookies plugin
44
+ * by adding a 'wpstorelocator' blocked content type.
45
+ *
46
+ * @since 2.2.22
47
+ * @return void
48
+ */
49
+ public function enable() {
50
+
51
+ /**
52
+ * First, we delete old Blocked Content Types with the id wpstorelocator.
53
+ * If the id doesn't exist, nothing happens.
54
+ *
55
+ * Doing so ensures that both plugins work as intended.
56
+ */
57
+ BorlabsCookieHelper()->deleteBlockedContentType( 'wpstorelocator' );
58
+
59
+ // Add new Blocked Content Type wpstorelocator - if the BCT exists nothing happens
60
+ BorlabsCookieHelper()->addBlockedContentType(
61
+ 'wpstorelocator',
62
+ 'WP Store Locator',
63
+ '',
64
+ [],
65
+ '<div class="borlabs-cookie-bct bc-bct-iframe bc-bct-google-maps">
66
+ <p class="bc-thumbnail"><img src="%%thumbnail%%" alt="%%name%%"></p>
67
+ <div class="bc-text">
68
+ <p>' . _x( 'To protect your personal data, your connection to Google Maps has been blocked.<br>Click on <strong>Load map</strong> to unblock Google Maps.<br>By loading the map you accept the privacy policy of Google.<br>More information about Google\'s privacy policy can be found here <a href="https://policies.google.com/privacy?hl=en&amp;gl=en" target="_blank" rel="nofollow">Google - Privacy &amp; Terms</a> . ', 'Borlabs Cookie', 'wpsl' ) . '</p>
69
+ <p><label><input type="checkbox" name="unblockAll" value="1" checked> ' . _x( 'Do not block Google Maps in the future anymore.', 'Borlabs Cookie', 'wpsl' ) . '</label>
70
+ <a role="button" data-borlabs-cookie-unblock>' . _x( 'Load map', 'Borlabs Cookie', 'wpsl' ) . '</a></p>
71
+ </div>
72
+ </div>',
73
+ '',
74
+ '',
75
+ [
76
+ 'responsiveIframe' => false,
77
+ ],
78
+ true,
79
+ true
80
+ );
81
+ }
82
+
83
+ /**
84
+ * Remove the 'wpstorelocator' blocked content type
85
+ * from the Borlabs Cookie plugin.
86
+ *
87
+ * @since 2.2.22
88
+ */
89
+ public function disable() {
90
+ if ( function_exists('BorlabsCookieHelper' ) ) {
91
+ BorlabsCookieHelper()->deleteBlockedContentType( 'wpstorelocator' );
92
+ }
93
+ }
94
+
95
+ /**
96
+ * modifyWPStoreLocatorContentBlocker function.
97
+ *
98
+ * @since 2.2.22
99
+ * @param mixed $id
100
+ * @param mixed $content
101
+ * @return void
102
+ */
103
+ public function update_content_blocker( $id, $content ) {
104
+
105
+ // Get settings of the Blocked Content Type
106
+ $wpsl_data = BorlabsCookieHelper()->getBlockedContentTypeDataByTypeId( 'wpstorelocator' );
107
+
108
+ // Workaround, fixed in newer versions of Borlabs Cookie
109
+ if ( !isset($wpsl_data['settings']['unblockAll'] ) ) {
110
+ $wpsl_data['settings']['unblockAll'] = false;
111
+ }
112
+
113
+ BorlabsCookieHelper()->updateBlockedContentTypeJavaScript(
114
+ 'wpstorelocator',
115
+ 'var myScriptTag = document.createElement("script"); myScriptTag.type = "text/javascript"; myScriptTag.src = "https://maps.google.com/maps/api/js' . wpsl_get_gmap_api_params( "browser_key" ) .'";jQuery("body").append(myScriptTag);',
116
+ 'initWpslMap();',
117
+ $wpsl_data['settings']
118
+ );
119
+
120
+ // Default thumbnail
121
+ $thumbnail = BORLABS_COOKIE_PLUGIN_URL.'images/bct-google-maps.png';
122
+
123
+ // Get the title which was maybe set via title-attribute in a shortcode
124
+ $title = BorlabsCookieHelper()->getCurrentTitleOfBlockedContentType();
125
+
126
+ // If no title was set use the Blocked Content Type name as title
127
+ if ( empty( $title ) ) {
128
+ $title = $wpsl_data['name'];
129
+ }
130
+
131
+ // Replace text variables
132
+ $wpsl_data['previewHTML'] = str_replace(
133
+ [
134
+ '%%name%%',
135
+ '%%thumbnail%%',
136
+ ],
137
+ [
138
+ $title,
139
+ $thumbnail
140
+ ],
141
+ $wpsl_data['previewHTML']
142
+ );
143
+
144
+ /* Return the HTML that displays the information, that the original content was blocked */
145
+ return $wpsl_data['previewHTML'];
146
+ }
147
+ }
148
+
149
+ new WPSL_Borlabs_Cookie();
150
+ }
inc/class-i18n.php CHANGED
@@ -1,160 +1,160 @@
1
- <?php
2
- /**
3
- * i18n class
4
- *
5
- * @author Tijmen Smit
6
- * @since 2.0.0
7
- */
8
-
9
- if ( !defined( 'ABSPATH' ) ) exit;
10
-
11
- if ( !class_exists( 'WPSL_i18n' ) ) {
12
-
13
- class WPSL_i18n {
14
-
15
- private $wpml_active = null;
16
-
17
- private $qtrans_active = null;
18
-
19
- /**
20
- * Class constructor
21
- */
22
- function __construct() {
23
- add_action( 'plugins_loaded', array( $this, 'load_plugin_textdomain' ) );
24
- }
25
-
26
- /**
27
- * Load the translations from the language folder
28
- *
29
- * @since 2.0.0
30
- * @return void
31
- */
32
- public function load_plugin_textdomain() {
33
-
34
- $domain = 'wpsl';
35
- $locale = apply_filters( 'plugin_locale', get_locale(), $domain );
36
-
37
- // Load the language file from the /wp-content/languages/wp-store-locator folder, custom + update proof translations.
38
- load_textdomain( $domain, WP_LANG_DIR . '/wp-store-locator/' . $domain . '-' . $locale . '.mo' );
39
-
40
- // Load the language file from the /wp-content/plugins/wp-store-locator/languages/ folder.
41
- load_plugin_textdomain( $domain, false, dirname( WPSL_BASENAME ) . '/languages/' );
42
- }
43
-
44
- /**
45
- * Check if WPML is active
46
- *
47
- * @since 2.0.0
48
- * @return boolean|null
49
- */
50
- public function wpml_exists() {
51
-
52
- if ( $this->wpml_active == null ) {
53
- $this->wpml_active = function_exists( 'icl_register_string' );
54
- }
55
-
56
- return $this->wpml_active;
57
- }
58
-
59
- /**
60
- * Check if a qTranslate compatible plugin is active.
61
- *
62
- * @since 2.0.0
63
- * @return boolean|null
64
- */
65
- public function qtrans_exists() {
66
-
67
- if ( $this->qtrans_active == null ) {
68
- $this->qtrans_active = ( function_exists( 'qtrans_useCurrentLanguageIfNotFoundUseDefaultLanguage' ) || function_exists( 'qtranxf_useCurrentLanguageIfNotFoundUseDefaultLanguage' ) );
69
- }
70
-
71
- return $this->qtrans_active;
72
- }
73
-
74
- /**
75
- * See if there is a translated page available for the provided store ID.
76
- *
77
- * @since 2.0.0
78
- * @see https://wpml.org/documentation/support/creating-multilingual-wordpress-themes/language-dependent-ids/#2
79
- * @param string $store_id
80
- * @return string empty or the id of the translated store
81
- */
82
- public function maybe_get_wpml_id( $store_id ) {
83
-
84
- $return_original_id = apply_filters( 'wpsl_return_original_wpml_id', true );
85
-
86
- // icl_object_id is deprecated as of 3.2
87
- if ( defined( 'ICL_SITEPRESS_VERSION' ) && version_compare( ICL_SITEPRESS_VERSION, 3.2, '>=' ) ) {
88
- $translated_id = apply_filters( 'wpml_object_id', $store_id, 'wpsl_stores', $return_original_id, ICL_LANGUAGE_CODE );
89
- } else {
90
- $translated_id = icl_object_id( $store_id, 'wpsl_stores', $return_original_id, ICL_LANGUAGE_CODE );
91
- }
92
-
93
- // If '$return_original_id' is set to false, NULL is returned if no translation exists.
94
- if ( is_null( $translated_id ) ) {
95
- $translated_id = '';
96
- }
97
-
98
- return $translated_id;
99
- }
100
-
101
- /**
102
- * Get the correct translation.
103
- *
104
- * Return the translated text from WPML or the translation
105
- * that was set on the settings page.
106
- *
107
- * @since 2.0.0
108
- * @param string $name The name of the translated string
109
- * @param string $text The text of the translated string
110
- * @return string The translation
111
- */
112
- public function get_translation( $name, $text ) {
113
-
114
- global $wpsl_settings;
115
-
116
- if ( defined( 'WPML_ST_VERSION' ) ) {
117
- $translation = $text;
118
- } elseif ( defined( 'POLYLANG_VERSION' ) && defined( 'PLL_INC' ) ) {
119
-
120
- if ( !function_exists( 'pll__' ) ) {
121
- require_once PLL_INC . '/api.php';
122
- }
123
-
124
- $translation = pll__( $text );
125
- } else {
126
- $translation = stripslashes( $wpsl_settings[$name] );
127
- }
128
-
129
- return $translation;
130
- }
131
-
132
- /**
133
- * If a multilingual plugin like WPML or qTranslate X is active
134
- * we return the active language code.
135
- *
136
- * @since 2.0.0
137
- * @return string Empty or the current language code
138
- */
139
- public function check_multilingual_code() {
140
-
141
- $language_code = '';
142
-
143
- if ( $this->wpml_exists() && defined( 'ICL_LANGUAGE_CODE' ) ) {
144
- $language_code = ICL_LANGUAGE_CODE;
145
- } else if ( $this->qtrans_exists() ) {
146
-
147
- if ( function_exists( 'qtranxf_getLanguage' ) ) {
148
- $language_code = qtranxf_getLanguage();
149
- } else if ( function_exists( 'qtrans_getLanguage' ) ) {
150
- $language_code = qtrans_getLanguage();
151
- }
152
- }
153
-
154
- return $language_code;
155
- }
156
-
157
- }
158
-
159
- new WPSL_i18n();
160
  }
1
+ <?php
2
+ /**
3
+ * i18n class
4
+ *
5
+ * @author Tijmen Smit
6
+ * @since 2.0.0
7
+ */
8
+
9
+ if ( !defined( 'ABSPATH' ) ) exit;
10
+
11
+ if ( !class_exists( 'WPSL_i18n' ) ) {
12
+
13
+ class WPSL_i18n {
14
+
15
+ private $wpml_active = null;
16
+
17
+ private $qtrans_active = null;
18
+
19
+ /**
20
+ * Class constructor
21
+ */
22
+ function __construct() {
23
+ add_action( 'plugins_loaded', array( $this, 'load_plugin_textdomain' ) );
24
+ }
25
+
26
+ /**
27
+ * Load the translations from the language folder
28
+ *
29
+ * @since 2.0.0
30
+ * @return void
31
+ */
32
+ public function load_plugin_textdomain() {
33
+
34
+ $domain = 'wpsl';
35
+ $locale = apply_filters( 'plugin_locale', get_locale(), $domain );
36
+
37
+ // Load the language file from the /wp-content/languages/wp-store-locator folder, custom + update proof translations.
38
+ load_textdomain( $domain, WP_LANG_DIR . '/wp-store-locator/' . $domain . '-' . $locale . '.mo' );
39
+
40
+ // Load the language file from the /wp-content/plugins/wp-store-locator/languages/ folder.
41
+ load_plugin_textdomain( $domain, false, dirname( WPSL_BASENAME ) . '/languages/' );
42
+ }
43
+
44
+ /**
45
+ * Check if WPML is active
46
+ *
47
+ * @since 2.0.0
48
+ * @return boolean|null
49
+ */
50
+ public function wpml_exists() {
51
+
52
+ if ( $this->wpml_active == null ) {
53
+ $this->wpml_active = function_exists( 'icl_register_string' );
54
+ }
55
+
56
+ return $this->wpml_active;
57
+ }
58
+
59
+ /**
60
+ * Check if a qTranslate compatible plugin is active.
61
+ *
62
+ * @since 2.0.0
63
+ * @return boolean|null
64
+ */
65
+ public function qtrans_exists() {
66
+
67
+ if ( $this->qtrans_active == null ) {
68
+ $this->qtrans_active = ( function_exists( 'qtrans_useCurrentLanguageIfNotFoundUseDefaultLanguage' ) || function_exists( 'qtranxf_useCurrentLanguageIfNotFoundUseDefaultLanguage' ) );
69
+ }
70
+
71
+ return $this->qtrans_active;
72
+ }
73
+
74
+ /**
75
+ * See if there is a translated page available for the provided store ID.
76
+ *
77
+ * @since 2.0.0
78
+ * @see https://wpml.org/documentation/support/creating-multilingual-wordpress-themes/language-dependent-ids/#2
79
+ * @param string $store_id
80
+ * @return string empty or the id of the translated store
81
+ */
82
+ public function maybe_get_wpml_id( $store_id ) {
83
+
84
+ $return_original_id = apply_filters( 'wpsl_return_original_wpml_id', true );
85
+
86
+ // icl_object_id is deprecated as of 3.2
87
+ if ( defined( 'ICL_SITEPRESS_VERSION' ) && version_compare( ICL_SITEPRESS_VERSION, 3.2, '>=' ) ) {
88
+ $translated_id = apply_filters( 'wpml_object_id', $store_id, 'wpsl_stores', $return_original_id, ICL_LANGUAGE_CODE );
89
+ } else {
90
+ $translated_id = icl_object_id( $store_id, 'wpsl_stores', $return_original_id, ICL_LANGUAGE_CODE );
91
+ }
92
+
93
+ // If '$return_original_id' is set to false, NULL is returned if no translation exists.
94
+ if ( is_null( $translated_id ) ) {
95
+ $translated_id = '';
96
+ }
97
+
98
+ return $translated_id;
99
+ }
100
+
101
+ /**
102
+ * Get the correct translation.
103
+ *
104
+ * Return the translated text from WPML or the translation
105
+ * that was set on the settings page.
106
+ *
107
+ * @since 2.0.0
108
+ * @param string $name The name of the translated string
109
+ * @param string $text The text of the translated string
110
+ * @return string The translation
111
+ */
112
+ public function get_translation( $name, $text ) {
113
+
114
+ global $wpsl_settings;
115
+
116
+ if ( defined( 'WPML_ST_VERSION' ) ) {
117
+ $translation = $text;
118
+ } elseif ( defined( 'POLYLANG_VERSION' ) && defined( 'PLL_INC' ) ) {
119
+
120
+ if ( !function_exists( 'pll__' ) ) {
121
+ require_once PLL_INC . '/api.php';
122
+ }
123
+
124
+ $translation = pll__( $text );
125
+ } else {
126
+ $translation = stripslashes( $wpsl_settings[$name] );
127
+ }
128
+
129
+ return $translation;
130
+ }
131
+
132
+ /**
133
+ * If a multilingual plugin like WPML or qTranslate X is active
134
+ * we return the active language code.
135
+ *
136
+ * @since 2.0.0
137
+ * @return string Empty or the current language code
138
+ */
139
+ public function check_multilingual_code() {
140
+
141
+ $language_code = '';
142
+
143
+ if ( $this->wpml_exists() && defined( 'ICL_LANGUAGE_CODE' ) ) {
144
+ $language_code = ICL_LANGUAGE_CODE;
145
+ } else if ( $this->qtrans_exists() ) {
146
+
147
+ if ( function_exists( 'qtranxf_getLanguage' ) ) {
148
+ $language_code = qtranxf_getLanguage();
149
+ } else if ( function_exists( 'qtrans_getLanguage' ) ) {
150
+ $language_code = qtrans_getLanguage();
151
+ }
152
+ }
153
+
154
+ return $language_code;
155
+ }
156
+
157
+ }
158
+
159
+ new WPSL_i18n();
160
  }
inc/class-post-types.php CHANGED
@@ -1,284 +1,284 @@
1
- <?php
2
- /**
3
- * Store Locator custom post type.
4
- *
5
- * @author Tijmen Smit
6
- * @since 2.0.0
7
- */
8
-
9
- if ( !defined( 'ABSPATH' ) ) exit;
10
-
11
- if ( !class_exists( 'WPSL_Post_Types' ) ) {
12
-
13
- class WPSL_Post_Types {
14
-
15
- /**
16
- * Constructor
17
- */
18
- public function __construct() {
19
- add_action( 'init', array( $this, 'maybe_show_in_rest' ) );
20
- add_action( 'init', array( $this, 'register_post_types' ), 10, 1 );
21
- add_action( 'init', array( $this, 'register_taxonomies' ), 10, 1 );
22
- add_action( 'manage_wpsl_stores_posts_custom_column', array( $this, 'custom_columns' ), 10, 2 );
23
-
24
- add_filter( 'enter_title_here', array( $this, 'change_default_title' ) );
25
- add_filter( 'manage_edit-wpsl_stores_columns', array( $this, 'edit_columns' ) );
26
- add_filter( 'manage_edit-wpsl_stores_sortable_columns', array( $this, 'sortable_columns' ) );
27
- add_filter( 'request', array( $this, 'sort_columns' ) );
28
- }
29
-
30
- /**
31
- * Check if we need to set 'show_in_rest' to true/false,
32
- * and thereby enabling the REST API.
33
- *
34
- * This needs to be set to true for
35
- * Gutenberg to be enabled.
36
- *
37
- * Full REST API support will come in the 3.0 update.
38
- *
39
- * @since 2.2.19
40
- * @return bool
41
- */
42
- public function maybe_show_in_rest() {
43
-
44
- global $wp_version;
45
-
46
- return ( version_compare( $wp_version, '5', '>=' ) ) ? true : false;
47
- }
48
-
49
-
50
- /**
51
- * Register the WPSL post type.
52
- *
53
- * @since 2.0.0
54
- * @return void
55
- */
56
- public function register_post_types() {
57
-
58
- global $wpsl_settings;
59
-
60
- // Enable permalinks for the post type?
61
- if ( isset( $wpsl_settings['permalinks'] ) && $wpsl_settings['permalinks'] ) {
62
- $public = true;
63
- $exclude_from_search = false;
64
- $rewrite = array( 'slug' => $wpsl_settings['permalink_slug'] );
65
-
66
- if ( $wpsl_settings['permalink_remove_front'] ) {
67
- $rewrite['with_front'] = false;
68
- }
69
- } else {
70
- $public = false;
71
- $exclude_from_search = true;
72
- $rewrite = false;
73
- }
74
-
75
- // The labels for the wpsl_stores post type.
76
- $labels = apply_filters( 'wpsl_post_type_labels', array(
77
- 'name' => __( 'Store Locator', 'wpsl' ),
78
- 'all_items' => __( 'All Stores', 'wpsl' ),
79
- 'singular_name' => __( 'Store', 'wpsl' ),
80
- 'add_new' => __( 'New Store', 'wpsl' ),
81
- 'add_new_item' => __( 'Add New Store', 'wpsl' ),
82
- 'edit_item' => __( 'Edit Store', 'wpsl' ),
83
- 'new_item' => __( 'New Store', 'wpsl' ),
84
- 'view_item' => __( 'View Stores', 'wpsl' ),
85
- 'search_items' => __( 'Search Stores', 'wpsl' ),
86
- 'not_found' => __( 'No Stores found', 'wpsl' ),
87
- 'not_found_in_trash' => __( 'No Stores found in trash', 'wpsl' ),
88
- )
89
- );
90
-
91
- // The arguments for the wpsl_stores post type.
92
- $args = apply_filters( 'wpsl_post_type_args', array(
93
- 'labels' => $labels,
94
- 'public' => $public,
95
- 'exclude_from_search' => $exclude_from_search,
96
- 'show_ui' => true,
97
- 'menu_position' => apply_filters( 'wpsl_post_type_menu_position', null ),
98
- 'capability_type' => 'store',
99
- 'map_meta_cap' => true,
100
- 'rewrite' => $rewrite,
101
- 'query_var' => 'wpsl_stores',
102
- 'supports' => array( 'title', 'editor', 'author', 'excerpt', 'revisions', 'thumbnail' ),
103
- 'show_in_rest' => $this->maybe_show_in_rest()
104
- )
105
- );
106
-
107
- register_post_type( 'wpsl_stores', $args );
108
- }
109
-
110
- /**
111
- * Register the WPSL custom taxonomy.
112
- *
113
- * @since 2.0.0
114
- * @return void
115
- */
116
- public function register_taxonomies() {
117
-
118
- global $wpsl_settings;
119
-
120
- // Enable permalinks for the taxonomy?
121
- if ( isset( $wpsl_settings['permalinks'] ) && $wpsl_settings['permalinks'] ) {
122
- $public = true;
123
- $rewrite = array( 'slug' => $wpsl_settings['category_slug'] );
124
- } else {
125
- $public = false;
126
- $rewrite = false;
127
- }
128
-
129
- $labels = array(
130
- 'name' => __( 'Store Categories', 'wpsl' ),
131
- 'singular_name' => __( 'Store Category', 'wpsl' ),
132
- 'search_items' => __( 'Search Store Categories', 'wpsl' ),
133
- 'all_items' => __( 'All Store Categories', 'wpsl' ),
134
- 'parent_item' => __( 'Parent Store Category', 'wpsl' ),
135
- 'parent_item_colon' => __( 'Parent Store Category:', 'wpsl' ),
136
- 'edit_item' => __( 'Edit Store Category', 'wpsl' ),
137
- 'update_item' => __( 'Update Store Category', 'wpsl' ),
138
- 'add_new_item' => __( 'Add New Store Category', 'wpsl' ),
139
- 'new_item_name' => __( 'New Store Category Name', 'wpsl' ),
140
- 'menu_name' => __( 'Store Categories', 'wpsl' ),
141
- );
142
-
143
- $args = apply_filters( 'wpsl_store_category_args', array(
144
- 'labels' => $labels,
145
- 'public' => $public,
146
- 'hierarchical' => true,
147
- 'show_ui' => true,
148
- 'show_admin_column' => true,
149
- 'update_count_callback' => '_update_post_term_count',
150
- 'query_var' => true,
151
- 'rewrite' => $rewrite,
152
- 'show_in_rest' => $this->maybe_show_in_rest()
153
- )
154
- );
155
-
156
- register_taxonomy( 'wpsl_store_category', 'wpsl_stores', $args );
157
- }
158
-
159
- /**
160
- * Change the default "Enter title here" placeholder.
161
- *
162
- * @since 2.0.0
163
- * @param string $title The default title placeholder
164
- * @return string $title The new title placeholder
165
- */
166
- public function change_default_title( $title ) {
167
-
168
- $screen = get_current_screen();
169
-
170
- if ( $screen->post_type == 'wpsl_stores' ) {
171
- $title = __( 'Enter store title here', 'wpsl' );
172
- }
173
-
174
- return $title;
175
- }
176
-
177
- /**
178
- * Add new columns to the store list table.
179
- *
180
- * @since 2.0.0
181
- * @param array $columns The default columns
182
- * @return array $columns Updated column list
183
- */
184
- public function edit_columns( $columns ) {
185
-
186
- $columns['address'] = __( 'Address', 'wpsl' );
187
- $columns['city'] = __( 'City', 'wpsl' );
188
- $columns['state'] = __( 'State', 'wpsl' );
189
- $columns['zip'] = __( 'Zip', 'wpsl' );
190
-
191
- return $columns;
192
- }
193
-
194
- /**
195
- * Show the correct store content in the correct custom column.
196
- *
197
- * @since 2.0.0
198
- * @param string $column The column name
199
- * @param int $post_id The post id
200
- * @return void
201
- */
202
- public function custom_columns( $column, $post_id ) {
203
-
204
- switch ( $column ) {
205
- case 'address':
206
- echo esc_html( get_post_meta( $post_id, 'wpsl_address', true ) );
207
- break;
208
- case 'city':
209
- echo esc_html( get_post_meta( $post_id, 'wpsl_city', true ) );
210
- break;
211
- case 'state':
212
- echo esc_html( get_post_meta( $post_id, 'wpsl_state', true ) );
213
- break;
214
- case 'zip':
215
- echo esc_html( get_post_meta( $post_id, 'wpsl_zip', true ) );
216
- break;
217
- }
218
- }
219
-
220
- /**
221
- * Define the columns that are sortable.
222
- *
223
- * @since 2.0.0
224
- * @param array $columns List of sortable columns
225
- * @return array
226
- */
227
- public function sortable_columns( $columns ) {
228
-
229
- $custom = array(
230
- 'address' => 'wpsl_address',
231
- 'city' => 'wpsl_city',
232
- 'state' => 'wpsl_state',
233
- 'zip' => 'wpsl_zip'
234
- );
235
-
236
- return wp_parse_args( $custom, $columns );
237
- }
238
-
239
- /**
240
- * Set the correct column sort parameters.
241
- *
242
- * @since 2.0.0
243
- * @param array $vars Column sorting parameters
244
- * @return array $vars The column sorting parameters inc the correct orderby and wpsl meta_key
245
- */
246
- public function sort_columns( $vars ) {
247
-
248
- if ( isset( $vars['post_type'] ) && $vars['post_type'] == 'wpsl_stores' ) {
249
- if ( isset( $vars['orderby'] ) ) {
250
- if ( $vars['orderby'] === 'wpsl_address' ) {
251
- $vars = array_merge( $vars, array(
252
- 'meta_key' => 'wpsl_address',
253
- 'orderby' => 'meta_value'
254
- ) );
255
- }
256
-
257
- if ( $vars['orderby'] === 'wpsl_city' ) {
258
- $vars = array_merge( $vars, array(
259
- 'meta_key' => 'wpsl_city',
260
- 'orderby' => 'meta_value'
261
- ) );
262
- }
263
-
264
- if ( $vars['orderby'] === 'wpsl_state' ) {
265
- $vars = array_merge( $vars, array(
266
- 'meta_key' => 'wpsl_state',
267
- 'orderby' => 'meta_value'
268
- ) );
269
- }
270
-
271
- if ( $vars['orderby'] === 'wpsl_zip' ) {
272
- $vars = array_merge( $vars, array(
273
- 'meta_key' => 'wpsl_zip',
274
- 'orderby' => 'meta_value'
275
- ) );
276
- }
277
- }
278
- }
279
-
280
- return $vars;
281
- }
282
- }
283
-
284
  }
1
+ <?php
2
+ /**
3
+ * Store Locator custom post type.
4
+ *
5
+ * @author Tijmen Smit
6
+ * @since 2.0.0
7
+ */
8
+
9
+ if ( !defined( 'ABSPATH' ) ) exit;
10
+
11
+ if ( !class_exists( 'WPSL_Post_Types' ) ) {
12
+
13
+ class WPSL_Post_Types {
14
+
15
+ /**
16
+ * Constructor
17
+ */
18
+ public function __construct() {
19
+ add_action( 'init', array( $this, 'maybe_show_in_rest' ) );
20
+ add_action( 'init', array( $this, 'register_post_types' ), 10, 1 );
21
+ add_action( 'init', array( $this, 'register_taxonomies' ), 10, 1 );
22
+ add_action( 'manage_wpsl_stores_posts_custom_column', array( $this, 'custom_columns' ), 10, 2 );
23
+
24
+ add_filter( 'enter_title_here', array( $this, 'change_default_title' ) );
25
+ add_filter( 'manage_edit-wpsl_stores_columns', array( $this, 'edit_columns' ) );
26
+ add_filter( 'manage_edit-wpsl_stores_sortable_columns', array( $this, 'sortable_columns' ) );
27
+ add_filter( 'request', array( $this, 'sort_columns' ) );
28
+ }
29
+
30
+ /**
31
+ * Check if we need to set 'show_in_rest' to true/false,
32
+ * and thereby enabling the REST API.
33
+ *
34
+ * This needs to be set to true for
35
+ * Gutenberg to be enabled.
36
+ *
37
+ * Full REST API support will come in the 3.0 update.
38
+ *
39
+ * @since 2.2.19
40
+ * @return bool
41
+ */
42
+ public function maybe_show_in_rest() {
43
+
44
+ global $wp_version;
45
+
46
+ return ( version_compare( $wp_version, '5', '>=' ) ) ? true : false;
47
+ }
48
+
49
+
50
+ /**
51
+ * Register the WPSL post type.
52
+ *
53
+ * @since 2.0.0
54
+ * @return void
55
+ */
56
+ public function register_post_types() {
57
+
58
+ global $wpsl_settings;
59
+
60
+ // Enable permalinks for the post type?
61
+ if ( isset( $wpsl_settings['permalinks'] ) && $wpsl_settings['permalinks'] ) {
62
+ $public = true;
63
+ $exclude_from_search = false;
64
+ $rewrite = array( 'slug' => $wpsl_settings['permalink_slug'] );
65
+
66
+ if ( $wpsl_settings['permalink_remove_front'] ) {
67
+ $rewrite['with_front'] = false;
68
+ }
69
+ } else {
70
+ $public = false;
71
+ $exclude_from_search = true;
72
+ $rewrite = false;
73
+ }
74
+
75
+ // The labels for the wpsl_stores post type.
76
+ $labels = apply_filters( 'wpsl_post_type_labels', array(
77
+ 'name' => __( 'Store Locator', 'wpsl' ),
78
+ 'all_items' => __( 'All Stores', 'wpsl' ),
79
+ 'singular_name' => __( 'Store', 'wpsl' ),
80
+ 'add_new' => __( 'New Store', 'wpsl' ),
81
+ 'add_new_item' => __( 'Add New Store', 'wpsl' ),
82
+ 'edit_item' => __( 'Edit Store', 'wpsl' ),
83
+ 'new_item' => __( 'New Store', 'wpsl' ),
84
+ 'view_item' => __( 'View Stores', 'wpsl' ),
85
+ 'search_items' => __( 'Search Stores', 'wpsl' ),
86
+ 'not_found' => __( 'No Stores found', 'wpsl' ),
87
+ 'not_found_in_trash' => __( 'No Stores found in trash', 'wpsl' ),
88
+ )
89
+ );
90
+
91
+ // The arguments for the wpsl_stores post type.
92
+ $args = apply_filters( 'wpsl_post_type_args', array(
93
+ 'labels' => $labels,
94
+ 'public' => $public,
95
+ 'exclude_from_search' => $exclude_from_search,
96
+ 'show_ui' => true,
97
+ 'menu_position' => apply_filters( 'wpsl_post_type_menu_position', null ),
98
+ 'capability_type' => 'store',
99
+ 'map_meta_cap' => true,
100
+ 'rewrite' => $rewrite,
101
+ 'query_var' => 'wpsl_stores',
102
+ 'supports' => array( 'title', 'editor', 'author', 'excerpt', 'revisions', 'thumbnail' ),
103
+ 'show_in_rest' => $this->maybe_show_in_rest()
104
+ )
105
+ );
106
+
107
+ register_post_type( 'wpsl_stores', $args );
108
+ }
109
+
110
+ /**
111
+ * Register the WPSL custom taxonomy.
112
+ *
113
+ * @since 2.0.0
114
+ * @return void
115
+ */
116
+ public function register_taxonomies() {
117
+
118
+ global $wpsl_settings;
119
+
120
+ // Enable permalinks for the taxonomy?
121
+ if ( isset( $wpsl_settings['permalinks'] ) && $wpsl_settings['permalinks'] ) {
122
+ $public = true;
123
+ $rewrite = array( 'slug' => $wpsl_settings['category_slug'] );
124
+ } else {
125
+ $public = false;
126
+ $rewrite = false;
127
+ }
128
+
129
+ $labels = array(
130
+ 'name' => __( 'Store Categories', 'wpsl' ),
131
+ 'singular_name' => __( 'Store Category', 'wpsl' ),
132
+ 'search_items' => __( 'Search Store Categories', 'wpsl' ),
133
+ 'all_items' => __( 'All Store Categories', 'wpsl' ),
134
+ 'parent_item' => __( 'Parent Store Category', 'wpsl' ),
135
+ 'parent_item_colon' => __( 'Parent Store Category:', 'wpsl' ),
136
+ 'edit_item' => __( 'Edit Store Category', 'wpsl' ),
137
+ 'update_item' => __( 'Update Store Category', 'wpsl' ),
138
+ 'add_new_item' => __( 'Add New Store Category', 'wpsl' ),
139
+ 'new_item_name' => __( 'New Store Category Name', 'wpsl' ),
140
+ 'menu_name' => __( 'Store Categories', 'wpsl' ),
141
+ );
142
+
143
+ $args = apply_filters( 'wpsl_store_category_args', array(
144
+ 'labels' => $labels,
145
+ 'public' => $public,
146
+ 'hierarchical' => true,
147
+ 'show_ui' => true,
148
+ 'show_admin_column' => true,
149
+ 'update_count_callback' => '_update_post_term_count',
150
+ 'query_var' => true,
151
+ 'rewrite' => $rewrite,
152
+ 'show_in_rest' => $this->maybe_show_in_rest()
153
+ )
154
+ );
155
+
156
+ register_taxonomy( 'wpsl_store_category', 'wpsl_stores', $args );
157
+ }
158
+
159
+ /**
160
+ * Change the default "Enter title here" placeholder.
161
+ *
162
+ * @since 2.0.0
163
+ * @param string $title The default title placeholder
164
+ * @return string $title The new title placeholder
165
+ */
166
+ public function change_default_title( $title ) {
167
+
168
+ $screen = get_current_screen();
169
+
170
+ if ( $screen->post_type == 'wpsl_stores' ) {
171
+ $title = __( 'Enter store title here', 'wpsl' );
172
+ }
173
+
174
+ return $title;
175
+ }
176
+
177
+ /**
178
+ * Add new columns to the store list table.
179
+ *
180
+ * @since 2.0.0
181
+ * @param array $columns The default columns
182
+ * @return array $columns Updated column list
183
+ */
184
+ public function edit_columns( $columns ) {
185
+
186
+ $columns['address'] = __( 'Address', 'wpsl' );
187
+ $columns['city'] = __( 'City', 'wpsl' );
188
+ $columns['state'] = __( 'State', 'wpsl' );
189
+ $columns['zip'] = __( 'Zip', 'wpsl' );
190
+
191
+ return $columns;
192
+ }
193
+
194
+ /**
195
+ * Show the correct store content in the correct custom column.
196
+ *
197
+ * @since 2.0.0
198
+ * @param string $column The column name
199
+ * @param int $post_id The post id
200
+ * @return void
201
+ */
202
+ public function custom_columns( $column, $post_id ) {
203
+
204
+ switch ( $column ) {
205
+ case 'address':
206
+ echo esc_html( get_post_meta( $post_id, 'wpsl_address', true ) );
207
+ break;
208
+ case 'city':
209
+ echo esc_html( get_post_meta( $post_id, 'wpsl_city', true ) );
210
+ break;
211
+ case 'state':
212
+ echo esc_html( get_post_meta( $post_id, 'wpsl_state', true ) );
213
+ break;
214
+ case 'zip':
215
+ echo esc_html( get_post_meta( $post_id, 'wpsl_zip', true ) );
216
+ break;
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Define the columns that are sortable.
222
+ *
223
+ * @since 2.0.0
224
+ * @param array $columns List of sortable columns
225
+ * @return array
226
+ */
227
+ public function sortable_columns( $columns ) {
228
+
229
+ $custom = array(
230
+ 'address' => 'wpsl_address',
231
+ 'city' => 'wpsl_city',
232
+ 'state' => 'wpsl_state',
233
+ 'zip' => 'wpsl_zip'
234
+ );
235
+
236
+ return wp_parse_args( $custom, $columns );
237
+ }
238
+
239
+ /**
240
+ * Set the correct column sort parameters.
241
+ *
242
+ * @since 2.0.0
243
+ * @param array $vars Column sorting parameters
244
+ * @return array $vars The column sorting parameters inc the correct orderby and wpsl meta_key
245
+ */
246
+ public function sort_columns( $vars ) {
247
+
248
+ if ( isset( $vars['post_type'] ) && $vars['post_type'] == 'wpsl_stores' ) {
249
+ if ( isset( $vars['orderby'] ) ) {
250
+ if ( $vars['orderby'] === 'wpsl_address' ) {
251
+ $vars = array_merge( $vars, array(
252
+ 'meta_key' => 'wpsl_address',
253
+ 'orderby' => 'meta_value'
254
+ ) );
255
+ }
256
+
257
+ if ( $vars['orderby'] === 'wpsl_city' ) {
258
+ $vars = array_merge( $vars, array(
259
+ 'meta_key' => 'wpsl_city',
260
+ 'orderby' => 'meta_value'
261
+ ) );
262
+ }
263
+
264
+ if ( $vars['orderby'] === 'wpsl_state' ) {
265
+ $vars = array_merge( $vars, array(
266
+ 'meta_key' => 'wpsl_state',
267
+ 'orderby' => 'meta_value'
268
+ ) );
269
+ }
270
+
271
+ if ( $vars['orderby'] === 'wpsl_zip' ) {
272
+ $vars = array_merge( $vars, array(
273
+ 'meta_key' => 'wpsl_zip',
274
+ 'orderby' => 'meta_value'
275
+ ) );
276
+ }
277
+ }
278
+ }
279
+
280
+ return $vars;
281
+ }
282
+ }
283
+
284
  }
inc/class-templates.php CHANGED
@@ -1,153 +1,153 @@
1
- <?php
2
- /**
3
- * Handle the WPSL and Add-on templates
4
- *
5
- * @author Tijmen Smit
6
- * @since 2.2.11
7
- */
8
-
9
- if ( !defined( 'ABSPATH' ) ) exit;
10
-
11
- if ( !class_exists( 'WPSL_Templates' ) ) {
12
-
13
- class WPSL_Templates {
14
-
15
- /**
16
- * Get the list of available templates
17
- *
18
- * @since 2.2.11
19
- * @param string $type The template type to return
20
- * @return array|void
21
- */
22
- public function get_template_list( $type = 'store_locator' ) {
23
-
24
- $template_list = array();
25
-
26
- // Add the WPSL templates or the add-on templates.
27
- if ( $type == 'store_locator' ) {
28
- $template_list['store_locator'] = wpsl_get_templates();
29
- } else {
30
- $template_list = apply_filters( 'wpsl_template_list', $template_list );
31
- }
32
-
33
- if ( isset( $template_list[$type] ) && !empty( $template_list[$type] ) ) {
34
- return $template_list[$type];
35
- }
36
- }
37
-
38
- /**
39
- * Get the template details
40
- *
41
- * @since 2.2.11
42
- * @param string $used_template The name of the template
43
- * @param string $type The type of template data to load
44
- * @return array $template_data The template data ( id, name, path )
45
- */
46
- public function get_template_details( $used_template, $type = 'store_locator' ) {
47
-
48
- $used_template = ( empty( $used_template ) ) ? 'default' : $used_template;
49
- $templates = $this->get_template_list( $type );
50
- $template_data = '';
51
- $template_path = '';
52
-
53
- if ( $templates ) {
54
- // Grab the the correct template data from the available templates.
55
- foreach ( $templates as $template ) {
56
- if ( $used_template == $template['id'] ) {
57
- $template_data = $template;
58
- break;
59
- }
60
- }
61
- }
62
-
63
- // Old structure ( WPSL only ) was only the path, new structure ( add-ons ) expects the file name as well.
64
- if ( isset( $template_data['path'] ) && isset( $template_data['file_name'] ) ) {
65
- $template_path = $template_data['path'] . $template_data['file_name'];
66
- } else if ( isset( $template_data['path'] ) ) {
67
- $template_path = $template_data['path'];
68
- }
69
-
70
- // If no match exists, or the template file doesnt exist, then use the default template.
71
- if ( !$template_data || ( !file_exists( $template_path ) ) ) {
72
- $template_data = $this->get_default_template( $type );
73
-
74
- // If no template can be loaded, then show a msg to the admin user.
75
- if ( !$template_data && current_user_can( 'administrator' ) ) {
76
- echo '<p>' . sprintf( __( 'No template found for %s', 'wpsl' ), $type ) . '</p>';
77
- echo '<p>' . sprintf( __( 'Make sure you call the %sget_template_details%s function with the correct parameters.', 'wpsl' ), '<code>', '</code>' ) . '</p>';
78
- }
79
- }
80
-
81
- return $template_data;
82
- }
83
-
84
- /**
85
- * Locate the default template
86
- *
87
- * @since 2.2.11
88
- * @param string $type The type of default template to return
89
- * @return array $default The default template data
90
- */
91
- public function get_default_template( $type = 'store_locator' ) {
92
-
93
- $template_list = $this->get_template_list( $type );
94
- $default = '';
95
-
96
- if ( $template_list ) {
97
- foreach ( $template_list as $template ) {
98
- if ( $template['id'] == 'default' ) {
99
- $default = $template;
100
- break;
101
- }
102
- }
103
- }
104
-
105
- return $default;
106
- }
107
-
108
- /**
109
- * Include the template file.
110
- *
111
- * @since 2.2.11
112
- * @param array $args The template path details
113
- * @param array $template_data The template data ( address, phone, fax etc ).
114
- * @return string The location template.
115
- */
116
- function get_template( $args, $template_data ) {
117
-
118
- // Don't continue if not path and file name is set.
119
- if ( !isset( $args['path'] ) || !isset( $args['file_name'] ) ) {
120
- return;
121
- }
122
-
123
- ob_start();
124
-
125
- include( $this->find_template_path( $args ) );
126
-
127
- return ob_get_clean();
128
- }
129
-
130
- /**
131
- * Locate the template file in either the
132
- * theme folder or the plugin folder itself.
133
- *
134
- * @since 2.2.11
135
- * @param array $args The template data
136
- * @return string $template The path to the template.
137
- */
138
- function find_template_path( $args ) {
139
-
140
- // Look for the template in the theme folder.
141
- $template = locate_template(
142
- array( trailingslashit( 'wpsl-templates' ) . $args['file_name'], $args['file_name'] )
143
- );
144
-
145
- // If the template doesn't exist in the theme folder load the one from the plugin dir.
146
- if ( !$template ) {
147
- $template = $args['path'] . $args['file_name'];
148
- }
149
-
150
- return $template;
151
- }
152
- }
153
  }
1
+ <?php
2
+ /**
3
+ * Handle the WPSL and Add-on templates
4
+ *
5
+ * @author Tijmen Smit
6
+ * @since 2.2.11
7
+ */
8
+
9
+ if ( !defined( 'ABSPATH' ) ) exit;
10
+
11
+ if ( !class_exists( 'WPSL_Templates' ) ) {
12
+
13
+ class WPSL_Templates {
14
+
15
+ /**
16
+ * Get the list of available templates
17
+ *
18
+ * @since 2.2.11
19
+ * @param string $type The template type to return
20
+ * @return array|void
21
+ */
22
+ public function get_template_list( $type = 'store_locator' ) {
23
+
24
+ $template_list = array();
25
+
26
+ // Add the WPSL templates or the add-on templates.
27
+ if ( $type == 'store_locator' ) {
28
+ $template_list['store_locator'] = wpsl_get_templates();
29
+ } else {
30
+ $template_list = apply_filters( 'wpsl_template_list', $template_list );
31
+ }
32
+
33
+ if ( isset( $template_list[$type] ) && !empty( $template_list[$type] ) ) {
34
+ return $template_list[$type];
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Get the template details
40
+ *
41
+ * @since 2.2.11
42
+ * @param string $used_template The name of the template
43
+ * @param string $type The type of template data to load
44
+ * @return array $template_data The template data ( id, name, path )
45
+ */
46
+ public function get_template_details( $used_template, $type = 'store_locator' ) {
47
+
48
+ $used_template = ( empty( $used_template ) ) ? 'default' : $used_template;
49
+ $templates = $this->get_template_list( $type );
50
+ $template_data = '';
51
+ $template_path = '';
52
+
53
+ if ( $templates ) {
54
+ // Grab the the correct template data from the available templates.
55
+ foreach ( $templates as $template ) {
56
+ if ( $used_template == $template['id'] ) {
57
+ $template_data = $template;
58
+ break;
59
+ }
60
+ }
61
+ }
62
+
63
+ // Old structure ( WPSL only ) was only the path, new structure ( add-ons ) expects the file name as well.
64
+ if ( isset( $template_data['path'] ) && isset( $template_data['file_name'] ) ) {
65
+ $template_path = $template_data['path'] . $template_data['file_name'];
66
+ } else if ( isset( $template_data['path'] ) ) {
67
+ $template_path = $template_data['path'];
68
+ }
69
+
70
+ // If no match exists, or the template file doesnt exist, then use the default template.
71
+ if ( !$template_data || ( !file_exists( $template_path ) ) ) {
72
+ $template_data = $this->get_default_template( $type );
73
+
74
+ // If no template can be loaded, then show a msg to the admin user.
75
+ if ( !$template_data && current_user_can( 'administrator' ) ) {
76
+ echo '<p>' . sprintf( __( 'No template found for %s', 'wpsl' ), $type ) . '</p>';
77
+ echo '<p>' . sprintf( __( 'Make sure you call the %sget_template_details%s function with the correct parameters.', 'wpsl' ), '<code>', '</code>' ) . '</p>';
78
+ }
79
+ }
80
+
81
+ return $template_data;
82
+ }
83
+
84
+ /**
85
+ * Locate the default template
86
+ *
87
+ * @since 2.2.11
88
+ * @param string $type The type of default template to return
89
+ * @return array $default The default template data
90
+ */
91
+ public function get_default_template( $type = 'store_locator' ) {
92
+
93
+ $template_list = $this->get_template_list( $type );
94
+ $default = '';
95
+
96
+ if ( $template_list ) {
97
+ foreach ( $template_list as $template ) {
98
+ if ( $template['id'] == 'default' ) {
99
+ $default = $template;
100
+ break;
101
+ }
102
+ }
103
+ }
104
+
105
+ return $default;
106
+ }
107
+
108
+ /**
109
+ * Include the template file.
110
+ *
111
+ * @since 2.2.11
112
+ * @param array $args The template path details
113
+ * @param array $template_data The template data ( address, phone, fax etc ).
114
+ * @return string The location template.
115
+ */
116
+ function get_template( $args, $template_data ) {
117
+
118
+ // Don't continue if not path and file name is set.
119
+ if ( !isset( $args['path'] ) || !isset( $args['file_name'] ) ) {
120
+ return;
121
+ }
122
+
123
+ ob_start();
124
+
125
+ include( $this->find_template_path( $args ) );
126
+
127
+ return ob_get_clean();
128
+ }
129
+
130
+ /**
131
+ * Locate the template file in either the
132
+ * theme folder or the plugin folder itself.
133
+ *
134
+ * @since 2.2.11
135
+ * @param array $args The template data
136
+ * @return string $template The path to the template.
137
+ */
138
+ function find_template_path( $args ) {
139
+
140
+ // Look for the template in the theme folder.
141
+ $template = locate_template(
142
+ array( trailingslashit( 'wpsl-templates' ) . $args['file_name'], $args['file_name'] )
143
+ );
144
+
145
+ // If the template doesn't exist in the theme folder load the one from the plugin dir.
146
+ if ( !$template ) {
147
+ $template = $args['path'] . $args['file_name'];
148
+ }
149
+
150
+ return $template;
151
+ }
152
+ }
153
  }
inc/install.php CHANGED
@@ -1,65 +1,72 @@
1
- <?php
2
- /**
3
- * WPSL Install
4
- *
5
- * @author Tijmen Smit
6
- * @since 2.0.0
7
- */
8
-
9
- if ( !defined( 'ABSPATH' ) ) exit;
10
-
11
- /**
12
- * Run the install.
13
- *
14
- * @since 1.2.20
15
- * @return void
16
- */
17
- function wpsl_install( $network_wide ) {
18
-
19
- global $wpdb;
20
-
21
- if ( function_exists( 'is_multisite' ) && is_multisite() ) {
22
-
23
- if ( $network_wide ) {
24
- $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
25
-
26
- foreach ( $blog_ids as $blog_id ) {
27
- switch_to_blog( $blog_id );
28
- wpsl_install_data();
29
- }
30
-
31
- restore_current_blog();
32
- } else {
33
- wpsl_install_data();
34
- }
35
- } else {
36
- wpsl_install_data();
37
- }
38
- }
39
-
40
- /**
41
- * Install the required data.
42
- *
43
- * @since 1.2.20
44
- * @return void
45
- */
46
- function wpsl_install_data() {
47
-
48
- global $wpsl;
49
-
50
- // Register the post type and flush the permalinks.
51
- $wpsl->post_types->register_post_types();
52
- flush_rewrite_rules();
53
-
54
- // Create the default settings.
55
- wpsl_set_default_settings();
56
-
57
- // Set the correct version.
58
- update_option( 'wpsl_version', WPSL_VERSION_NUM );
59
-
60
- // Add user roles.
61
- wpsl_add_roles();
62
-
63
- // Add user capabilities.
64
- wpsl_add_caps();
 
 
 
 
 
 
 
65
  }
1
+ <?php
2
+ /**
3
+ * WPSL Install
4
+ *
5
+ * @author Tijmen Smit
6
+ * @since 2.0.0
7
+ */
8
+
9
+ if ( !defined( 'ABSPATH' ) ) exit;
10
+
11
+ /**
12
+ * Run the install.
13
+ *
14
+ * @since 1.2.20
15
+ * @return void
16
+ */
17
+ function wpsl_install( $network_wide ) {
18
+
19
+ global $wpdb;
20
+
21
+ if ( function_exists( 'is_multisite' ) && is_multisite() ) {
22
+
23
+ if ( $network_wide ) {
24
+ $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
25
+
26
+ foreach ( $blog_ids as $blog_id ) {
27
+ switch_to_blog( $blog_id );
28
+ wpsl_install_data();
29
+ }
30
+
31
+ restore_current_blog();
32
+ } else {
33
+ wpsl_install_data();
34
+ }
35
+ } else {
36
+ wpsl_install_data();
37
+ }
38
+
39
+ if ( function_exists( 'BorlabsCookieHelper' ) ) {
40
+ require_once( 'class-borlabs-cookie.php' );
41
+
42
+ $borlabs = New WPSL_Borlabs_Cookie();
43
+ $borlabs->enable();
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Install the required data.
49
+ *
50
+ * @since 1.2.20
51
+ * @return void
52
+ */
53
+ function wpsl_install_data() {
54
+
55
+ global $wpsl;
56
+
57
+ // Register the post type and flush the permalinks.
58
+ $wpsl->post_types->register_post_types();
59
+ flush_rewrite_rules();
60
+
61
+ // Create the default settings.
62
+ wpsl_set_default_settings();
63
+
64
+ // Set the correct version.
65
+ update_option( 'wpsl_version', WPSL_VERSION_NUM );
66
+
67
+ // Add user roles.
68
+ wpsl_add_roles();
69
+
70
+ // Add user capabilities.
71
+ wpsl_add_caps();
72
  }
inc/wpsl-functions.php CHANGED
@@ -1,675 +1,676 @@
1
- <?php
2
-
3
- /**
4
- * Collect all the parameters ( language, key, region )
5
- * we need before making a request to the Google Maps API.
6
- *
7
- * @since 1.0.0
8
- * @param string $api_key_type The type of API key we need to include ( server_key or browser_key ).
9
- * @param boolean $geocode_params
10
- * @return string $api_params The API parameters.
11
- */
12
- function wpsl_get_gmap_api_params( $api_key_type, $geocode_params = false ) {
13
-
14
- global $wpsl, $wpsl_settings;
15
-
16
- $api_params = '';
17
- $param_keys = array( 'language', 'region', 'key' );
18
-
19
- /*
20
- * The geocode params are included after the address so we need to
21
- * use a '&' as the first char, but when the maps script is included on
22
- * the front-end it does need to start with a '?'.
23
- */
24
- $first_sep = ( $geocode_params ) ? '&' : '?';
25
-
26
- foreach ( $param_keys as $param_key ) {
27
- $option_key = ( $param_key == 'key' ) ? $api_key_type : $param_key;
28
-
29
- /*
30
- * Get the current language code if WPML or qTranslate-X is active.
31
- * Otherwise get the param value from the settings var.
32
- */
33
- if ( $option_key == 'language' && ( $wpsl->i18n->wpml_exists() || $wpsl->i18n->qtrans_exists() ) ) {
34
- $param_val = $wpsl->i18n->check_multilingual_code();
35
- } else {
36
- $param_val = $wpsl_settings['api_' . $option_key];
37
- }
38
-
39
- if ( !empty( $param_val ) ) {
40
- $api_params .= $param_key . '=' . $param_val . '&';
41
- }
42
- }
43
-
44
- if ( $api_params ) {
45
- $api_params = $first_sep . rtrim( $api_params, '&' );
46
- }
47
-
48
- // Do we need to include the autocomplete library?
49
- if ( ( $wpsl_settings['autocomplete'] && $api_key_type == 'browser_key' ) || is_admin() ) {
50
- $api_params .= '&libraries=places';
51
- }
52
-
53
- if ( $api_key_type == 'browser_key' ) {
54
- $api_version = apply_filters( 'wpsl_gmap_api_version', '3.36' );
55
- $api_params .= '&v=' . $api_version;
56
- }
57
-
58
- return apply_filters( 'wpsl_gmap_api_params', $api_params );
59
- }
60
-
61
- /**
62
- * Get the default plugin settings.
63
- *
64
- * @since 1.0.0
65
- * @return array $default_settings The default settings
66
- */
67
- function wpsl_get_default_settings() {
68
-
69
- $default_settings = array(
70
- 'api_browser_key' => '',
71
- 'api_server_key' => '',
72
- 'api_language' => 'en',
73
- 'api_region' => '',
74
- 'api_geocode_component' => 0,
75
- 'distance_unit' => 'km',
76
- 'max_results' => '[25],50,75,100',
77
- 'search_radius' => '10,25,[50],100,200,500',
78
- 'force_postalcode' => 0,
79
- 'marker_effect' => 'bounce',
80
- 'address_format' => 'city_state_zip',
81
- 'hide_distance' => 0,
82
- 'hide_country' => 0,
83
- 'show_contact_details' => 0,
84
- 'clickable_contact_details' => 0,
85
- 'auto_locate' => 1,
86
- 'autocomplete' => 0,
87
- 'autoload' => 1,
88
- 'autoload_limit' => 50,
89
- 'run_fitbounds' => 1,
90
- 'zoom_level' => 3,
91
- 'auto_zoom_level' => 15,
92
- 'start_name' => '',
93
- 'start_latlng' => '',
94
- 'height' => 350,
95
- 'map_type' => 'roadmap',
96
- 'map_style' => '',
97
- 'type_control' => 0,
98
- 'streetview' => 0,
99
- 'results_dropdown' => 1,
100
- 'radius_dropdown' => 1,
101
- 'category_filter' => 0,
102
- 'category_filter_type' => 'dropdown',
103
- 'infowindow_width' => 225,
104
- 'search_width' => 179,
105
- 'label_width' => 95,
106
- 'control_position' => 'left',
107
- 'scrollwheel' => 1,
108
- 'marker_clusters' => 0,
109
- 'cluster_zoom' => 0,
110
- 'cluster_size' => 0,
111
- 'new_window' => 0,
112
- 'reset_map' => 0,
113
- 'template_id' => 'default',
114
- 'listing_below_no_scroll' => 0,
115
- 'direction_redirect' => 0,
116
- 'more_info' => 0,
117
- 'store_url' => 0,
118
- 'phone_url' => 0,
119
- 'marker_streetview' => 0,
120
- 'marker_zoom_to' => 0,
121
- 'more_info_location' => 'info window',
122
- 'mouse_focus' => 0,
123
- 'start_marker' => 'red.png',
124
- 'store_marker' => 'blue.png',
125
- 'editor_country' => '',
126
- 'editor_hours' => wpsl_default_opening_hours(),
127
- 'editor_hour_input' => 'dropdown',
128
- 'editor_hour_format' => 12,
129
- 'editor_map_type' => 'roadmap',
130
- 'hide_hours' => 0,
131
- 'permalinks' => 0,
132
- 'permalink_remove_front' => 0,
133
- 'permalink_slug' => __( 'stores', 'wpsl' ),
134
- 'category_slug' => __( 'store-category', 'wpsl' ),
135
- 'infowindow_style' => 'default',
136
- 'show_credits' => 0,
137
- 'debug' => 0,
138
- 'deregister_gmaps' => 0,
139
- 'start_label' => __( 'Start location', 'wpsl' ),
140
- 'search_label' => __( 'Your location', 'wpsl' ),
141
- 'search_btn_label' => __( 'Search', 'wpsl' ),
142
- 'preloader_label' => __( 'Searching...', 'wpsl' ),
143
- 'radius_label' => __( 'Search radius', 'wpsl' ),
144
- 'no_results_label' => __( 'No results found', 'wpsl' ),
145
- 'results_label' => __( 'Results', 'wpsl' ),
146
- 'more_label' => __( 'More info', 'wpsl' ),
147
- 'directions_label' => __( 'Directions', 'wpsl' ),
148
- 'no_directions_label' => __( 'No route could be found between the origin and destination', 'wpsl' ),
149
- 'back_label' => __( 'Back', 'wpsl' ),
150
- 'street_view_label' => __( 'Street view', 'wpsl' ),
151
- 'zoom_here_label' => __( 'Zoom here', 'wpsl' ),
152
- 'error_label' => __( 'Something went wrong, please try again!', 'wpsl' ),
153
- 'limit_label' => __( 'API usage limit reached', 'wpsl' ),
154
- 'phone_label' => __( 'Phone', 'wpsl' ),
155
- 'fax_label' => __( 'Fax', 'wpsl' ),
156
- 'email_label' => __( 'Email', 'wpsl' ),
157
- 'url_label' => __( 'Url', 'wpsl' ),
158
- 'hours_label' => __( 'Hours', 'wpsl' ),
159
- 'category_label' => __( 'Category filter', 'wpsl' ),
160
- 'category_default_label' => __( 'Any', 'wpsl' )
161
- );
162
-
163
- return $default_settings;
164
- }
165
-
166
- /**
167
- * Get the current plugin settings.
168
- *
169
- * @since 1.0.0
170
- * @return array $setting The current plugin settings
171
- */
172
- function wpsl_get_settings() {
173
-
174
- $settings = get_option( 'wpsl_settings' );
175
-
176
- if ( !$settings ) {
177
- update_option( 'wpsl_settings', wpsl_get_default_settings() );
178
- $settings = wpsl_get_default_settings();
179
- }
180
-
181
- return $settings;
182
- }
183
-
184
- /**
185
- * Get a single value from the default settings.
186
- *
187
- * @since 1.0.0
188
- * @param string $setting The value that should be restored
189
- * @return string $wpsl_default_settings The default setting value
190
- */
191
- function wpsl_get_default_setting( $setting ) {
192
-
193
- global $wpsl_default_settings;
194
-
195
- return $wpsl_default_settings[$setting];
196
- }
197
-
198
- /**
199
- * Set the default plugin settings.
200
- *
201
- * @since 1.0.0
202
- * @return void
203
- */
204
- function wpsl_set_default_settings() {
205
-
206
- $settings = get_option( 'wpsl_settings' );
207
-
208
- if ( !$settings ) {
209
- update_option( 'wpsl_settings', wpsl_get_default_settings() );
210
- }
211
- }
212
-
213
- /**
214
- * Return a list of the store templates.
215
- *
216
- * @since 1.2.20
217
- * @return array $templates The list of default store templates
218
- */
219
- function wpsl_get_templates() {
220
-
221
- $templates = array(
222
- array(
223
- 'id' => 'default',
224
- 'name' => __( 'Default', 'wpsl' ),
225
- 'path' => WPSL_PLUGIN_DIR . 'frontend/templates/default.php'
226
- ),
227
- array(
228
- 'id' => 'below_map',
229
- 'name' => __( 'Show the store list below the map', 'wpsl' ),
230
- 'path' => WPSL_PLUGIN_DIR . 'frontend/templates/store-listings-below.php'
231
- )
232
- );
233
-
234
- return apply_filters( 'wpsl_templates', $templates );
235
- }
236
-
237
- /**
238
- * Return the days of the week.
239
- *
240
- * @since 2.0.0
241
- * @return array $weekdays The days of the week
242
- */
243
- function wpsl_get_weekdays() {
244
-
245
- $weekdays = array(
246
- 'monday' => __( 'Monday', 'wpsl' ),
247
- 'tuesday' => __( 'Tuesday', 'wpsl' ),
248
- 'wednesday' => __( 'Wednesday', 'wpsl' ),
249
- 'thursday' => __( 'Thursday', 'wpsl' ),
250
- 'friday' => __( 'Friday', 'wpsl' ),
251
- 'saturday' => __( 'Saturday', 'wpsl' ),
252
- 'sunday' => __( 'Sunday' , 'wpsl' )
253
- );
254
-
255
- return $weekdays;
256
- }
257
-
258
- /**
259
- * Get the default opening hours.
260
- *
261
- * @since 2.0.0
262
- * @return array $opening_hours The default opening hours
263
- */
264
- function wpsl_default_opening_hours() {
265
-
266
- $current_version = get_option( 'wpsl_version' );
267
-
268
- $opening_hours = array(
269
- 'dropdown' => array(
270
- 'monday' => array( '9:00 AM,5:00 PM' ),
271
- 'tuesday' => array( '9:00 AM,5:00 PM' ),
272
- 'wednesday' => array( '9:00 AM,5:00 PM' ),
273
- 'thursday' => array( '9:00 AM,5:00 PM' ),
274
- 'friday' => array( '9:00 AM,5:00 PM' ),
275
- 'saturday' => '',
276
- 'sunday' => ''
277
- )
278
- );
279
-
280
- /* Only add the textarea defaults for users that upgraded from 1.x */
281
- if ( version_compare( $current_version, '2.0', '<' ) ) {
282
- $opening_hours['textarea'] = sprintf( __( 'Mon %sTue %sWed %sThu %sFri %sSat Closed %sSun Closed', 'wpsl' ), '9:00 AM - 5:00 PM' . "\n", '9:00 AM - 5:00 PM' . "\n", '9:00 AM - 5:00 PM' . "\n", '9:00 AM - 5:00 PM' . "\n", '9:00 AM - 5:00 PM' . "\n", "\n" ); //cleaner way without repeating it 5 times??
283
- }
284
-
285
- return $opening_hours;
286
- }
287
-
288
- /**
289
- * Get the available map types.
290
- *
291
- * @since 2.0.0
292
- * @return array $map_types The available map types
293
- */
294
- function wpsl_get_map_types() {
295
-
296
- $map_types = array(
297
- 'roadmap' => __( 'Roadmap', 'wpsl' ),
298
- 'satellite' => __( 'Satellite', 'wpsl' ),
299
- 'hybrid' => __( 'Hybrid', 'wpsl' ),
300
- 'terrain' => __( 'Terrain', 'wpsl' )
301
- );
302
-
303
- return $map_types;
304
- }
305
-
306
- /**
307
- * Get the address formats.
308
- *
309
- * @since 2.0.0
310
- * @return array $address_formats The address formats
311
- */
312
- function wpsl_get_address_formats() {
313
-
314
- $address_formats = array(
315
- 'city_state_zip' => __( '(city) (state) (zip code)', 'wpsl' ),
316
- 'city_comma_state_zip' => __( '(city), (state) (zip code)', 'wpsl' ),
317
- 'city_zip' => __( '(city) (zip code)', 'wpsl' ),
318
- 'city_comma_zip' => __( '(city), (zip code)', 'wpsl' ),
319
- 'zip_city_state' => __( '(zip code) (city) (state)', 'wpsl' ),
320
- 'zip_city' => __( '(zip code) (city)', 'wpsl' )
321
- );
322
-
323
- return apply_filters( 'wpsl_address_formats', $address_formats );
324
- }
325
-
326
- /**
327
- * Make sure the provided map type is valid.
328
- *
329
- * If the map type is invalid the default is used ( roadmap ).
330
- *
331
- * @since 2.0.0
332
- * @param string $map_type The provided map type
333
- * @return string $map_type A valid map type
334
- */
335
- function wpsl_valid_map_type( $map_type ) {
336
-
337
- $allowed_map_types = wpsl_get_map_types();
338
-
339
- if ( !array_key_exists( $map_type, $allowed_map_types ) ) {
340
- $map_type = wpsl_get_default_setting( 'map_type' );
341
- }
342
-
343
- return $map_type;
344
- }
345
-
346
- /**
347
- * Make sure the provided zoom level is valid.
348
- *
349
- * If the zoom level is invalid the default is used ( 3 ).
350
- *
351
- * @since 2.0.0
352
- * @param string $zoom_level The provided zoom level
353
- * @return string $zoom_level A valid zoom level
354
- */
355
- function wpsl_valid_zoom_level( $zoom_level ) {
356
-
357
- $zoom_level = absint( $zoom_level );
358
-
359
- if ( ( $zoom_level < 1 ) || ( $zoom_level > 21 ) ) {
360
- $zoom_level = wpsl_get_default_setting( 'zoom_level' );
361
- }
362
-
363
- return $zoom_level;
364
- }
365
-
366
- /**
367
- * Get the max auto zoom levels for the map.
368
- *
369
- * @since 2.0.0
370
- * @return array $max_zoom_levels The array holding the min - max zoom levels
371
- */
372
- function wpsl_get_max_zoom_levels() {
373
-
374
- $max_zoom_levels = array();
375
- $zoom_level = array(
376
- 'min' => 10,
377
- 'max' => 21
378
- );
379
-
380
- $i = $zoom_level['min'];
381
-
382
- while ( $i <= $zoom_level['max'] ) {
383
- $max_zoom_levels[$i] = $i;
384
- $i++;
385
- }
386
-
387
- return $max_zoom_levels;
388
- }
389
-
390
- /**
391
- * The labels and the values that can be set through the settings page.
392
- *
393
- * @since 2.0.0
394
- * @return array $labels The label names from the settings page.
395
- */
396
- function wpsl_labels() {
397
-
398
- $labels = array(
399
- 'search',
400
- 'search_btn',
401
- 'preloader',
402
- 'radius',
403
- 'no_results',
404
- 'results',
405
- 'more',
406
- 'directions',
407
- 'no_directions',
408
- 'back',
409
- 'street_view',
410
- 'zoom_here',
411
- 'error',
412
- 'phone',
413
- 'fax',
414
- 'email',
415
- 'url',
416
- 'hours',
417
- 'start',
418
- 'limit',
419
- 'category',
420
- 'category_default'
421
- );
422
-
423
- return $labels;
424
- }
425
-
426
- /**
427
- * Callback for array_walk_recursive, sanitize items in a multidimensional array.
428
- *
429
- * @since 2.0.0
430
- * @param string $item The value
431
- * @param integer $key The key
432
- */
433
- function wpsl_sanitize_multi_array( &$item, $key ) {
434
- $item = sanitize_text_field( $item );
435
- }
436
-
437
- /**
438
- * Check whether the array is multidimensional.
439
- *
440
- * @since 2.0.0
441
- * @param array $array The array to check
442
- * @return boolean
443
- */
444
- function wpsl_is_multi_array( $array ) {
445
-
446
- foreach ( $array as $value ) {
447
- if ( is_array( $value ) ) return true;
448
- }
449
-
450
- return false;
451
- }
452
-
453
- /**
454
- * @since 2.1.1
455
- * @param string $address The address to geocode.
456
- * @return array $response Either a WP_Error or the response from the Geocode API.
457
- */
458
- function wpsl_call_geocode_api( $address ) {
459
-
460
- $url = 'https://maps.googleapis.com/maps/api/geocode/json?address=' . urlencode( $address ) . wpsl_get_gmap_api_params( 'server_key', true );
461
- $response = wp_remote_get( $url );
462
-
463
- return $response;
464
- }
465
-
466
- /**
467
- * Get the latlng for the provided address.
468
- *
469
- * This is used to geocode the address set as the start point on
470
- * the settings page in case the autocomplete fails
471
- * ( only happens when there is a JS error on the page ),
472
- * or to get the latlng when the 'start_location' attr is set
473
- * on the wpsl shortcode.
474
- *
475
- * @since 2.2
476
- * @param string $address The address to geocode.
477
- * @return array|void $latlng The returned latlng or nothing if there was an error.
478
- */
479
- function wpsl_get_address_latlng( $address ) {
480
-
481
- $latlng = '';
482
- $response = wpsl_call_geocode_api( $address );
483
-
484
- if ( !is_wp_error( $response ) ) {
485
- $response = json_decode( $response['body'], true );
486
-
487
- if ( $response['status'] == 'OK' ) {
488
- $latlng = $response['results'][0]['geometry']['location']['lat'] . ',' . $response['results'][0]['geometry']['location']['lng'];
489
- }
490
- }
491
-
492
- return $latlng;
493
- }
494
-
495
- /**
496
- * Check if there's a transient that holds
497
- * the coordinates for the passed address.
498
- *
499
- * If not, then we geocode the address and
500
- * set the returned value in the transient.
501
- *
502
- * @since 2.2.11
503
- * @param string $address The location to geocode
504
- * @return string $latlng The coordinates of the geocoded location
505
- */
506
- function wpsl_check_latlng_transient( $address ) {
507
-
508
- $name_section = explode( ',', $address );
509
- $transient_name = 'wpsl_' . trim( strtolower( $name_section[0] ) ) . '_latlng';
510
-
511
- if ( false === ( $latlng = get_transient( $transient_name ) ) ) {
512
- $latlng = wpsl_get_address_latlng( $address );
513
-
514
- if ( $latlng ) {
515
- set_transient( $transient_name, $latlng, 0 );
516
- }
517
- }
518
-
519
- return $latlng;
520
- }
521
-
522
- /**
523
- * Make sure the shortcode attributes are booleans
524
- * when they are expected to be.
525
- *
526
- * @since 2.0.4
527
- * @param array $atts Shortcode attributes
528
- * @return array $atts Shortcode attributes
529
- */
530
- function wpsl_bool_check( $atts ) {
531
-
532
- foreach ( $atts as $key => $val ) {
533
- if ( in_array( $val, array( 'true', '1', 'yes', 'on' ) ) ) {
534
- $atts[$key] = true;
535
- } else if ( in_array( $val, array( 'false', '0', 'no', 'off' ) ) ) {
536
- $atts[$key] = false;
537
- }
538
- }
539
-
540
- return $atts;
541
- }
542
-
543
- /**
544
- * Create a string with random characters.
545
- *
546
- * @since 2.2.4
547
- * @param int $length Used length
548
- * @return string $random_chars Random characters
549
- */
550
- function wpsl_random_chars( $length = 5 ) {
551
-
552
- $random_chars = substr( str_shuffle( "abcdefghijklmnopqrstuvwxyz" ), 0, $length );
553
-
554
- return $random_chars;
555
- }
556
-
557
- /**
558
- * Deregister other Google Maps scripts.
559
- *
560
- * If plugins / themes also include the Google Maps library, then it can cause
561
- * problems with the autocomplete function on the settings page and break
562
- * the store locator on the front-end.
563
- *
564
- * @since 2.2.4
565
- * @return void
566
- */
567
- function wpsl_deregister_other_gmaps() {
568
-
569
- global $wp_scripts;
570
-
571
- foreach ( $wp_scripts->registered as $index => $script ) {
572
- if ( ( strpos( $script->src, 'maps.google.com' ) !== false ) || ( strpos( $script->src, 'maps.googleapis.com' ) !== false ) && ( $script->handle !== 'wpsl-gmap' ) ) {
573
- wp_deregister_script( $script->handle );
574
- }
575
- }
576
- }
577
-
578
- /**
579
- * Return the used distance unit.
580
- *
581
- * @since 2.2.8
582
- * @return string Either km or mi
583
- */
584
- function wpsl_get_distance_unit() {
585
-
586
- global $wpsl_settings;
587
-
588
- return apply_filters( 'wpsl_distance_unit', $wpsl_settings['distance_unit'] );
589
- }
590
-
591
- /**
592
- * Find the term ids for the provided term slugs.
593
- *
594
- * @since 2.2.10
595
- * @param array $cat_list List of term slugs
596
- * @return array $term_ids The term ids
597
- */
598
- function wpsl_get_term_ids( $cat_list ) {
599
-
600
- $term_ids = array();
601
- $cats = explode( ',', $cat_list );
602
-
603
- foreach ( $cats as $key => $term_slug ) {
604
- $term_data = get_term_by( 'slug', $term_slug, 'wpsl_store_category' );
605
-
606
- if ( isset( $term_data->term_id ) && $term_data->term_id ) {
607
- $term_ids[] = $term_data->term_id;
608
- }
609
- }
610
-
611
- return $term_ids;
612
- }
613
-
614
- /**
615
- * Get the url to the admin-ajax.php
616
- *
617
- * @since 2.2.3
618
- * @return string $ajax_url URL to the admin-ajax.php possibly with the WPML lang param included.
619
- */
620
- function wpsl_get_ajax_url() {
621
-
622
- $i18n = new WPSL_i18n();
623
-
624
- $param = '';
625
-
626
- if ( $i18n->wpml_exists() && defined( 'ICL_LANGUAGE_CODE' ) ) {
627
- $param = '?lang=' . ICL_LANGUAGE_CODE;
628
- }
629
-
630
- $ajax_url = admin_url( 'admin-ajax.php' . $param );
631
-
632
- return $ajax_url;
633
- }
634
-
635
- /**
636
- * Get a list of the used meta fields.
637
- *
638
- * Used by add-ons and the REST-API.
639
- *
640
- * @since 2.2.14
641
- * @param array $args Argument to grab the locations field. See the $defaults structure.
642
- * @return array $fields
643
- */
644
- function wpsl_get_location_fields( $args = array() ) {
645
-
646
- // Required to make sure it works with API calls.
647
- if ( !class_exists( 'WPSL_Metaboxes' ) ) {
648
- require_once( WPSL_PLUGIN_DIR . 'admin/class-metaboxes.php' );
649
- }
650
-
651
- $metaboxes = new WPSL_Metaboxes();
652
- $meta_fields = $metaboxes->meta_box_fields();
653
-
654
- $fields = array();
655
- $defaults = array(
656
- 'exclude' => array( 'country_iso' ),
657
- 'prefix' => '',
658
- 'set_values' => true
659
- );
660
-
661
- /**
662
- * Parse incoming $args into an array and merge it with $defaults
663
- */
664
- $args = wp_parse_args( $args, $defaults );
665
-
666
- foreach ( $meta_fields as $k => $field_section ) {
667
- foreach ( $field_section as $field_name => $field_value ) {
668
- if ( in_array( $field_name, $args['exclude'] ) ) {
669
- continue;
670
- }
671
- $fields[$args['prefix'] . $field_name] = ( $args['set_values'] ) ? $field_name : '';
672
- }
673
- }
674
- return $fields;
 
675
  }
1
+ <?php
2
+
3
+ /**
4
+ * Collect all the parameters ( language, key, region )
5
+ * we need before making a request to the Google Maps API.
6
+ *
7
+ * @since 1.0.0
8
+ * @param string $api_key_type The type of API key we need to include ( server_key or browser_key ).
9
+ * @param boolean $geocode_params
10
+ * @return string $api_params The API parameters.
11
+ */
12
+ function wpsl_get_gmap_api_params( $api_key_type, $geocode_params = false ) {
13
+
14
+ global $wpsl, $wpsl_settings;
15
+
16
+ $api_params = '';
17
+ $param_keys = array( 'language', 'region', 'key' );
18
+
19
+ /*
20
+ * The geocode params are included after the address so we need to
21
+ * use a '&' as the first char, but when the maps script is included on
22
+ * the front-end it does need to start with a '?'.
23
+ */
24
+ $first_sep = ( $geocode_params ) ? '&' : '?';
25
+
26
+ foreach ( $param_keys as $param_key ) {
27
+ $option_key = ( $param_key == 'key' ) ? $api_key_type : $param_key;
28
+
29
+ /*
30
+ * Get the current language code if WPML or qTranslate-X is active.
31
+ * Otherwise get the param value from the settings var.
32
+ */
33
+ if ( $option_key == 'language' && ( $wpsl->i18n->wpml_exists() || $wpsl->i18n->qtrans_exists() ) ) {
34
+ $param_val = $wpsl->i18n->check_multilingual_code();
35
+ } else {
36
+ $param_val = $wpsl_settings['api_' . $option_key];
37
+ }
38
+
39
+ if ( !empty( $param_val ) ) {
40
+ $api_params .= $param_key . '=' . $param_val . '&';
41
+ }
42
+ }
43
+
44
+ if ( $api_params ) {
45
+ $api_params = $first_sep . rtrim( $api_params, '&' );
46
+ }
47
+
48
+ // Do we need to include the autocomplete library?
49
+ if ( ( $wpsl_settings['autocomplete'] && $api_key_type == 'browser_key' ) || is_admin() ) {
50
+ $api_params .= '&libraries=places';
51
+ }
52
+
53
+ if ( $api_key_type == 'browser_key' ) {
54
+ $api_version = apply_filters( 'wpsl_gmap_api_version', '3.36' );
55
+ $api_params .= '&v=' . $api_version;
56
+ }
57
+
58
+ return apply_filters( 'wpsl_gmap_api_params', $api_params );
59
+ }
60
+
61
+ /**
62
+ * Get the default plugin settings.
63
+ *
64
+ * @since 1.0.0
65
+ * @return array $default_settings The default settings
66
+ */
67
+ function wpsl_get_default_settings() {
68
+
69
+ $default_settings = array(
70
+ 'api_browser_key' => '',
71
+ 'api_server_key' => '',
72
+ 'api_language' => 'en',
73
+ 'api_region' => '',
74
+ 'api_geocode_component' => 0,
75
+ 'distance_unit' => 'km',
76
+ 'max_results' => '[25],50,75,100',
77
+ 'search_radius' => '10,25,[50],100,200,500',
78
+ 'force_postalcode' => 0,
79
+ 'marker_effect' => 'bounce',
80
+ 'address_format' => 'city_state_zip',
81
+ 'hide_distance' => 0,
82
+ 'hide_country' => 0,
83
+ 'show_contact_details' => 0,
84
+ 'clickable_contact_details' => 0,
85
+ 'auto_locate' => 1,
86
+ 'autocomplete' => 0,
87
+ 'autoload' => 1,
88
+ 'autoload_limit' => 50,
89
+ 'run_fitbounds' => 1,
90
+ 'zoom_level' => 3,
91
+ 'auto_zoom_level' => 15,
92
+ 'start_name' => '',
93
+ 'start_latlng' => '',
94
+ 'height' => 350,
95
+ 'map_type' => 'roadmap',
96
+ 'map_style' => '',
97
+ 'type_control' => 0,
98
+ 'streetview' => 0,
99
+ 'results_dropdown' => 1,
100
+ 'radius_dropdown' => 1,
101
+ 'category_filter' => 0,
102
+ 'category_filter_type' => 'dropdown',
103
+ 'infowindow_width' => 225,
104
+ 'search_width' => 179,
105
+ 'label_width' => 95,
106
+ 'control_position' => 'left',
107
+ 'scrollwheel' => 1,
108
+ 'marker_clusters' => 0,
109
+ 'cluster_zoom' => 0,
110
+ 'cluster_size' => 0,
111
+ 'new_window' => 0,
112
+ 'reset_map' => 0,
113
+ 'template_id' => 'default',
114
+ 'listing_below_no_scroll' => 0,
115
+ 'direction_redirect' => 0,
116
+ 'more_info' => 0,
117
+ 'store_url' => 0,
118
+ 'phone_url' => 0,
119
+ 'marker_streetview' => 0,
120
+ 'marker_zoom_to' => 0,
121
+ 'more_info_location' => 'info window',
122
+ 'mouse_focus' => 0,
123
+ 'start_marker' => 'red.png',
124
+ 'store_marker' => 'blue.png',
125
+ 'editor_country' => '',
126
+ 'editor_hours' => wpsl_default_opening_hours(),
127
+ 'editor_hour_input' => 'dropdown',
128
+ 'editor_hour_format' => 12,
129
+ 'editor_map_type' => 'roadmap',
130
+ 'hide_hours' => 0,
131
+ 'permalinks' => 0,
132
+ 'permalink_remove_front' => 0,
133
+ 'permalink_slug' => __( 'stores', 'wpsl' ),
134
+ 'category_slug' => __( 'store-category', 'wpsl' ),
135
+ 'infowindow_style' => 'default',
136
+ 'show_credits' => 0,
137
+ 'debug' => 0,
138
+ 'deregister_gmaps' => 0,
139
+ 'delay_loading' => 0,
140
+ 'start_label' => __( 'Start location', 'wpsl' ),
141
+ 'search_label' => __( 'Your location', 'wpsl' ),
142
+ 'search_btn_label' => __( 'Search', 'wpsl' ),
143
+ 'preloader_label' => __( 'Searching...', 'wpsl' ),
144
+ 'radius_label' => __( 'Search radius', 'wpsl' ),
145
+ 'no_results_label' => __( 'No results found', 'wpsl' ),
146
+ 'results_label' => __( 'Results', 'wpsl' ),
147
+ 'more_label' => __( 'More info', 'wpsl' ),
148
+ 'directions_label' => __( 'Directions', 'wpsl' ),
149
+ 'no_directions_label' => __( 'No route could be found between the origin and destination', 'wpsl' ),
150
+ 'back_label' => __( 'Back', 'wpsl' ),
151
+ 'street_view_label' => __( 'Street view', 'wpsl' ),
152
+ 'zoom_here_label' => __( 'Zoom here', 'wpsl' ),
153
+ 'error_label' => __( 'Something went wrong, please try again!', 'wpsl' ),
154
+ 'limit_label' => __( 'API usage limit reached', 'wpsl' ),
155
+ 'phone_label' => __( 'Phone', 'wpsl' ),
156
+ 'fax_label' => __( 'Fax', 'wpsl' ),
157
+ 'email_label' => __( 'Email', 'wpsl' ),
158
+ 'url_label' => __( 'Url', 'wpsl' ),
159
+ 'hours_label' => __( 'Hours', 'wpsl' ),
160
+ 'category_label' => __( 'Category filter', 'wpsl' ),
161
+ 'category_default_label' => __( 'Any', 'wpsl' )
162
+ );
163
+
164
+ return $default_settings;
165
+ }
166
+
167
+ /**
168
+ * Get the current plugin settings.
169
+ *
170
+ * @since 1.0.0
171
+ * @return array $setting The current plugin settings
172
+ */
173
+ function wpsl_get_settings() {
174
+
175
+ $settings = get_option( 'wpsl_settings' );
176
+
177
+ if ( !$settings ) {
178
+ update_option( 'wpsl_settings', wpsl_get_default_settings() );
179
+ $settings = wpsl_get_default_settings();
180
+ }
181
+
182
+ return $settings;
183
+ }
184
+
185
+ /**
186
+ * Get a single value from the default settings.
187
+ *
188
+ * @since 1.0.0
189
+ * @param string $setting The value that should be restored
190
+ * @return string $wpsl_default_settings The default setting value
191
+ */
192
+ function wpsl_get_default_setting( $setting ) {
193
+
194
+ global $wpsl_default_settings;
195
+
196
+ return $wpsl_default_settings[$setting];
197
+ }
198
+
199
+ /**
200
+ * Set the default plugin settings.
201
+ *
202
+ * @since 1.0.0
203
+ * @return void
204
+ */
205
+ function wpsl_set_default_settings() {
206
+
207
+ $settings = get_option( 'wpsl_settings' );
208
+
209
+ if ( !$settings ) {
210
+ update_option( 'wpsl_settings', wpsl_get_default_settings() );
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Return a list of the store templates.
216
+ *
217
+ * @since 1.2.20
218
+ * @return array $templates The list of default store templates
219
+ */
220
+ function wpsl_get_templates() {
221
+
222
+ $templates = array(
223
+ array(
224
+ 'id' => 'default',
225
+ 'name' => __( 'Default', 'wpsl' ),
226
+ 'path' => WPSL_PLUGIN_DIR . 'frontend/templates/default.php'
227
+ ),
228
+ array(
229
+ 'id' => 'below_map',
230
+ 'name' => __( 'Show the store list below the map', 'wpsl' ),
231
+ 'path' => WPSL_PLUGIN_DIR . 'frontend/templates/store-listings-below.php'
232
+ )
233
+ );
234
+
235
+ return apply_filters( 'wpsl_templates', $templates );
236
+ }
237
+
238
+ /**
239
+ * Return the days of the week.
240
+ *
241
+ * @since 2.0.0
242
+ * @return array $weekdays The days of the week
243
+ */
244
+ function wpsl_get_weekdays() {
245
+
246
+ $weekdays = array(
247
+ 'monday' => __( 'Monday', 'wpsl' ),
248
+ 'tuesday' => __( 'Tuesday', 'wpsl' ),
249
+ 'wednesday' => __( 'Wednesday', 'wpsl' ),
250
+ 'thursday' => __( 'Thursday', 'wpsl' ),
251
+ 'friday' => __( 'Friday', 'wpsl' ),
252
+ 'saturday' => __( 'Saturday', 'wpsl' ),
253
+ 'sunday' => __( 'Sunday' , 'wpsl' )
254
+ );
255
+
256
+ return $weekdays;
257
+ }
258
+
259
+ /**
260
+ * Get the default opening hours.
261
+ *
262
+ * @since 2.0.0
263
+ * @return array $opening_hours The default opening hours
264
+ */
265
+ function wpsl_default_opening_hours() {
266
+
267
+ $current_version = get_option( 'wpsl_version' );
268
+
269
+ $opening_hours = array(
270
+ 'dropdown' => array(
271
+ 'monday' => array( '9:00 AM,5:00 PM' ),
272
+ 'tuesday' => array( '9:00 AM,5:00 PM' ),
273
+ 'wednesday' => array( '9:00 AM,5:00 PM' ),
274
+ 'thursday' => array( '9:00 AM,5:00 PM' ),
275
+ 'friday' => array( '9:00 AM,5:00 PM' ),
276
+ 'saturday' => '',
277
+ 'sunday' => ''
278
+ )
279
+ );
280
+
281
+ /* Only add the textarea defaults for users that upgraded from 1.x */
282
+ if ( version_compare( $current_version, '2.0', '<' ) ) {
283
+ $opening_hours['textarea'] = sprintf( __( 'Mon %sTue %sWed %sThu %sFri %sSat Closed %sSun Closed', 'wpsl' ), '9:00 AM - 5:00 PM' . "\n", '9:00 AM - 5:00 PM' . "\n", '9:00 AM - 5:00 PM' . "\n", '9:00 AM - 5:00 PM' . "\n", '9:00 AM - 5:00 PM' . "\n", "\n" ); //cleaner way without repeating it 5 times??
284
+ }
285
+
286
+ return $opening_hours;
287
+ }
288
+
289
+ /**
290
+ * Get the available map types.
291
+ *
292
+ * @since 2.0.0
293
+ * @return array $map_types The available map types
294
+ */
295
+ function wpsl_get_map_types() {
296
+
297
+ $map_types = array(
298
+ 'roadmap' => __( 'Roadmap', 'wpsl' ),
299
+ 'satellite' => __( 'Satellite', 'wpsl' ),
300
+ 'hybrid' => __( 'Hybrid', 'wpsl' ),
301
+ 'terrain' => __( 'Terrain', 'wpsl' )
302
+ );
303
+
304
+ return $map_types;
305
+ }
306
+
307
+ /**
308
+ * Get the address formats.
309
+ *
310
+ * @since 2.0.0
311
+ * @return array $address_formats The address formats
312
+ */
313
+ function wpsl_get_address_formats() {
314
+
315
+ $address_formats = array(
316
+ 'city_state_zip' => __( '(city) (state) (zip code)', 'wpsl' ),
317
+ 'city_comma_state_zip' => __( '(city), (state) (zip code)', 'wpsl' ),
318
+ 'city_zip' => __( '(city) (zip code)', 'wpsl' ),
319
+ 'city_comma_zip' => __( '(city), (zip code)', 'wpsl' ),
320
+ 'zip_city_state' => __( '(zip code) (city) (state)', 'wpsl' ),
321
+ 'zip_city' => __( '(zip code) (city)', 'wpsl' )
322
+ );
323
+
324
+ return apply_filters( 'wpsl_address_formats', $address_formats );
325
+ }
326
+
327
+ /**
328
+ * Make sure the provided map type is valid.
329
+ *
330
+ * If the map type is invalid the default is used ( roadmap ).
331
+ *
332
+ * @since 2.0.0
333
+ * @param string $map_type The provided map type
334
+ * @return string $map_type A valid map type
335
+ */
336
+ function wpsl_valid_map_type( $map_type ) {
337
+
338
+ $allowed_map_types = wpsl_get_map_types();
339
+
340
+ if ( !array_key_exists( $map_type, $allowed_map_types ) ) {
341
+ $map_type = wpsl_get_default_setting( 'map_type' );
342
+ }
343
+
344
+ return $map_type;
345
+ }
346
+
347
+ /**
348
+ * Make sure the provided zoom level is valid.
349
+ *
350
+ * If the zoom level is invalid the default is used ( 3 ).
351
+ *
352
+ * @since 2.0.0
353
+ * @param string $zoom_level The provided zoom level
354
+ * @return string $zoom_level A valid zoom level
355
+ */
356
+ function wpsl_valid_zoom_level( $zoom_level ) {
357
+
358
+ $zoom_level = absint( $zoom_level );
359
+
360
+ if ( ( $zoom_level < 1 ) || ( $zoom_level > 21 ) ) {
361
+ $zoom_level = wpsl_get_default_setting( 'zoom_level' );
362
+ }
363
+
364
+ return $zoom_level;
365
+ }
366
+
367
+ /**
368
+ * Get the max auto zoom levels for the map.
369
+ *
370
+ * @since 2.0.0
371
+ * @return array $max_zoom_levels The array holding the min - max zoom levels
372
+ */
373
+ function wpsl_get_max_zoom_levels() {
374
+
375
+ $max_zoom_levels = array();
376
+ $zoom_level = array(
377
+ 'min' => 10,
378
+ 'max' => 21
379
+ );
380
+
381
+ $i = $zoom_level['min'];
382
+
383
+ while ( $i <= $zoom_level['max'] ) {
384
+ $max_zoom_levels[$i] = $i;
385
+ $i++;
386
+ }
387
+
388
+ return $max_zoom_levels;
389
+ }
390
+
391
+ /**
392
+ * The labels and the values that can be set through the settings page.
393
+ *
394
+ * @since 2.0.0
395
+ * @return array $labels The label names from the settings page.
396
+ */
397
+ function wpsl_labels() {
398
+
399
+ $labels = array(
400
+ 'search',
401
+ 'search_btn',
402
+ 'preloader',
403
+ 'radius',
404
+ 'no_results',
405
+ 'results',
406
+ 'more',
407
+ 'directions',
408
+ 'no_directions',
409
+ 'back',
410
+ 'street_view',
411
+ 'zoom_here',
412
+ 'error',
413
+ 'phone',
414
+ 'fax',
415
+ 'email',
416
+ 'url',
417
+ 'hours',
418
+ 'start',
419
+ 'limit',
420
+ 'category',
421
+ 'category_default'
422
+ );
423
+
424
+ return $labels;
425
+ }
426
+
427
+ /**
428
+ * Callback for array_walk_recursive, sanitize items in a multidimensional array.
429
+ *
430
+ * @since 2.0.0
431
+ * @param string $item The value
432
+ * @param integer $key The key
433
+ */
434
+ function wpsl_sanitize_multi_array( &$item, $key ) {
435
+ $item = sanitize_text_field( $item );
436
+ }
437
+
438
+ /**
439
+ * Check whether the array is multidimensional.
440
+ *
441
+ * @since 2.0.0
442
+ * @param array $array The array to check
443
+ * @return boolean
444
+ */
445
+ function wpsl_is_multi_array( $array ) {
446
+
447
+ foreach ( $array as $value ) {
448
+ if ( is_array( $value ) ) return true;
449
+ }
450
+
451
+ return false;
452
+ }
453
+
454
+ /**
455
+ * @since 2.1.1
456
+ * @param string $address The address to geocode.
457
+ * @return array $response Either a WP_Error or the response from the Geocode API.
458
+ */
459
+ function wpsl_call_geocode_api( $address ) {
460
+
461
+ $url = 'https://maps.googleapis.com/maps/api/geocode/json?address=' . urlencode( $address ) . wpsl_get_gmap_api_params( 'server_key', true );
462
+ $response = wp_remote_get( $url );
463
+
464
+ return $response;
465
+ }
466
+
467
+ /**
468
+ * Get the latlng for the provided address.
469
+ *
470
+ * This is used to geocode the address set as the start point on
471
+ * the settings page in case the autocomplete fails
472
+ * ( only happens when there is a JS error on the page ),
473
+ * or to get the latlng when the 'start_location' attr is set
474
+ * on the wpsl shortcode.
475
+ *
476
+ * @since 2.2
477
+ * @param string $address The address to geocode.
478
+ * @return array|void $latlng The returned latlng or nothing if there was an error.
479
+ */
480
+ function wpsl_get_address_latlng( $address ) {
481
+
482
+ $latlng = '';
483
+ $response = wpsl_call_geocode_api( $address );
484
+
485
+ if ( !is_wp_error( $response ) ) {
486
+ $response = json_decode( $response['body'], true );
487
+
488
+ if ( $response['status'] == 'OK' ) {
489
+ $latlng = $response['results'][0]['geometry']['location']['lat'] . ',' . $response['results'][0]['geometry']['location']['lng'];
490
+ }
491
+ }
492
+
493
+ return $latlng;
494
+ }
495
+
496
+ /**
497
+ * Check if there's a transient that holds
498
+ * the coordinates for the passed address.
499
+ *
500
+ * If not, then we geocode the address and
501
+ * set the returned value in the transient.
502
+ *
503
+ * @since 2.2.11
504
+ * @param string $address The location to geocode
505
+ * @return string $latlng The coordinates of the geocoded location
506
+ */
507
+ function wpsl_check_latlng_transient( $address ) {
508
+
509
+ $name_section = explode( ',', $address );
510
+ $transient_name = 'wpsl_' . trim( strtolower( $name_section[0] ) ) . '_latlng';
511
+
512
+ if ( false === ( $latlng = get_transient( $transient_name ) ) ) {
513
+ $latlng = wpsl_get_address_latlng( $address );
514
+
515
+ if ( $latlng ) {
516
+ set_transient( $transient_name, $latlng, 0 );
517
+ }
518
+ }
519
+
520
+ return $latlng;
521
+ }
522
+
523
+ /**
524
+ * Make sure the shortcode attributes are booleans
525
+ * when they are expected to be.
526
+ *
527
+ * @since 2.0.4
528
+ * @param array $atts Shortcode attributes
529
+ * @return array $atts Shortcode attributes
530
+ */
531
+ function wpsl_bool_check( $atts ) {
532
+
533
+ foreach ( $atts as $key => $val ) {
534
+ if ( in_array( $val, array( 'true', '1', 'yes', 'on' ) ) ) {
535
+ $atts[$key] = true;
536
+ } else if ( in_array( $val, array( 'false', '0', 'no', 'off' ) ) ) {
537
+ $atts[$key] = false;
538
+ }
539
+ }
540
+
541
+ return $atts;
542
+ }
543
+
544
+ /**
545
+ * Create a string with random characters.
546
+ *
547
+ * @since 2.2.4
548
+ * @param int $length Used length
549
+ * @return string $random_chars Random characters
550
+ */
551
+ function wpsl_random_chars( $length = 5 ) {
552
+
553
+ $random_chars = substr( str_shuffle( "abcdefghijklmnopqrstuvwxyz" ), 0, $length );
554
+
555
+ return $random_chars;
556
+ }
557
+
558
+ /**
559
+ * Deregister other Google Maps scripts.
560
+ *
561
+ * If plugins / themes also include the Google Maps library, then it can cause
562
+ * problems with the autocomplete function on the settings page and break
563
+ * the store locator on the front-end.
564
+ *
565
+ * @since 2.2.4
566
+ * @return void
567
+ */
568
+ function wpsl_deregister_other_gmaps() {
569
+
570
+ global $wp_scripts;
571
+
572
+ foreach ( $wp_scripts->registered as $index => $script ) {
573
+ if ( ( strpos( $script->src, 'maps.google.com' ) !== false ) || ( strpos( $script->src, 'maps.googleapis.com' ) !== false ) && ( $script->handle !== 'wpsl-gmap' ) ) {
574
+ wp_deregister_script( $script->handle );
575
+ }
576
+ }
577
+ }
578
+
579
+ /**
580
+ * Return the used distance unit.
581
+ *
582
+ * @since 2.2.8
583
+ * @return string Either km or mi
584
+ */
585
+ function wpsl_get_distance_unit() {
586
+
587
+ global $wpsl_settings;
588
+
589
+ return apply_filters( 'wpsl_distance_unit', $wpsl_settings['distance_unit'] );
590
+ }
591
+
592
+ /**
593
+ * Find the term ids for the provided term slugs.
594
+ *
595
+ * @since 2.2.10
596
+ * @param array $cat_list List of term slugs
597
+ * @return array $term_ids The term ids
598
+ */
599
+ function wpsl_get_term_ids( $cat_list ) {
600
+
601
+ $term_ids = array();
602
+ $cats = explode( ',', $cat_list );
603
+
604
+ foreach ( $cats as $key => $term_slug ) {
605
+ $term_data = get_term_by( 'slug', $term_slug, 'wpsl_store_category' );
606
+
607
+ if ( isset( $term_data->term_id ) && $term_data->term_id ) {
608
+ $term_ids[] = $term_data->term_id;
609
+ }
610
+ }
611
+
612
+ return $term_ids;
613
+ }
614
+
615
+ /**
616
+ * Get the url to the admin-ajax.php
617
+ *
618
+ * @since 2.2.3
619
+ * @return string $ajax_url URL to the admin-ajax.php possibly with the WPML lang param included.
620
+ */
621
+ function wpsl_get_ajax_url() {
622
+
623
+ $i18n = new WPSL_i18n();
624
+
625
+ $param = '';
626
+
627
+ if ( $i18n->wpml_exists() && defined( 'ICL_LANGUAGE_CODE' ) ) {
628
+ $param = '?lang=' . ICL_LANGUAGE_CODE;
629
+ }
630
+
631
+ $ajax_url = admin_url( 'admin-ajax.php' . $param );
632
+
633
+ return $ajax_url;
634
+ }
635
+
636
+ /**
637
+ * Get a list of the used meta fields.
638
+ *
639
+ * Used by add-ons and the REST-API.
640
+ *
641
+ * @since 2.2.14
642
+ * @param array $args Argument to grab the locations field. See the $defaults structure.
643
+ * @return array $fields
644
+ */
645
+ function wpsl_get_location_fields( $args = array() ) {
646
+
647
+ // Required to make sure it works with API calls.
648
+ if ( !class_exists( 'WPSL_Metaboxes' ) ) {
649
+ require_once( WPSL_PLUGIN_DIR . 'admin/class-metaboxes.php' );
650
+ }
651
+
652
+ $metaboxes = new WPSL_Metaboxes();
653
+ $meta_fields = $metaboxes->meta_box_fields();
654
+
655
+ $fields = array();
656
+ $defaults = array(
657
+ 'exclude' => array( 'country_iso' ),
658
+ 'prefix' => '',
659
+ 'set_values' => true
660
+ );
661
+
662
+ /**
663
+ * Parse incoming $args into an array and merge it with $defaults
664
+ */
665
+ $args = wp_parse_args( $args, $defaults );
666
+
667
+ foreach ( $meta_fields as $k => $field_section ) {
668
+ foreach ( $field_section as $field_name => $field_value ) {
669
+ if ( in_array( $field_name, $args['exclude'] ) ) {
670
+ continue;
671
+ }
672
+ $fields[$args['prefix'] . $field_name] = ( $args['set_values'] ) ? $field_name : '';
673
+ }
674
+ }
675
+ return $fields;
676
  }
js/infobox.js CHANGED
@@ -1,817 +1,817 @@
1
- /**
2
- * @name InfoBox
3
- * @version 1.1.13 [March 19, 2014]
4
- * @author Gary Little (inspired by proof-of-concept code from Pamela Fox of Google)
5
- * @copyright Copyright 2010 Gary Little [gary at luxcentral.com]
6
- * @fileoverview InfoBox extends the Google Maps JavaScript API V3 <tt>OverlayView</tt> class.
7
- * <p>
8
- * An InfoBox behaves like a <tt>google.maps.InfoWindow</tt>, but it supports several
9
- * additional properties for advanced styling. An InfoBox can also be used as a map label.
10
- * <p>
11
- * An InfoBox also fires the same events as a <tt>google.maps.InfoWindow</tt>.
12
- */
13
-
14
- /*!
15
- *
16
- * Licensed under the Apache License, Version 2.0 (the "License");
17
- * you may not use this file except in compliance with the License.
18
- * You may obtain a copy of the License at
19
- *
20
- * http://www.apache.org/licenses/LICENSE-2.0
21
- *
22
- * Unless required by applicable law or agreed to in writing, software
23
- * distributed under the License is distributed on an "AS IS" BASIS,
24
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25
- * See the License for the specific language governing permissions and
26
- * limitations under the License.
27
- */
28
-
29
- /*jslint browser:true */
30
- /*global google */
31
-
32
- /**
33
- * @name InfoBoxOptions
34
- * @class This class represents the optional parameter passed to the {@link InfoBox} constructor.
35
- * @property {string|Node} content The content of the InfoBox (plain text or an HTML DOM node).
36
- * @property {boolean} [disableAutoPan=false] Disable auto-pan on <tt>open</tt>.
37
- * @property {number} maxWidth The maximum width (in pixels) of the InfoBox. Set to 0 if no maximum.
38
- * @property {Size} pixelOffset The offset (in pixels) from the top left corner of the InfoBox
39
- * (or the bottom left corner if the <code>alignBottom</code> property is <code>true</code>)
40
- * to the map pixel corresponding to <tt>position</tt>.
41
- * @property {LatLng} position The geographic location at which to display the InfoBox.
42
- * @property {number} zIndex The CSS z-index style value for the InfoBox.
43
- * Note: This value overrides a zIndex setting specified in the <tt>boxStyle</tt> property.
44
- * @property {string} [boxClass="infoBox"] The name of the CSS class defining the styles for the InfoBox container.
45
- * @property {Object} [boxStyle] An object literal whose properties define specific CSS
46
- * style values to be applied to the InfoBox. Style values defined here override those that may
47
- * be defined in the <code>boxClass</code> style sheet. If this property is changed after the
48
- * InfoBox has been created, all previously set styles (except those defined in the style sheet)
49
- * are removed from the InfoBox before the new style values are applied.
50
- * @property {string} closeBoxMargin The CSS margin style value for the close box.
51
- * The default is "2px" (a 2-pixel margin on all sides).
52
- * @property {string} closeBoxURL The URL of the image representing the close box.
53
- * Note: The default is the URL for Google's standard close box.
54
- * Set this property to "" if no close box is required.
55
- * @property {Size} infoBoxClearance Minimum offset (in pixels) from the InfoBox to the
56
- * map edge after an auto-pan.
57
- * @property {boolean} [isHidden=false] Hide the InfoBox on <tt>open</tt>.
58
- * [Deprecated in favor of the <tt>visible</tt> property.]
59
- * @property {boolean} [visible=true] Show the InfoBox on <tt>open</tt>.
60
- * @property {boolean} alignBottom Align the bottom left corner of the InfoBox to the <code>position</code>
61
- * location (default is <tt>false</tt> which means that the top left corner of the InfoBox is aligned).
62
- * @property {string} pane The pane where the InfoBox is to appear (default is "floatPane").
63
- * Set the pane to "mapPane" if the InfoBox is being used as a map label.
64
- * Valid pane names are the property names for the <tt>google.maps.MapPanes</tt> object.
65
- * @property {boolean} enableEventPropagation Propagate mousedown, mousemove, mouseover, mouseout,
66
- * mouseup, click, dblclick, touchstart, touchend, touchmove, and contextmenu events in the InfoBox
67
- * (default is <tt>false</tt> to mimic the behavior of a <tt>google.maps.InfoWindow</tt>). Set
68
- * this property to <tt>true</tt> if the InfoBox is being used as a map label.
69
- */
70
-
71
- /**
72
- * Creates an InfoBox with the options specified in {@link InfoBoxOptions}.
73
- * Call <tt>InfoBox.open</tt> to add the box to the map.
74
- * @constructor
75
- * @param {InfoBoxOptions} [opt_opts]
76
- */
77
- function InfoBox(opt_opts) {
78
-
79
- opt_opts = opt_opts || {};
80
-
81
- google.maps.OverlayView.apply(this, arguments);
82
-
83
- // Standard options (in common with google.maps.InfoWindow):
84
- //
85
- this.content_ = opt_opts.content || "";
86
- this.disableAutoPan_ = opt_opts.disableAutoPan || false;
87
- this.maxWidth_ = opt_opts.maxWidth || 0;
88
- this.pixelOffset_ = opt_opts.pixelOffset || new google.maps.Size(0, 0);
89
- this.position_ = opt_opts.position || new google.maps.LatLng(0, 0);
90
- this.zIndex_ = opt_opts.zIndex || null;
91
-
92
- // Additional options (unique to InfoBox):
93
- //
94
- this.boxClass_ = opt_opts.boxClass || "infoBox";
95
- this.boxStyle_ = opt_opts.boxStyle || {};
96
- this.closeBoxMargin_ = opt_opts.closeBoxMargin || "2px";
97
- this.closeBoxURL_ = opt_opts.closeBoxURL || "http://www.google.com/intl/en_us/mapfiles/close.gif";
98
- if (opt_opts.closeBoxURL === "") {
99
- this.closeBoxURL_ = "";
100
- }
101
- this.infoBoxClearance_ = opt_opts.infoBoxClearance || new google.maps.Size(1, 1);
102
-
103
- if (typeof opt_opts.visible === "undefined") {
104
- if (typeof opt_opts.isHidden === "undefined") {
105
- opt_opts.visible = true;
106
- } else {
107
- opt_opts.visible = !opt_opts.isHidden;
108
- }
109
- }
110
- this.isHidden_ = !opt_opts.visible;
111
-
112
- this.alignBottom_ = opt_opts.alignBottom || false;
113
- this.pane_ = opt_opts.pane || "floatPane";
114
- this.enableEventPropagation_ = opt_opts.enableEventPropagation || false;
115
-
116
- this.div_ = null;
117
- this.closeListener_ = null;
118
- this.moveListener_ = null;
119
- this.contextListener_ = null;
120
- this.eventListeners_ = null;
121
- this.fixedWidthSet_ = null;
122
- }
123
-
124
- /* InfoBox extends OverlayView in the Google Maps API v3.
125
- */
126
- InfoBox.prototype = new google.maps.OverlayView();
127
-
128
- /**
129
- * Creates the DIV representing the InfoBox.
130
- * @private
131
- */
132
- InfoBox.prototype.createInfoBoxDiv_ = function () {
133
-
134
- var i;
135
- var events;
136
- var bw;
137
- var me = this;
138
-
139
- // This handler prevents an event in the InfoBox from being passed on to the map.
140
- //
141
- var cancelHandler = function (e) {
142
- e.cancelBubble = true;
143
- if (e.stopPropagation) {
144
- e.stopPropagation();
145
- }
146
- };
147
-
148
- // This handler ignores the current event in the InfoBox and conditionally prevents
149
- // the event from being passed on to the map. It is used for the contextmenu event.
150
- //
151
- var ignoreHandler = function (e) {
152
-
153
- e.returnValue = false;
154
-
155
- if (e.preventDefault) {
156
-
157
- e.preventDefault();
158
- }
159
-
160
- if (!me.enableEventPropagation_) {
161
-
162
- cancelHandler(e);
163
- }
164
- };
165
-
166
- if (!this.div_) {
167
-
168
- this.div_ = document.createElement("div");
169
-
170
- this.setBoxStyle_();
171
-
172
- if (typeof this.content_.nodeType === "undefined") {
173
- this.div_.innerHTML = this.getCloseBoxImg_() + this.content_;
174
- } else {
175
- this.div_.innerHTML = this.getCloseBoxImg_();
176
- this.div_.appendChild(this.content_);
177
- }
178
-
179
- // Add the InfoBox DIV to the DOM
180
- this.getPanes()[this.pane_].appendChild(this.div_);
181
-
182
- this.addClickHandler_();
183
-
184
- if (this.div_.style.width) {
185
-
186
- this.fixedWidthSet_ = true;
187
-
188
- } else {
189
-
190
- if (this.maxWidth_ !== 0 && this.div_.offsetWidth > this.maxWidth_) {
191
-
192
- this.div_.style.width = this.maxWidth_;
193
- this.div_.style.overflow = "auto";
194
- this.fixedWidthSet_ = true;
195
-
196
- } else { // The following code is needed to overcome problems with MSIE
197
-
198
- bw = this.getBoxWidths_();
199
-
200
- this.div_.style.width = (this.div_.offsetWidth - bw.left - bw.right) + "px";
201
- this.fixedWidthSet_ = false;
202
- }
203
- }
204
-
205
- this.panBox_(this.disableAutoPan_);
206
-
207
- if (!this.enableEventPropagation_) {
208
-
209
- this.eventListeners_ = [];
210
-
211
- // Cancel event propagation.
212
- //
213
- // Note: mousemove not included (to resolve Issue 152)
214
- events = ["mousedown", "mouseover", "mouseout", "mouseup",
215
- "click", "dblclick", "touchstart", "touchend", "touchmove"];
216
-
217
- for (i = 0; i < events.length; i++) {
218
-
219
- this.eventListeners_.push(google.maps.event.addDomListener(this.div_, events[i], cancelHandler));
220
- }
221
-
222
- // Workaround for Google bug that causes the cursor to change to a pointer
223
- // when the mouse moves over a marker underneath InfoBox.
224
- this.eventListeners_.push(google.maps.event.addDomListener(this.div_, "mouseover", function (e) {
225
- this.style.cursor = "default";
226
- }));
227
- }
228
-
229
- this.contextListener_ = google.maps.event.addDomListener(this.div_, "contextmenu", ignoreHandler);
230
-
231
- /**
232
- * This event is fired when the DIV containing the InfoBox's content is attached to the DOM.
233
- * @name InfoBox#domready
234
- * @event
235
- */
236
- google.maps.event.trigger(this, "domready");
237
- }
238
- };
239
-
240
- /**
241
- * Returns the HTML <IMG> tag for the close box.
242
- * @private
243
- */
244
- InfoBox.prototype.getCloseBoxImg_ = function () {
245
-
246
- var img = "";
247
-
248
- if (this.closeBoxURL_ !== "") {
249
-
250
- img = "<img";
251
- img += " src='" + this.closeBoxURL_ + "'";
252
- img += " align=right"; // Do this because Opera chokes on style='float: right;'
253
- img += " style='";
254
- img += " position: relative;"; // Required by MSIE
255
- img += " cursor: pointer;";
256
- img += " margin: " + this.closeBoxMargin_ + ";";
257
- img += "'>";
258
- }
259
-
260
- return img;
261
- };
262
-
263
- /**
264
- * Adds the click handler to the InfoBox close box.
265
- * @private
266
- */
267
- InfoBox.prototype.addClickHandler_ = function () {
268
-
269
- var closeBox;
270
-
271
- if (this.closeBoxURL_ !== "") {
272
-
273
- closeBox = this.div_.firstChild;
274
- this.closeListener_ = google.maps.event.addDomListener(closeBox, "click", this.getCloseClickHandler_());
275
-
276
- } else {
277
-
278
- this.closeListener_ = null;
279
- }
280
- };
281
-
282
- /**
283
- * Returns the function to call when the user clicks the close box of an InfoBox.
284
- * @private
285
- */
286
- InfoBox.prototype.getCloseClickHandler_ = function () {
287
-
288
- var me = this;
289
-
290
- return function (e) {
291
-
292
- // 1.0.3 fix: Always prevent propagation of a close box click to the map:
293
- e.cancelBubble = true;
294
-
295
- if (e.stopPropagation) {
296
-
297
- e.stopPropagation();
298
- }
299
-
300
- /**
301
- * This event is fired when the InfoBox's close box is clicked.
302
- * @name InfoBox#closeclick
303
- * @event
304
- */
305
- google.maps.event.trigger(me, "closeclick");
306
-
307
- me.close();
308
- };
309
- };
310
-
311
- /**
312
- * Pans the map so that the InfoBox appears entirely within the map's visible area.
313
- * @private
314
- */
315
- InfoBox.prototype.panBox_ = function (disablePan) {
316
-
317
- var map;
318
- var bounds;
319
- var xOffset = 0, yOffset = 0;
320
-
321
- if (!disablePan) {
322
-
323
- map = this.getMap();
324
-
325
- if (map instanceof google.maps.Map) { // Only pan if attached to map, not panorama
326
-
327
- if (!map.getBounds().contains(this.position_)) {
328
- // Marker not in visible area of map, so set center
329
- // of map to the marker position first.
330
- map.setCenter(this.position_);
331
- }
332
-
333
- bounds = map.getBounds();
334
-
335
- var mapDiv = map.getDiv();
336
- var mapWidth = mapDiv.offsetWidth;
337
- var mapHeight = mapDiv.offsetHeight;
338
- var iwOffsetX = this.pixelOffset_.width;
339
- var iwOffsetY = this.pixelOffset_.height;
340
- var iwWidth = this.div_.offsetWidth;
341
- var iwHeight = this.div_.offsetHeight;
342
- var padX = this.infoBoxClearance_.width;
343
- var padY = this.infoBoxClearance_.height;
344
- var pixPosition = this.getProjection().fromLatLngToContainerPixel(this.position_);
345
-
346
- if (pixPosition.x < (-iwOffsetX + padX)) {
347
- xOffset = pixPosition.x + iwOffsetX - padX;
348
- } else if ((pixPosition.x + iwWidth + iwOffsetX + padX) > mapWidth) {
349
- xOffset = pixPosition.x + iwWidth + iwOffsetX + padX - mapWidth;
350
- }
351
- if (this.alignBottom_) {
352
- if (pixPosition.y < (-iwOffsetY + padY + iwHeight)) {
353
- yOffset = pixPosition.y + iwOffsetY - padY - iwHeight;
354
- } else if ((pixPosition.y + iwOffsetY + padY) > mapHeight) {
355
- yOffset = pixPosition.y + iwOffsetY + padY - mapHeight;
356
- }
357
- } else {
358
- if (pixPosition.y < (-iwOffsetY + padY)) {
359
- yOffset = pixPosition.y + iwOffsetY - padY;
360
- } else if ((pixPosition.y + iwHeight + iwOffsetY + padY) > mapHeight) {
361
- yOffset = pixPosition.y + iwHeight + iwOffsetY + padY - mapHeight;
362
- }
363
- }
364
-
365
- if (!(xOffset === 0 && yOffset === 0)) {
366
-
367
- // Move the map to the shifted center.
368
- //
369
- var c = map.getCenter();
370
- map.panBy(xOffset, yOffset);
371
- }
372
- }
373
- }
374
- };
375
-
376
- /**
377
- * Sets the style of the InfoBox by setting the style sheet and applying
378
- * other specific styles requested.
379
- * @private
380
- */
381
- InfoBox.prototype.setBoxStyle_ = function () {
382
-
383
- var i, boxStyle;
384
-
385
- if (this.div_) {
386
-
387
- // Apply style values from the style sheet defined in the boxClass parameter:
388
- this.div_.className = this.boxClass_;
389
-
390
- // Clear existing inline style values:
391
- this.div_.style.cssText = "";
392
-
393
- // Apply style values defined in the boxStyle parameter:
394
- boxStyle = this.boxStyle_;
395
- for (i in boxStyle) {
396
-
397
- if (boxStyle.hasOwnProperty(i)) {
398
-
399
- this.div_.style[i] = boxStyle[i];
400
- }
401
- }
402
-
403
- // Fix for iOS disappearing InfoBox problem.
404
- // See http://stackoverflow.com/questions/9229535/google-maps-markers-disappear-at-certain-zoom-level-only-on-iphone-ipad
405
- this.div_.style.WebkitTransform = "translateZ(0)";
406
-
407
- // Fix up opacity style for benefit of MSIE:
408
- //
409
- if (typeof this.div_.style.opacity !== "undefined" && this.div_.style.opacity !== "") {
410
- // See http://www.quirksmode.org/css/opacity.html
411
- this.div_.style.MsFilter = "\"progid:DXImageTransform.Microsoft.Alpha(Opacity=" + (this.div_.style.opacity * 100) + ")\"";
412
- this.div_.style.filter = "alpha(opacity=" + (this.div_.style.opacity * 100) + ")";
413
- }
414
-
415
- // Apply required styles:
416
- //
417
- this.div_.style.position = "absolute";
418
- this.div_.style.visibility = 'hidden';
419
- if (this.zIndex_ !== null) {
420
-
421
- this.div_.style.zIndex = this.zIndex_;
422
- }
423
- }
424
- };
425
-
426
- /**
427
- * Get the widths of the borders of the InfoBox.
428
- * @private
429
- * @return {Object} widths object (top, bottom left, right)
430
- */
431
- InfoBox.prototype.getBoxWidths_ = function () {
432
-
433
- var computedStyle;
434
- var bw = {top: 0, bottom: 0, left: 0, right: 0};
435
- var box = this.div_;
436
-
437
- if (document.defaultView && document.defaultView.getComputedStyle) {
438
-
439
- computedStyle = box.ownerDocument.defaultView.getComputedStyle(box, "");
440
-
441
- if (computedStyle) {
442
-
443
- // The computed styles are always in pixel units (good!)
444
- bw.top = parseInt(computedStyle.borderTopWidth, 10) || 0;
445
- bw.bottom = parseInt(computedStyle.borderBottomWidth, 10) || 0;
446
- bw.left = parseInt(computedStyle.borderLeftWidth, 10) || 0;
447
- bw.right = parseInt(computedStyle.borderRightWidth, 10) || 0;
448
- }
449
-
450
- } else if (document.documentElement.currentStyle) { // MSIE
451
-
452
- if (box.currentStyle) {
453
-
454
- // The current styles may not be in pixel units, but assume they are (bad!)
455
- bw.top = parseInt(box.currentStyle.borderTopWidth, 10) || 0;
456
- bw.bottom = parseInt(box.currentStyle.borderBottomWidth, 10) || 0;
457
- bw.left = parseInt(box.currentStyle.borderLeftWidth, 10) || 0;
458
- bw.right = parseInt(box.currentStyle.borderRightWidth, 10) || 0;
459
- }
460
- }
461
-
462
- return bw;
463
- };
464
-
465
- /**
466
- * Invoked when <tt>close</tt> is called. Do not call it directly.
467
- */
468
- InfoBox.prototype.onRemove = function () {
469
-
470
- if (this.div_) {
471
-
472
- this.div_.parentNode.removeChild(this.div_);
473
- this.div_ = null;
474
- }
475
- };
476
-
477
- /**
478
- * Draws the InfoBox based on the current map projection and zoom level.
479
- */
480
- InfoBox.prototype.draw = function () {
481
-
482
- this.createInfoBoxDiv_();
483
-
484
- var pixPosition = this.getProjection().fromLatLngToDivPixel(this.position_);
485
-
486
- this.div_.style.left = (pixPosition.x + this.pixelOffset_.width) + "px";
487
-
488
- if (this.alignBottom_) {
489
- this.div_.style.bottom = -(pixPosition.y + this.pixelOffset_.height) + "px";
490
- } else {
491
- this.div_.style.top = (pixPosition.y + this.pixelOffset_.height) + "px";
492
- }
493
-
494
- if (this.isHidden_) {
495
-
496
- this.div_.style.visibility = "hidden";
497
-
498
- } else {
499
-
500
- this.div_.style.visibility = "visible";
501
- }
502
- };
503
-
504
- /**
505
- * Sets the options for the InfoBox. Note that changes to the <tt>maxWidth</tt>,
506
- * <tt>closeBoxMargin</tt>, <tt>closeBoxURL</tt>, and <tt>enableEventPropagation</tt>
507
- * properties have no affect until the current InfoBox is <tt>close</tt>d and a new one
508
- * is <tt>open</tt>ed.
509
- * @param {InfoBoxOptions} opt_opts
510
- */
511
- InfoBox.prototype.setOptions = function (opt_opts) {
512
- if (typeof opt_opts.boxClass !== "undefined") { // Must be first
513
-
514
- this.boxClass_ = opt_opts.boxClass;
515
- this.setBoxStyle_();
516
- }
517
- if (typeof opt_opts.boxStyle !== "undefined") { // Must be second
518
-
519
- this.boxStyle_ = opt_opts.boxStyle;
520
- this.setBoxStyle_();
521
- }
522
- if (typeof opt_opts.content !== "undefined") {
523
-
524
- this.setContent(opt_opts.content);
525
- }
526
- if (typeof opt_opts.disableAutoPan !== "undefined") {
527
-
528
- this.disableAutoPan_ = opt_opts.disableAutoPan;
529
- }
530
- if (typeof opt_opts.maxWidth !== "undefined") {
531
-
532
- this.maxWidth_ = opt_opts.maxWidth;
533
- }
534
- if (typeof opt_opts.pixelOffset !== "undefined") {
535
-
536
- this.pixelOffset_ = opt_opts.pixelOffset;
537
- }
538
- if (typeof opt_opts.alignBottom !== "undefined") {
539
-
540
- this.alignBottom_ = opt_opts.alignBottom;
541
- }
542
- if (typeof opt_opts.position !== "undefined") {
543
-
544
- this.setPosition(opt_opts.position);
545
- }
546
- if (typeof opt_opts.zIndex !== "undefined") {
547
-
548
- this.setZIndex(opt_opts.zIndex);
549
- }
550
- if (typeof opt_opts.closeBoxMargin !== "undefined") {
551
-
552
- this.closeBoxMargin_ = opt_opts.closeBoxMargin;
553
- }
554
- if (typeof opt_opts.closeBoxURL !== "undefined") {
555
-
556
- this.closeBoxURL_ = opt_opts.closeBoxURL;
557
- }
558
- if (typeof opt_opts.infoBoxClearance !== "undefined") {
559
-
560
- this.infoBoxClearance_ = opt_opts.infoBoxClearance;
561
- }
562
- if (typeof opt_opts.isHidden !== "undefined") {
563
-
564
- this.isHidden_ = opt_opts.isHidden;
565
- }
566
- if (typeof opt_opts.visible !== "undefined") {
567
-
568
- this.isHidden_ = !opt_opts.visible;
569
- }
570
- if (typeof opt_opts.enableEventPropagation !== "undefined") {
571
-
572
- this.enableEventPropagation_ = opt_opts.enableEventPropagation;
573
- }
574
-
575
- if (this.div_) {
576
-
577
- this.draw();
578
- }
579
- };
580
-
581
- /**
582
- * Sets the content of the InfoBox.
583
- * The content can be plain text or an HTML DOM node.
584
- * @param {string|Node} content
585
- */
586
- InfoBox.prototype.setContent = function (content) {
587
- this.content_ = content;
588
-
589
- if (this.div_) {
590
-
591
- if (this.closeListener_) {
592
-
593
- google.maps.event.removeListener(this.closeListener_);
594
- this.closeListener_ = null;
595
- }
596
-
597
- // Odd code required to make things work with MSIE.
598
- //
599
- if (!this.fixedWidthSet_) {
600
-
601
- this.div_.style.width = "";
602
- }
603
-
604
- if (typeof content.nodeType === "undefined") {
605
- this.div_.innerHTML = this.getCloseBoxImg_() + content;
606
- } else {
607
- this.div_.innerHTML = this.getCloseBoxImg_();
608
- this.div_.appendChild(content);
609
- }
610
-
611
- // Perverse code required to make things work with MSIE.
612
- // (Ensures the close box does, in fact, float to the right.)
613
- //
614
- if (!this.fixedWidthSet_) {
615
- this.div_.style.width = this.div_.offsetWidth + "px";
616
- if (typeof content.nodeType === "undefined") {
617
- this.div_.innerHTML = this.getCloseBoxImg_() + content;
618
- } else {
619
- this.div_.innerHTML = this.getCloseBoxImg_();
620
- this.div_.appendChild(content);
621
- }
622
- }
623
-
624
- this.addClickHandler_();
625
- }
626
-
627
- /**
628
- * This event is fired when the content of the InfoBox changes.
629
- * @name InfoBox#content_changed
630
- * @event
631
- */
632
- google.maps.event.trigger(this, "content_changed");
633
- };
634
-
635
- /**
636
- * Sets the geographic location of the InfoBox.
637
- * @param {LatLng} latlng
638
- */
639
- InfoBox.prototype.setPosition = function (latlng) {
640
-
641
- this.position_ = latlng;
642
-
643
- if (this.div_) {
644
-
645
- this.draw();
646
- }
647
-
648
- /**
649
- * This event is fired when the position of the InfoBox changes.
650
- * @name InfoBox#position_changed
651
- * @event
652
- */
653
- google.maps.event.trigger(this, "position_changed");
654
- };
655
-
656
- /**
657
- * Sets the zIndex style for the InfoBox.
658
- * @param {number} index
659
- */
660
- InfoBox.prototype.setZIndex = function (index) {
661
-
662
- this.zIndex_ = index;
663
-
664
- if (this.div_) {
665
-
666
- this.div_.style.zIndex = index;
667
- }
668
-
669
- /**
670
- * This event is fired when the zIndex of the InfoBox changes.
671
- * @name InfoBox#zindex_changed
672
- * @event
673
- */
674
- google.maps.event.trigger(this, "zindex_changed");
675
- };
676
-
677
- /**
678
- * Sets the visibility of the InfoBox.
679
- * @param {boolean} isVisible
680
- */
681
- InfoBox.prototype.setVisible = function (isVisible) {
682
-
683
- this.isHidden_ = !isVisible;
684
- if (this.div_) {
685
- this.div_.style.visibility = (this.isHidden_ ? "hidden" : "visible");
686
- }
687
- };
688
-
689
- /**
690
- * Returns the content of the InfoBox.
691
- * @returns {string}
692
- */
693
- InfoBox.prototype.getContent = function () {
694
-
695
- return this.content_;
696
- };
697
-
698
- /**
699
- * Returns the geographic location of the InfoBox.
700
- * @returns {LatLng}
701
- */
702
- InfoBox.prototype.getPosition = function () {
703
-
704
- return this.position_;
705
- };
706
-
707
- /**
708
- * Returns the zIndex for the InfoBox.
709
- * @returns {number}
710
- */
711
- InfoBox.prototype.getZIndex = function () {
712
-
713
- return this.zIndex_;
714
- };
715
-
716
- /**
717
- * Returns a flag indicating whether the InfoBox is visible.
718
- * @returns {boolean}
719
- */
720
- InfoBox.prototype.getVisible = function () {
721
-
722
- var isVisible;
723
-
724
- if ((typeof this.getMap() === "undefined") || (this.getMap() === null)) {
725
- isVisible = false;
726
- } else {
727
- isVisible = !this.isHidden_;
728
- }
729
- return isVisible;
730
- };
731
-
732
- /**
733
- * Shows the InfoBox. [Deprecated; use <tt>setVisible</tt> instead.]
734
- */
735
- InfoBox.prototype.show = function () {
736
-
737
- this.isHidden_ = false;
738
- if (this.div_) {
739
- this.div_.style.visibility = "visible";
740
- }
741
- };
742
-
743
- /**
744
- * Hides the InfoBox. [Deprecated; use <tt>setVisible</tt> instead.]
745
- */
746
- InfoBox.prototype.hide = function () {
747
-
748
- this.isHidden_ = true;
749
- if (this.div_) {
750
- this.div_.style.visibility = "hidden";
751
- }
752
- };
753
-
754
- /**
755
- * Adds the InfoBox to the specified map or Street View panorama. If <tt>anchor</tt>
756
- * (usually a <tt>google.maps.Marker</tt>) is specified, the position
757
- * of the InfoBox is set to the position of the <tt>anchor</tt>. If the
758
- * anchor is dragged to a new location, the InfoBox moves as well.
759
- * @param {Map|StreetViewPanorama} map
760
- * @param {MVCObject} [anchor]
761
- */
762
- InfoBox.prototype.open = function (map, anchor) {
763
-
764
- var me = this;
765
-
766
- if (anchor) {
767
-
768
- this.position_ = anchor.getPosition();
769
- this.moveListener_ = google.maps.event.addListener(anchor, "position_changed", function () {
770
- me.setPosition(this.getPosition());
771
- });
772
- }
773
-
774
- this.setMap(map);
775
-
776
- if (this.div_) {
777
-
778
- this.panBox_();
779
- }
780
- };
781
-
782
- /**
783
- * Removes the InfoBox from the map.
784
- */
785
- InfoBox.prototype.close = function () {
786
-
787
- var i;
788
-
789
- if (this.closeListener_) {
790
-
791
- google.maps.event.removeListener(this.closeListener_);
792
- this.closeListener_ = null;
793
- }
794
-
795
- if (this.eventListeners_) {
796
-
797
- for (i = 0; i < this.eventListeners_.length; i++) {
798
-
799
- google.maps.event.removeListener(this.eventListeners_[i]);
800
- }
801
- this.eventListeners_ = null;
802
- }
803
-
804
- if (this.moveListener_) {
805
-
806
- google.maps.event.removeListener(this.moveListener_);
807
- this.moveListener_ = null;
808
- }
809
-
810
- if (this.contextListener_) {
811
-
812
- google.maps.event.removeListener(this.contextListener_);
813
- this.contextListener_ = null;
814
- }
815
-
816
- this.setMap(null);
817
- };
1
+ /**
2
+ * @name InfoBox
3
+ * @version 1.1.13 [March 19, 2014]
4
+ * @author Gary Little (inspired by proof-of-concept code from Pamela Fox of Google)
5
+ * @copyright Copyright 2010 Gary Little [gary at luxcentral.com]
6
+ * @fileoverview InfoBox extends the Google Maps JavaScript API V3 <tt>OverlayView</tt> class.
7
+ * <p>
8
+ * An InfoBox behaves like a <tt>google.maps.InfoWindow</tt>, but it supports several
9
+ * additional properties for advanced styling. An InfoBox can also be used as a map label.
10
+ * <p>
11
+ * An InfoBox also fires the same events as a <tt>google.maps.InfoWindow</tt>.
12
+ */
13
+
14
+ /*!
15
+ *
16
+ * Licensed under the Apache License, Version 2.0 (the "License");
17
+ * you may not use this file except in compliance with the License.
18
+ * You may obtain a copy of the License at
19
+ *
20
+ * http://www.apache.org/licenses/LICENSE-2.0
21
+ *
22
+ * Unless required by applicable law or agreed to in writing, software
23
+ * distributed under the License is distributed on an "AS IS" BASIS,
24
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25
+ * See the License for the specific language governing permissions and
26
+ * limitations under the License.
27
+ */
28
+
29
+ /*jslint browser:true */
30
+ /*global google */
31
+
32
+ /**
33
+ * @name InfoBoxOptions
34
+ * @class This class represents the optional parameter passed to the {@link InfoBox} constructor.
35
+ * @property {string|Node} content The content of the InfoBox (plain text or an HTML DOM node).
36
+ * @property {boolean} [disableAutoPan=false] Disable auto-pan on <tt>open</tt>.
37
+ * @property {number} maxWidth The maximum width (in pixels) of the InfoBox. Set to 0 if no maximum.
38
+ * @property {Size} pixelOffset The offset (in pixels) from the top left corner of the InfoBox
39
+ * (or the bottom left corner if the <code>alignBottom</code> property is <code>true</code>)
40
+ * to the map pixel corresponding to <tt>position</tt>.
41
+ * @property {LatLng} position The geographic location at which to display the InfoBox.
42
+ * @property {number} zIndex The CSS z-index style value for the InfoBox.
43
+ * Note: This value overrides a zIndex setting specified in the <tt>boxStyle</tt> property.
44
+ * @property {string} [boxClass="infoBox"] The name of the CSS class defining the styles for the InfoBox container.
45
+ * @property {Object} [boxStyle] An object literal whose properties define specific CSS
46
+ * style values to be applied to the InfoBox. Style values defined here override those that may
47
+ * be defined in the <code>boxClass</code> style sheet. If this property is changed after the
48
+ * InfoBox has been created, all previously set styles (except those defined in the style sheet)
49
+ * are removed from the InfoBox before the new style values are applied.
50
+ * @property {string} closeBoxMargin The CSS margin style value for the close box.
51
+ * The default is "2px" (a 2-pixel margin on all sides).
52
+ * @property {string} closeBoxURL The URL of the image representing the close box.
53
+ * Note: The default is the URL for Google's standard close box.
54
+ * Set this property to "" if no close box is required.
55
+ * @property {Size} infoBoxClearance Minimum offset (in pixels) from the InfoBox to the
56
+ * map edge after an auto-pan.
57
+ * @property {boolean} [isHidden=false] Hide the InfoBox on <tt>open</tt>.
58
+ * [Deprecated in favor of the <tt>visible</tt> property.]
59
+ * @property {boolean} [visible=true] Show the InfoBox on <tt>open</tt>.
60
+ * @property {boolean} alignBottom Align the bottom left corner of the InfoBox to the <code>position</code>
61
+ * location (default is <tt>false</tt> which means that the top left corner of the InfoBox is aligned).
62
+ * @property {string} pane The pane where the InfoBox is to appear (default is "floatPane").
63
+ * Set the pane to "mapPane" if the InfoBox is being used as a map label.
64
+ * Valid pane names are the property names for the <tt>google.maps.MapPanes</tt> object.
65
+ * @property {boolean} enableEventPropagation Propagate mousedown, mousemove, mouseover, mouseout,
66
+ * mouseup, click, dblclick, touchstart, touchend, touchmove, and contextmenu events in the InfoBox
67
+ * (default is <tt>false</tt> to mimic the behavior of a <tt>google.maps.InfoWindow</tt>). Set
68
+ * this property to <tt>true</tt> if the InfoBox is being used as a map label.
69
+ */
70
+
71
+ /**
72
+ * Creates an InfoBox with the options specified in {@link InfoBoxOptions}.
73
+ * Call <tt>InfoBox.open</tt> to add the box to the map.
74
+ * @constructor
75
+ * @param {InfoBoxOptions} [opt_opts]
76
+ */
77
+ function InfoBox(opt_opts) {
78
+
79
+ opt_opts = opt_opts || {};
80
+
81
+ google.maps.OverlayView.apply(this, arguments);
82
+
83
+ // Standard options (in common with google.maps.InfoWindow):
84
+ //
85
+ this.content_ = opt_opts.content || "";
86
+ this.disableAutoPan_ = opt_opts.disableAutoPan || false;
87
+ this.maxWidth_ = opt_opts.maxWidth || 0;
88
+ this.pixelOffset_ = opt_opts.pixelOffset || new google.maps.Size(0, 0);
89
+ this.position_ = opt_opts.position || new google.maps.LatLng(0, 0);
90
+ this.zIndex_ = opt_opts.zIndex || null;
91
+
92
+ // Additional options (unique to InfoBox):
93
+ //
94
+ this.boxClass_ = opt_opts.boxClass || "infoBox";
95
+ this.boxStyle_ = opt_opts.boxStyle || {};
96
+ this.closeBoxMargin_ = opt_opts.closeBoxMargin || "2px";
97
+ this.closeBoxURL_ = opt_opts.closeBoxURL || "http://www.google.com/intl/en_us/mapfiles/close.gif";
98
+ if (opt_opts.closeBoxURL === "") {
99
+ this.closeBoxURL_ = "";
100
+ }
101
+ this.infoBoxClearance_ = opt_opts.infoBoxClearance || new google.maps.Size(1, 1);
102
+
103
+ if (typeof opt_opts.visible === "undefined") {
104
+ if (typeof opt_opts.isHidden === "undefined") {
105
+ opt_opts.visible = true;
106
+ } else {
107
+ opt_opts.visible = !opt_opts.isHidden;
108
+ }
109
+ }
110
+ this.isHidden_ = !opt_opts.visible;
111
+
112
+ this.alignBottom_ = opt_opts.alignBottom || false;
113
+ this.pane_ = opt_opts.pane || "floatPane";
114
+ this.enableEventPropagation_ = opt_opts.enableEventPropagation || false;
115
+
116
+ this.div_ = null;
117
+ this.closeListener_ = null;
118
+ this.moveListener_ = null;
119
+ this.contextListener_ = null;
120
+ this.eventListeners_ = null;
121
+ this.fixedWidthSet_ = null;
122
+ }
123
+
124
+ /* InfoBox extends OverlayView in the Google Maps API v3.
125
+ */
126
+ InfoBox.prototype = new google.maps.OverlayView();
127
+
128
+ /**
129
+ * Creates the DIV representing the InfoBox.
130
+ * @private
131
+ */
132
+ InfoBox.prototype.createInfoBoxDiv_ = function () {
133
+
134
+ var i;
135
+ var events;
136
+ var bw;
137
+ var me = this;
138
+
139
+ // This handler prevents an event in the InfoBox from being passed on to the map.
140
+ //
141
+ var cancelHandler = function (e) {
142
+ e.cancelBubble = true;
143
+ if (e.stopPropagation) {
144
+ e.stopPropagation();
145
+ }
146
+ };
147
+
148
+ // This handler ignores the current event in the InfoBox and conditionally prevents
149
+ // the event from being passed on to the map. It is used for the contextmenu event.
150
+ //
151
+ var ignoreHandler = function (e) {
152
+
153
+ e.returnValue = false;
154
+
155
+ if (e.preventDefault) {
156
+
157
+ e.preventDefault();
158
+ }
159
+
160
+ if (!me.enableEventPropagation_) {
161
+
162
+ cancelHandler(e);
163
+ }
164
+ };
165
+
166
+ if (!this.div_) {
167
+
168
+ this.div_ = document.createElement("div");
169
+
170
+ this.setBoxStyle_();
171
+
172
+ if (typeof this.content_.nodeType === "undefined") {
173
+ this.div_.innerHTML = this.getCloseBoxImg_() + this.content_;
174
+ } else {
175
+ this.div_.innerHTML = this.getCloseBoxImg_();
176
+ this.div_.appendChild(this.content_);
177
+ }
178
+
179
+ // Add the InfoBox DIV to the DOM
180
+ this.getPanes()[this.pane_].appendChild(this.div_);
181
+
182
+ this.addClickHandler_();
183
+
184
+ if (this.div_.style.width) {
185
+
186
+ this.fixedWidthSet_ = true;
187
+
188
+ } else {
189
+
190
+ if (this.maxWidth_ !== 0 && this.div_.offsetWidth > this.maxWidth_) {
191
+
192
+ this.div_.style.width = this.maxWidth_;
193
+ this.div_.style.overflow = "auto";
194
+ this.fixedWidthSet_ = true;
195
+
196
+ } else { // The following code is needed to overcome problems with MSIE
197
+
198
+ bw = this.getBoxWidths_();
199
+
200
+ this.div_.style.width = (this.div_.offsetWidth - bw.left - bw.right) + "px";
201
+ this.fixedWidthSet_ = false;
202
+ }
203
+ }
204
+
205
+ this.panBox_(this.disableAutoPan_);
206
+
207
+ if (!this.enableEventPropagation_) {
208
+
209
+ this.eventListeners_ = [];
210
+
211
+ // Cancel event propagation.
212
+ //
213
+ // Note: mousemove not included (to resolve Issue 152)
214
+ events = ["mousedown", "mouseover", "mouseout", "mouseup",
215
+ "click", "dblclick", "touchstart", "touchend", "touchmove"];
216
+
217
+ for (i = 0; i < events.length; i++) {
218
+
219
+ this.eventListeners_.push(google.maps.event.addDomListener(this.div_, events[i], cancelHandler));
220
+ }
221
+
222
+ // Workaround for Google bug that causes the cursor to change to a pointer
223
+ // when the mouse moves over a marker underneath InfoBox.
224
+ this.eventListeners_.push(google.maps.event.addDomListener(this.div_, "mouseover", function (e) {
225
+ this.style.cursor = "default";
226
+ }));
227
+ }
228
+
229
+ this.contextListener_ = google.maps.event.addDomListener(this.div_, "contextmenu", ignoreHandler);
230
+
231
+ /**
232
+ * This event is fired when the DIV containing the InfoBox's content is attached to the DOM.
233
+ * @name InfoBox#domready
234
+ * @event
235
+ */
236
+ google.maps.event.trigger(this, "domready");
237
+ }
238
+ };
239
+
240
+ /**
241
+ * Returns the HTML <IMG> tag for the close box.
242
+ * @private
243
+ */
244
+ InfoBox.prototype.getCloseBoxImg_ = function () {
245
+
246
+ var img = "";
247
+
248
+ if (this.closeBoxURL_ !== "") {
249
+
250
+ img = "<img";
251
+ img += " src='" + this.closeBoxURL_ + "'";
252
+ img += " align=right"; // Do this because Opera chokes on style='float: right;'
253
+ img += " style='";
254
+ img += " position: relative;"; // Required by MSIE
255
+ img += " cursor: pointer;";
256
+ img += " margin: " + this.closeBoxMargin_ + ";";
257
+ img += "'>";
258
+ }
259
+
260
+ return img;
261
+ };
262
+
263
+ /**
264
+ * Adds the click handler to the InfoBox close box.
265
+ * @private
266
+ */
267
+ InfoBox.prototype.addClickHandler_ = function () {
268
+
269
+ var closeBox;
270
+
271
+ if (this.closeBoxURL_ !== "") {
272
+
273
+ closeBox = this.div_.firstChild;
274
+ this.closeListener_ = google.maps.event.addDomListener(closeBox, "click", this.getCloseClickHandler_());
275
+
276
+ } else {
277
+
278
+ this.closeListener_ = null;
279
+ }
280
+ };
281
+
282
+ /**
283
+ * Returns the function to call when the user clicks the close box of an InfoBox.
284
+ * @private
285
+ */
286
+ InfoBox.prototype.getCloseClickHandler_ = function () {
287
+
288
+ var me = this;
289
+
290
+ return function (e) {
291
+
292
+ // 1.0.3 fix: Always prevent propagation of a close box click to the map:
293
+ e.cancelBubble = true;
294
+
295
+ if (e.stopPropagation) {
296
+
297
+ e.stopPropagation();
298
+ }
299
+
300
+ /**
301
+ * This event is fired when the InfoBox's close box is clicked.
302
+ * @name InfoBox#closeclick
303
+ * @event
304
+ */
305
+ google.maps.event.trigger(me, "closeclick");
306
+
307
+ me.close();
308
+ };
309
+ };
310
+
311
+ /**
312
+ * Pans the map so that the InfoBox appears entirely within the map's visible area.
313
+ * @private
314
+ */
315
+ InfoBox.prototype.panBox_ = function (disablePan) {
316
+
317
+ var map;
318
+ var bounds;
319
+ var xOffset = 0, yOffset = 0;
320
+
321
+ if (!disablePan) {
322
+
323
+ map = this.getMap();
324
+
325
+ if (map instanceof google.maps.Map) { // Only pan if attached to map, not panorama
326
+
327
+ if (!map.getBounds().contains(this.position_)) {
328
+ // Marker not in visible area of map, so set center
329
+ // of map to the marker position first.
330
+ map.setCenter(this.position_);
331
+ }
332
+
333
+ bounds = map.getBounds();
334
+
335
+ var mapDiv = map.getDiv();
336
+ var mapWidth = mapDiv.offsetWidth;
337
+ var mapHeight = mapDiv.offsetHeight;
338
+ var iwOffsetX = this.pixelOffset_.width;
339
+ var iwOffsetY = this.pixelOffset_.height;
340
+ var iwWidth = this.div_.offsetWidth;
341
+ var iwHeight = this.div_.offsetHeight;
342
+ var padX = this.infoBoxClearance_.width;
343
+ var padY = this.infoBoxClearance_.height;
344
+ var pixPosition = this.getProjection().fromLatLngToContainerPixel(this.position_);
345
+
346
+ if (pixPosition.x < (-iwOffsetX + padX)) {
347
+ xOffset = pixPosition.x + iwOffsetX - padX;
348
+ } else if ((pixPosition.x + iwWidth + iwOffsetX + padX) > mapWidth) {
349
+ xOffset = pixPosition.x + iwWidth + iwOffsetX + padX - mapWidth;
350
+ }
351
+ if (this.alignBottom_) {
352
+ if (pixPosition.y < (-iwOffsetY + padY + iwHeight)) {
353
+ yOffset = pixPosition.y + iwOffsetY - padY - iwHeight;
354
+ } else if ((pixPosition.y + iwOffsetY + padY) > mapHeight) {
355
+ yOffset = pixPosition.y + iwOffsetY + padY - mapHeight;
356
+ }
357
+ } else {
358
+ if (pixPosition.y < (-iwOffsetY + padY)) {
359
+ yOffset = pixPosition.y + iwOffsetY - padY;
360
+ } else if ((pixPosition.y + iwHeight + iwOffsetY + padY) > mapHeight) {
361
+ yOffset = pixPosition.y + iwHeight + iwOffsetY + padY - mapHeight;
362
+ }
363
+ }
364
+
365
+ if (!(xOffset === 0 && yOffset === 0)) {
366
+
367
+ // Move the map to the shifted center.
368
+ //
369
+ var c = map.getCenter();
370
+ map.panBy(xOffset, yOffset);
371
+ }
372
+ }
373
+ }
374
+ };
375
+
376
+ /**
377
+ * Sets the style of the InfoBox by setting the style sheet and applying
378
+ * other specific styles requested.
379
+ * @private
380
+ */
381
+ InfoBox.prototype.setBoxStyle_ = function () {
382
+
383
+ var i, boxStyle;
384
+
385
+ if (this.div_) {
386
+
387
+ // Apply style values from the style sheet defined in the boxClass parameter:
388
+ this.div_.className = this.boxClass_;
389
+
390
+ // Clear existing inline style values:
391
+ this.div_.style.cssText = "";
392
+
393
+ // Apply style values defined in the boxStyle parameter:
394
+ boxStyle = this.boxStyle_;
395
+ for (i in boxStyle) {
396
+
397
+ if (boxStyle.hasOwnProperty(i)) {
398
+
399
+ this.div_.style[i] = boxStyle[i];
400
+ }
401
+ }
402
+
403
+ // Fix for iOS disappearing InfoBox problem.
404
+ // See http://stackoverflow.com/questions/9229535/google-maps-markers-disappear-at-certain-zoom-level-only-on-iphone-ipad
405
+ this.div_.style.WebkitTransform = "translateZ(0)";
406
+
407
+ // Fix up opacity style for benefit of MSIE:
408
+ //
409
+ if (typeof this.div_.style.opacity !== "undefined" && this.div_.style.opacity !== "") {
410
+ // See http://www.quirksmode.org/css/opacity.html
411
+ this.div_.style.MsFilter = "\"progid:DXImageTransform.Microsoft.Alpha(Opacity=" + (this.div_.style.opacity * 100) + ")\"";
412
+ this.div_.style.filter = "alpha(opacity=" + (this.div_.style.opacity * 100) + ")";
413
+ }
414
+
415
+ // Apply required styles:
416
+ //
417
+ this.div_.style.position = "absolute";
418
+ this.div_.style.visibility = 'hidden';
419
+ if (this.zIndex_ !== null) {
420
+
421
+ this.div_.style.zIndex = this.zIndex_;
422
+ }
423
+ }
424
+ };
425
+
426
+ /**
427
+ * Get the widths of the borders of the InfoBox.
428
+ * @private
429
+ * @return {Object} widths object (top, bottom left, right)
430
+ */
431
+ InfoBox.prototype.getBoxWidths_ = function () {
432
+
433
+ var computedStyle;
434
+ var bw = {top: 0, bottom: 0, left: 0, right: 0};
435
+ var box = this.div_;
436
+
437
+ if (document.defaultView && document.defaultView.getComputedStyle) {
438
+
439
+ computedStyle = box.ownerDocument.defaultView.getComputedStyle(box, "");
440
+
441
+ if (computedStyle) {
442
+
443
+ // The computed styles are always in pixel units (good!)
444
+ bw.top = parseInt(computedStyle.borderTopWidth, 10) || 0;
445
+ bw.bottom = parseInt(computedStyle.borderBottomWidth, 10) || 0;
446
+ bw.left = parseInt(computedStyle.borderLeftWidth, 10) || 0;
447
+ bw.right = parseInt(computedStyle.borderRightWidth, 10) || 0;
448
+ }
449
+
450
+ } else if (document.documentElement.currentStyle) { // MSIE
451
+
452
+ if (box.currentStyle) {
453
+
454
+ // The current styles may not be in pixel units, but assume they are (bad!)
455
+ bw.top = parseInt(box.currentStyle.borderTopWidth, 10) || 0;
456
+ bw.bottom = parseInt(box.currentStyle.borderBottomWidth, 10) || 0;
457
+ bw.left = parseInt(box.currentStyle.borderLeftWidth, 10) || 0;
458
+ bw.right = parseInt(box.currentStyle.borderRightWidth, 10) || 0;
459
+ }
460
+ }
461
+
462
+ return bw;
463
+ };
464
+
465
+ /**
466
+ * Invoked when <tt>close</tt> is called. Do not call it directly.
467
+ */
468
+ InfoBox.prototype.onRemove = function () {
469
+
470
+ if (this.div_) {
471
+
472
+ this.div_.parentNode.removeChild(this.div_);
473
+ this.div_ = null;
474
+ }
475
+ };
476
+
477
+ /**
478
+ * Draws the InfoBox based on the current map projection and zoom level.
479
+ */
480
+ InfoBox.prototype.draw = function () {
481
+
482
+ this.createInfoBoxDiv_();
483
+
484
+ var pixPosition = this.getProjection().fromLatLngToDivPixel(this.position_);
485
+
486
+ this.div_.style.left = (pixPosition.x + this.pixelOffset_.width) + "px";
487
+
488
+ if (this.alignBottom_) {
489
+ this.div_.style.bottom = -(pixPosition.y + this.pixelOffset_.height) + "px";
490
+ } else {
491
+ this.div_.style.top = (pixPosition.y + this.pixelOffset_.height) + "px";
492
+ }
493
+
494
+ if (this.isHidden_) {
495
+
496
+ this.div_.style.visibility = "hidden";
497
+
498
+ } else {
499
+
500
+ this.div_.style.visibility = "visible";
501
+ }
502
+ };
503
+
504
+ /**
505
+ * Sets the options for the InfoBox. Note that changes to the <tt>maxWidth</tt>,
506
+ * <tt>closeBoxMargin</tt>, <tt>closeBoxURL</tt>, and <tt>enableEventPropagation</tt>
507
+ * properties have no affect until the current InfoBox is <tt>close</tt>d and a new one
508
+ * is <tt>open</tt>ed.
509
+ * @param {InfoBoxOptions} opt_opts
510
+ */
511
+ InfoBox.prototype.setOptions = function (opt_opts) {
512
+ if (typeof opt_opts.boxClass !== "undefined") { // Must be first
513
+
514
+ this.boxClass_ = opt_opts.boxClass;
515
+ this.setBoxStyle_();
516
+ }
517
+ if (typeof opt_opts.boxStyle !== "undefined") { // Must be second
518
+
519
+ this.boxStyle_ = opt_opts.boxStyle;
520
+ this.setBoxStyle_();
521
+ }
522
+ if (typeof opt_opts.content !== "undefined") {
523
+
524
+ this.setContent(opt_opts.content);
525
+ }
526
+ if (typeof opt_opts.disableAutoPan !== "undefined") {
527
+
528
+ this.disableAutoPan_ = opt_opts.disableAutoPan;
529
+ }
530
+ if (typeof opt_opts.maxWidth !== "undefined") {
531
+
532
+ this.maxWidth_ = opt_opts.maxWidth;
533
+ }
534
+ if (typeof opt_opts.pixelOffset !== "undefined") {
535
+
536
+ this.pixelOffset_ = opt_opts.pixelOffset;
537
+ }
538
+ if (typeof opt_opts.alignBottom !== "undefined") {
539
+
540
+ this.alignBottom_ = opt_opts.alignBottom;
541
+ }
542
+ if (typeof opt_opts.position !== "undefined") {
543
+
544
+ this.setPosition(opt_opts.position);
545
+ }
546
+ if (typeof opt_opts.zIndex !== "undefined") {
547
+
548
+ this.setZIndex(opt_opts.zIndex);
549
+ }
550
+ if (typeof opt_opts.closeBoxMargin !== "undefined") {
551
+
552
+ this.closeBoxMargin_ = opt_opts.closeBoxMargin;
553
+ }
554
+ if (typeof opt_opts.closeBoxURL !== "undefined") {
555
+
556
+ this.closeBoxURL_ = opt_opts.closeBoxURL;
557
+ }
558
+ if (typeof opt_opts.infoBoxClearance !== "undefined") {
559
+
560
+ this.infoBoxClearance_ = opt_opts.infoBoxClearance;
561
+ }
562
+ if (typeof opt_opts.isHidden !== "undefined") {
563
+
564
+ this.isHidden_ = opt_opts.isHidden;
565
+ }
566
+ if (typeof opt_opts.visible !== "undefined") {
567
+
568
+ this.isHidden_ = !opt_opts.visible;
569
+ }
570
+ if (typeof opt_opts.enableEventPropagation !== "undefined") {
571
+
572
+ this.enableEventPropagation_ = opt_opts.enableEventPropagation;
573
+ }
574
+
575
+ if (this.div_) {
576
+
577
+ this.draw();
578
+ }
579
+ };
580
+
581
+ /**
582
+ * Sets the content of the InfoBox.
583
+ * The content can be plain text or an HTML DOM node.
584
+ * @param {string|Node} content
585
+ */
586
+ InfoBox.prototype.setContent = function (content) {
587
+ this.content_ = content;
588
+
589
+ if (this.div_) {
590
+
591
+ if (this.closeListener_) {
592
+
593
+ google.maps.event.removeListener(this.closeListener_);
594
+ this.closeListener_ = null;
595
+ }
596
+
597
+ // Odd code required to make things work with MSIE.
598
+ //
599
+ if (!this.fixedWidthSet_) {
600
+
601
+ this.div_.style.width = "";
602
+ }
603
+
604
+ if (typeof content.nodeType === "undefined") {
605
+ this.div_.innerHTML = this.getCloseBoxImg_() + content;
606
+ } else {
607
+ this.div_.innerHTML = this.getCloseBoxImg_();
608
+ this.div_.appendChild(content);
609
+ }
610
+
611
+ // Perverse code required to make things work with MSIE.
612
+ // (Ensures the close box does, in fact, float to the right.)
613
+ //
614
+ if (!this.fixedWidthSet_) {
615
+ this.div_.style.width = this.div_.offsetWidth + "px";
616
+ if (typeof content.nodeType === "undefined") {
617
+ this.div_.innerHTML = this.getCloseBoxImg_() + content;
618
+ } else {
619
+ this.div_.innerHTML = this.getCloseBoxImg_();
620
+ this.div_.appendChild(content);
621
+ }
622
+ }
623
+
624
+ this.addClickHandler_();
625
+ }
626
+
627
+ /**
628
+ * This event is fired when the content of the InfoBox changes.
629
+ * @name InfoBox#content_changed
630
+ * @event
631
+ */
632
+ google.maps.event.trigger(this, "content_changed");
633
+ };
634
+
635
+ /**
636
+ * Sets the geographic location of the InfoBox.
637
+ * @param {LatLng} latlng
638
+ */
639
+ InfoBox.prototype.setPosition = function (latlng) {
640
+
641
+ this.position_ = latlng;
642
+
643
+ if (this.div_) {
644
+
645
+ this.draw();
646
+ }
647
+
648
+ /**
649
+ * This event is fired when the position of the InfoBox changes.
650
+ * @name InfoBox#position_changed
651
+ * @event
652
+ */
653
+ google.maps.event.trigger(this, "position_changed");
654
+ };
655
+
656
+ /**
657
+ * Sets the zIndex style for the InfoBox.
658
+ * @param {number} index
659
+ */
660
+ InfoBox.prototype.setZIndex = function (index) {
661
+
662
+ this.zIndex_ = index;
663
+
664
+ if (this.div_) {
665
+
666
+ this.div_.style.zIndex = index;
667
+ }
668
+
669
+ /**
670
+ * This event is fired when the zIndex of the InfoBox changes.
671
+ * @name InfoBox#zindex_changed
672
+ * @event
673
+ */
674
+ google.maps.event.trigger(this, "zindex_changed");
675
+ };
676
+
677
+ /**
678
+ * Sets the visibility of the InfoBox.
679
+ * @param {boolean} isVisible
680
+ */
681
+ InfoBox.prototype.setVisible = function (isVisible) {
682
+
683
+ this.isHidden_ = !isVisible;
684
+ if (this.div_) {
685
+ this.div_.style.visibility = (this.isHidden_ ? "hidden" : "visible");
686
+ }
687
+ };
688
+
689
+ /**
690
+ * Returns the content of the InfoBox.
691
+ * @returns {string}
692
+ */
693
+ InfoBox.prototype.getContent = function () {
694
+
695
+ return this.content_;
696
+ };
697
+
698
+ /**
699
+ * Returns the geographic location of the InfoBox.
700
+ * @returns {LatLng}
701
+ */
702
+ InfoBox.prototype.getPosition = function () {
703
+
704
+ return this.position_;
705
+ };
706
+
707
+ /**
708
+ * Returns the zIndex for the InfoBox.
709
+ * @returns {number}
710
+ */
711
+ InfoBox.prototype.getZIndex = function () {
712
+
713
+ return this.zIndex_;
714
+ };
715
+
716
+ /**
717
+ * Returns a flag indicating whether the InfoBox is visible.
718
+ * @returns {boolean}
719
+ */
720
+ InfoBox.prototype.getVisible = function () {
721
+
722
+ var isVisible;
723
+
724
+ if ((typeof this.getMap() === "undefined") || (this.getMap() === null)) {
725
+ isVisible = false;
726
+ } else {
727
+ isVisible = !this.isHidden_;
728
+ }
729
+ return isVisible;
730
+ };
731
+
732
+ /**
733
+ * Shows the InfoBox. [Deprecated; use <tt>setVisible</tt> instead.]
734
+ */
735
+ InfoBox.prototype.show = function () {
736
+
737
+ this.isHidden_ = false;
738
+ if (this.div_) {
739
+ this.div_.style.visibility = "visible";
740
+ }
741
+ };
742
+
743
+ /**
744
+ * Hides the InfoBox. [Deprecated; use <tt>setVisible</tt> instead.]
745
+ */
746
+ InfoBox.prototype.hide = function () {
747
+
748
+ this.isHidden_ = true;
749
+ if (this.div_) {
750
+ this.div_.style.visibility = "hidden";
751
+ }
752
+ };
753
+
754
+ /**
755
+ * Adds the InfoBox to the specified map or Street View panorama. If <tt>anchor</tt>
756
+ * (usually a <tt>google.maps.Marker</tt>) is specified, the position
757
+ * of the InfoBox is set to the position of the <tt>anchor</tt>. If the
758
+ * anchor is dragged to a new location, the InfoBox moves as well.
759
+ * @param {Map|StreetViewPanorama} map
760
+ * @param {MVCObject} [anchor]
761
+ */
762
+ InfoBox.prototype.open = function (map, anchor) {
763
+
764
+ var me = this;
765
+
766
+ if (anchor) {
767
+
768
+ this.position_ = anchor.getPosition();
769
+ this.moveListener_ = google.maps.event.addListener(anchor, "position_changed", function () {
770
+ me.setPosition(this.getPosition());
771
+ });
772
+ }
773
+
774
+ this.setMap(map);
775
+
776
+ if (this.div_) {
777
+
778
+ this.panBox_();
779
+ }
780
+ };
781
+
782
+ /**
783
+ * Removes the InfoBox from the map.
784
+ */
785
+ InfoBox.prototype.close = function () {
786
+
787
+ var i;
788
+
789
+ if (this.closeListener_) {
790
+
791
+ google.maps.event.removeListener(this.closeListener_);
792
+ this.closeListener_ = null;
793
+ }
794
+
795
+ if (this.eventListeners_) {
796
+
797
+ for (i = 0; i < this.eventListeners_.length; i++) {
798
+
799
+ google.maps.event.removeListener(this.eventListeners_[i]);
800
+ }
801
+ this.eventListeners_ = null;
802
+ }
803
+
804
+ if (this.moveListener_) {
805
+
806
+ google.maps.event.removeListener(this.moveListener_);
807
+ this.moveListener_ = null;
808
+ }
809
+
810
+ if (this.contextListener_) {
811
+
812
+ google.maps.event.removeListener(this.contextListener_);
813
+ this.contextListener_ = null;
814
+ }
815
+
816
+ this.setMap(null);
817
+ };
js/markerclusterer.js CHANGED
@@ -1,1304 +1,1304 @@
1
- // ==ClosureCompiler==
2
- // @compilation_level ADVANCED_OPTIMIZATIONS
3
- // @externs_url http://closure-compiler.googlecode.com/svn/trunk/contrib/externs/maps/google_maps_api_v3_3.js
4
- // ==/ClosureCompiler==
5
-
6
- /**
7
- * @name MarkerClusterer for Google Maps v3
8
- * @version version 1.0.1
9
- * @author Luke Mahe
10
- * @fileoverview
11
- * The library creates and manages per-zoom-level clusters for large amounts of
12
- * markers.
13
- * <br/>
14
- * This is a v3 implementation of the
15
- * <a href="http://gmaps-utility-library-dev.googlecode.com/svn/tags/markerclusterer/"
16
- * >v2 MarkerClusterer</a>.
17
- */
18
-
19
- /**
20
- * Licensed under the Apache License, Version 2.0 (the "License");
21
- * you may not use this file except in compliance with the License.
22
- * You may obtain a copy of the License at
23
- *
24
- * http://www.apache.org/licenses/LICENSE-2.0
25
- *
26
- * Unless required by applicable law or agreed to in writing, software
27
- * distributed under the License is distributed on an "AS IS" BASIS,
28
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29
- * See the License for the specific language governing permissions and
30
- * limitations under the License.
31
- */
32
-
33
-
34
- /**
35
- * A Marker Clusterer that clusters markers.
36
- *
37
- * @param {google.maps.Map} map The Google map to attach to.
38
- * @param {Array.<google.maps.Marker>=} opt_markers Optional markers to add to
39
- * the cluster.
40
- * @param {Object=} opt_options support the following options:
41
- * 'gridSize': (number) The grid size of a cluster in pixels.
42
- * 'maxZoom': (number) The maximum zoom level that a marker can be part of a
43
- * cluster.
44
- * 'zoomOnClick': (boolean) Whether the default behaviour of clicking on a
45
- * cluster is to zoom into it.
46
- * 'averageCenter': (boolean) Wether the center of each cluster should be
47
- * the average of all markers in the cluster.
48
- * 'minimumClusterSize': (number) The minimum number of markers to be in a
49
- * cluster before the markers are hidden and a count
50
- * is shown.
51
- * 'styles': (object) An object that has style properties:
52
- * 'url': (string) The image url.
53
- * 'height': (number) The image height.
54
- * 'width': (number) The image width.
55
- * 'anchor': (Array) The anchor position of the label text.
56
- * 'textColor': (string) The text color.
57
- * 'textSize': (number) The text size.
58
- * 'backgroundPosition': (string) The position of the backgound x, y.
59
- * @constructor
60
- * @extends google.maps.OverlayView
61
- */
62
- function MarkerClusterer(map, opt_markers, opt_options) {
63
- // MarkerClusterer implements google.maps.OverlayView interface. We use the
64
- // extend function to extend MarkerClusterer with google.maps.OverlayView
65
- // because it might not always be available when the code is defined so we
66
- // look for it at the last possible moment. If it doesn't exist now then
67
- // there is no point going ahead :)
68
- this.extend(MarkerClusterer, google.maps.OverlayView);
69
- this.map_ = map;
70
-
71
- /**
72
- * @type {Array.<google.maps.Marker>}
73
- * @private
74
- */
75
- this.markers_ = [];
76
-
77
- /**
78
- * @type {Array.<Cluster>}
79
- */
80
- this.clusters_ = [];
81
-
82
- this.sizes = [53, 56, 66, 78, 90];
83
-
84
- /**
85
- * @private
86
- */
87
- this.styles_ = [];
88
-
89
- /**
90
- * @type {boolean}
91
- * @private
92
- */
93
- this.ready_ = false;
94
-
95
- var options = opt_options || {};
96
-
97
- /**
98
- * @type {number}
99
- * @private
100
- */
101
- this.gridSize_ = options['gridSize'] || 60;
102
-
103
- /**
104
- * @private
105
- */
106
- this.minClusterSize_ = options['minimumClusterSize'] || 2;
107
-
108
-
109
- /**
110
- * @type {?number}
111
- * @private
112
- */
113
- this.maxZoom_ = options['maxZoom'] || null;
114
-
115
- this.styles_ = options['styles'] || [];
116
-
117
- /**
118
- * @type {string}
119
- * @private
120
- */
121
- this.imagePath_ = options['imagePath'] ||
122
- this.MARKER_CLUSTER_IMAGE_PATH_;
123
-
124
- /**
125
- * @type {string}
126
- * @private
127
- */
128
- this.imageExtension_ = options['imageExtension'] ||
129
- this.MARKER_CLUSTER_IMAGE_EXTENSION_;
130
-
131
- /**
132
- * @type {boolean}
133
- * @private
134
- */
135
- this.zoomOnClick_ = true;
136
-
137
- if (options['zoomOnClick'] != undefined) {
138
- this.zoomOnClick_ = options['zoomOnClick'];
139
- }
140
-
141
- /**
142
- * @type {boolean}
143
- * @private
144
- */
145
- this.averageCenter_ = false;
146
-
147
- if (options['averageCenter'] != undefined) {
148
- this.averageCenter_ = options['averageCenter'];
149
- }
150
-
151
- this.setupStyles_();
152
-
153
- this.setMap(map);
154
-
155
- /**
156
- * @type {number}
157
- * @private
158
- */
159
- this.prevZoom_ = this.map_.getZoom();
160
-
161
- // Add the map event listeners
162
- var that = this;
163
- google.maps.event.addListener(this.map_, 'zoom_changed', function() {
164
- // Determines map type and prevent illegal zoom levels
165
- var zoom = that.map_.getZoom();
166
- var minZoom = that.map_.minZoom || 0;
167
- var maxZoom = Math.min(that.map_.maxZoom || 100,
168
- that.map_.mapTypes[that.map_.getMapTypeId()].maxZoom);
169
- zoom = Math.min(Math.max(zoom,minZoom),maxZoom);
170
-
171
- if (that.prevZoom_ != zoom) {
172
- that.prevZoom_ = zoom;
173
- that.resetViewport();
174
- }
175
- });
176
-
177
- google.maps.event.addListener(this.map_, 'idle', function() {
178
- that.redraw();
179
- });
180
-
181
- // Finally, add the markers
182
- if (opt_markers && (opt_markers.length || Object.keys(opt_markers).length)) {
183
- this.addMarkers(opt_markers, false);
184
- }
185
- }
186
-
187
- /**
188
- * The marker cluster image path.
189
- *
190
- * @type {string}
191
- * @private
192
- */
193
- MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_PATH_ = wpslSettings.clusterImagePath;
194
-
195
- /**
196
- * The marker cluster image path.
197
- *
198
- * @type {string}
199
- * @private
200
- */
201
- MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_EXTENSION_ = 'png';
202
-
203
- /**
204
- * Extends a objects prototype by anothers.
205
- *
206
- * @param {Object} obj1 The object to be extended.
207
- * @param {Object} obj2 The object to extend with.
208
- * @return {Object} The new extended object.
209
- * @ignore
210
- */
211
- MarkerClusterer.prototype.extend = function(obj1, obj2) {
212
- return (function(object) {
213
- for (var property in object.prototype) {
214
- this.prototype[property] = object.prototype[property];
215
- }
216
- return this;
217
- }).apply(obj1, [obj2]);
218
- };
219
-
220
- /**
221
- * Implementaion of the interface method.
222
- * @ignore
223
- */
224
- MarkerClusterer.prototype.onAdd = function() {
225
- this.setReady_(true);
226
- };
227
-
228
- /**
229
- * Implementaion of the interface method.
230
- * @ignore
231
- */
232
- MarkerClusterer.prototype.draw = function() {};
233
-
234
- /**
235
- * Sets up the styles object.
236
- *
237
- * @private
238
- */
239
- MarkerClusterer.prototype.setupStyles_ = function() {
240
- if (this.styles_.length) {
241
- return;
242
- }
243
-
244
- for (var i = 0, size; size = this.sizes[i]; i++) {
245
- this.styles_.push({
246
- url: this.imagePath_ + (i + 1) + '.' + this.imageExtension_,
247
- height: size,
248
- width: size
249
- });
250
- }
251
- };
252
-
253
- /**
254
- * Fit the map to the bounds of the markers in the clusterer.
255
- */
256
- MarkerClusterer.prototype.fitMapToMarkers = function() {
257
- var markers = this.getMarkers();
258
- var bounds = new google.maps.LatLngBounds();
259
- for (var i = 0, marker; marker = markers[i]; i++) {
260
- bounds.extend(marker.getPosition());
261
- }
262
-
263
- this.map_.fitBounds(bounds);
264
- };
265
-
266
-
267
- /**
268
- * Sets the styles.
269
- *
270
- * @param {Object} styles The style to set.
271
- */
272
- MarkerClusterer.prototype.setStyles = function(styles) {
273
- this.styles_ = styles;
274
- };
275
-
276
-
277
- /**
278
- * Gets the styles.
279
- *
280
- * @return {Object} The styles object.
281
- */
282
- MarkerClusterer.prototype.getStyles = function() {
283
- return this.styles_;
284
- };
285
-
286
-
287
- /**
288
- * Whether zoom on click is set.
289
- *
290
- * @return {boolean} True if zoomOnClick_ is set.
291
- */
292
- MarkerClusterer.prototype.isZoomOnClick = function() {
293
- return this.zoomOnClick_;
294
- };
295
-
296
- /**
297
- * Whether average center is set.
298
- *
299
- * @return {boolean} True if averageCenter_ is set.
300
- */
301
- MarkerClusterer.prototype.isAverageCenter = function() {
302
- return this.averageCenter_;
303
- };
304
-
305
-
306
- /**
307
- * Returns the array of markers in the clusterer.
308
- *
309
- * @return {Array.<google.maps.Marker>} The markers.
310
- */
311
- MarkerClusterer.prototype.getMarkers = function() {
312
- return this.markers_;
313
- };
314
-
315
-
316
- /**
317
- * Returns the number of markers in the clusterer
318
- *
319
- * @return {Number} The number of markers.
320
- */
321
- MarkerClusterer.prototype.getTotalMarkers = function() {
322
- return this.markers_.length;
323
- };
324
-
325
-
326
- /**
327
- * Sets the max zoom for the clusterer.
328
- *
329
- * @param {number} maxZoom The max zoom level.
330
- */
331
- MarkerClusterer.prototype.setMaxZoom = function(maxZoom) {
332
- this.maxZoom_ = maxZoom;
333
- };
334
-
335
-
336
- /**
337
- * Gets the max zoom for the clusterer.
338
- *
339
- * @return {number} The max zoom level.
340
- */
341
- MarkerClusterer.prototype.getMaxZoom = function() {
342
- return this.maxZoom_;
343
- };
344
-
345
-
346
- /**
347
- * The function for calculating the cluster icon image.
348
- *
349
- * @param {Array.<google.maps.Marker>} markers The markers in the clusterer.
350
- * @param {number} numStyles The number of styles available.
351
- * @return {Object} A object properties: 'text' (string) and 'index' (number).
352
- * @private
353
- */
354
- MarkerClusterer.prototype.calculator_ = function(markers, numStyles) {
355
- var index = 0;
356
- var count = markers.length;
357
- var dv = count;
358
- while (dv !== 0) {
359
- dv = parseInt(dv / 10, 10);
360
- index++;
361
- }
362
-
363
- index = Math.min(index, numStyles);
364
- return {
365
- text: count,
366
- index: index
367
- };
368
- };
369
-
370
-
371
- /**
372
- * Set the calculator function.
373
- *
374
- * @param {function(Array, number)} calculator The function to set as the
375
- * calculator. The function should return a object properties:
376
- * 'text' (string) and 'index' (number).
377
- *
378
- */
379
- MarkerClusterer.prototype.setCalculator = function(calculator) {
380
- this.calculator_ = calculator;
381
- };
382
-
383
-
384
- /**
385
- * Get the calculator function.
386
- *
387
- * @return {function(Array, number)} the calculator function.
388
- */
389
- MarkerClusterer.prototype.getCalculator = function() {
390
- return this.calculator_;
391
- };
392
-
393
-
394
- /**
395
- * Add an array of markers to the clusterer.
396
- *
397
- * @param {Array.<google.maps.Marker>} markers The markers to add.
398
- * @param {boolean=} opt_nodraw Whether to redraw the clusters.
399
- */
400
- MarkerClusterer.prototype.addMarkers = function(markers, opt_nodraw) {
401
- if (markers.length) {
402
- for (var i = 0, marker; marker = markers[i]; i++) {
403
- this.pushMarkerTo_(marker);
404
- }
405
- } else if (Object.keys(markers).length) {
406
- for (var marker in markers) {
407
- this.pushMarkerTo_(markers[marker]);
408
- }
409
- }
410
- if (!opt_nodraw) {
411
- this.redraw();
412
- }
413
- };
414
-
415
-
416
- /**
417
- * Pushes a marker to the clusterer.
418
- *
419
- * @param {google.maps.Marker} marker The marker to add.
420
- * @private
421
- */
422
- MarkerClusterer.prototype.pushMarkerTo_ = function(marker) {
423
- marker.isAdded = false;
424
- if (marker['draggable']) {
425
- // If the marker is draggable add a listener so we update the clusters on
426
- // the drag end.
427
- var that = this;
428
- google.maps.event.addListener(marker, 'dragend', function() {
429
- marker.isAdded = false;
430
- that.repaint();
431
- });
432
- }
433
- this.markers_.push(marker);
434
- };
435
-
436
-
437
- /**
438
- * Adds a marker to the clusterer and redraws if needed.
439
- *
440
- * @param {google.maps.Marker} marker The marker to add.
441
- * @param {boolean=} opt_nodraw Whether to redraw the clusters.
442
- */
443
- MarkerClusterer.prototype.addMarker = function(marker, opt_nodraw) {
444
- this.pushMarkerTo_(marker);
445
- if (!opt_nodraw) {
446
- this.redraw();
447
- }
448
- };
449
-
450
-
451
- /**
452
- * Removes a marker and returns true if removed, false if not
453
- *
454
- * @param {google.maps.Marker} marker The marker to remove
455
- * @return {boolean} Whether the marker was removed or not
456
- * @private
457
- */
458
- MarkerClusterer.prototype.removeMarker_ = function(marker) {
459
- var index = -1;
460
- if (this.markers_.indexOf) {
461
- index = this.markers_.indexOf(marker);
462
- } else {
463
- for (var i = 0, m; m = this.markers_[i]; i++) {
464
- if (m == marker) {
465
- index = i;
466
- break;
467
- }
468
- }
469
- }
470
-
471
- if (index == -1) {
472
- // Marker is not in our list of markers.
473
- return false;
474
- }
475
-
476
- marker.setMap(null);
477
-
478
- this.markers_.splice(index, 1);
479
-
480
- return true;
481
- };
482
-
483
-
484
- /**
485
- * Remove a marker from the cluster.
486
- *
487
- * @param {google.maps.Marker} marker The marker to remove.
488
- * @param {boolean=} opt_nodraw Optional boolean to force no redraw.
489
- * @return {boolean} True if the marker was removed.
490
- */
491
- MarkerClusterer.prototype.removeMarker = function(marker, opt_nodraw) {
492
- var removed = this.removeMarker_(marker);
493
-
494
- if (!opt_nodraw && removed) {
495
- this.resetViewport();
496
- this.redraw();
497
- return true;
498
- } else {
499
- return false;
500
- }
501
- };
502
-
503
-
504
- /**
505
- * Removes an array of markers from the cluster.
506
- *
507
- * @param {Array.<google.maps.Marker>} markers The markers to remove.
508
- * @param {boolean=} opt_nodraw Optional boolean to force no redraw.
509
- */
510
- MarkerClusterer.prototype.removeMarkers = function(markers, opt_nodraw) {
511
- var removed = false;
512
-
513
- for (var i = 0, marker; marker = markers[i]; i++) {
514
- var r = this.removeMarker_(marker);
515
- removed = removed || r;
516
- }
517
-
518
- if (!opt_nodraw && removed) {
519
- this.resetViewport();
520
- this.redraw();
521
- return true;
522
- }
523
- };
524
-
525
-
526
- /**
527
- * Sets the clusterer's ready state.
528
- *
529
- * @param {boolean} ready The state.
530
- * @private
531
- */
532
- MarkerClusterer.prototype.setReady_ = function(ready) {
533
- if (!this.ready_) {
534
- this.ready_ = ready;
535
- this.createClusters_();
536
- }
537
- };
538
-
539
-
540
- /**
541
- * Returns the number of clusters in the clusterer.
542
- *
543
- * @return {number} The number of clusters.
544
- */
545
- MarkerClusterer.prototype.getTotalClusters = function() {
546
- return this.clusters_.length;
547
- };
548
-
549
-
550
- /**
551
- * Returns the google map that the clusterer is associated with.
552
- *
553
- * @return {google.maps.Map} The map.
554
- */
555
- MarkerClusterer.prototype.getMap = function() {
556
- return this.map_;
557
- };
558
-
559
-
560
- /**
561
- * Sets the google map that the clusterer is associated with.
562
- *
563
- * @param {google.maps.Map} map The map.
564
- */
565
- MarkerClusterer.prototype.setMap = function(map) {
566
- this.map_ = map;
567
- };
568
-
569
-
570
- /**
571
- * Returns the size of the grid.
572
- *
573
- * @return {number} The grid size.
574
- */
575
- MarkerClusterer.prototype.getGridSize = function() {
576
- return this.gridSize_;
577
- };
578
-
579
-
580
- /**
581
- * Sets the size of the grid.
582
- *
583
- * @param {number} size The grid size.
584
- */
585
- MarkerClusterer.prototype.setGridSize = function(size) {
586
- this.gridSize_ = size;
587
- };
588
-
589
-
590
- /**
591
- * Returns the min cluster size.
592
- *
593
- * @return {number} The grid size.
594
- */
595
- MarkerClusterer.prototype.getMinClusterSize = function() {
596
- return this.minClusterSize_;
597
- };
598
-
599
- /**
600
- * Sets the min cluster size.
601
- *
602
- * @param {number} size The grid size.
603
- */
604
- MarkerClusterer.prototype.setMinClusterSize = function(size) {
605
- this.minClusterSize_ = size;
606
- };
607
-
608
-
609
- /**
610
- * Extends a bounds object by the grid size.
611
- *
612
- * @param {google.maps.LatLngBounds} bounds The bounds to extend.
613
- * @return {google.maps.LatLngBounds} The extended bounds.
614
- */
615
- MarkerClusterer.prototype.getExtendedBounds = function(bounds) {
616
- var projection = this.getProjection();
617
-
618
- // Turn the bounds into latlng.
619
- var tr = new google.maps.LatLng(bounds.getNorthEast().lat(),
620
- bounds.getNorthEast().lng());
621
- var bl = new google.maps.LatLng(bounds.getSouthWest().lat(),
622
- bounds.getSouthWest().lng());
623
-
624
- // Convert the points to pixels and the extend out by the grid size.
625
- var trPix = projection.fromLatLngToDivPixel(tr);
626
- trPix.x += this.gridSize_;
627
- trPix.y -= this.gridSize_;
628
-
629
- var blPix = projection.fromLatLngToDivPixel(bl);
630
- blPix.x -= this.gridSize_;
631
- blPix.y += this.gridSize_;
632
-
633
- // Convert the pixel points back to LatLng
634
- var ne = projection.fromDivPixelToLatLng(trPix);
635
- var sw = projection.fromDivPixelToLatLng(blPix);
636
-
637
- // Extend the bounds to contain the new bounds.
638
- bounds.extend(ne);
639
- bounds.extend(sw);
640
-
641
- return bounds;
642
- };
643
-
644
-
645
- /**
646
- * Determins if a marker is contained in a bounds.
647
- *
648
- * @param {google.maps.Marker} marker The marker to check.
649
- * @param {google.maps.LatLngBounds} bounds The bounds to check against.
650
- * @return {boolean} True if the marker is in the bounds.
651
- * @private
652
- */
653
- MarkerClusterer.prototype.isMarkerInBounds_ = function(marker, bounds) {
654
- return bounds.contains(marker.getPosition());
655
- };
656
-
657
-
658
- /**
659
- * Clears all clusters and markers from the clusterer.
660
- */
661
- MarkerClusterer.prototype.clearMarkers = function() {
662
- this.resetViewport(true);
663
-
664
- // Set the markers a empty array.
665
- this.markers_ = [];
666
- };
667
-
668
-
669
- /**
670
- * Clears all existing clusters and recreates them.
671
- * @param {boolean} opt_hide To also hide the marker.
672
- */
673
- MarkerClusterer.prototype.resetViewport = function(opt_hide) {
674
- // Remove all the clusters
675
- for (var i = 0, cluster; cluster = this.clusters_[i]; i++) {
676
- cluster.remove();
677
- }
678
-
679
- // Reset the markers to not be added and to be invisible.
680
- for (var i = 0, marker; marker = this.markers_[i]; i++) {
681
- marker.isAdded = false;
682
- if (opt_hide) {
683
- marker.setMap(null);
684
- }
685
- }
686
-
687
- this.clusters_ = [];
688
- };
689
-
690
- /**
691
- *
692
- */
693
- MarkerClusterer.prototype.repaint = function() {
694
- var oldClusters = this.clusters_.slice();
695
- this.clusters_.length = 0;
696
- this.resetViewport();
697
- this.redraw();
698
-
699
- // Remove the old clusters.
700
- // Do it in a timeout so the other clusters have been drawn first.
701
- window.setTimeout(function() {
702
- for (var i = 0, cluster; cluster = oldClusters[i]; i++) {
703
- cluster.remove();
704
- }
705
- }, 0);
706
- };
707
-
708
-
709
- /**
710
- * Redraws the clusters.
711
- */
712
- MarkerClusterer.prototype.redraw = function() {
713
- this.createClusters_();
714
- };
715
-
716
-
717
- /**
718
- * Calculates the distance between two latlng locations in km.
719
- * @see http://www.movable-type.co.uk/scripts/latlong.html
720
- *
721
- * @param {google.maps.LatLng} p1 The first lat lng point.
722
- * @param {google.maps.LatLng} p2 The second lat lng point.
723
- * @return {number} The distance between the two points in km.
724
- * @private
725
- */
726
- MarkerClusterer.prototype.distanceBetweenPoints_ = function(p1, p2) {
727
- if (!p1 || !p2) {
728
- return 0;
729
- }
730
-
731
- var R = 6371; // Radius of the Earth in km
732
- var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
733
- var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
734
- var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
735
- Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) *
736
- Math.sin(dLon / 2) * Math.sin(dLon / 2);
737
- var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
738
- var d = R * c;
739
- return d;
740
- };
741
-
742
-
743
- /**
744
- * Add a marker to a cluster, or creates a new cluster.
745
- *
746
- * @param {google.maps.Marker} marker The marker to add.
747
- * @private
748
- */
749
- MarkerClusterer.prototype.addToClosestCluster_ = function(marker) {
750
- var distance = 40000; // Some large number
751
- var clusterToAddTo = null;
752
- var pos = marker.getPosition();
753
- for (var i = 0, cluster; cluster = this.clusters_[i]; i++) {
754
- var center = cluster.getCenter();
755
- if (center) {
756
- var d = this.distanceBetweenPoints_(center, marker.getPosition());
757
- if (d < distance) {
758
- distance = d;
759
- clusterToAddTo = cluster;
760
- }
761
- }
762
- }
763
-
764
- if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) {
765
- clusterToAddTo.addMarker(marker);
766
- } else {
767
- var cluster = new Cluster(this);
768
- cluster.addMarker(marker);
769
- this.clusters_.push(cluster);
770
- }
771
- };
772
-
773
-
774
- /**
775
- * Creates the clusters.
776
- *
777
- * @private
778
- */
779
- MarkerClusterer.prototype.createClusters_ = function() {
780
- if (!this.ready_) {
781
- return;
782
- }
783
-
784
- // Get our current map view bounds.
785
- // Create a new bounds object so we don't affect the map.
786
- var mapBounds = new google.maps.LatLngBounds(this.map_.getBounds().getSouthWest(),
787
- this.map_.getBounds().getNorthEast());
788
- var bounds = this.getExtendedBounds(mapBounds);
789
-
790
- for (var i = 0, marker; marker = this.markers_[i]; i++) {
791
- if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) {
792
- this.addToClosestCluster_(marker);
793
- }
794
- }
795
- };
796
-
797
-
798
- /**
799
- * A cluster that contains markers.
800
- *
801
- * @param {MarkerClusterer} markerClusterer The markerclusterer that this
802
- * cluster is associated with.
803
- * @constructor
804
- * @ignore
805
- */
806
- function Cluster(markerClusterer) {
807
- this.markerClusterer_ = markerClusterer;
808
- this.map_ = markerClusterer.getMap();
809
- this.gridSize_ = markerClusterer.getGridSize();
810
- this.minClusterSize_ = markerClusterer.getMinClusterSize();
811
- this.averageCenter_ = markerClusterer.isAverageCenter();
812
- this.center_ = null;
813
- this.markers_ = [];
814
- this.bounds_ = null;
815
- this.clusterIcon_ = new ClusterIcon(this, markerClusterer.getStyles(),
816
- markerClusterer.getGridSize());
817
- }
818
-
819
- /**
820
- * Determins if a marker is already added to the cluster.
821
- *
822
- * @param {google.maps.Marker} marker The marker to check.
823
- * @return {boolean} True if the marker is already added.
824
- */
825
- Cluster.prototype.isMarkerAlreadyAdded = function(marker) {
826
- if (this.markers_.indexOf) {
827
- return this.markers_.indexOf(marker) != -1;
828
- } else {
829
- for (var i = 0, m; m = this.markers_[i]; i++) {
830
- if (m == marker) {
831
- return true;
832
- }
833
- }
834
- }
835
- return false;
836
- };
837
-
838
-
839
- /**
840
- * Add a marker the cluster.
841
- *
842
- * @param {google.maps.Marker} marker The marker to add.
843
- * @return {boolean} True if the marker was added.
844
- */
845
- Cluster.prototype.addMarker = function(marker) {
846
- if (this.isMarkerAlreadyAdded(marker)) {
847
- return false;
848
- }
849
-
850
- if (!this.center_) {
851
- this.center_ = marker.getPosition();
852
- this.calculateBounds_();
853
- } else {
854
- if (this.averageCenter_) {
855
- var l = this.markers_.length + 1;
856
- var lat = (this.center_.lat() * (l-1) + marker.getPosition().lat()) / l;
857
- var lng = (this.center_.lng() * (l-1) + marker.getPosition().lng()) / l;
858
- this.center_ = new google.maps.LatLng(lat, lng);
859
- this.calculateBounds_();
860
- }
861
- }
862
-
863
- marker.isAdded = true;
864
- this.markers_.push(marker);
865
-
866
- var len = this.markers_.length;
867
- if (len < this.minClusterSize_ && marker.getMap() != this.map_) {
868
- // Min cluster size not reached so show the marker.
869
- marker.setMap(this.map_);
870
- }
871
-
872
- if (len == this.minClusterSize_) {
873
- // Hide the markers that were showing.
874
- for (var i = 0; i < len; i++) {
875
- this.markers_[i].setMap(null);
876
- }
877
- }
878
-
879
- if (len >= this.minClusterSize_) {
880
- marker.setMap(null);
881
- }
882
-
883
- this.updateIcon();
884
- return true;
885
- };
886
-
887
-
888
- /**
889
- * Returns the marker clusterer that the cluster is associated with.
890
- *
891
- * @return {MarkerClusterer} The associated marker clusterer.
892
- */
893
- Cluster.prototype.getMarkerClusterer = function() {
894
- return this.markerClusterer_;
895
- };
896
-
897
-
898
- /**
899
- * Returns the bounds of the cluster.
900
- *
901
- * @return {google.maps.LatLngBounds} the cluster bounds.
902
- */
903
- Cluster.prototype.getBounds = function() {
904
- var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
905
- var markers = this.getMarkers();
906
- for (var i = 0, marker; marker = markers[i]; i++) {
907
- bounds.extend(marker.getPosition());
908
- }
909
- return bounds;
910
- };
911
-
912
-
913
- /**
914
- * Removes the cluster
915
- */
916
- Cluster.prototype.remove = function() {
917
- this.clusterIcon_.remove();
918
- this.markers_.length = 0;
919
- delete this.markers_;
920
- };
921
-
922
-
923
- /**
924
- * Returns the center of the cluster.
925
- *
926
- * @return {number} The cluster center.
927
- */
928
- Cluster.prototype.getSize = function() {
929
- return this.markers_.length;
930
- };
931
-
932
-
933
- /**
934
- * Returns the center of the cluster.
935
- *
936
- * @return {Array.<google.maps.Marker>} The cluster center.
937
- */
938
- Cluster.prototype.getMarkers = function() {
939
- return this.markers_;
940
- };
941
-
942
-
943
- /**
944
- * Returns the center of the cluster.
945
- *
946
- * @return {google.maps.LatLng} The cluster center.
947
- */
948
- Cluster.prototype.getCenter = function() {
949
- return this.center_;
950
- };
951
-
952
-
953
- /**
954
- * Calculated the extended bounds of the cluster with the grid.
955
- *
956
- * @private
957
- */
958
- Cluster.prototype.calculateBounds_ = function() {
959
- var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
960
- this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds);
961
- };
962
-
963
-
964
- /**
965
- * Determines if a marker lies in the clusters bounds.
966
- *
967
- * @param {google.maps.Marker} marker The marker to check.
968
- * @return {boolean} True if the marker lies in the bounds.
969
- */
970
- Cluster.prototype.isMarkerInClusterBounds = function(marker) {
971
- return this.bounds_.contains(marker.getPosition());
972
- };
973
-
974
-
975
- /**
976
- * Returns the map that the cluster is associated with.
977
- *
978
- * @return {google.maps.Map} The map.
979
- */
980
- Cluster.prototype.getMap = function() {
981
- return this.map_;
982
- };
983
-
984
-
985
- /**
986
- * Updates the cluster icon
987
- */
988
- Cluster.prototype.updateIcon = function() {
989
- var zoom = this.map_.getZoom();
990
- var mz = this.markerClusterer_.getMaxZoom();
991
-
992
- if (mz && zoom > mz) {
993
- // The zoom is greater than our max zoom so show all the markers in cluster.
994
- for (var i = 0, marker; marker = this.markers_[i]; i++) {
995
- marker.setMap(this.map_);
996
- }
997
- return;
998
- }
999
-
1000
- if (this.markers_.length < this.minClusterSize_) {
1001
- // Min cluster size not yet reached.
1002
- this.clusterIcon_.hide();
1003
- return;
1004
- }
1005
-
1006
- var numStyles = this.markerClusterer_.getStyles().length;
1007
- var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles);
1008
- this.clusterIcon_.setCenter(this.center_);
1009
- this.clusterIcon_.setSums(sums);
1010
- this.clusterIcon_.show();
1011
- };
1012
-
1013
-
1014
- /**
1015
- * A cluster icon
1016
- *
1017
- * @param {Cluster} cluster The cluster to be associated with.
1018
- * @param {Object} styles An object that has style properties:
1019
- * 'url': (string) The image url.
1020
- * 'height': (number) The image height.
1021
- * 'width': (number) The image width.
1022
- * 'anchor': (Array) The anchor position of the label text.
1023
- * 'textColor': (string) The text color.
1024
- * 'textSize': (number) The text size.
1025
- * 'backgroundPosition: (string) The background postition x, y.
1026
- * @param {number=} opt_padding Optional padding to apply to the cluster icon.
1027
- * @constructor
1028
- * @extends google.maps.OverlayView
1029
- * @ignore
1030
- */
1031
- function ClusterIcon(cluster, styles, opt_padding) {
1032
- cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView);
1033
-
1034
- this.styles_ = styles;
1035
- this.padding_ = opt_padding || 0;
1036
- this.cluster_ = cluster;
1037
- this.center_ = null;
1038
- this.map_ = cluster.getMap();
1039
- this.div_ = null;
1040
- this.sums_ = null;
1041
- this.visible_ = false;
1042
-
1043
- this.setMap(this.map_);
1044
- }
1045
-
1046
-
1047
- /**
1048
- * Triggers the clusterclick event and zoom's if the option is set.
1049
- */
1050
- ClusterIcon.prototype.triggerClusterClick = function() {
1051
- var markerClusterer = this.cluster_.getMarkerClusterer();
1052
-
1053
- // Trigger the clusterclick event.
1054
- google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster_);
1055
-
1056
- if (markerClusterer.isZoomOnClick()) {
1057
- // Zoom into the cluster.
1058
- this.map_.fitBounds(this.cluster_.getBounds());
1059
- }
1060
- };
1061
-
1062
-
1063
- /**
1064
- * Adding the cluster icon to the dom.
1065
- * @ignore
1066
- */
1067
- ClusterIcon.prototype.onAdd = function() {
1068
- this.div_ = document.createElement('DIV');
1069
- if (this.visible_) {
1070
- var pos = this.getPosFromLatLng_(this.center_);
1071
- this.div_.style.cssText = this.createCss(pos);
1072
- this.div_.innerHTML = this.sums_.text;
1073
- }
1074
-
1075
- var panes = this.getPanes();
1076
- panes.overlayMouseTarget.appendChild(this.div_);
1077
-
1078
- var that = this;
1079
- google.maps.event.addDomListener(this.div_, 'click', function() {
1080
- that.triggerClusterClick();
1081
- });
1082
- };
1083
-
1084
-
1085
- /**
1086
- * Returns the position to place the div dending on the latlng.
1087
- *
1088
- * @param {google.maps.LatLng} latlng The position in latlng.
1089
- * @return {google.maps.Point} The position in pixels.
1090
- * @private
1091
- */
1092
- ClusterIcon.prototype.getPosFromLatLng_ = function(latlng) {
1093
- var pos = this.getProjection().fromLatLngToDivPixel(latlng);
1094
- pos.x -= parseInt(this.width_ / 2, 10);
1095
- pos.y -= parseInt(this.height_ / 2, 10);
1096
- return pos;
1097
- };
1098
-
1099
-
1100
- /**
1101
- * Draw the icon.
1102
- * @ignore
1103
- */
1104
- ClusterIcon.prototype.draw = function() {
1105
- if (this.visible_) {
1106
- var pos = this.getPosFromLatLng_(this.center_);
1107
- this.div_.style.top = pos.y + 'px';
1108
- this.div_.style.left = pos.x + 'px';
1109
- }
1110
- };
1111
-
1112
-
1113
- /**
1114
- * Hide the icon.
1115
- */
1116
- ClusterIcon.prototype.hide = function() {
1117
- if (this.div_) {
1118
- this.div_.style.display = 'none';
1119
- }
1120
- this.visible_ = false;
1121
- };
1122
-
1123
-
1124
- /**
1125
- * Position and show the icon.
1126
- */
1127
- ClusterIcon.prototype.show = function() {
1128
- if (this.div_) {
1129
- var pos = this.getPosFromLatLng_(this.center_);
1130
- this.div_.style.cssText = this.createCss(pos);
1131
- this.div_.style.display = '';
1132
- }
1133
- this.visible_ = true;
1134
- };
1135
-
1136
-
1137
- /**
1138
- * Remove the icon from the map
1139
- */
1140
- ClusterIcon.prototype.remove = function() {
1141
- this.setMap(null);
1142
- };
1143
-
1144
-
1145
- /**
1146
- * Implementation of the onRemove interface.
1147
- * @ignore
1148
- */
1149
- ClusterIcon.prototype.onRemove = function() {
1150
- if (this.div_ && this.div_.parentNode) {
1151
- this.hide();
1152
- this.div_.parentNode.removeChild(this.div_);
1153
- this.div_ = null;
1154
- }
1155
- };
1156
-
1157
-
1158
- /**
1159
- * Set the sums of the icon.
1160
- *
1161
- * @param {Object} sums The sums containing:
1162
- * 'text': (string) The text to display in the icon.
1163
- * 'index': (number) The style index of the icon.
1164
- */
1165
- ClusterIcon.prototype.setSums = function(sums) {
1166
- this.sums_ = sums;
1167
- this.text_ = sums.text;
1168
- this.index_ = sums.index;
1169
- if (this.div_) {
1170
- this.div_.innerHTML = sums.text;
1171
- }
1172
-
1173
- this.useStyle();
1174
- };
1175
-
1176
-
1177
- /**
1178
- * Sets the icon to the the styles.
1179
- */
1180
- ClusterIcon.prototype.useStyle = function() {
1181
- var index = Math.max(0, this.sums_.index - 1);
1182
- index = Math.min(this.styles_.length - 1, index);
1183
- var style = this.styles_[index];
1184
- this.url_ = style['url'];
1185
- this.height_ = style['height'];
1186
- this.width_ = style['width'];
1187
- this.textColor_ = style['textColor'];
1188
- this.anchor_ = style['anchor'];
1189
- this.textSize_ = style['textSize'];
1190
- this.backgroundPosition_ = style['backgroundPosition'];
1191
- };
1192
-
1193
-
1194
- /**
1195
- * Sets the center of the icon.
1196
- *
1197
- * @param {google.maps.LatLng} center The latlng to set as the center.
1198
- */
1199
- ClusterIcon.prototype.setCenter = function(center) {
1200
- this.center_ = center;
1201
- };
1202
-
1203
-
1204
- /**
1205
- * Create the css text based on the position of the icon.
1206
- *
1207
- * @param {google.maps.Point} pos The position.
1208
- * @return {string} The css style text.
1209
- */
1210
- ClusterIcon.prototype.createCss = function(pos) {
1211
- var style = [];
1212
- style.push('background-image:url(' + this.url_ + ');');
1213
- var backgroundPosition = this.backgroundPosition_ ? this.backgroundPosition_ : '0 0';
1214
- style.push('background-position:' + backgroundPosition + ';');
1215
-
1216
- if (typeof this.anchor_ === 'object') {
1217
- if (typeof this.anchor_[0] === 'number' && this.anchor_[0] > 0 &&
1218
- this.anchor_[0] < this.height_) {
1219
- style.push('height:' + (this.height_ - this.anchor_[0]) +
1220
- 'px; padding-top:' + this.anchor_[0] + 'px;');
1221
- } else {
1222
- style.push('height:' + this.height_ + 'px; line-height:' + this.height_ +
1223
- 'px;');
1224
- }
1225
- if (typeof this.anchor_[1] === 'number' && this.anchor_[1] > 0 &&
1226
- this.anchor_[1] < this.width_) {
1227
- style.push('width:' + (this.width_ - this.anchor_[1]) +
1228
- 'px; padding-left:' + this.anchor_[1] + 'px;');
1229
- } else {
1230
- style.push('width:' + this.width_ + 'px; text-align:center;');
1231
- }
1232
- } else {
1233
- style.push('height:' + this.height_ + 'px; line-height:' +
1234
- this.height_ + 'px; width:' + this.width_ + 'px; text-align:center;');
1235
- }
1236
-
1237
- var txtColor = this.textColor_ ? this.textColor_ : 'black';
1238
- var txtSize = this.textSize_ ? this.textSize_ : 11;
1239
-
1240
- style.push('cursor:pointer; top:' + pos.y + 'px; left:' +
1241
- pos.x + 'px; color:' + txtColor + '; position:absolute; font-size:' +
1242
- txtSize + 'px; font-family:Arial,sans-serif; font-weight:bold');
1243
- return style.join('');
1244
- };
1245
-
1246
-
1247
- // Export Symbols for Closure
1248
- // If you are not going to compile with closure then you can remove the
1249
- // code below.
1250
- window['MarkerClusterer'] = MarkerClusterer;
1251
- MarkerClusterer.prototype['addMarker'] = MarkerClusterer.prototype.addMarker;
1252
- MarkerClusterer.prototype['addMarkers'] = MarkerClusterer.prototype.addMarkers;
1253
- MarkerClusterer.prototype['clearMarkers'] =
1254
- MarkerClusterer.prototype.clearMarkers;
1255
- MarkerClusterer.prototype['fitMapToMarkers'] =
1256
- MarkerClusterer.prototype.fitMapToMarkers;
1257
- MarkerClusterer.prototype['getCalculator'] =
1258
- MarkerClusterer.prototype.getCalculator;
1259
- MarkerClusterer.prototype['getGridSize'] =
1260
- MarkerClusterer.prototype.getGridSize;
1261
- MarkerClusterer.prototype['getExtendedBounds'] =
1262
- MarkerClusterer.prototype.getExtendedBounds;
1263
- MarkerClusterer.prototype['getMap'] = MarkerClusterer.prototype.getMap;
1264
- MarkerClusterer.prototype['getMarkers'] = MarkerClusterer.prototype.getMarkers;
1265
- MarkerClusterer.prototype['getMaxZoom'] = MarkerClusterer.prototype.getMaxZoom;
1266
- MarkerClusterer.prototype['getStyles'] = MarkerClusterer.prototype.getStyles;
1267
- MarkerClusterer.prototype['getTotalClusters'] =
1268
- MarkerClusterer.prototype.getTotalClusters;
1269
- MarkerClusterer.prototype['getTotalMarkers'] =
1270
- MarkerClusterer.prototype.getTotalMarkers;
1271
- MarkerClusterer.prototype['redraw'] = MarkerClusterer.prototype.redraw;
1272
- MarkerClusterer.prototype['removeMarker'] =
1273
- MarkerClusterer.prototype.removeMarker;
1274
- MarkerClusterer.prototype['removeMarkers'] =
1275
- MarkerClusterer.prototype.removeMarkers;
1276
- MarkerClusterer.prototype['resetViewport'] =
1277
- MarkerClusterer.prototype.resetViewport;
1278
- MarkerClusterer.prototype['repaint'] =
1279
- MarkerClusterer.prototype.repaint;
1280
- MarkerClusterer.prototype['setCalculator'] =
1281
- MarkerClusterer.prototype.setCalculator;
1282
- MarkerClusterer.prototype['setGridSize'] =
1283
- MarkerClusterer.prototype.setGridSize;
1284
- MarkerClusterer.prototype['setMaxZoom'] =
1285
- MarkerClusterer.prototype.setMaxZoom;
1286
- MarkerClusterer.prototype['onAdd'] = MarkerClusterer.prototype.onAdd;
1287
- MarkerClusterer.prototype['draw'] = MarkerClusterer.prototype.draw;
1288
-
1289
- Cluster.prototype['getCenter'] = Cluster.prototype.getCenter;
1290
- Cluster.prototype['getSize'] = Cluster.prototype.getSize;
1291
- Cluster.prototype['getMarkers'] = Cluster.prototype.getMarkers;
1292
-
1293
- ClusterIcon.prototype['onAdd'] = ClusterIcon.prototype.onAdd;
1294
- ClusterIcon.prototype['draw'] = ClusterIcon.prototype.draw;
1295
- ClusterIcon.prototype['onRemove'] = ClusterIcon.prototype.onRemove;
1296
-
1297
- Object.keys = Object.keys || function(o) {
1298
- var result = [];
1299
- for(var name in o) {
1300
- if (o.hasOwnProperty(name))
1301
- result.push(name);
1302
- }
1303
- return result;
1304
- };
1
+ // ==ClosureCompiler==
2
+ // @compilation_level ADVANCED_OPTIMIZATIONS
3
+ // @externs_url http://closure-compiler.googlecode.com/svn/trunk/contrib/externs/maps/google_maps_api_v3_3.js
4
+ // ==/ClosureCompiler==
5
+
6
+ /**
7
+ * @name MarkerClusterer for Google Maps v3
8
+ * @version version 1.0.1
9
+ * @author Luke Mahe
10
+ * @fileoverview
11
+ * The library creates and manages per-zoom-level clusters for large amounts of
12
+ * markers.
13
+ * <br/>
14
+ * This is a v3 implementation of the
15
+ * <a href="http://gmaps-utility-library-dev.googlecode.com/svn/tags/markerclusterer/"
16
+ * >v2 MarkerClusterer</a>.
17
+ */
18
+
19
+ /**
20
+ * Licensed under the Apache License, Version 2.0 (the "License");
21
+ * you may not use this file except in compliance with the License.
22
+ * You may obtain a copy of the License at
23
+ *
24
+ * http://www.apache.org/licenses/LICENSE-2.0
25
+ *
26
+ * Unless required by applicable law or agreed to in writing, software
27
+ * distributed under the License is distributed on an "AS IS" BASIS,
28
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29
+ * See the License for the specific language governing permissions and
30
+ * limitations under the License.
31
+ */
32
+
33
+
34
+ /**
35
+ * A Marker Clusterer that clusters markers.
36
+ *
37
+ * @param {google.maps.Map} map The Google map to attach to.
38
+ * @param {Array.<google.maps.Marker>=} opt_markers Optional markers to add to
39
+ * the cluster.
40
+ * @param {Object=} opt_options support the following options:
41
+ * 'gridSize': (number) The grid size of a cluster in pixels.
42
+ * 'maxZoom': (number) The maximum zoom level that a marker can be part of a
43
+ * cluster.
44
+ * 'zoomOnClick': (boolean) Whether the default behaviour of clicking on a
45
+ * cluster is to zoom into it.
46
+ * 'averageCenter': (boolean) Wether the center of each cluster should be
47
+ * the average of all markers in the cluster.
48
+ * 'minimumClusterSize': (number) The minimum number of markers to be in a
49
+ * cluster before the markers are hidden and a count
50
+ * is shown.
51
+ * 'styles': (object) An object that has style properties:
52
+ * 'url': (string) The image url.
53
+ * 'height': (number) The image height.
54
+ * 'width': (number) The image width.
55
+ * 'anchor': (Array) The anchor position of the label text.
56
+ * 'textColor': (string) The text color.
57
+ * 'textSize': (number) The text size.
58
+ * 'backgroundPosition': (string) The position of the backgound x, y.
59
+ * @constructor
60
+ * @extends google.maps.OverlayView
61
+ */
62
+ function MarkerClusterer(map, opt_markers, opt_options) {
63
+ // MarkerClusterer implements google.maps.OverlayView interface. We use the
64
+ // extend function to extend MarkerClusterer with google.maps.OverlayView
65
+ // because it might not always be available when the code is defined so we
66
+ // look for it at the last possible moment. If it doesn't exist now then
67
+ // there is no point going ahead :)
68
+ this.extend(MarkerClusterer, google.maps.OverlayView);
69
+ this.map_ = map;
70
+
71
+ /**
72
+ * @type {Array.<google.maps.Marker>}
73
+ * @private
74
+ */
75
+ this.markers_ = [];
76
+
77
+ /**
78
+ * @type {Array.<Cluster>}
79
+ */
80
+ this.clusters_ = [];
81
+
82
+ this.sizes = [53, 56, 66, 78, 90];
83
+
84
+ /**
85
+ * @private
86
+ */
87
+ this.styles_ = [];
88
+
89
+ /**
90
+ * @type {boolean}
91
+ * @private
92
+ */
93
+ this.ready_ = false;
94
+
95
+ var options = opt_options || {};
96
+
97
+ /**
98
+ * @type {number}
99
+ * @private
100
+ */
101
+ this.gridSize_ = options['gridSize'] || 60;
102
+
103
+ /**
104
+ * @private
105
+ */
106
+ this.minClusterSize_ = options['minimumClusterSize'] || 2;
107
+
108
+
109
+ /**
110
+ * @type {?number}
111
+ * @private
112
+ */
113
+ this.maxZoom_ = options['maxZoom'] || null;
114
+
115
+ this.styles_ = options['styles'] || [];
116
+
117
+ /**
118
+ * @type {string}
119
+ * @private
120
+ */
121
+ this.imagePath_ = options['imagePath'] ||
122
+ this.MARKER_CLUSTER_IMAGE_PATH_;
123
+
124
+ /**
125
+ * @type {string}
126
+ * @private
127
+ */
128
+ this.imageExtension_ = options['imageExtension'] ||
129
+ this.MARKER_CLUSTER_IMAGE_EXTENSION_;
130
+
131
+ /**
132
+ * @type {boolean}
133
+ * @private
134
+ */
135
+ this.zoomOnClick_ = true;
136
+
137
+ if (options['zoomOnClick'] != undefined) {
138
+ this.zoomOnClick_ = options['zoomOnClick'];
139
+ }
140
+
141
+ /**
142
+ * @type {boolean}
143
+ * @private
144
+ */
145
+ this.averageCenter_ = false;
146
+
147
+ if (options['averageCenter'] != undefined) {
148
+ this.averageCenter_ = options['averageCenter'];
149
+ }
150
+
151
+ this.setupStyles_();
152
+
153
+ this.setMap(map);
154
+
155
+ /**
156
+ * @type {number}
157
+ * @private
158
+ */
159
+ this.prevZoom_ = this.map_.getZoom();
160
+
161
+ // Add the map event listeners
162
+ var that = this;
163
+ google.maps.event.addListener(this.map_, 'zoom_changed', function() {
164
+ // Determines map type and prevent illegal zoom levels
165
+ var zoom = that.map_.getZoom();
166
+ var minZoom = that.map_.minZoom || 0;
167
+ var maxZoom = Math.min(that.map_.maxZoom || 100,
168
+ that.map_.mapTypes[that.map_.getMapTypeId()].maxZoom);
169
+ zoom = Math.min(Math.max(zoom,minZoom),maxZoom);
170
+
171
+ if (that.prevZoom_ != zoom) {
172
+ that.prevZoom_ = zoom;
173
+ that.resetViewport();
174
+ }
175
+ });
176
+
177
+ google.maps.event.addListener(this.map_, 'idle', function() {
178
+ that.redraw();
179
+ });
180
+
181
+ // Finally, add the markers
182
+ if (opt_markers && (opt_markers.length || Object.keys(opt_markers).length)) {
183
+ this.addMarkers(opt_markers, false);
184
+ }
185
+ }
186
+
187
+ /**
188
+ * The marker cluster image path.
189
+ *
190
+ * @type {string}
191
+ * @private
192
+ */
193
+ MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_PATH_ = wpslSettings.clusterImagePath;
194
+
195
+ /**
196
+ * The marker cluster image path.
197
+ *
198
+ * @type {string}
199
+ * @private
200
+ */
201
+ MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_EXTENSION_ = 'png';
202
+
203
+ /**
204
+ * Extends a objects prototype by anothers.
205
+ *
206
+ * @param {Object} obj1 The object to be extended.
207
+ * @param {Object} obj2 The object to extend with.
208
+ * @return {Object} The new extended object.
209
+ * @ignore
210
+ */
211
+ MarkerClusterer.prototype.extend = function(obj1, obj2) {
212
+ return (function(object) {
213
+ for (var property in object.prototype) {
214
+ this.prototype[property] = object.prototype[property];
215
+ }
216
+ return this;
217
+ }).apply(obj1, [obj2]);
218
+ };
219
+
220
+ /**
221
+ * Implementaion of the interface method.
222
+ * @ignore
223
+ */
224
+ MarkerClusterer.prototype.onAdd = function() {
225
+ this.setReady_(true);
226
+ };
227
+
228
+ /**
229
+ * Implementaion of the interface method.
230
+ * @ignore
231
+ */
232
+ MarkerClusterer.prototype.draw = function() {};
233
+
234
+ /**
235
+ * Sets up the styles object.
236
+ *
237
+ * @private
238
+ */
239
+ MarkerClusterer.prototype.setupStyles_ = function() {
240
+ if (this.styles_.length) {
241
+ return;
242
+ }
243
+
244
+ for (var i = 0, size; size = this.sizes[i]; i++) {
245
+ this.styles_.push({
246
+ url: this.imagePath_ + (i + 1) + '.' + this.imageExtension_,
247
+ height: size,
248
+ width: size
249
+ });
250
+ }
251
+ };
252
+
253
+ /**
254
+ * Fit the map to the bounds of the markers in the clusterer.
255
+ */
256
+ MarkerClusterer.prototype.fitMapToMarkers = function() {
257
+ var markers = this.getMarkers();
258
+ var bounds = new google.maps.LatLngBounds();
259
+ for (var i = 0, marker; marker = markers[i]; i++) {
260
+ bounds.extend(marker.getPosition());
261
+ }
262
+
263
+ this.map_.fitBounds(bounds);
264
+ };
265
+
266
+
267
+ /**
268
+ * Sets the styles.
269
+ *
270
+ * @param {Object} styles The style to set.
271
+ */
272
+ MarkerClusterer.prototype.setStyles = function(styles) {
273
+ this.styles_ = styles;
274
+ };
275
+
276
+
277
+ /**
278
+ * Gets the styles.
279
+ *
280
+ * @return {Object} The styles object.
281
+ */
282
+ MarkerClusterer.prototype.getStyles = function() {
283
+ return this.styles_;
284
+ };
285
+
286
+
287
+ /**
288
+ * Whether zoom on click is set.
289
+ *
290
+ * @return {boolean} True if zoomOnClick_ is set.
291
+ */
292
+ MarkerClusterer.prototype.isZoomOnClick = function() {
293
+ return this.zoomOnClick_;
294
+ };
295
+
296
+ /**
297
+ * Whether average center is set.
298
+ *
299
+ * @return {boolean} True if averageCenter_ is set.
300
+ */
301
+ MarkerClusterer.prototype.isAverageCenter = function() {
302
+ return this.averageCenter_;
303
+ };
304
+
305
+
306
+ /**
307
+ * Returns the array of markers in the clusterer.
308
+ *
309
+ * @return {Array.<google.maps.Marker>} The markers.
310
+ */
311
+ MarkerClusterer.prototype.getMarkers = function() {
312
+ return this.markers_;
313
+ };
314
+
315
+
316
+ /**
317
+ * Returns the number of markers in the clusterer
318
+ *
319
+ * @return {Number} The number of markers.
320
+ */
321
+ MarkerClusterer.prototype.getTotalMarkers = function() {
322
+ return this.markers_.length;
323
+ };
324
+
325
+
326
+ /**
327
+ * Sets the max zoom for the clusterer.
328
+ *
329
+ * @param {number} maxZoom The max zoom level.
330
+ */
331
+ MarkerClusterer.prototype.setMaxZoom = function(maxZoom) {
332
+ this.maxZoom_ = maxZoom;
333
+ };
334
+
335
+
336
+ /**
337
+ * Gets the max zoom for the clusterer.
338
+ *
339
+ * @return {number} The max zoom level.
340
+ */
341
+ MarkerClusterer.prototype.getMaxZoom = function() {
342
+ return this.maxZoom_;
343
+ };
344
+
345
+
346
+ /**
347
+ * The function for calculating the cluster icon image.
348
+ *
349
+ * @param {Array.<google.maps.Marker>} markers The markers in the clusterer.
350
+ * @param {number} numStyles The number of styles available.
351
+ * @return {Object} A object properties: 'text' (string) and 'index' (number).
352
+ * @private
353
+ */
354
+ MarkerClusterer.prototype.calculator_ = function(markers, numStyles) {
355
+ var index = 0;
356
+ var count = markers.length;
357
+ var dv = count;
358
+ while (dv !== 0) {
359
+ dv = parseInt(dv / 10, 10);
360
+ index++;
361
+ }
362
+
363
+ index = Math.min(index, numStyles);
364
+ return {
365
+ text: count,
366
+ index: index
367
+ };
368
+ };
369
+
370
+
371
+ /**
372
+ * Set the calculator function.
373
+ *
374
+ * @param {function(Array, number)} calculator The function to set as the
375
+ * calculator. The function should return a object properties:
376
+ * 'text' (string) and 'index' (number).
377
+ *
378
+ */
379
+ MarkerClusterer.prototype.setCalculator = function(calculator) {
380
+ this.calculator_ = calculator;
381
+ };
382
+
383
+
384
+ /**
385
+ * Get the calculator function.
386
+ *
387
+ * @return {function(Array, number)} the calculator function.
388
+ */
389
+ MarkerClusterer.prototype.getCalculator = function() {
390
+ return this.calculator_;
391
+ };
392
+
393
+
394
+ /**
395
+ * Add an array of markers to the clusterer.
396
+ *
397
+ * @param {Array.<google.maps.Marker>} markers The markers to add.
398
+ * @param {boolean=} opt_nodraw Whether to redraw the clusters.
399
+ */
400
+ MarkerClusterer.prototype.addMarkers = function(markers, opt_nodraw) {
401
+ if (markers.length) {
402
+ for (var i = 0, marker; marker = markers[i]; i++) {
403
+ this.pushMarkerTo_(marker);
404
+ }
405
+ } else if (Object.keys(markers).length) {
406
+ for (var marker in markers) {
407
+ this.pushMarkerTo_(markers[marker]);
408
+ }
409
+ }
410
+ if (!opt_nodraw) {
411
+ this.redraw();
412
+ }
413
+ };
414
+
415
+
416
+ /**
417
+ * Pushes a marker to the clusterer.
418
+ *
419
+ * @param {google.maps.Marker} marker The marker to add.
420
+ * @private
421
+ */
422
+ MarkerClusterer.prototype.pushMarkerTo_ = function(marker) {
423
+ marker.isAdded = false;
424
+ if (marker['draggable']) {
425
+ // If the marker is draggable add a listener so we update the clusters on
426
+ // the drag end.
427
+ var that = this;
428
+ google.maps.event.addListener(marker, 'dragend', function() {
429
+ marker.isAdded = false;
430
+ that.repaint();
431
+ });
432
+ }
433
+ this.markers_.push(marker);
434
+ };
435
+
436
+
437
+ /**
438
+ * Adds a marker to the clusterer and redraws if needed.
439
+ *
440
+ * @param {google.maps.Marker} marker The marker to add.
441
+ * @param {boolean=} opt_nodraw Whether to redraw the clusters.
442
+ */
443
+ MarkerClusterer.prototype.addMarker = function(marker, opt_nodraw) {
444
+ this.pushMarkerTo_(marker);
445
+ if (!opt_nodraw) {
446
+ this.redraw();
447
+ }
448
+ };
449
+
450
+
451
+ /**
452
+ * Removes a marker and returns true if removed, false if not
453
+ *
454
+ * @param {google.maps.Marker} marker The marker to remove
455
+ * @return {boolean} Whether the marker was removed or not
456
+ * @private
457
+ */
458
+ MarkerClusterer.prototype.removeMarker_ = function(marker) {
459
+ var index = -1;
460
+ if (this.markers_.indexOf) {
461
+ index = this.markers_.indexOf(marker);
462
+ } else {
463
+ for (var i = 0, m; m = this.markers_[i]; i++) {
464
+ if (m == marker) {
465
+ index = i;
466
+ break;
467
+ }
468
+ }
469
+ }
470
+
471
+ if (index == -1) {
472
+ // Marker is not in our list of markers.
473
+ return false;
474
+ }
475
+
476
+ marker.setMap(null);
477
+
478
+ this.markers_.splice(index, 1);
479
+
480
+ return true;
481
+ };
482
+
483
+
484
+ /**
485
+ * Remove a marker from the cluster.
486
+ *
487
+ * @param {google.maps.Marker} marker The marker to remove.
488
+ * @param {boolean=} opt_nodraw Optional boolean to force no redraw.
489
+ * @return {boolean} True if the marker was removed.
490
+ */
491
+ MarkerClusterer.prototype.removeMarker = function(marker, opt_nodraw) {
492
+ var removed = this.removeMarker_(marker);
493
+
494
+ if (!opt_nodraw && removed) {
495
+ this.resetViewport();
496
+ this.redraw();
497
+ return true;
498
+ } else {
499
+ return false;
500
+ }
501
+ };
502
+
503
+
504
+ /**
505
+ * Removes an array of markers from the cluster.
506
+ *
507
+ * @param {Array.<google.maps.Marker>} markers The markers to remove.
508
+ * @param {boolean=} opt_nodraw Optional boolean to force no redraw.
509
+ */
510
+ MarkerClusterer.prototype.removeMarkers = function(markers, opt_nodraw) {
511
+ var removed = false;
512
+
513
+ for (var i = 0, marker; marker = markers[i]; i++) {
514
+ var r = this.removeMarker_(marker);
515
+ removed = removed || r;
516
+ }
517
+
518
+ if (!opt_nodraw && removed) {
519
+ this.resetViewport();
520
+ this.redraw();
521
+ return true;
522
+ }
523
+ };
524
+
525
+
526
+ /**
527
+ * Sets the clusterer's ready state.
528
+ *
529
+ * @param {boolean} ready The state.
530
+ * @private
531
+ */
532
+ MarkerClusterer.prototype.setReady_ = function(ready) {
533
+ if (!this.ready_) {
534
+ this.ready_ = ready;
535
+ this.createClusters_();
536
+ }
537
+ };
538
+
539
+
540
+ /**
541
+ * Returns the number of clusters in the clusterer.
542
+ *
543
+ * @return {number} The number of clusters.
544
+ */
545
+ MarkerClusterer.prototype.getTotalClusters = function() {
546
+ return this.clusters_.length;
547
+ };
548
+
549
+
550
+ /**
551
+ * Returns the google map that the clusterer is associated with.
552
+ *
553
+ * @return {google.maps.Map} The map.
554
+ */
555
+ MarkerClusterer.prototype.getMap = function() {
556
+ return this.map_;
557
+ };
558
+
559
+
560
+ /**
561
+ * Sets the google map that the clusterer is associated with.
562
+ *
563
+ * @param {google.maps.Map} map The map.
564
+ */
565
+ MarkerClusterer.prototype.setMap = function(map) {
566
+ this.map_ = map;
567
+ };
568
+
569
+
570
+ /**
571
+ * Returns the size of the grid.
572
+ *
573
+ * @return {number} The grid size.
574
+ */
575
+ MarkerClusterer.prototype.getGridSize = function() {
576
+ return this.gridSize_;
577
+ };
578
+
579
+
580
+ /**
581
+ * Sets the size of the grid.
582
+ *
583
+ * @param {number} size The grid size.
584
+ */
585
+ MarkerClusterer.prototype.setGridSize = function(size) {
586
+ this.gridSize_ = size;
587
+ };
588
+
589
+
590
+ /**
591
+ * Returns the min cluster size.
592
+ *
593
+ * @return {number} The grid size.
594
+ */
595
+ MarkerClusterer.prototype.getMinClusterSize = function() {
596
+ return this.minClusterSize_;
597
+ };
598
+
599
+ /**
600
+ * Sets the min cluster size.
601
+ *
602
+ * @param {number} size The grid size.
603
+ */
604
+ MarkerClusterer.prototype.setMinClusterSize = function(size) {
605
+ this.minClusterSize_ = size;
606
+ };
607
+
608
+
609
+ /**
610
+ * Extends a bounds object by the grid size.
611
+ *
612
+ * @param {google.maps.LatLngBounds} bounds The bounds to extend.
613
+ * @return {google.maps.LatLngBounds} The extended bounds.
614
+ */
615
+ MarkerClusterer.prototype.getExtendedBounds = function(bounds) {
616
+ var projection = this.getProjection();
617
+
618
+ // Turn the bounds into latlng.
619
+ var tr = new google.maps.LatLng(bounds.getNorthEast().lat(),
620
+ bounds.getNorthEast().lng());
621
+ var bl = new google.maps.LatLng(bounds.getSouthWest().lat(),
622
+ bounds.getSouthWest().lng());
623
+
624
+ // Convert the points to pixels and the extend out by the grid size.
625
+ var trPix = projection.fromLatLngToDivPixel(tr);
626
+ trPix.x += this.gridSize_;
627
+ trPix.y -= this.gridSize_;
628
+
629
+ var blPix = projection.fromLatLngToDivPixel(bl);
630
+ blPix.x -= this.gridSize_;
631
+ blPix.y += this.gridSize_;
632
+
633
+ // Convert the pixel points back to LatLng
634
+ var ne = projection.fromDivPixelToLatLng(trPix);
635
+ var sw = projection.fromDivPixelToLatLng(blPix);
636
+
637
+ // Extend the bounds to contain the new bounds.
638
+ bounds.extend(ne);
639
+ bounds.extend(sw);
640
+
641
+ return bounds;
642
+ };
643
+
644
+
645
+ /**
646
+ * Determins if a marker is contained in a bounds.
647
+ *
648
+ * @param {google.maps.Marker} marker The marker to check.
649
+ * @param {google.maps.LatLngBounds} bounds The bounds to check against.
650
+ * @return {boolean} True if the marker is in the bounds.
651
+ * @private
652
+ */
653
+ MarkerClusterer.prototype.isMarkerInBounds_ = function(marker, bounds) {
654
+ return bounds.contains(marker.getPosition());
655
+ };
656
+
657
+
658
+ /**
659
+ * Clears all clusters and markers from the clusterer.
660
+ */
661
+ MarkerClusterer.prototype.clearMarkers = function() {
662
+ this.resetViewport(true);
663
+
664
+ // Set the markers a empty array.
665
+ this.markers_ = [];
666
+ };
667
+
668
+
669
+ /**
670
+ * Clears all existing clusters and recreates them.
671
+ * @param {boolean} opt_hide To also hide the marker.
672
+ */
673
+ MarkerClusterer.prototype.resetViewport = function(opt_hide) {
674
+ // Remove all the clusters
675
+ for (var i = 0, cluster; cluster = this.clusters_[i]; i++) {
676
+ cluster.remove();
677
+ }
678
+
679
+ // Reset the markers to not be added and to be invisible.
680
+ for (var i = 0, marker; marker = this.markers_[i]; i++) {
681
+ marker.isAdded = false;
682
+ if (opt_hide) {
683
+ marker.setMap(null);
684
+ }
685
+ }
686
+
687
+ this.clusters_ = [];
688
+ };
689
+
690
+ /**
691
+ *
692
+ */
693
+ MarkerClusterer.prototype.repaint = function() {
694
+ var oldClusters = this.clusters_.slice();
695
+ this.clusters_.length = 0;
696
+ this.resetViewport();
697
+ this.redraw();
698
+
699
+ // Remove the old clusters.
700
+ // Do it in a timeout so the other clusters have been drawn first.
701
+ window.setTimeout(function() {
702
+ for (var i = 0, cluster; cluster = oldClusters[i]; i++) {
703
+ cluster.remove();
704
+ }
705
+ }, 0);
706
+ };
707
+
708
+
709
+ /**
710
+ * Redraws the clusters.
711
+ */
712
+ MarkerClusterer.prototype.redraw = function() {
713
+ this.createClusters_();
714
+ };
715
+
716
+
717
+ /**
718
+ * Calculates the distance between two latlng locations in km.
719
+ * @see http://www.movable-type.co.uk/scripts/latlong.html
720
+ *
721
+ * @param {google.maps.LatLng} p1 The first lat lng point.
722
+ * @param {google.maps.LatLng} p2 The second lat lng point.
723
+ * @return {number} The distance between the two points in km.
724
+ * @private
725
+ */
726
+ MarkerClusterer.prototype.distanceBetweenPoints_ = function(p1, p2) {
727
+ if (!p1 || !p2) {
728
+ return 0;
729
+ }
730
+
731
+ var R = 6371; // Radius of the Earth in km
732
+ var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
733
+ var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
734
+ var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
735
+ Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) *
736
+ Math.sin(dLon / 2) * Math.sin(dLon / 2);
737
+ var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
738
+ var d = R * c;
739
+ return d;
740
+ };
741
+
742
+
743
+ /**
744
+ * Add a marker to a cluster, or creates a new cluster.
745
+ *
746
+ * @param {google.maps.Marker} marker The marker to add.
747
+ * @private
748
+ */
749
+ MarkerClusterer.prototype.addToClosestCluster_ = function(marker) {
750
+ var distance = 40000; // Some large number
751
+ var clusterToAddTo = null;
752
+ var pos = marker.getPosition();
753
+ for (var i = 0, cluster; cluster = this.clusters_[i]; i++) {
754
+ var center = cluster.getCenter();
755
+ if (center) {
756
+ var d = this.distanceBetweenPoints_(center, marker.getPosition());
757
+ if (d < distance) {
758
+ distance = d;
759
+ clusterToAddTo = cluster;
760
+ }
761
+ }
762
+ }
763
+
764
+ if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) {
765
+ clusterToAddTo.addMarker(marker);
766
+ } else {
767
+ var cluster = new Cluster(this);
768
+ cluster.addMarker(marker);
769
+ this.clusters_.push(cluster);
770
+ }
771
+ };
772
+
773
+
774
+ /**
775
+ * Creates the clusters.
776
+ *
777
+ * @private
778
+ */
779
+ MarkerClusterer.prototype.createClusters_ = function() {
780
+ if (!this.ready_) {
781
+ return;
782
+ }
783
+
784
+ // Get our current map view bounds.
785
+ // Create a new bounds object so we don't affect the map.
786
+ var mapBounds = new google.maps.LatLngBounds(this.map_.getBounds().getSouthWest(),
787
+ this.map_.getBounds().getNorthEast());
788
+ var bounds = this.getExtendedBounds(mapBounds);
789
+
790
+ for (var i = 0, marker; marker = this.markers_[i]; i++) {
791
+ if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) {
792
+ this.addToClosestCluster_(marker);
793
+ }
794
+ }
795
+ };
796
+
797
+
798
+ /**
799
+ * A cluster that contains markers.
800
+ *
801
+ * @param {MarkerClusterer} markerClusterer The markerclusterer that this
802
+ * cluster is associated with.
803
+ * @constructor
804
+ * @ignore
805
+ */
806
+ function Cluster(markerClusterer) {
807
+ this.markerClusterer_ = markerClusterer;
808
+ this.map_ = markerClusterer.getMap();
809
+ this.gridSize_ = markerClusterer.getGridSize();
810
+ this.minClusterSize_ = markerClusterer.getMinClusterSize();
811
+ this.averageCenter_ = markerClusterer.isAverageCenter();
812
+ this.center_ = null;
813
+ this.markers_ = [];
814
+ this.bounds_ = null;
815
+ this.clusterIcon_ = new ClusterIcon(this, markerClusterer.getStyles(),
816
+ markerClusterer.getGridSize());
817
+ }
818
+
819
+ /**
820
+ * Determins if a marker is already added to the cluster.
821
+ *
822
+ * @param {google.maps.Marker} marker The marker to check.
823
+ * @return {boolean} True if the marker is already added.
824
+ */
825
+ Cluster.prototype.isMarkerAlreadyAdded = function(marker) {
826
+ if (this.markers_.indexOf) {
827
+ return this.markers_.indexOf(marker) != -1;
828
+ } else {
829
+ for (var i = 0, m; m = this.markers_[i]; i++) {
830
+ if (m == marker) {
831
+ return true;
832
+ }
833
+ }
834
+ }
835
+ return false;
836
+ };
837
+
838
+
839
+ /**
840
+ * Add a marker the cluster.
841
+ *
842
+ * @param {google.maps.Marker} marker The marker to add.
843
+ * @return {boolean} True if the marker was added.
844
+ */
845
+ Cluster.prototype.addMarker = function(marker) {
846
+ if (this.isMarkerAlreadyAdded(marker)) {
847
+ return false;
848
+ }
849
+
850
+ if (!this.center_) {
851
+ this.center_ = marker.getPosition();
852
+ this.calculateBounds_();
853
+ } else {
854
+ if (this.averageCenter_) {
855
+ var l = this.markers_.length + 1;
856
+ var lat = (this.center_.lat() * (l-1) + marker.getPosition().lat()) / l;
857
+ var lng = (this.center_.lng() * (l-1) + marker.getPosition().lng()) / l;
858
+ this.center_ = new google.maps.LatLng(lat, lng);
859
+ this.calculateBounds_();
860
+ }
861
+ }
862
+
863
+ marker.isAdded = true;
864
+ this.markers_.push(marker);
865
+
866
+ var len = this.markers_.length;
867
+ if (len < this.minClusterSize_ && marker.getMap() != this.map_) {
868
+ // Min cluster size not reached so show the marker.
869
+ marker.setMap(this.map_);
870
+ }
871
+
872
+ if (len == this.minClusterSize_) {
873
+ // Hide the markers that were showing.
874
+ for (var i = 0; i < len; i++) {
875
+ this.markers_[i].setMap(null);
876
+ }
877
+ }
878
+
879
+ if (len >= this.minClusterSize_) {
880
+ marker.setMap(null);
881
+ }
882
+
883
+ this.updateIcon();
884
+ return true;
885
+ };
886
+
887
+
888
+ /**
889
+ * Returns the marker clusterer that the cluster is associated with.
890
+ *
891
+ * @return {MarkerClusterer} The associated marker clusterer.
892
+ */
893
+ Cluster.prototype.getMarkerClusterer = function() {
894
+ return this.markerClusterer_;
895
+ };
896
+
897
+
898
+ /**
899
+ * Returns the bounds of the cluster.
900
+ *
901
+ * @return {google.maps.LatLngBounds} the cluster bounds.
902
+ */
903
+ Cluster.prototype.getBounds = function() {
904
+ var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
905
+ var markers = this.getMarkers();
906
+ for (var i = 0, marker; marker = markers[i]; i++) {
907
+ bounds.extend(marker.getPosition());
908
+ }
909
+ return bounds;
910
+ };
911
+
912
+
913
+ /**
914
+ * Removes the cluster
915
+ */
916
+ Cluster.prototype.remove = function() {
917
+ this.clusterIcon_.remove();
918
+ this.markers_.length = 0;
919
+ delete this.markers_;
920
+ };
921
+
922
+
923
+ /**
924
+ * Returns the center of the cluster.
925
+ *
926
+ * @return {number} The cluster center.
927
+ */
928
+ Cluster.prototype.getSize = function() {
929
+ return this.markers_.length;
930
+ };
931
+
932
+
933
+ /**
934
+ * Returns the center of the cluster.
935
+ *
936
+ * @return {Array.<google.maps.Marker>} The cluster center.
937
+ */
938
+ Cluster.prototype.getMarkers = function() {
939
+ return this.markers_;
940
+ };
941
+
942
+
943
+ /**
944
+ * Returns the center of the cluster.
945
+ *
946
+ * @return {google.maps.LatLng} The cluster center.
947
+ */
948
+ Cluster.prototype.getCenter = function() {
949
+ return this.center_;
950
+ };
951
+
952
+
953
+ /**
954
+ * Calculated the extended bounds of the cluster with the grid.
955
+ *
956
+ * @private
957
+ */
958
+ Cluster.prototype.calculateBounds_ = function() {
959
+ var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
960
+ this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds);
961
+ };
962
+
963
+
964
+ /**
965
+ * Determines if a marker lies in the clusters bounds.
966
+ *
967
+ * @param {google.maps.Marker} marker The marker to check.
968
+ * @return {boolean} True if the marker lies in the bounds.
969
+ */
970
+ Cluster.prototype.isMarkerInClusterBounds = function(marker) {
971
+ return this.bounds_.contains(marker.getPosition());
972
+ };
973
+
974
+
975
+ /**
976
+ * Returns the map that the cluster is associated with.
977
+ *
978
+ * @return {google.maps.Map} The map.
979
+ */
980
+ Cluster.prototype.getMap = function() {
981
+ return this.map_;
982
+ };
983
+
984
+
985
+ /**
986
+ * Updates the cluster icon
987
+ */
988
+ Cluster.prototype.updateIcon = function() {
989
+ var zoom = this.map_.getZoom();
990
+ var mz = this.markerClusterer_.getMaxZoom();
991
+
992
+ if (mz && zoom > mz) {
993
+ // The zoom is greater than our max zoom so show all the markers in cluster.
994
+ for (var i = 0, marker; marker = this.markers_[i]; i++) {
995
+ marker.setMap(this.map_);
996
+ }
997
+ return;
998
+ }
999
+
1000
+ if (this.markers_.length < this.minClusterSize_) {
1001
+ // Min cluster size not yet reached.
1002
+ this.clusterIcon_.hide();
1003
+ return;
1004
+ }
1005
+
1006
+ var numStyles = this.markerClusterer_.getStyles().length;
1007
+ var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles);
1008
+ this.clusterIcon_.setCenter(this.center_);
1009
+ this.clusterIcon_.setSums(sums);
1010
+ this.clusterIcon_.show();
1011
+ };
1012
+
1013
+
1014
+ /**
1015
+ * A cluster icon
1016
+ *
1017
+ * @param {Cluster} cluster The cluster to be associated with.
1018
+ * @param {Object} styles An object that has style properties:
1019
+ * 'url': (string) The image url.
1020
+ * 'height': (number) The image height.
1021
+ * 'width': (number) The image width.
1022
+ * 'anchor': (Array) The anchor position of the label text.
1023
+ * 'textColor': (string) The text color.
1024
+ * 'textSize': (number) The text size.
1025
+ * 'backgroundPosition: (string) The background postition x, y.
1026
+ * @param {number=} opt_padding Optional padding to apply to the cluster icon.
1027
+ * @constructor
1028
+ * @extends google.maps.OverlayView
1029
+ * @ignore
1030
+ */
1031
+ function ClusterIcon(cluster, styles, opt_padding) {
1032
+ cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView);
1033
+
1034
+ this.styles_ = styles;
1035
+ this.padding_ = opt_padding || 0;
1036
+ this.cluster_ = cluster;
1037
+ this.center_ = null;
1038
+ this.map_ = cluster.getMap();
1039
+ this.div_ = null;
1040
+ this.sums_ = null;
1041
+ this.visible_ = false;
1042
+
1043
+ this.setMap(this.map_);
1044
+ }
1045
+
1046
+
1047
+ /**
1048
+ * Triggers the clusterclick event and zoom's if the option is set.
1049
+ */
1050
+ ClusterIcon.prototype.triggerClusterClick = function() {
1051
+ var markerClusterer = this.cluster_.getMarkerClusterer();
1052
+
1053
+ // Trigger the clusterclick event.
1054
+ google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster_);
1055
+
1056
+ if (markerClusterer.isZoomOnClick()) {
1057
+ // Zoom into the cluster.
1058
+ this.map_.fitBounds(this.cluster_.getBounds());
1059
+ }
1060
+ };
1061
+
1062
+
1063
+ /**
1064
+ * Adding the cluster icon to the dom.
1065
+ * @ignore
1066
+ */
1067
+ ClusterIcon.prototype.onAdd = function() {
1068
+ this.div_ = document.createElement('DIV');
1069
+ if (this.visible_) {
1070
+ var pos = this.getPosFromLatLng_(this.center_);
1071
+ this.div_.style.cssText = this.createCss(pos);
1072
+ this.div_.innerHTML = this.sums_.text;
1073
+ }
1074
+
1075
+ var panes = this.getPanes();
1076
+ panes.overlayMouseTarget.appendChild(this.div_);
1077
+
1078
+ var that = this;
1079
+ google.maps.event.addDomListener(this.div_, 'click', function() {
1080
+ that.triggerClusterClick();
1081
+ });
1082
+ };
1083
+
1084
+
1085
+ /**
1086
+ * Returns the position to place the div dending on the latlng.
1087
+ *
1088
+ * @param {google.maps.LatLng} latlng The position in latlng.
1089
+ * @return {google.maps.Point} The position in pixels.
1090
+ * @private
1091
+ */
1092
+ ClusterIcon.prototype.getPosFromLatLng_ = function(latlng) {
1093
+ var pos = this.getProjection().fromLatLngToDivPixel(latlng);
1094
+ pos.x -= parseInt(this.width_ / 2, 10);
1095
+ pos.y -= parseInt(this.height_ / 2, 10);
1096
+ return pos;
1097
+ };
1098
+
1099
+
1100
+ /**
1101
+ * Draw the icon.
1102
+ * @ignore
1103
+ */
1104
+ ClusterIcon.prototype.draw = function() {
1105
+ if (this.visible_) {
1106
+ var pos = this.getPosFromLatLng_(this.center_);
1107
+ this.div_.style.top = pos.y + 'px';
1108
+ this.div_.style.left = pos.x + 'px';
1109
+ }
1110
+ };
1111
+
1112
+
1113
+ /**
1114
+ * Hide the icon.
1115
+ */
1116
+ ClusterIcon.prototype.hide = function() {
1117
+ if (this.div_) {
1118
+ this.div_.style.display = 'none';
1119
+ }
1120
+ this.visible_ = false;
1121
+ };
1122
+
1123
+
1124
+ /**
1125
+ * Position and show the icon.
1126
+ */
1127
+ ClusterIcon.prototype.show = function() {
1128
+ if (this.div_) {
1129
+ var pos = this.getPosFromLatLng_(this.center_);
1130
+ this.div_.style.cssText = this.createCss(pos);
1131
+ this.div_.style.display = '';
1132
+ }
1133
+ this.visible_ = true;
1134
+ };
1135
+
1136
+
1137
+ /**
1138
+ * Remove the icon from the map
1139
+ */
1140
+ ClusterIcon.prototype.remove = function() {
1141
+ this.setMap(null);
1142
+ };
1143
+
1144
+
1145
+ /**
1146
+ * Implementation of the onRemove interface.
1147
+ * @ignore
1148
+ */
1149
+ ClusterIcon.prototype.onRemove = function() {
1150
+ if (this.div_ && this.div_.parentNode) {
1151
+ this.hide();
1152
+ this.div_.parentNode.removeChild(this.div_);
1153
+ this.div_ = null;
1154
+ }
1155
+ };
1156
+
1157
+
1158
+ /**
1159
+ * Set the sums of the icon.
1160
+ *
1161
+ * @param {Object} sums The sums containing:
1162
+ * 'text': (string) The text to display in the icon.
1163
+ * 'index': (number) The style index of the icon.
1164
+ */
1165
+ ClusterIcon.prototype.setSums = function(sums) {
1166
+ this.sums_ = sums;
1167
+ this.text_ = sums.text;
1168
+ this.index_ = sums.index;
1169
+ if (this.div_) {
1170
+ this.div_.innerHTML = sums.text;
1171
+ }
1172
+
1173
+ this.useStyle();
1174
+ };
1175
+
1176
+
1177
+ /**
1178
+ * Sets the icon to the the styles.
1179
+ */
1180
+ ClusterIcon.prototype.useStyle = function() {
1181
+ var index = Math.max(0, this.sums_.index - 1);
1182
+ index = Math.min(this.styles_.length - 1, index);
1183
+ var style = this.styles_[index];
1184
+ this.url_ = style['url'];
1185
+ this.height_ = style['height'];
1186
+ this.width_ = style['width'];
1187
+ this.textColor_ = style['textColor'];
1188
+ this.anchor_ = style['anchor'];
1189
+ this.textSize_ = style['textSize'];
1190
+ this.backgroundPosition_ = style['backgroundPosition'];
1191
+ };
1192
+
1193
+
1194
+ /**
1195
+ * Sets the center of the icon.
1196
+ *
1197
+ * @param {google.maps.LatLng} center The latlng to set as the center.
1198
+ */
1199
+ ClusterIcon.prototype.setCenter = function(center) {
1200
+ this.center_ = center;
1201
+ };
1202
+
1203
+
1204
+ /**
1205
+ * Create the css text based on the position of the icon.
1206
+ *
1207
+ * @param {google.maps.Point} pos The position.
1208
+ * @return {string} The css style text.
1209
+ */
1210
+ ClusterIcon.prototype.createCss = function(pos) {
1211
+ var style = [];
1212
+ style.push('background-image:url(' + this.url_ + ');');
1213
+ var backgroundPosition = this.backgroundPosition_ ? this.backgroundPosition_ : '0 0';
1214
+ style.push('background-position:' + backgroundPosition + ';');
1215
+
1216
+ if (typeof this.anchor_ === 'object') {
1217
+ if (typeof this.anchor_[0] === 'number' && this.anchor_[0] > 0 &&
1218
+ this.anchor_[0] < this.height_) {
1219
+ style.push('height:' + (this.height_ - this.anchor_[0]) +
1220
+ 'px; padding-top:' + this.anchor_[0] + 'px;');
1221
+ } else {
1222
+ style.push('height:' + this.height_ + 'px; line-height:' + this.height_ +
1223
+ 'px;');
1224
+ }
1225
+ if (typeof this.anchor_[1] === 'number' && this.anchor_[1] > 0 &&
1226
+ this.anchor_[1] < this.width_) {
1227
+ style.push('width:' + (this.width_ - this.anchor_[1]) +
1228
+ 'px; padding-left:' + this.anchor_[1] + 'px;');
1229
+ } else {
1230
+ style.push('width:' + this.width_ + 'px; text-align:center;');
1231
+ }
1232
+ } else {
1233
+ style.push('height:' + this.height_ + 'px; line-height:' +
1234
+ this.height_ + 'px; width:' + this.width_ + 'px; text-align:center;');
1235
+ }
1236
+
1237
+ var txtColor = this.textColor_ ? this.textColor_ : 'black';
1238
+ var txtSize = this.textSize_ ? this.textSize_ : 11;
1239
+
1240
+ style.push('cursor:pointer; top:' + pos.y + 'px; left:' +
1241
+ pos.x + 'px; color:' + txtColor + '; position:absolute; font-size:' +
1242
+ txtSize + 'px; font-family:Arial,sans-serif; font-weight:bold');
1243
+ return style.join('');
1244
+ };
1245
+
1246
+
1247
+ // Export Symbols for Closure
1248
+ // If you are not going to compile with closure then you can remove the
1249
+ // code below.
1250
+ window['MarkerClusterer'] = MarkerClusterer;
1251
+ MarkerClusterer.prototype['addMarker'] = MarkerClusterer.prototype.addMarker;
1252
+ MarkerClusterer.prototype['addMarkers'] = MarkerClusterer.prototype.addMarkers;
1253
+ MarkerClusterer.prototype['clearMarkers'] =
1254
+ MarkerClusterer.prototype.clearMarkers;
1255
+ MarkerClusterer.prototype['fitMapToMarkers'] =
1256
+ MarkerClusterer.prototype.fitMapToMarkers;
1257
+ MarkerClusterer.prototype['getCalculator'] =
1258
+ MarkerClusterer.prototype.getCalculator;
1259
+ MarkerClusterer.prototype['getGridSize'] =
1260
+ MarkerClusterer.prototype.getGridSize;
1261
+ MarkerClusterer.prototype['getExtendedBounds'] =
1262
+ MarkerClusterer.prototype.getExtendedBounds;
1263
+ MarkerClusterer.prototype['getMap'] = MarkerClusterer.prototype.getMap;
1264
+ MarkerClusterer.prototype['getMarkers'] = MarkerClusterer.prototype.getMarkers;
1265
+ MarkerClusterer.prototype['getMaxZoom'] = MarkerClusterer.prototype.getMaxZoom;
1266
+ MarkerClusterer.prototype['getStyles'] = MarkerClusterer.prototype.getStyles;
1267
+ MarkerClusterer.prototype['getTotalClusters'] =
1268
+ MarkerClusterer.prototype.getTotalClusters;
1269
+ MarkerClusterer.prototype['getTotalMarkers'] =
1270
+ MarkerClusterer.prototype.getTotalMarkers;
1271
+ MarkerClusterer.prototype['redraw'] = MarkerClusterer.prototype.redraw;
1272
+ MarkerClusterer.prototype['removeMarker'] =
1273
+ MarkerClusterer.prototype.removeMarker;
1274
+ MarkerClusterer.prototype['removeMarkers'] =
1275
+ MarkerClusterer.prototype.removeMarkers;
1276
+ MarkerClusterer.prototype['resetViewport'] =
1277
+ MarkerClusterer.prototype.resetViewport;
1278
+ MarkerClusterer.prototype['repaint'] =
1279
+ MarkerClusterer.prototype.repaint;
1280
+ MarkerClusterer.prototype['setCalculator'] =
1281
+ MarkerClusterer.prototype.setCalculator;
1282
+ MarkerClusterer.prototype['setGridSize'] =
1283
+ MarkerClusterer.prototype.setGridSize;
1284
+ MarkerClusterer.prototype['setMaxZoom'] =
1285
+ MarkerClusterer.prototype.setMaxZoom;
1286
+ MarkerClusterer.prototype['onAdd'] = MarkerClusterer.prototype.onAdd;
1287
+ MarkerClusterer.prototype['draw'] = MarkerClusterer.prototype.draw;
1288
+
1289
+ Cluster.prototype['getCenter'] = Cluster.prototype.getCenter;
1290
+ Cluster.prototype['getSize'] = Cluster.prototype.getSize;
1291
+ Cluster.prototype['getMarkers'] = Cluster.prototype.getMarkers;
1292
+
1293
+ ClusterIcon.prototype['onAdd'] = ClusterIcon.prototype.onAdd;
1294
+ ClusterIcon.prototype['draw'] = ClusterIcon.prototype.draw;
1295
+ ClusterIcon.prototype['onRemove'] = ClusterIcon.prototype.onRemove;
1296
+
1297
+ Object.keys = Object.keys || function(o) {
1298
+ var result = [];
1299
+ for(var name in o) {
1300
+ if (o.hasOwnProperty(name))
1301
+ result.push(name);
1302
+ }
1303
+ return result;
1304
+ };
js/wpsl-gmap.js CHANGED
@@ -1,2663 +1,2712 @@
1
- jQuery( document ).ready( function( $ ) {
2
- var geocoder, map, directionsDisplay, directionsService, autoCompleteLatLng,
3
- activeWindowMarkerId, infoWindow, markerClusterer, startMarkerData, startAddress,
4
- openInfoWindow = [],
5
- markersArray = [],
6
- mapsArray = [],
7
- markerSettings = {},
8
- directionMarkerPosition = {},
9
- mapDefaults = {},
10
- resetMap = false,
11
- streetViewAvailable = false,
12
- autoLoad = ( typeof wpslSettings !== "undefined" ) ? wpslSettings.autoLoad : "",
13
- userGeolocation = {},
14
- statistics = {
15
- enabled: ( typeof wpslSettings.collectStatistics !== "undefined" ) ? true : false,
16
- addressComponents: ''
17
- };
18
-
19
- /**
20
- * Set the underscore template settings.
21
- *
22
- * Defining them here prevents other plugins
23
- * that also use underscore / backbone, and defined a
24
- * different _.templateSettings from breaking the
25
- * rendering of the store locator template.
26
- *
27
- * @link http://underscorejs.org/#template
28
- * @requires underscore.js
29
- * @since 2.0.0
30
- */
31
- _.templateSettings = {
32
- evaluate: /\<\%(.+?)\%\>/g,
33
- interpolate: /\<\%=(.+?)\%\>/g,
34
- escape: /\<\%-(.+?)\%\>/g
35
- };
36
-
37
- // Only continue if a map is present.
38
- if ( $( ".wpsl-gmap-canvas" ).length ) {
39
- $( "<img />" ).attr( "src", wpslSettings.url + "img/ajax-loader.gif" );
40
-
41
- /*
42
- * The [wpsl] shortcode can only exist once on a page,
43
- * but the [wpsl_map] shortcode can exist multiple times.
44
- *
45
- * So to make sure we init all the maps we loop over them.
46
- */
47
- $( ".wpsl-gmap-canvas" ).each( function( mapIndex ) {
48
- var mapId = $( this ).attr( "id" );
49
-
50
- initializeGmap( mapId, mapIndex );
51
- });
52
-
53
- /*
54
- * Check if we are dealing with a map that's placed in a tab,
55
- * if so run a fix to prevent the map from showing up grey.
56
- */
57
- maybeApplyTabFix();
58
- }
59
-
60
- /**
61
- * Initialize the map with the correct settings.
62
- *
63
- * @since 1.0.0
64
- * @param {string} mapId The id of the map div
65
- * @param {number} mapIndex Number of the map
66
- * @returns {void}
67
- */
68
- function initializeGmap( mapId, mapIndex ) {
69
- var mapOptions, mapDetails, settings, infoWindow, latLng,
70
- bounds, mapData, zoomLevel,
71
- defaultZoomLevel = Number( wpslSettings.zoomLevel ),
72
- maxZoom = Number( wpslSettings.autoZoomLevel );
73
-
74
- // Get the settings that belongs to the current map.
75
- settings = getMapSettings( mapIndex );
76
-
77
- /*
78
- * This is the value from either the settings page,
79
- * or the zoom level set through the shortcode.
80
- */
81
- zoomLevel = Number( settings.zoomLevel );
82
-
83
- /*
84
- * If they are not equal, then the zoom value is set through the shortcode.
85
- * If this is the case, then we use that as the max zoom level.
86
- */
87
- if ( zoomLevel !== defaultZoomLevel ) {
88
- maxZoom = zoomLevel;
89
- }
90
-
91
- // Create a new infoWindow, either with the infobox libray or use the default one.
92
- infoWindow = newInfoWindow();
93
-
94
- geocoder = new google.maps.Geocoder();
95
- directionsDisplay = new google.maps.DirectionsRenderer();
96
- directionsService = new google.maps.DirectionsService();
97
-
98
- // Set the map options.
99
- mapOptions = {
100
- zoom: zoomLevel,
101
- center: settings.startLatLng,
102
- mapTypeId: google.maps.MapTypeId[ settings.mapType.toUpperCase() ],
103
- mapTypeControl: Number( settings.mapTypeControl ) ? true : false,
104
- streetViewControl: Number( settings.streetView ) ? true : false,
105
- gestureHandling: settings.gestureHandling,
106
- zoomControlOptions: {
107
- position: google.maps.ControlPosition[ settings.controlPosition.toUpperCase() + '_TOP' ]
108
- }
109
- };
110
-
111
- /**
112
- * When the gestureHandling is set to cooperative and the scrollWheel
113
- * options is also set, then the gestureHandling value is ingored.
114
- *
115
- * To fix this we only include the scrollWheel options when 'cooperative' isn't used.
116
- */
117
- if ( settings.gestureHandling !== 'cooperative' ) {
118
- mapOptions.scrollwheel = Number( settings.scrollWheel ) ? true : false;
119
- }
120
-
121
- // Get the correct marker path & properties.
122
- markerSettings = getMarkerSettings();
123
-
124
- map = new google.maps.Map( document.getElementById( mapId ), mapOptions );
125
-
126
- // Check if we need to apply a map style.
127
- maybeApplyMapStyle( settings.mapStyle );
128
-
129
- if ( ( typeof window[ "wpslMap_" + mapIndex ] !== "undefined" ) && ( typeof window[ "wpslMap_" + mapIndex ].locations !== "undefined" ) ) {
130
- bounds = new google.maps.LatLngBounds(),
131
- mapData = window[ "wpslMap_" + mapIndex ].locations;
132
-
133
- // Loop over the map data, create the infowindow object and add each marker.
134
- $.each( mapData, function( index ) {
135
- latLng = new google.maps.LatLng( mapData[index].lat, mapData[index].lng );
136
- addMarker( latLng, mapData[index].id, mapData[index], false, infoWindow );
137
- bounds.extend( latLng );
138
- });
139
-
140
- // If we have more then one location on the map, then make sure to not zoom to far.
141
- if ( mapData.length > 1 ) {
142
- // Make sure we don't zoom to far when fitBounds runs.
143
- attachBoundsChangedListener( map, maxZoom );
144
-
145
- // Make all the markers fit on the map.
146
- map.fitBounds( bounds );
147
- }
148
-
149
- /*
150
- * If we need to apply the fix for the map showing up grey because
151
- * it's used in a tabbed nav multiple times, then collect the active maps.
152
- *
153
- * See the fixGreyTabMap function.
154
- */
155
- if ( _.isArray( wpslSettings.mapTabAnchor ) ) {
156
- mapDetails = {
157
- map: map,
158
- bounds: bounds,
159
- maxZoom: maxZoom
160
- };
161
-
162
- mapsArray.push( mapDetails );
163
- }
164
- }
165
-
166
- // Only run this part if the store locator exist and we don't just have a basic map.
167
- if ( $( "#wpsl-gmap" ).length ) {
168
-
169
- if ( wpslSettings.autoComplete == 1 ) {
170
- activateAutocomplete();
171
- }
172
-
173
- /*
174
- * Not the most optimal solution, but we check the useragent if we should enable the styled dropdowns.
175
- *
176
- * We do this because several people have reported issues with the styled dropdowns on
177
- * iOS and Android devices. So on mobile devices the dropdowns will be styled according
178
- * to the browser styles on that device.
179
- */
180
- if ( !checkMobileUserAgent() && $( ".wpsl-dropdown" ).length && wpslSettings.enableStyledDropdowns == 1 ) {
181
- createDropdowns();
182
- } else {
183
- $( "#wpsl-search-wrap select" ).show();
184
-
185
- if ( checkMobileUserAgent() ) {
186
- $( "#wpsl-wrap" ).addClass( "wpsl-mobile" );
187
- } else {
188
- $( "#wpsl-wrap" ).addClass( "wpsl-default-filters" );
189
- }
190
- }
191
-
192
- // Check if we need to autolocate the user, or autoload the store locations.
193
- if ( !$( ".wpsl-search" ).hasClass( "wpsl-widget" ) ) {
194
- if ( wpslSettings.autoLocate == 1 ) {
195
- checkGeolocation( settings.startLatLng, infoWindow );
196
- } else if ( wpslSettings.autoLoad == 1 ) {
197
- showStores( settings.startLatLng, infoWindow );
198
- }
199
- }
200
-
201
- // Move the mousecursor to the store search field if the focus option is enabled.
202
- if ( wpslSettings.mouseFocus == 1 && !checkMobileUserAgent() ) {
203
- $( "#wpsl-search-input" ).focus();
204
- }
205
-
206
- // Bind store search button.
207
- searchLocationBtn( infoWindow );
208
-
209
- // Add the 'reload' and 'find location' icon to the map.
210
- mapControlIcons( settings, map, infoWindow );
211
-
212
- // Check if the user submitted a search through a search widget.
213
- checkWidgetSubmit();
214
- }
215
-
216
- // Bind the zoom_changed listener.
217
- zoomChangedListener();
218
- }
219
-
220
- /**
221
- * Activate the autocomplete for the store search.
222
- *
223
- * @since 2.2.0
224
- * @link https://developers.google.com/maps/documentation/javascript/places-autocomplete
225
- * @returns {void}
226
- */
227
- function activateAutocomplete() {
228
- var input, autocomplete, place,
229
- options = {};
230
-
231
- // Handle autocomplete queries submitted by the user using the 'enter' key.
232
- keyboardAutoCompleteSubmit();
233
-
234
- /**
235
- * Check if we need to set the geocode component restrictions.
236
- * This is automatically included when a fixed map region is
237
- * selected on the WPSL settings page.
238
- */
239
- if ( typeof wpslSettings.geocodeComponents !== "undefined" && !$.isEmptyObject( wpslSettings.geocodeComponents ) ) {
240
- options.componentRestrictions = wpslSettings.geocodeComponents;
241
-
242
- /**
243
- * If the postalCode is included in the autocomplete together with '(regions)' ( which is included ),
244
- * then it will break it. So we have to remove it.
245
- */
246
- options.componentRestrictions = _.omit( options.componentRestrictions, 'postalCode' );
247
- }
248
-
249
- // Check if we need to restrict the autocomplete data.
250
- if ( typeof wpslSettings.autoCompleteOptions !== "undefined" && !$.isEmptyObject( wpslSettings.autoCompleteOptions ) ) {
251
- for ( var key in wpslSettings.autoCompleteOptions ) {
252
- if ( wpslSettings.autoCompleteOptions.hasOwnProperty( key ) ) {
253
- options[key] = wpslSettings.autoCompleteOptions[key];
254
- }
255
- }
256
- }
257
-
258
- input = document.getElementById( "wpsl-search-input" );
259
- autocomplete = new google.maps.places.Autocomplete( input, options );
260
-
261
- autocomplete.addListener( "place_changed", function() {
262
- place = autocomplete.getPlace();
263
-
264
- /**
265
- * Assign the returned latlng to the autoCompleteLatLng var.
266
- * This var is used when the users submits the search.
267
- */
268
- if ( place.geometry ) {
269
- autoCompleteLatLng = place.geometry.location;
270
- }
271
- });
272
- }
273
-
274
- /**
275
- * Make sure that the 'Zoom here' link in the info window
276
- * doesn't zoom past the max auto zoom level.
277
- *
278
- * The 'max auto zoom level' is set on the settings page.
279
- *
280
- * @since 2.0.0
281
- * @returns {void}
282
- */
283
- function zoomChangedListener() {
284
- if ( typeof wpslSettings.markerZoomTo !== "undefined" && wpslSettings.markerZoomTo == 1 ) {
285
- google.maps.event.addListener( map, "zoom_changed", function() {
286
- checkMaxZoomLevel();
287
- });
288
- }
289
- }
290
-
291
- /**
292
- * Get the correct map settings.
293
- *
294
- * @since 2.0.0
295
- * @param {number} mapIndex Number of the map
296
- * @returns {object} mapSettings The map settings either set through a shortcode or the default settings
297
- */
298
- function getMapSettings( mapIndex ) {
299
- var j, len, shortCodeVal,
300
- settingOptions = [ "zoomLevel", "mapType", "mapTypeControl", "mapStyle", "streetView", "scrollWheel", "controlPosition" ],
301
- mapSettings = {
302
- zoomLevel: wpslSettings.zoomLevel,
303
- mapType: wpslSettings.mapType,
304
- mapTypeControl: wpslSettings.mapTypeControl,
305
- mapStyle: wpslSettings.mapStyle,
306
- streetView: wpslSettings.streetView,
307
- scrollWheel: wpslSettings.scrollWheel,
308
- controlPosition: wpslSettings.controlPosition,
309
- gestureHandling: wpslSettings.gestureHandling
310
- };
311
-
312
- // If there are settings that are set through the shortcode, then we use them instead of the default ones.
313
- if ( ( typeof window[ "wpslMap_" + mapIndex ] !== "undefined" ) && ( typeof window[ "wpslMap_" + mapIndex ].shortCode !== "undefined" ) ) {
314
- for ( j = 0, len = settingOptions.length; j < len; j++ ) {
315
- shortCodeVal = window[ "wpslMap_" + mapIndex ].shortCode[ settingOptions[j] ];
316
-
317
- // If the value is set through the shortcode, we overwrite the default value.
318
- if ( typeof shortCodeVal !== "undefined" ) {
319
- mapSettings[ settingOptions[j] ] = shortCodeVal;
320
- }
321
- }
322
- }
323
-
324
- mapSettings.startLatLng = getStartLatlng( mapIndex );
325
-
326
- return mapSettings;
327
- }
328
-
329
- /**
330
- * Get the latlng coordinates that are used to init the map.
331
- *
332
- * @since 2.0.0
333
- * @param {number} mapIndex Number of the map
334
- * @returns {object} startLatLng The latlng value where the map will initially focus on
335
- */
336
- function getStartLatlng( mapIndex ) {
337
- var startLatLng, latLng,
338
- firstLocation = "";
339
-
340
- /*
341
- * Maps that are added with the [wpsl_map] shortcode will have the locations key set.
342
- * If it exists we use the coordinates from the first location to center the map on.
343
- */
344
- if ( ( typeof window[ "wpslMap_" + mapIndex ] !== "undefined" ) && ( typeof window[ "wpslMap_" + mapIndex ].locations !== "undefined" ) ) {
345
- firstLocation = window[ "wpslMap_" + mapIndex ].locations[0];
346
- }
347
-
348
- /*
349
- * Either use the coordinates from the first location as the start coordinates
350
- * or the default start point defined on the settings page.
351
- *
352
- * If both are not available we set it to 0,0
353
- */
354
- if ( ( typeof firstLocation !== "undefined" && typeof firstLocation.lat !== "undefined" ) && ( typeof firstLocation.lng !== "undefined" ) ) {
355
- startLatLng = new google.maps.LatLng( firstLocation.lat, firstLocation.lng );
356
- } else if ( wpslSettings.startLatlng !== "" ) {
357
- latLng = wpslSettings.startLatlng.split( "," );
358
- startLatLng = new google.maps.LatLng( latLng[0], latLng[1] );
359
- } else {
360
- startLatLng = new google.maps.LatLng( 0,0 );
361
- }
362
-
363
- return startLatLng;
364
- }
365
-
366
- /**
367
- * Create a new infoWindow object.
368
- *
369
- * Either use the default infoWindow or use the infobox library.
370
- *
371
- * @since 2.0.0
372
- * @return {object} infoWindow The infoWindow object
373
- */
374
- function newInfoWindow() {
375
- var boxClearance, boxPixelOffset,
376
- infoBoxOptions = {};
377
-
378
- // Do we need to use the infobox script or use the default info windows?
379
- if ( ( typeof wpslSettings.infoWindowStyle !== "undefined" ) && ( wpslSettings.infoWindowStyle == "infobox" ) ) {
380
-
381
- // See http://google-maps-utility-library-v3.googlecode.com/svn/trunk/infobox/docs/reference.html.
382
- boxClearance = wpslSettings.infoBoxClearance.split( "," );
383
- boxPixelOffset = wpslSettings.infoBoxPixelOffset.split( "," );
384
- infoBoxOptions = {
385
- alignBottom: true,
386
- boxClass: wpslSettings.infoBoxClass,
387
- closeBoxMargin: wpslSettings.infoBoxCloseMargin,
388
- closeBoxURL: wpslSettings.infoBoxCloseUrl,
389
- content: "",
390
- disableAutoPan: ( Number( wpslSettings.infoBoxDisableAutoPan ) ) ? true : false,
391
- enableEventPropagation: ( Number( wpslSettings.infoBoxEnableEventPropagation ) ) ? true : false,
392
- infoBoxClearance: new google.maps.Size( Number( boxClearance[0] ), Number( boxClearance[1] ) ),
393
- pixelOffset: new google.maps.Size( Number( boxPixelOffset[0] ), Number( boxPixelOffset[1] ) ),
394
- zIndex: Number( wpslSettings.infoBoxZindex )
395
- };
396
-
397
- infoWindow = new InfoBox( infoBoxOptions );
398
- } else {
399
- infoWindow = new google.maps.InfoWindow();
400
- }
401
-
402
- return infoWindow;
403
- }
404
-
405
- /**
406
- * Get the required marker settings.
407
- *
408
- * @since 2.1.0
409
- * @return {object} settings The marker settings.
410
- */
411
- function getMarkerSettings() {
412
- var markerProp,
413
- markerProps = wpslSettings.markerIconProps,
414
- settings = {};
415
-
416
- // Use the correct marker path.
417
- if ( typeof markerProps.url !== "undefined" ) {
418
- settings.url = markerProps.url;
419
- } else if ( typeof markerProps.categoryMarkerUrl !== "undefined" ) {
420
- settings.categoryMarkerUrl = markerProps.categoryMarkerUrl;
421
- } else if ( typeof markerProps.alternateMarkerUrl !== "undefined" ) {
422
- settings.alternateMarkerUrl = markerProps.alternateMarkerUrl;
423
- } else {
424
- settings.url = wpslSettings.url + "img/markers/";
425
- }
426
-
427
- for ( var key in markerProps ) {
428
- if ( markerProps.hasOwnProperty( key ) ) {
429
- markerProp = markerProps[key].split( "," );
430
-
431
- if ( markerProp.length == 2 ) {
432
- settings[key] = markerProp;
433
- }
434
- }
435
- }
436
-
437
- return settings;
438
- }
439
-
440
- /**
441
- * Check if we have a map style that we need to apply to the map.
442
- *
443
- * @since 2.0.0
444
- * @param {string} mapStyle The id of the map
445
- * @return {void}
446
- */
447
- function maybeApplyMapStyle( mapStyle ) {
448
-
449
- // Make sure the JSON is valid before applying it as a map style.
450
- mapStyle = tryParseJSON( mapStyle );
451
-
452
- if ( mapStyle ) {
453
- map.setOptions({ styles: mapStyle });
454
- }
455
- }
456
-
457
- /**
458
- * Make sure the JSON is valid.
459
- *
460
- * @link http://stackoverflow.com/a/20392392/1065294
461
- * @since 2.0.0
462
- * @param {string} jsonString The JSON data
463
- * @return {object|boolean} The JSON string or false if it's invalid json.
464
- */
465
- function tryParseJSON( jsonString ) {
466
-
467
- try {
468
- var o = JSON.parse( jsonString );
469
-
470
- /*
471
- * Handle non-exception-throwing cases:
472
- * Neither JSON.parse(false) or JSON.parse(1234) throw errors, hence the type-checking,
473
- * but... JSON.parse(null) returns 'null', and typeof null === "object",
474
- * so we must check for that, too.
475
- */
476
- if ( o && typeof o === "object" && o !== null ) {
477
- return o;
478
- }
479
- }
480
- catch ( e ) { }
481
-
482
- return false;
483
- }
484
-
485
- /**
486
- * Add the start marker and call the function that inits the store search.
487
- *
488
- * @since 1.1.0
489
- * @param {object} startLatLng The start coordinates
490
- * @param {object} infoWindow The infoWindow object
491
- * @returns {void}
492
- */
493
- function showStores( startLatLng, infoWindow ) {
494
- addMarker( startLatLng, 0, '', true, infoWindow ); // This marker is the 'start location' marker. With a storeId of 0, no name and is draggable
495
- findStoreLocations( startLatLng, resetMap, autoLoad, infoWindow );
496
- }
497
-
498
- /**
499
- * Compare the current useragent to a list of known mobile useragents ( not optimal, I know ).
500
- *
501
- * @since 1.2.20
502
- * @returns {boolean} Whether the useragent is from a known mobile useragent or not.
503
- */
504
- function checkMobileUserAgent() {
505
- return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( navigator.userAgent );
506
- }
507
-
508
- /**
509
- * Check if Geolocation detection is supported.
510
- *
511
- * If there is an error / timeout with determining the users
512
- * location, then we use the 'start point' value from the settings
513
- * as the start location through the showStores function.
514
- *
515
- * @since 1.0.0
516
- * @param {object} startLatLng The start coordinates
517
- * @param {object} infoWindow The infoWindow object
518
- * @returns {void}
519
- */
520
- function checkGeolocation( startLatLng, infoWindow ) {
521
-
522
- if ( navigator.geolocation ) {
523
- var geolocationInProgress, locationTimeout,
524
- keepStartMarker = false,
525
- timeout = Number( wpslSettings.geoLocationTimeout );
526
-
527
- // Make the direction icon flash every 600ms to indicate the geolocation attempt is in progress.
528
- geolocationInProgress = setInterval( function() {
529
- $( ".wpsl-icon-direction" ).toggleClass( "wpsl-active-icon" );
530
- }, 600 );
531
-
532
- /*
533
- * If the user doesn't approve the geolocation request within the value set in
534
- * wpslSettings.geoLocationTimeout, then the default map is loaded.
535
- *
536
- * You can increase the timeout value with the wpsl_geolocation_timeout filter.
537
- */
538
- locationTimeout = setTimeout( function() {
539
- geolocationFinished( geolocationInProgress );
540
- showStores( startLatLng, infoWindow );
541
- }, timeout );
542
-
543
- navigator.geolocation.getCurrentPosition( function( position ) {
544
- geolocationFinished( geolocationInProgress );
545
- clearTimeout( locationTimeout );
546
-
547
- /*
548
- * If the timeout is triggerd and the user later decides to enable
549
- * the geolocation detection again, it gets messy with multiple start markers.
550
- *
551
- * So we first clear the map before adding new ones.
552
- */
553
- deleteOverlays( keepStartMarker );
554
- handleGeolocationQuery( startLatLng, position, resetMap, infoWindow );
555
-
556
- /*
557
- * Workaround for this bug in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1283563.
558
- * to keep track if the geolocation code has already run.
559
- *
560
- * Otherwise after the users location is determined succesfully the code
561
- * will also detect the returned error, and triggers showStores() to
562
- * run with the start location set in the incorrect location.
563
- */
564
-
565
- $( ".wpsl-search").addClass( "wpsl-geolocation-run" );
566
- }, function( error ) {
567
-
568
- /*
569
- * Only show the geocode errors if the user actually clicked on the direction icon.
570
- *
571
- * Otherwise if the "Attempt to auto-locate the user" option is enabled on the settings page,
572
- * and the geolocation attempt fails for whatever reason ( blocked in browser, unavailable etc ).
573
- * Then the first thing the visitor will see on pageload is an alert box, which isn't very userfriendly.
574
- *
575
- * If an error occurs on pageload without the user clicking on the direction icon,
576
- * the default map is shown without any alert boxes.
577
- */
578
- if ( $( ".wpsl-icon-direction" ).hasClass( "wpsl-user-activated" ) && !$( ".wpsl-search" ).hasClass( "wpsl-geolocation-run" ) ) {
579
- switch ( error.code ) {
580
- case error.PERMISSION_DENIED:
581
- alert( wpslGeolocationErrors.denied );
582
- break;
583
- case error.POSITION_UNAVAILABLE:
584
- alert( wpslGeolocationErrors.unavailable );
585
- break;
586
- case error.TIMEOUT:
587
- alert( wpslGeolocationErrors.timeout );
588
- break;
589
- default:
590
- alert( wpslGeolocationErrors.generalError );
591
- break;
592
- }
593
-
594
- $( ".wpsl-icon-direction" ).removeClass( "wpsl-active-icon" );
595
- } else if ( !$( ".wpsl-search" ).hasClass( "wpsl-geolocation-run" ) ) {
596
- clearTimeout( locationTimeout );
597
- showStores( startLatLng, infoWindow );
598
- }
599
- },
600
- { maximumAge: 60000, timeout: timeout, enableHighAccuracy: true } );
601
- } else {
602
- alert( wpslGeolocationErrors.unavailable );
603
- showStores( startLatLng, infoWindow );
604
- }
605
- }
606
-
607
- /**
608
- * Clean up after the geolocation attempt finished.
609
- *
610
- * @since 2.0.0
611
- * @param {number} geolocationInProgress
612
- * @returns {void}
613
- */
614
- function geolocationFinished( geolocationInProgress ) {
615
- clearInterval( geolocationInProgress );
616
- $( ".wpsl-icon-direction" ).removeClass( "wpsl-active-icon" );
617
- }
618
-
619
- /**
620
- * Handle the data returned from the Geolocation API.
621
- *
622
- * If there is an error / timeout determining the users location,
623
- * then we use the 'start point' value from the settings as the start location through the showStores function.
624
- *
625
- * @since 1.0.0
626
- * @param {object} startLatLng The start coordinates
627
- * @param {object} position The latlng coordinates from the geolocation attempt
628
- * @param {boolean} resetMap Whether we should reset the map or not
629
- * @param {object} infoWindow The infoWindow object
630
- * @returns {void}
631
- */
632
- function handleGeolocationQuery( startLatLng, position, resetMap, infoWindow ) {
633
-
634
- if ( typeof( position ) === "undefined" ) {
635
- showStores( startLatLng, infoWindow );
636
- } else {
637
- var latLng = new google.maps.LatLng( position.coords.latitude, position.coords.longitude );
638
-
639
- /*
640
- * Store the latlng from the geolocation for when the user hits "reset" again
641
- * without having to ask for permission again.
642
- */
643
- userGeolocation = {
644
- position: position,
645
- newRequest: true
646
- };
647
-
648
- map.setCenter( latLng );
649
- addMarker( latLng, 0, '', true, infoWindow ); // This marker is the 'start location' marker. With a storeId of 0, no name and is draggable
650
- findStoreLocations( latLng, resetMap, autoLoad, infoWindow );
651
- }
652
- }
653
-
654
- /**
655
- * Handle clicks on the store locator search button.
656
- *
657
- * @since 1.0.0
658
- * @todo disable button while AJAX request still runs.
659
- * @param {object} infoWindow The infoWindow object
660
- * @returns {void}
661
- */
662
- function searchLocationBtn( infoWindow ) {
663
-
664
- $( "#wpsl-search-btn" ).unbind( "click" ).bind( "click", function( e ) {
665
- $( "#wpsl-search-input" ).removeClass();
666
-
667
- if ( !$( "#wpsl-search-input" ).val() ) {
668
- $( "#wpsl-search-input" ).addClass( "wpsl-error" ).focus();
669
- } else {
670
- resetSearchResults();
671
-
672
- /*
673
- * Check if we need to geocode the user input,
674
- * or if autocomplete is enabled and we already
675
- * have the latlng values.
676
- */
677
- if ( wpslSettings.autoComplete == 1 && typeof autoCompleteLatLng !== "undefined" ) {
678
- prepareStoreSearch( autoCompleteLatLng, infoWindow );
679
- } else {
680
- codeAddress( infoWindow );
681
- }
682
- }
683
-
684
- return false;
685
- });
686
- }
687
-
688
- /**
689
- * Force the open InfoBox info window to close
690
- *
691
- * This is required if the user makes a new search,
692
- * or clicks on the "Directions" link.
693
- *
694
- * @since 2.0.0
695
- * @return {void}
696
- */
697
- function closeInfoBoxWindow() {
698
- if ( ( typeof wpslSettings.infoWindowStyle !== "undefined" ) && ( wpslSettings.infoWindowStyle == "infobox" ) && typeof openInfoWindow[0] !== "undefined" ) {
699
- openInfoWindow[0].close();
700
- }
701
- }
702
-
703
- /**
704
- * Add the 'reload' and 'find location' icon to the map.
705
- *
706
- * @since 2.0.0
707
- * @param {object} settings Map settings
708
- * @param {object} map The map object
709
- * @param {object} infoWindow The info window object
710
- * @return {void}
711
- */
712
- function mapControlIcons( settings, map, infoWindow ) {
713
-
714
- // Once the map has finished loading include the map control button(s).
715
- google.maps.event.addListenerOnce( map, "tilesloaded", function() {
716
-
717
- // Add the html for the map controls to the map.
718
- $( ".gm-style" ).append( wpslSettings.mapControls );
719
-
720
- if ( $( ".wpsl-icon-reset, #wpsl-reset-map" ).length > 0 ) {
721
-
722
- // Bind the reset map button.
723
- resetMapBtn( settings.startLatLng, infoWindow );
724
-
725
- /*
726
- * Hide it to prevent users from clicking it before
727
- * the store location are placed on the map.
728
- */
729
- $( ".wpsl-icon-reset" ).hide();
730
- }
731
-
732
- // Bind the direction button to trigger a new geolocation request.
733
- $( ".wpsl-icon-direction" ).on( "click", function() {
734
- $( this ).addClass( "wpsl-user-activated" );
735
- checkGeolocation( settings.startLatLng, infoWindow );
736
- });
737
- });
738
- }
739
-
740
- /**
741
- * Handle clicks on the "Reset" button.
742
- *
743
- * @since 1.0.0
744
- * @param {object} startLatLng The start coordinates
745
- * @param {object} infoWindow The infoWindow object
746
- * @returns {void}
747
- */
748
- function resetMapBtn( startLatLng, infoWindow ) {
749
- $( ".wpsl-icon-reset, #wpsl-reset-map" ).on( "click", function() {
750
- var keepStartMarker = false,
751
- resetMap = true;
752
-
753
- /*
754
- * Check if a map reset is already in progress,
755
- * if so prevent another one from starting.
756
- */
757
- if ( $( this ).hasClass( "wpsl-in-progress" ) ) {
758
- return;
759
- }
760
-
761
- /*
762
- * When the start marker is dragged the autoload value is set to false.
763
- * So we need to check the correct value when the reset button is
764
- * pushed before reloading the stores.
765
- */
766
- if ( wpslSettings.autoLoad == 1 ) {
767
- autoLoad = 1;
768
- }
769
-
770
- // Check if the latlng or zoom has changed since pageload, if so there is something to reset.
771
- if ( ( ( ( map.getCenter().lat() !== mapDefaults.centerLatlng.lat() ) || ( map.getCenter().lng() !== mapDefaults.centerLatlng.lng() ) || ( map.getZoom() !== mapDefaults.zoomLevel ) ) ) ) {
772
- deleteOverlays( keepStartMarker );
773
-
774
- $( "#wpsl-search-input" ).val( "" ).removeClass();
775
-
776
- // We use this to prevent multiple reset request.
777
- $( ".wpsl-icon-reset" ).addClass( "wpsl-in-progress" );
778
-
779
- // If marker clusters exist, remove them from the map.
780
- if ( markerClusterer ) {
781
- markerClusterer.clearMarkers();
782
- }
783
-
784
- // Remove the start marker.
785
- deleteStartMarker();
786
-
787
- // Reset the dropdown values.
788
- resetDropdowns();
789
-
790
- if ( wpslSettings.autoLocate == 1 ) {
791
- handleGeolocationQuery( startLatLng, userGeolocation.position, resetMap, infoWindow );
792
- } else {
793
- showStores( startLatLng, infoWindow );
794
- }
795
- }
796
-
797
- // Make sure the stores are shown and the direction details are hidden.
798
- $( "#wpsl-stores" ).show();
799
- $( "#wpsl-direction-details" ).hide();
800
- });
801
- }
802
-
803
- /**
804
- * Remove the start marker from the map.
805
- *
806
- * @since 1.2.12
807
- * @returns {void}
808
- */
809
- function deleteStartMarker() {
810
- if ( ( typeof( startMarkerData ) !== "undefined" ) && ( startMarkerData !== "" ) ) {
811
- startMarkerData.setMap( null );
812
- startMarkerData = "";
813
- }
814
- }
815
-
816
- /**
817
- * Reset the dropdown values for the max results,
818
- * and search radius after the "reset" button is triggerd.
819
- *
820
- * @since 1.1.0
821
- * @returns {void}
822
- */
823
- function resetDropdowns() {
824
- var i, arrayLength, dataValue, catText, $customDiv, $customFirstLi, customSelectedText, customSelectedData,
825
- defaultFilters = $( "#wpsl-wrap" ).hasClass( "wpsl-default-filters" ),
826
- defaultValues = [wpslSettings.searchRadius + ' ' + wpslSettings.distanceUnit, wpslSettings.maxResults],
827
- dropdowns = ["wpsl-radius", "wpsl-results"];
828
-
829
- for ( i = 0, arrayLength = dropdowns.length; i < arrayLength; i++ ) {
830
- $( "#" + dropdowns[i] + " select" ).val( parseInt( defaultValues[i] ) );
831
- $( "#" + dropdowns[i] + " li" ).removeClass();
832
-
833
- if ( dropdowns[i] == "wpsl-radius" ) {
834
- dataValue = wpslSettings.searchRadius;
835
- } else if ( dropdowns[i] == "wpsl-results" ) {
836
- dataValue = wpslSettings.maxResults;
837
- }
838
-
839
- $( "#" + dropdowns[i] + " li" ).each( function() {
840
- if ( $( this ).text() === defaultValues[i] ) {
841
- $( this ).addClass( "wpsl-selected-dropdown" );
842
-
843
- $( "#" + dropdowns[i] + " .wpsl-selected-item" ).html( defaultValues[i] ).attr( "data-value", dataValue );
844
- }
845
- });
846
- }
847
-
848
- /**
849
- * Reset the category dropdown.
850
- * @todo look for other way to do this in combination with above code. Maybe allow users to define a default cat on the settings page?
851
- */
852
- if ( $( "#wpsl-category" ).length ) {
853
- $( "#wpsl-category select" ).val( 0 );
854
- $( "#wpsl-category li" ).removeClass();
855
- $( "#wpsl-category li:first-child" ).addClass( "wpsl-selected-dropdown" );
856
-
857
- catText = $( "#wpsl-category li:first-child" ).text();
858
-
859
- $( "#wpsl-category .wpsl-selected-item" ).html( catText ).attr( "data-value", 0 );
860
- }
861
-
862
- // If any custom dropdowns exist, then we reset them as well.
863
- if ( $( ".wpsl-custom-dropdown" ).length > 0 ) {
864
- $( ".wpsl-custom-dropdown" ).each( function( index ) {
865
-
866
- // Check if we are dealing with the styled dropdowns, or the default select dropdowns.
867
- if ( !defaultFilters ) {
868
- $customDiv = $( this ).siblings( "div" );
869
- $customFirstLi = $customDiv.find( "li:first-child" );
870
- customSelectedText = $customFirstLi.text();
871
- customSelectedData = $customFirstLi.attr( "data-value" );
872
-
873
- $customDiv.find( "li" ).removeClass();
874
- $customDiv.prev().html( customSelectedText ).attr( "data-value", customSelectedData );
875
- } else {
876
- $( this ).find( "option" ).removeAttr( "selected" );
877
- }
878
- });
879
- }
880
- }
881
-
882
- // Handle the click on the back button when the route directions are displayed.
883
- $( "#wpsl-result-list" ).on( "click", ".wpsl-back", function() {
884
- var i, len;
885
-
886
- // Remove the directions from the map.
887
- directionsDisplay.setMap( null );
888
-
889
- // Restore the store markers on the map.
890
- for ( i = 0, len = markersArray.length; i < len; i++ ) {
891
- markersArray[i].setMap( map );
892
- }
893
-
894
- // Restore the start marker on the map.
895
- if ( ( typeof( startMarkerData ) !== "undefined" ) && ( startMarkerData !== "" ) ) {
896
- startMarkerData.setMap( map );
897
- }
898
-
899
- // If marker clusters are enabled, restore them.
900
- if ( markerClusterer ) {
901
- checkMarkerClusters();
902
- }
903
-
904
- map.setCenter( directionMarkerPosition.centerLatlng );
905
- map.setZoom( directionMarkerPosition.zoomLevel );
906
-
907
- $( ".wpsl-direction-before, .wpsl-direction-after" ).remove();
908
- $( "#wpsl-stores" ).show();
909
- $( "#wpsl-direction-details" ).hide();
910
-
911
- return false;
912
- });
913
-
914
- /**
915
- * Show the driving directions.
916
- *
917
- * @since 1.1.0
918
- * @param {object} e The clicked elemennt
919
- * @returns {void}
920
- */
921
- function renderDirections( e ) {
922
- var i, start, end, len, storeId;
923
-
924
- // Force the open InfoBox info window to close.
925
- closeInfoBoxWindow();
926
-
927
- /*
928
- * The storeId is placed on the li in the results list,
929
- * but in the marker it will be on the wrapper div. So we check which one we need to target.
930
- */
931
- if ( e.parents( "li" ).length > 0 ) {
932
- storeId = e.parents( "li" ).data( "store-id" );
933
- } else {
934
- storeId = e.parents( ".wpsl-info-window" ).data( "store-id" );
935
- }
936
-
937
- // Check if we need to get the start point from a dragged marker.
938
- if ( ( typeof( startMarkerData ) !== "undefined" ) && ( startMarkerData !== "" ) ) {
939
- start = startMarkerData.getPosition();
940
- }
941
-
942
- // Used to restore the map back to the state it was in before the user clicked on 'directions'.
943
- directionMarkerPosition = {
944
- centerLatlng: map.getCenter(),
945
- zoomLevel: map.getZoom()
946
- };
947
-
948
- // Find the latlng that belongs to the start and end point.
949
- for ( i = 0, len = markersArray.length; i < len; i++ ) {
950
-
951
- // Only continue if the start data is still empty or undefined.
952
- if ( ( markersArray[i].storeId == 0 ) && ( ( typeof( start ) === "undefined" ) || ( start === "" ) ) ) {
953
- start = markersArray[i].getPosition();
954
- } else if ( markersArray[i].storeId == storeId ) {
955
- end = markersArray[i].getPosition();
956
- }
957
- }
958
-
959
- if ( start && end ) {
960
- $( "#wpsl-direction-details ul" ).empty();
961
- $( ".wpsl-direction-before, .wpsl-direction-after" ).remove();
962
- calcRoute( start, end );
963
- } else {
964
- alert( wpslLabels.generalError );
965
- }
966
- }
967
-
968
- /**
969
- * Check what effect is triggerd once a user hovers over the store list.
970
- * Either bounce the corresponding marker up and down, open the info window or ignore it.
971
- */
972
- if ( $( "#wpsl-gmap" ).length ) {
973
- if ( wpslSettings.markerEffect == 'bounce' ) {
974
- $( "#wpsl-stores" ).on( "mouseenter", "li", function() {
975
- letsBounce( $( this ).data( "store-id" ), "start" );
976
- });
977
-
978
- $( "#wpsl-stores" ).on( "mouseleave", "li", function() {
979
- letsBounce( $( this ).data( "store-id" ), "stop" );
980
- });
981
- } else if ( wpslSettings.markerEffect == 'info_window' ) {
982
- $( "#wpsl-stores" ).on( "mouseenter", "li", function() {
983
- var i, len;
984
-
985
- for ( i = 0, len = markersArray.length; i < len; i++ ) {
986
- if ( markersArray[i].storeId == $( this ).data( "store-id" ) ) {
987
- google.maps.event.trigger( markersArray[i], "click" );
988
- map.setCenter( markersArray[i].position );
989
- }
990
- }
991
- });
992
- }
993
- }
994
-
995
- /**
996
- * Let a single marker bounce.
997
- *
998
- * @since 1.0.0
999
- * @param {number} storeId The storeId of the marker that we need to bounce on the map
1000
- * @param {string} status Indicates whether we should stop or start the bouncing
1001
- * @returns {void}
1002
- */
1003
- function letsBounce( storeId, status ) {
1004
- var i, len, marker;
1005
-
1006
- // Find the correct marker to bounce based on the storeId.
1007
- for ( i = 0, len = markersArray.length; i < len; i++ ) {
1008
- if ( markersArray[i].storeId == storeId ) {
1009
- marker = markersArray[i];
1010
-
1011
- if ( status == "start" ) {
1012
- marker.setAnimation( google.maps.Animation.BOUNCE );
1013
- } else {
1014
- marker.setAnimation( null );
1015
- }
1016
- }
1017
- }
1018
- }
1019
-
1020
- /**
1021
- * Calculate the route from the start to the end.
1022
- *
1023
- * @since 1.0.0
1024
- * @param {object} start The latlng from the start point
1025
- * @param {object} end The latlng from the end point
1026
- * @returns {void}
1027
- */
1028
- function calcRoute( start, end ) {
1029
- var legs, len, step, index, direction, i, j,
1030
- distanceUnit, directionOffset, request,
1031
- directionStops = "";
1032
-
1033
- if ( wpslSettings.distanceUnit == "km" ) {
1034
- distanceUnit = 'METRIC';
1035
- } else {
1036
- distanceUnit = 'IMPERIAL';
1037
- }
1038
-
1039
- request = {
1040
- origin: start,
1041
- destination: end,
1042
- travelMode: wpslSettings.directionsTravelMode,
1043
- unitSystem: google.maps.UnitSystem[ distanceUnit ]
1044
- };
1045
-
1046
- directionsService.route( request, function( response, status ) {
1047
- if ( status == google.maps.DirectionsStatus.OK ) {
1048
- directionsDisplay.setMap( map );
1049
- directionsDisplay.setDirections( response );
1050
-
1051
- if ( response.routes.length > 0 ) {
1052
- direction = response.routes[0];
1053
-
1054
- // Loop over the legs and steps of the directions.
1055
- for ( i = 0; i < direction.legs.length; i++ ) {
1056
- legs = direction.legs[i];
1057
-
1058
- for ( j = 0, len = legs.steps.length; j < len; j++ ) {
1059
- step = legs.steps[j];
1060
- index = j+1;
1061
- directionStops = directionStops + "<li><div class='wpsl-direction-index'>" + index + "</div><div class='wpsl-direction-txt'>" + step.instructions + "</div><div class='wpsl-direction-distance'>" + step.distance.text + "</div></li>";
1062
- }
1063
- }
1064
-
1065
- $( "#wpsl-direction-details ul" ).append( directionStops ).before( "<div class='wpsl-direction-before'><a class='wpsl-back' id='wpsl-direction-start' href='#'>" + wpslLabels.back + "</a><div><span class='wpsl-total-distance'>" + direction.legs[0].distance.text + "</span> - <span class='wpsl-total-durations'>" + direction.legs[0].duration.text + "</span></div></div>" ).after( "<p class='wpsl-direction-after'>" + response.routes[0].copyrights + "</p>" );
1066
- $( "#wpsl-direction-details" ).show();
1067
-
1068
- // Remove all single markers from the map.
1069
- for ( i = 0, len = markersArray.length; i < len; i++ ) {
1070
- markersArray[i].setMap( null );
1071
- }
1072
-
1073
- // Remove the marker clusters from the map.
1074
- if ( markerClusterer ) {
1075
- markerClusterer.clearMarkers();
1076
- }
1077
-
1078
- // Remove the start marker from the map.
1079
- if ( ( typeof( startMarkerData ) !== "undefined" ) && ( startMarkerData !== "" ) ) {
1080
- startMarkerData.setMap( null );
1081
- }
1082
-
1083
- $( "#wpsl-stores" ).hide();
1084
-
1085
- // Make sure the start of the route directions are visible if the store listings are shown below the map.
1086
- if ( wpslSettings.templateId == 1 ) {
1087
- directionOffset = $( "#wpsl-gmap" ).offset();
1088
- $( window ).scrollTop( directionOffset.top );
1089
- }
1090
- }
1091
- } else {
1092
- directionErrors( status );
1093
- }
1094
- });
1095
- }
1096
-
1097
- /**
1098
- * Geocode the user input.
1099
- *
1100
- * @since 1.0.0
1101
- * @param {object} infoWindow The infoWindow object
1102
- * @returns {void}
1103
- */
1104
- function codeAddress( infoWindow ) {
1105
- var latLng, request = {};
1106
-
1107
- // Check if we need to set the geocode component restrictions.
1108
- if ( typeof wpslSettings.geocodeComponents !== "undefined" && !$.isEmptyObject( wpslSettings.geocodeComponents ) ) {
1109
- request.componentRestrictions = wpslSettings.geocodeComponents;
1110
-
1111
- if ( typeof request.componentRestrictions.postalCode !== "undefined" ) {
1112
- request.componentRestrictions.postalCode = $( "#wpsl-search-input" ).val();
1113
- } else {
1114
- request.address = $( "#wpsl-search-input" ).val();
1115
- }
1116
- } else {
1117
- request.address = $( "#wpsl-search-input" ).val();
1118
- }
1119
-
1120
- geocoder.geocode( request, function( response, status ) {
1121
- if ( status == google.maps.GeocoderStatus.OK ) {
1122
-
1123
- if ( statistics.enabled ) {
1124
- collectStatsData( response );
1125
- }
1126
-
1127
- latLng = response[0].geometry.location;
1128
-
1129
- prepareStoreSearch( latLng, infoWindow );
1130
- } else {
1131
- geocodeErrors( status );
1132
- }
1133
- });
1134
- }
1135
-
1136
- /**
1137
- * Prepare a new location search.
1138
- *
1139
- * @since 2.2.0
1140
- * @param {object} latLng The coordinates
1141
- * @param {object} infoWindow The infoWindow object.
1142
- * @returns {void}
1143
- */
1144
- function prepareStoreSearch( latLng, infoWindow ) {
1145
- var autoLoad = false;
1146
-
1147
- // Add a new start marker.
1148
- addMarker( latLng, 0, '', true, infoWindow );
1149
-
1150
- // Try to find stores that match the radius, location criteria.
1151
- findStoreLocations( latLng, resetMap, autoLoad, infoWindow );
1152
- }
1153
-
1154
- /**
1155
- * Reverse geocode the passed coordinates and set the returned zipcode in the input field.
1156
- *
1157
- * @since 1.0.0
1158
- * @param {object} latLng The coordinates of the location that should be reverse geocoded
1159
- * @returns {object} response The address components if the stats add-on is active.
1160
- */
1161
- function reverseGeocode( latLng, callback ) {
1162
- var lat = latLng.lat().toFixed( 5 ),
1163
- lng = latLng.lng().toFixed( 5 );
1164
-
1165
- latLng.lat = function() {
1166
- return parseFloat( lat );
1167
- };
1168
-
1169
- latLng.lng = function() {
1170
- return parseFloat( lng );
1171
- };
1172
-
1173
- geocoder.geocode( {'latLng': latLng }, function( response, status ) {
1174
- if ( status == google.maps.GeocoderStatus.OK ) {
1175
-
1176
- if ( wpslSettings.autoLocate == 1 && userGeolocation.newRequest ) {
1177
- var zipCode = filterApiResponse( response );
1178
-
1179
- if ( zipCode !== "" ) {
1180
- $( "#wpsl-search-input" ).val( zipCode );
1181
- }
1182
-
1183
- /*
1184
- * Prevent the zip from being placed in the input field
1185
- * again after the users location is determined.
1186
- */
1187
- userGeolocation.newRequest = false;
1188
- }
1189
-
1190
- if ( wpslSettings.directionRedirect ) {
1191
- startAddress = response[0].formatted_address;
1192
- }
1193
-
1194
- // Prevent it from running on autoload when the input field is empty.
1195
- if ( statistics.enabled && $( "#wpsl-search-input" ).val().length > 0 ) {
1196
- if ( $.isEmptyObject( statistics.addressComponents ) ) {
1197
- collectStatsData( response );
1198
- }
1199
- }
1200
-
1201
- callback();
1202
- } else {
1203
- geocodeErrors( status );
1204
- }
1205
- });
1206
- }
1207
-
1208
- /**
1209
- * Collect the data for the statistics
1210
- * add-on from the Google Geocode API.
1211
- *
1212
- * @since 2.2.18
1213
- * @param response
1214
- * @returns {void}
1215
- */
1216
- function collectStatsData( response ) {
1217
- var requiredFields, addressLength, responseType,
1218
- countryCode, responseLength,
1219
- missingFields = {},
1220
- statsData = {};
1221
-
1222
- countryCode = findCountryCode( response );
1223
-
1224
- /**
1225
- * The UK is a special case how the city / town / region / country data
1226
- * is structured in the Geocode API response. So we adjust the structure a bit.
1227
- *
1228
- * We later check which field contained the city / town data
1229
- * and if necessary later move it to the correct one.
1230
- */
1231
- if ( countryCode == "GB" ) {
1232
- requiredFields = {
1233
- 'city': 'postal_town',
1234
- 'city_locality': 'locality,political',
1235
- 'region': 'administrative_area_level_2,political',
1236
- 'country': 'administrative_area_level_1,political'
1237
- };
1238
- } else {
1239
- requiredFields = {
1240
- 'city': 'locality,political',
1241
- 'region': 'administrative_area_level_1,political',
1242
- 'country': 'country,political'
1243
- };
1244
- }
1245
-
1246
- addressLength = response[0].address_components.length;
1247
-
1248
- // Loop over the first row in the API response.
1249
- for ( i = 0; i < addressLength; i++ ) {
1250
- responseType = response[0].address_components[i].types;
1251
-
1252
- for ( var key in requiredFields ) {
1253
- if ( requiredFields[key] == responseType.join( "," ) ) {
1254
-
1255
- // In rare cases the long name is empty.
1256
- if ( response[0].address_components[i].long_name.length > 0 ) {
1257
- statsData[key] = response[0].address_components[i].long_name;
1258
- } else {
1259
- statsData[key] = response[0].address_components[i].short_name;
1260
- }
1261
- }
1262
- }
1263
- }
1264
-
1265
- /**
1266
- * Check if we have the required fields. This is often the case after
1267
- * grabbing the data from the first row, but in some cases we have to loop
1268
- * through all the data to get all the required data.
1269
- */
1270
- for ( var key in requiredFields ) {
1271
- if ( typeof statsData[key] === "undefined" ) {
1272
- missingFields[key] = requiredFields[key];
1273
- }
1274
- }
1275
-
1276
- /**
1277
- * In the UK the data we want is most of the time in the
1278
- * postal_town ( city ) field, which is often set on the first row.
1279
- *
1280
- * If this field contains data then don't continue and ignore
1281
- * the missing data in the locality field, which is more of a
1282
- * backup in case the 'postal_town' is missing in the API response.
1283
- */
1284
- if ( countryCode == "GB" ) {
1285
- if ( typeof missingFields.city_locality !== "undefined" && typeof missingFields.city === "undefined" ) {
1286
- missingFields = {};
1287
- }
1288
- }
1289
-
1290
- /**
1291
- * If one or more required fields are missing,
1292
- * then loop through the remaining API data.
1293
- */
1294
- if ( Object.keys( missingFields ).length > 0 ) {
1295
- responseLength = response.length;
1296
-
1297
- /**
1298
- * Loop over the remaining API results,
1299
- * but skip the first row since we already checked that one.
1300
- */
1301
- for ( i = 1; i < responseLength; i++ ) {
1302
- addressLength = response[i].address_components.length;
1303
-
1304
- for ( j = 0; j < addressLength; j++ ) {
1305
- responseType = response[i].address_components[j].types;
1306
-
1307
- for ( var key in missingFields ) {
1308
- if ( requiredFields[key] == responseType.join( "," ) ) {
1309
- statsData[key] = response[i].address_components[j].long_name;
1310
- }
1311
- }
1312
- }
1313
- }
1314
- }
1315
-
1316
- /**
1317
- * In rare cases, and as far I know this only happens in the UK, the city / town name
1318
- * is often set in the 'postal_town' ( city ) field in the Google API response.
1319
- *
1320
- * But in some cases the 'locality,political' ( city_locality ) field is also
1321
- * set in the first row ( where it's located for locations in the rest of the world ).
1322
- *
1323
- * When both fields are set the 'locality,political' ( city_locality ) will contain more
1324
- * accurate details, so we copy it's value back to the city field.
1325
- */
1326
- if ( typeof statsData.city_locality !== "undefined" && statsData.city_locality.length > 0 ) {
1327
- statsData.city = statsData.city_locality;
1328
-
1329
- delete statsData.city_locality;
1330
- }
1331
-
1332
- statistics.addressComponents = statsData;
1333
- }
1334
-
1335
- /**
1336
- * Grab the country name from the API response.
1337
- *
1338
- * @since 2.2.18
1339
- * @param {object} response The API response
1340
- * @return {string} countryCode The country code found in the API response.
1341
- */
1342
- function findCountryCode( response ) {
1343
- var responseType, countryCode = '';
1344
-
1345
- $.each( response[0].address_components, function( index ) {
1346
- responseType = response[0].address_components[index].types;
1347
-
1348
- if ( responseType.join( ',' ) == 'country,political' ) {
1349
- countryCode = response[0].address_components[index].short_name;
1350
-
1351
- return false;
1352
- }
1353
- });
1354
-
1355
- return countryCode;
1356
- }
1357
-
1358
- /**
1359
- * Filter out the zipcode from the response.
1360
- *
1361
- * @since 1.0.0
1362
- * @param {object} response The complete Google API response
1363
- * @returns {string} zipcode The zipcode
1364
- */
1365
- function filterApiResponse( response ) {
1366
- var zipcode, responseType, i,
1367
- addressLength = response[0].address_components.length;
1368
-
1369
- // Loop over the API response.
1370
- for ( i = 0; i < addressLength; i++ ) {
1371
- responseType = response[0].address_components[i].types;
1372
-
1373
- // filter out the postal code.
1374
- if ( ( /^postal_code$/.test( responseType ) ) || ( /^postal_code,postal_code_prefix$/.test( responseType ) ) ) {
1375
- zipcode = response[0].address_components[i].long_name;
1376
- }
1377
- }
1378
-
1379
- return zipcode;
1380
- }
1381
-
1382
- /**
1383
- * Call the function to make the ajax request to load the store locations.
1384
- *
1385
- * If we need to show the driving directions on maps.google.com itself,
1386
- * we first need to geocode the start latlng into a formatted address.
1387
- *
1388
- * @since 1.0.0
1389
- * @param {object} startLatLng The coordinates
1390
- * @param {boolean} resetMap Whether we should reset the map or not
1391
- * @param {string} autoLoad Check if we need to autoload all the stores
1392
- * @param {object} infoWindow The infoWindow object
1393
- * @returns {void}
1394
- */
1395
- function findStoreLocations( startLatLng, resetMap, autoLoad, infoWindow ) {
1396
-
1397
- if ( wpslSettings.directionRedirect == 1 || statistics.enabled ) {
1398
- reverseGeocode( startLatLng, function() {
1399
- makeAjaxRequest( startLatLng, resetMap, autoLoad, infoWindow );
1400
- });
1401
- } else {
1402
- makeAjaxRequest( startLatLng, resetMap, autoLoad, infoWindow );
1403
- }
1404
- }
1405
-
1406
- /**
1407
- * Make the AJAX request to load the store data.
1408
- *
1409
- * @since 1.2.0
1410
- * @param {object} startLatLng The latlng used as the starting point
1411
- * @param {boolean} resetMap Whether we should reset the map or not
1412
- * @param {string} autoLoad Check if we need to autoload all the stores
1413
- * @param {object} infoWindow The infoWindow object
1414
- * @returns {void}
1415
- */
1416
- function makeAjaxRequest( startLatLng, resetMap, autoLoad, infoWindow ) {
1417
- var latLng, noResultsMsg, ajaxData,
1418
- storeData = "",
1419
- draggable = false,
1420
- template = $( "#wpsl-listing-template" ).html(),
1421
- $storeList = $( "#wpsl-stores ul" ),
1422
- preloader = wpslSettings.url + "img/ajax-loader.gif";
1423
-
1424
- ajaxData = collectAjaxData( startLatLng, resetMap, autoLoad );
1425
-
1426
- // Add the preloader.
1427
- $storeList.empty().append( "<li class='wpsl-preloader'><img src='" + preloader + "'/>" + wpslLabels.preloader + "</li>" );
1428
-
1429
- $( "#wpsl-wrap" ).removeClass( "wpsl-no-results" );
1430
-
1431
- $.get( wpslSettings.ajaxurl, ajaxData, function( response ) {
1432
-
1433
- // Remove the preloaders and no results msg.
1434
- $( ".wpsl-preloader" ).remove();
1435
-
1436
- if ( response.length > 0 && typeof response.addon == "undefined" ) {
1437
-
1438
- // Loop over the returned locations.
1439
- $.each( response, function( index ) {
1440
- _.extend( response[index], templateHelpers );
1441
-
1442
- // Add the location maker to the map.
1443
- latLng = new google.maps.LatLng( response[index].lat, response[index].lng );
1444
- addMarker( latLng, response[index].id, response[index], draggable, infoWindow );
1445
-
1446
- // Create the HTML output with help from underscore js.
1447
- storeData = storeData + _.template( template )( response[index] );
1448
- });
1449
-
1450
- $( "#wpsl-result-list" ).off( "click", ".wpsl-directions" );
1451
-
1452
- // Remove the old search results.
1453
- $storeList.empty();
1454
-
1455
- // Add the html for the store listing to the <ul>.
1456
- $storeList.append( storeData );
1457
-
1458
- $( "#wpsl-result-list" ).on( "click", ".wpsl-directions", function() {
1459
-
1460
- // Check if we need to render the direction on the map.
1461
- if ( wpslSettings.directionRedirect != 1 ) {
1462
- renderDirections( $( this ) );
1463
-
1464
- return false;
1465
- }
1466
- });
1467
-
1468
- // Do we need to create a marker cluster?
1469
- checkMarkerClusters();
1470
-
1471
- $( "#wpsl-result-list p:empty" ).remove();
1472
- } else {
1473
- addMarker( startLatLng, 0, '', true, infoWindow );
1474
-
1475
- noResultsMsg = getNoResultsMsg();
1476
-
1477
- $( "#wpsl-wrap" ).addClass( "wpsl-no-results" );
1478
-
1479
- $storeList.html( "<li class='wpsl-no-results-msg'>" + noResultsMsg + "</li>" );
1480
- }
1481
-
1482
- /*
1483
- * Do we need to adjust the zoom level so that all the markers fit in the viewport,
1484
- * or just center the map on the start marker.
1485
- */
1486
- if ( wpslSettings.runFitBounds == 1 ) {
1487
- fitBounds();
1488
- } else {
1489
- map.setZoom( Number( wpslSettings.zoomLevel ) );
1490
- map.setCenter( markersArray[0].position );
1491
- }
1492
-
1493
- /*
1494
- * Store the default zoom and latlng values the first time
1495
- * all the stores are added to the map.
1496
- *
1497
- * This way when a user clicks the reset button we can check if the
1498
- * zoom/latlng values have changed, and if they have, then we know we
1499
- * need to reload the map.
1500
- */
1501
- if ( wpslSettings.resetMap == 1 ) {
1502
- if ( $.isEmptyObject( mapDefaults ) ) {
1503
- google.maps.event.addListenerOnce( map, "tilesloaded", function() {
1504
- mapDefaults = {
1505
- centerLatlng: map.getCenter(),
1506
- zoomLevel: map.getZoom()
1507
- };
1508
-
1509
- /*
1510
- * Because the reset icon exists, we need to adjust
1511
- * the styling of the direction icon.
1512
- */
1513
- $( "#wpsl-map-controls" ).addClass( "wpsl-reset-exists" );
1514
-
1515
- /*
1516
- * The reset initialy is set to hidden to prevent
1517
- * users from clicking it before the map is loaded.
1518
- */
1519
- $( ".wpsl-icon-reset, #wpsl-reset-map" ).show();
1520
- });
1521
- }
1522
-
1523
- $( ".wpsl-icon-reset" ).removeClass( "wpsl-in-progress" );
1524
- }
1525
- });
1526
-
1527
- // Move the mousecursor to the store search field if the focus option is enabled.
1528
- if ( wpslSettings.mouseFocus == 1 && !checkMobileUserAgent() ) {
1529
- $( "#wpsl-search-input" ).focus();
1530
- }
1531
- }
1532
-
1533
- /**
1534
- * Collect the data we need to include in the AJAX request.
1535
- *
1536
- * @since 2.2.0
1537
- * @param {object} startLatLng The latlng used as the starting point
1538
- * @param {boolean} resetMap Whether we should reset the map or not
1539
- * @param {string} autoLoad Check if we need to autoload all the stores
1540
- * @returns {object} ajaxData The collected data.
1541
- */
1542
- function collectAjaxData( startLatLng, resetMap, autoLoad ) {
1543
- var maxResult, radius, customDropdownName, customDropdownValue,
1544
- customCheckboxName,
1545
- categoryId = "",
1546
- isMobile = $( "#wpsl-wrap" ).hasClass( "wpsl-mobile" ),
1547
- defaultFilters = $( "#wpsl-wrap" ).hasClass( "wpsl-default-filters" ),
1548
- ajaxData = {
1549
- action: "store_search",
1550
- lat: startLatLng.lat(),
1551
- lng: startLatLng.lng()
1552
- };
1553
-
1554
- /*
1555
- * If we reset the map we use the default dropdown values instead of the selected values.
1556
- * Otherwise we first make sure the filter val is valid before including the radius / max_results param
1557
- */
1558
- if ( resetMap ) {
1559
- ajaxData.max_results = wpslSettings.maxResults;
1560
- ajaxData.search_radius = wpslSettings.searchRadius;
1561
- } else {
1562
- if ( isMobile || defaultFilters ) {
1563
- maxResult = parseInt( $( "#wpsl-results .wpsl-dropdown" ).val() );
1564
- radius = parseInt( $( "#wpsl-radius .wpsl-dropdown" ).val() );
1565
- } else {
1566
- maxResult = parseInt( $( "#wpsl-results .wpsl-selected-item" ).attr( "data-value" ) );
1567
- radius = parseInt( $( "#wpsl-radius .wpsl-selected-item" ).attr( "data-value" ) );
1568
- }
1569
-
1570
- // If the max results or radius filter values are NaN, then we use the default value.
1571
- if ( isNaN( maxResult ) ) {
1572
- ajaxData.max_results = wpslSettings.maxResults;
1573
- } else {
1574
- ajaxData.max_results = maxResult;
1575
- }
1576
-
1577
- if ( isNaN( radius ) ) {
1578
- ajaxData.search_radius = wpslSettings.searchRadius;
1579
- } else {
1580
- ajaxData.search_radius = radius;
1581
- }
1582
-
1583
- /*
1584
- * If category ids are set through the wpsl shortcode, then we always need to include them.
1585
- * Otherwise check if the category dropdown exist, or if the checkboxes are used.
1586
- */
1587
- if ( typeof wpslSettings.categoryIds !== "undefined" ) {
1588
- ajaxData.filter = wpslSettings.categoryIds;
1589
- } else if ( $( "#wpsl-category" ).length > 0 ) {
1590
- if ( isMobile || defaultFilters ) {
1591
- categoryId = parseInt( $( "#wpsl-category .wpsl-dropdown" ).val() );
1592
- } else {
1593
- categoryId = parseInt( $( "#wpsl-category .wpsl-selected-item" ).attr( "data-value" ) );
1594
- }
1595
-
1596
- if ( ( !isNaN( categoryId ) && ( categoryId !== 0 ) ) ) {
1597
- ajaxData.filter = categoryId;
1598
- }
1599
- } else if ( $( "#wpsl-checkbox-filter" ).length > 0 ) {
1600
- if ( $( "#wpsl-checkbox-filter input:checked" ).length > 0 ) {
1601
- ajaxData.filter = getCheckboxIds();
1602
- }
1603
- }
1604
-
1605
- // Include values from custom dropdowns.
1606
- if ( $( ".wpsl-custom-dropdown" ).length > 0 ) {
1607
- $( ".wpsl-custom-dropdown" ).each( function( index ) {
1608
- customDropdownName = '';
1609
- customDropdownValue = '';
1610
-
1611
- if ( isMobile || defaultFilters ) {
1612
- customDropdownName = $( this ).attr( "name" );
1613
- customDropdownValue = $( this ).val();
1614
- } else {
1615
- customDropdownName = $( this ).attr( "name" );
1616
- customDropdownValue = $( this ).next( ".wpsl-selected-item" ).attr( "data-value" );
1617
- }
1618
-
1619
- if ( customDropdownName && customDropdownValue ) {
1620
- ajaxData[customDropdownName] = customDropdownValue;
1621
- }
1622
- });
1623
- }
1624
-
1625
- // Include values from custom checkboxes
1626
- if ( $( ".wpsl-custom-checkboxes" ).length > 0 ) {
1627
- $( ".wpsl-custom-checkboxes" ).each( function( index ) {
1628
- customCheckboxName = $( this ).attr( "data-name" );
1629
-
1630
- if ( customCheckboxName ) {
1631
- ajaxData[customCheckboxName] = getCustomCheckboxValue( customCheckboxName );
1632
- }
1633
- });
1634
- }
1635
- }
1636
-
1637
- /*
1638
- * If the autoload option is enabled, then we need to check if the included latlng
1639
- * is based on a geolocation attempt before including the autoload param.
1640
- *
1641
- * Because if both the geolocation and autoload options are enabled,
1642
- * and the geolocation attempt was successful, then we need to to include
1643
- * the skip_cache param.
1644
- *
1645
- * This makes sure the results don't come from an older transient based on the
1646
- * start location from the settings page, instead of the users actual location.
1647
- */
1648
- if ( autoLoad == 1 ) {
1649
- if ( typeof userGeolocation.position !== "undefined" ) {
1650
- ajaxData.skip_cache = 1;
1651
- } else {
1652
- ajaxData.autoload = 1;
1653
-
1654
- /*
1655
- * If the user set the 'category' attr on the wpsl shortcode, then include the cat ids
1656
- * to make sure only locations from the set categories are loaded on autoload.
1657
- */
1658
- if ( typeof wpslSettings.categoryIds !== "undefined" ) {
1659
- ajaxData.filter = wpslSettings.categoryIds;
1660
- }
1661
- }
1662
- }
1663
-
1664
- // If the collection of statistics is enabled, then we include the searched value.
1665
- if ( statistics.enabled && autoLoad == 0 ) {
1666
- ajaxData.search = $( "#wpsl-search-input" ).val();
1667
- ajaxData.statistics = statistics.addressComponents;
1668
- }
1669
-
1670
- return ajaxData;
1671
- }
1672
-
1673
- /**
1674
- * Get custom checkbox values by data-name group.
1675
- *
1676
- * If multiple selection are made, then the returned
1677
- * values are comma separated
1678
- *
1679
- * @since 2.2.8
1680
- * @param {string} customCheckboxName The data-name value of the custom checkbox
1681
- * @return {string} customValue The collected checkbox values separated by a comma
1682
- */
1683
- function getCustomCheckboxValue( customCheckboxName ) {
1684
- var dataName = $( "[data-name=" + customCheckboxName + "]" ),
1685
- customValue = [];
1686
-
1687
- $( dataName ).find( "input:checked" ).each( function( index ) {
1688
- customValue.push( $( this ).val() );
1689
- });
1690
-
1691
- return customValue.join();
1692
- }
1693
-
1694
- /**
1695
- * Check which no results msg we need to show.
1696
- *
1697
- * Either the default txt or a longer custom msg.
1698
- *
1699
- * @since 2.2.0
1700
- * @return string noResults The no results msg to show.
1701
- */
1702
- function getNoResultsMsg() {
1703
- var noResults;
1704
-
1705
- if ( typeof wpslSettings.noResults !== "undefined" && wpslSettings.noResults !== "" ) {
1706
- noResults = wpslSettings.noResults;
1707
- } else {
1708
- noResults = wpslLabels.noResults;
1709
- }
1710
-
1711
- return noResults;
1712
- }
1713
-
1714
- /**
1715
- * Collect the ids of the checked checkboxes.
1716
- *
1717
- * @since 2.2.0
1718
- * @return string catIds The cat ids from the checkboxes.
1719
- */
1720
- function getCheckboxIds() {
1721
- var catIds = $( "#wpsl-checkbox-filter input:checked" ).map( function() {
1722
- return $( this ).val();
1723
- });
1724
-
1725
- catIds = catIds.get();
1726
- catIds = catIds.join(',');
1727
-
1728
- return catIds;
1729
- }
1730
-
1731
- /**
1732
- * Check if cluster markers are enabled.
1733
- * If so, init the marker clustering with the
1734
- * correct gridsize and max zoom.
1735
- *
1736
- * @since 1.2.20
1737
- * @return {void}
1738
- */
1739
- function checkMarkerClusters() {
1740
- if ( wpslSettings.markerClusters == 1 ) {
1741
- var markers, markersArrayNoStart,
1742
- clusterZoom = Number( wpslSettings.clusterZoom ),
1743
- clusterSize = Number( wpslSettings.clusterSize );
1744
-
1745
- if ( isNaN( clusterZoom ) ) {
1746
- clusterZoom = "";
1747
- }
1748
-
1749
- if ( isNaN( clusterSize ) ) {
1750
- clusterSize = "";
1751
- }
1752
-
1753
- /*
1754
- * Remove the start location marker from the cluster so the location
1755
- * count represents the actual returned locations, and not +1 for the start location.
1756
- */
1757
- if ( typeof wpslSettings.excludeStartFromCluster !== "undefined" && wpslSettings.excludeStartFromCluster == 1 ) {
1758
- markersArrayNoStart = markersArray.slice( 0 );
1759
- markersArrayNoStart.splice( 0,1 );
1760
- }
1761
-
1762
- markers = ( typeof markersArrayNoStart === "undefined" ) ? markersArray : markersArrayNoStart;
1763
-
1764
- markerClusterer = new MarkerClusterer( map, markers, {
1765
- gridSize: clusterSize,
1766
- maxZoom: clusterZoom
1767
- });
1768
- }
1769
- }
1770
-
1771
- /**
1772
- * Add a new marker to the map based on the provided location (latlng).
1773
- *
1774
- * @since 1.0.0
1775
- * @param {object} latLng The coordinates
1776
- * @param {number} storeId The store id
1777
- * @param {object} infoWindowData The data we need to show in the info window
1778
- * @param {boolean} draggable Should the marker be draggable
1779
- * @param {object} infoWindow The infoWindow object
1780
- * @return {void}
1781
- */
1782
- function addMarker( latLng, storeId, infoWindowData, draggable, infoWindow ) {
1783
- var url, mapIcon, marker,
1784
- keepStartMarker = true;
1785
-
1786
- if ( storeId === 0 ) {
1787
- infoWindowData = {
1788
- store: wpslLabels.startPoint
1789
- };
1790
-
1791
- url = markerSettings.url + wpslSettings.startMarker;
1792
- } else if ( typeof infoWindowData.alternateMarkerUrl !== "undefined" && infoWindowData.alternateMarkerUrl ) {
1793
- url = infoWindowData.alternateMarkerUrl;
1794
- } else if ( typeof infoWindowData.categoryMarkerUrl !== "undefined" && infoWindowData.categoryMarkerUrl ) {
1795
- url = infoWindowData.categoryMarkerUrl;
1796
- } else {
1797
- url = markerSettings.url + wpslSettings.storeMarker;
1798
- }
1799
-
1800
- mapIcon = {
1801
- url: url,
1802
- scaledSize: new google.maps.Size( Number( markerSettings.scaledSize[0] ), Number( markerSettings.scaledSize[1] ) ), //retina format
1803
- origin: new google.maps.Point( Number( markerSettings.origin[0] ), Number( markerSettings.origin[1] ) ),
1804
- anchor: new google.maps.Point( Number( markerSettings.anchor[0] ), Number( markerSettings.anchor[1] ) )
1805
- };
1806
-
1807
- marker = new google.maps.Marker({
1808
- position: latLng,
1809
- map: map,
1810
- optimized: false, //fixes markers flashing while bouncing
1811
- title: decodeHtmlEntity( infoWindowData.store ),
1812
- draggable: draggable,
1813
- storeId: storeId,
1814
- icon: mapIcon
1815
- });
1816
-
1817
- // Store the marker for later use.
1818
- markersArray.push( marker );
1819
-
1820
- google.maps.event.addListener( marker, "click",( function( currentMap ) {
1821
- return function() {
1822
-
1823
- // The start marker will have a store id of 0, all others won't.
1824
- if ( storeId != 0 ) {
1825
-
1826
- // Check if streetview is available at the clicked location.
1827
- if ( typeof wpslSettings.markerStreetView !== "undefined" && wpslSettings.markerStreetView == 1 ) {
1828
- checkStreetViewStatus( latLng, function() {
1829
- setInfoWindowContent( marker, createInfoWindowHtml( infoWindowData ), infoWindow, currentMap );
1830
- });
1831
- } else {
1832
- setInfoWindowContent( marker, createInfoWindowHtml( infoWindowData ), infoWindow, currentMap );
1833
- }
1834
- } else {
1835
- setInfoWindowContent( marker, wpslLabels.startPoint, infoWindow, currentMap );
1836
- }
1837
-
1838
- google.maps.event.clearListeners( infoWindow, "domready" );
1839
-
1840
- google.maps.event.addListener( infoWindow, "domready", function() {
1841
- infoWindowClickActions( marker, currentMap );
1842
- checkMaxZoomLevel();
1843
- });
1844
- };
1845
- }( map ) ) );
1846
-
1847
- // Only the start marker will be draggable.
1848
- if ( draggable ) {
1849
- google.maps.event.addListener( marker, "dragend", function( event ) {
1850
- deleteOverlays( keepStartMarker );
1851
- map.setCenter( event.latLng );
1852
- reverseGeocode( event.latLng );
1853
- findStoreLocations( event.latLng, resetMap, autoLoad = false, infoWindow );
1854
- });
1855
- }
1856
- }
1857
-
1858
- /**
1859
- * Decode HTML entities.
1860
- *
1861
- * @link https://gist.github.com/CatTail/4174511
1862
- * @since 2.0.4
1863
- * @param {string} str The string to decode.
1864
- * @returns {string} The string with the decoded HTML entities.
1865
- */
1866
- function decodeHtmlEntity( str ) {
1867
- if ( str ) {
1868
- return str.replace( /&#(\d+);/g, function( match, dec) {
1869
- return String.fromCharCode( dec );
1870
- });
1871
- }
1872
- };
1873
-
1874
- // Check if we are using both the infobox for the info windows and have marker clusters.
1875
- if ( typeof wpslSettings.infoWindowStyle !== "undefined" && wpslSettings.infoWindowStyle == "infobox" && wpslSettings.markerClusters == 1 ) {
1876
- var clusters, clusterLen, markerLen, i, j;
1877
-
1878
- /*
1879
- * We need to listen to both zoom_changed and idle.
1880
- *
1881
- * If the zoom level changes, then the marker clusters either merges nearby
1882
- * markers, or changes into individual markers. Which is the moment we
1883
- * either show or hide the opened info window.
1884
- *
1885
- * "idle" is necessary to make sure the getClusters() is up
1886
- * to date with the correct cluster data.
1887
- */
1888
- google.maps.event.addListener( map, "zoom_changed", function() {
1889
- google.maps.event.addListenerOnce( map, "idle", function() {
1890
-
1891
- if ( typeof markerClusterer !== "undefined" ) {
1892
- clusters = markerClusterer.clusters_;
1893
-
1894
- if ( clusters.length ) {
1895
- for ( i = 0, clusterLen = clusters.length; i < clusterLen; i++ ) {
1896
- for ( j = 0, markerLen = clusters[i].markers_.length; j < markerLen; j++ ) {
1897
-
1898
- /*
1899
- * Match the storeId from the cluster marker with the
1900
- * marker id that was set when the info window was opened
1901
- */
1902
- if ( clusters[i].markers_[j].storeId == activeWindowMarkerId ) {
1903
-
1904
- /*
1905
- * If there is a visible info window, but the markers_[j].map is null ( hidden )
1906
- * it means the info window belongs to a marker that is part of a marker cluster.
1907
- *
1908
- * If that is the case then we hide the info window ( the individual marker isn't visible ).
1909
- *
1910
- * The default info window script handles this automatically, but the
1911
- * infobox library in combination with the marker clusters doesn't.
1912
- */
1913
- if ( infoWindow.getVisible() && clusters[i].markers_[j].map === null ) {
1914
- infoWindow.setVisible( false );
1915
- } else if ( !infoWindow.getVisible() && clusters[i].markers_[j].map !== null ) {
1916
- infoWindow.setVisible( true );
1917
- }
1918
-
1919
- break;
1920
- }
1921
- }
1922
- }
1923
- }
1924
- }
1925
- });
1926
- });
1927
- }
1928
-
1929
- /**
1930
- * Set the correct info window content for the marker.
1931
- *
1932
- * @since 1.2.20
1933
- * @param {object} marker Marker data
1934
- * @param {string} infoWindowContent The infoWindow content
1935
- * @param {object} infoWindow The infoWindow object
1936
- * @param {object} currentMap The map object
1937
- * @returns {void}
1938
- */
1939
- function setInfoWindowContent( marker, infoWindowContent, infoWindow, currentMap ) {
1940
- openInfoWindow.length = 0;
1941
-
1942
- infoWindow.setContent( infoWindowContent );
1943
- infoWindow.open( currentMap, marker );
1944
-
1945
- openInfoWindow.push( infoWindow );
1946
-
1947
- /*
1948
- * Store the marker id if both the marker clusters and the infobox are enabled.
1949
- *
1950
- * With the normal info window script the info window is automatically closed
1951
- * once a user zooms out, and the marker clusters are enabled,
1952
- * but this doesn't happen with the infobox library.
1953
- *
1954
- * So we need to show/hide it manually when the user zooms out,
1955
- * and for this to work we need to know which marker to target.
1956
- */
1957
- if ( typeof wpslSettings.infoWindowStyle !== "undefined" && wpslSettings.infoWindowStyle == "infobox" && wpslSettings.markerClusters == 1 ) {
1958
- activeWindowMarkerId = marker.storeId;
1959
- infoWindow.setVisible( true );
1960
- }
1961
- }
1962
-
1963
- /**
1964
- * Handle clicks for the different info window actions like,
1965
- * direction, streetview and zoom here.
1966
- *
1967
- * @since 1.2.20
1968
- * @param {object} marker Holds the marker data
1969
- * @param {object} currentMap The map object
1970
- * @returns {void}
1971
- */
1972
- function infoWindowClickActions( marker, currentMap ) {
1973
- $( ".wpsl-info-actions a" ).on( "click", function( e ) {
1974
- var maxZoom = Number( wpslSettings.autoZoomLevel );
1975
-
1976
- e.stopImmediatePropagation();
1977
-
1978
- if ( $( this ).hasClass( "wpsl-directions" ) ) {
1979
-
1980
- /*
1981
- * Check if we need to show the direction on the map
1982
- * or send the users to maps.google.com
1983
- */
1984
- if ( wpslSettings.directionRedirect == 1 ) {
1985
- return true;
1986
- } else {
1987
- renderDirections( $( this ) );
1988
- }
1989
- } else if ( $( this ).hasClass( "wpsl-streetview" ) ) {
1990
- activateStreetView( marker, currentMap );
1991
- } else if ( $( this ).hasClass( "wpsl-zoom-here" ) ) {
1992
- currentMap.setCenter( marker.getPosition() );
1993
- currentMap.setZoom( maxZoom );
1994
- }
1995
-
1996
- return false;
1997
- });
1998
- }
1999
-
2000
- /**
2001
- * Check if have reached the max auto zoom level.
2002
- *
2003
- * If so we hide the 'Zoom here' text in the info window,
2004
- * otherwise we show it.
2005
- *
2006
- * @since 2.0.0
2007
- * @returns {void}
2008
- */
2009
- function checkMaxZoomLevel() {
2010
- var zoomLevel = map.getZoom();
2011
-
2012
- if ( zoomLevel >= wpslSettings.autoZoomLevel ) {
2013
- $( ".wpsl-zoom-here" ).hide();
2014
- } else {
2015
- $( ".wpsl-zoom-here" ).show();
2016
- }
2017
- }
2018
-
2019
- /**
2020
- * Activate streetview for the clicked location.
2021
- *
2022
- * @since 1.2.20
2023
- * @param {object} marker The current marker
2024
- * @param {object} currentMap The map object
2025
- * @returns {void}
2026
- */
2027
- function activateStreetView( marker, currentMap ) {
2028
- var panorama = currentMap.getStreetView();
2029
- panorama.setPosition( marker.getPosition() );
2030
- panorama.setVisible( true );
2031
-
2032
- $( "#wpsl-map-controls" ).hide();
2033
-
2034
- StreetViewListener( panorama, currentMap );
2035
- }
2036
-
2037
- /**
2038
- * Listen for changes in the streetview visibility.
2039
- *
2040
- * Sometimes the infowindow offset is incorrect after switching back from streetview.
2041
- * We fix this by zooming in and out. If someone has a better fix, then let me know at
2042
- * info at tijmensmit.com
2043
- *
2044
- * @since 1.2.20
2045
- * @param {object} panorama The streetview object
2046
- * @param {object} currentMap The map object
2047
- * @returns {void}
2048
- */
2049
- function StreetViewListener( panorama, currentMap ) {
2050
- google.maps.event.addListener( panorama, "visible_changed", function() {
2051
- if ( !panorama.getVisible() ) {
2052
- var currentZoomLevel = currentMap.getZoom();
2053
-
2054
- $( "#wpsl-map-controls" ).show();
2055
-
2056
- currentMap.setZoom( currentZoomLevel-1 );
2057
- currentMap.setZoom( currentZoomLevel );
2058
- }
2059
- });
2060
- }
2061
-
2062
- /**
2063
- * Check the streetview status.
2064
- *
2065
- * Make sure that a streetview exists for
2066
- * the latlng for the open info window.
2067
- *
2068
- * @since 1.2.20
2069
- * @param {object} latLng The latlng coordinates
2070
- * @param {callback} callback
2071
- * @returns {void}
2072
- */
2073
- function checkStreetViewStatus( latLng, callback ) {
2074
- var service = new google.maps.StreetViewService();
2075
-
2076
- service.getPanoramaByLocation( latLng, 50, function( result, status ) {
2077
- streetViewAvailable = ( status == google.maps.StreetViewStatus.OK ) ? true : false;
2078
- callback();
2079
- });
2080
- }
2081
-
2082
- /**
2083
- * Helper methods for the underscore templates.
2084
- *
2085
- * @link http://underscorejs.org/#template
2086
- * @requires underscore.js
2087
- * @todo move it to another JS file to make it accessible for add-ons?
2088
- * @since 2.0.0
2089
- */
2090
- var templateHelpers = {
2091
- /**
2092
- * Make the phone number clickable if we are dealing with a mobile useragent.
2093
- *
2094
- * @since 1.2.20
2095
- * @param {string} phoneNumber The phone number
2096
- * @returns {string} phoneNumber Either just the plain number, or with a link wrapped around it with tel:
2097
- */
2098
- formatPhoneNumber: function( phoneNumber ) {
2099
- if ( ( wpslSettings.phoneUrl == 1 ) && ( checkMobileUserAgent() ) || wpslSettings.clickableDetails == 1 ) {
2100
- phoneNumber = "<a href='tel:" + templateHelpers.formatClickablePhoneNumber( phoneNumber ) + "'>" + phoneNumber + "</a>";
2101
- }
2102
-
2103
- return phoneNumber;
2104
- },
2105
- /**
2106
- * Replace spaces - . and () from phone numbers.
2107
- * Also if the number starts with a + we check for a (0) and remove it.
2108
- *
2109
- * @since 1.2.20
2110
- * @param {string} phoneNumber The phone number
2111
- * @returns {string} phoneNumber The 'cleaned' number
2112
- */
2113
- formatClickablePhoneNumber: function( phoneNumber ) {
2114
- if ( ( phoneNumber.indexOf( "+" ) != -1 ) && ( phoneNumber.indexOf( "(0)" ) != -1 ) ) {
2115
- phoneNumber = phoneNumber.replace( "(0)", "" );
2116
- }
2117
-
2118
- return phoneNumber.replace( /(-| |\(|\)|\.|)/g, "" );
2119
- },
2120
- /**
2121
- * Check if we need to make the email address clickable.
2122
- *
2123
- * @since 2.2.13
2124
- * @param {string} email The email address
2125
- * @returns {string} email Either the normal email address, or the clickable version.
2126
- */
2127
- formatEmail: function( email ) {
2128
- if ( wpslSettings.clickableDetails == 1 ) {
2129
- email = "<a href='mailto:" + email + "'>" + email + "</a>";
2130
- }
2131
-
2132
- return email;
2133
- },
2134
- /**
2135
- * Create the html for the info window action.
2136
- *
2137
- * @since 2.0.0
2138
- * @param {string} id The store id
2139
- * @returns {string} output The html for the info window actions
2140
- */
2141
- createInfoWindowActions: function( id ) {
2142
- var output,
2143
- streetView = "",
2144
- zoomTo = "";
2145
-
2146
- if ( $( "#wpsl-gmap" ).length ) {
2147
- if ( streetViewAvailable ) {
2148
- streetView = "<a class='wpsl-streetview' href='#'>" + wpslLabels.streetView + "</a>";
2149
- }
2150
-
2151
- if ( wpslSettings.markerZoomTo == 1 ) {
2152
- zoomTo = "<a class='wpsl-zoom-here' href='#'>" + wpslLabels.zoomHere + "</a>";
2153
- }
2154
-
2155
- output = "<div class='wpsl-info-actions'>" + templateHelpers.createDirectionUrl( id ) + streetView + zoomTo + "</div>";
2156
- }
2157
-
2158
- return output;
2159
- },
2160
- /**
2161
- * Create the url that takes the user to the maps.google.com page
2162
- * and shows the correct driving directions.
2163
- *
2164
- * @since 1.0.0
2165
- * @param {string} id The store id
2166
- * @returns {string} directionUrl The full maps.google.com url with the encoded start + end address
2167
- */
2168
- createDirectionUrl: function( id ) {
2169
- var directionUrl, destinationAddress, zip,
2170
- url = {};
2171
-
2172
- if ( wpslSettings.directionRedirect == 1 ) {
2173
-
2174
- // If we somehow failed to determine the start address, just set it to empty.
2175
- if ( typeof startAddress === "undefined" ) {
2176
- startAddress = "";
2177
- }
2178
-
2179
- url.target = "target='_blank'";
2180
-
2181
- // If the id exists the user clicked on a marker we get the direction url from the search results.
2182
- if ( typeof id !== "undefined" ) {
2183
- url.src = $( "[data-store-id=" + id + "] .wpsl-directions" ).attr( "href" );
2184
- } else {
2185
-
2186
- // Only add a , after the zip if the zip value exists.
2187
- if ( this.zip ) {
2188
- zip = this.zip + ", ";
2189
- } else {
2190
- zip = "";
2191
- }
2192
-
2193
- destinationAddress = this.address + ", " + this.city + ", " + zip + this.country;
2194
-
2195
- url.src = "https://www.google.com/maps/dir/?api=1&origin=" + templateHelpers.rfc3986EncodeURIComponent( startAddress ) + "&destination=" + templateHelpers.rfc3986EncodeURIComponent( destinationAddress ) + "&travelmode=" + wpslSettings.directionsTravelMode.toLowerCase() + "";
2196
- }
2197
- } else {
2198
- url = {
2199
- src: "#",
2200
- target: ""
2201
- };
2202
- }
2203
-
2204
- directionUrl = "<a class='wpsl-directions' " + url.target + " href='" + url.src + "'>" + wpslLabels.directions + "</a>";
2205
-
2206
- return directionUrl;
2207
- },
2208
- /**
2209
- * Make the URI encoding compatible with RFC 3986.
2210
- *
2211
- * !, ', (, ), and * will be escaped, otherwise they break the string.
2212
- *
2213
- * @since 1.2.20
2214
- * @param {string} str The string to encode
2215
- * @returns {string} The encoded string
2216
- */
2217
- rfc3986EncodeURIComponent: function( str ) {
2218
- return encodeURIComponent( str ).replace( /[!'()*]/g, escape );
2219
- }
2220
- };
2221
-
2222
- /**
2223
- * Create the HTML template used in the info windows on the map.
2224
- *
2225
- * @since 1.0.0
2226
- * @param {object} infoWindowData The data that is shown in the info window (address, url, phone etc)
2227
- * @returns {string} windowContent The HTML content that is placed in the info window
2228
- */
2229
- function createInfoWindowHtml( infoWindowData ) {
2230
- var windowContent, template;
2231
-
2232
- if ( $( "#wpsl-base-gmap_0" ).length ) {
2233
- template = $( "#wpsl-cpt-info-window-template" ).html();
2234
- } else {
2235
- template = $( "#wpsl-info-window-template" ).html();
2236
- }
2237
-
2238
- windowContent = _.template( template )( infoWindowData ); //see http://underscorejs.org/#template
2239
-
2240
- return windowContent;
2241
- }
2242
-
2243
- /**
2244
- * Zoom the map so that all markers fit in the window.
2245
- *
2246
- * @since 1.0.0
2247
- * @returns {void}
2248
- */
2249
- function fitBounds() {
2250
- var i, markerLen,
2251
- maxZoom = Number( wpslSettings.autoZoomLevel ),
2252
- bounds = new google.maps.LatLngBounds();
2253
-
2254
- // Make sure we don't zoom to far.
2255
- attachBoundsChangedListener( map, maxZoom );
2256
-
2257
- for ( i = 0, markerLen = markersArray.length; i < markerLen; i++ ) {
2258
- bounds.extend ( markersArray[i].position );
2259
- }
2260
-
2261
- map.fitBounds( bounds );
2262
- }
2263
-
2264
- /**
2265
- * Remove all existing markers from the map.
2266
- *
2267
- * @since 1.0.0
2268
- * @param {boolean} keepStartMarker Whether or not to keep the start marker while removing all the other markers from the map
2269
- * @returns {void}
2270
- */
2271
- function deleteOverlays( keepStartMarker ) {
2272
- var markerLen, i;
2273
-
2274
- directionsDisplay.setMap( null );
2275
-
2276
- // Remove all the markers from the map, and empty the array.
2277
- if ( markersArray ) {
2278
- for ( i = 0, markerLen = markersArray.length; i < markerLen; i++ ) {
2279
-
2280
- // Check if we need to keep the start marker, or remove everything.
2281
- if ( keepStartMarker ) {
2282
- if ( markersArray[i].draggable != true ) {
2283
- markersArray[i].setMap( null );
2284
- } else {
2285
- startMarkerData = markersArray[i];
2286
- }
2287
- } else {
2288
- markersArray[i].setMap( null );
2289
- }
2290
- }
2291
-
2292
- markersArray.length = 0;
2293
- }
2294
-
2295
- // If marker clusters exist, remove them from the map.
2296
- if ( markerClusterer ) {
2297
- markerClusterer.clearMarkers();
2298
- }
2299
- }
2300
-
2301
- /**
2302
- * Handle the geocode errors.
2303
- *
2304
- * @since 1.0.0
2305
- * @param {string} status Contains the error code
2306
- * @returns {void}
2307
- */
2308
- function geocodeErrors( status ) {
2309
- var msg;
2310
-
2311
- switch ( status ) {
2312
- case "ZERO_RESULTS":
2313
- msg = wpslLabels.noResults;
2314
- break;
2315
- case "OVER_QUERY_LIMIT":
2316
- msg = wpslLabels.queryLimit;
2317
- break;
2318
- default:
2319
- msg = wpslLabels.generalError;
2320
- break;
2321
- }
2322
-
2323
- alert( msg );
2324
- }
2325
-
2326
- /**
2327
- * Handle the driving direction errors.
2328
- *
2329
- * @since 1.2.20
2330
- * @param {string} status Contains the error code
2331
- * @returns {void}
2332
- */
2333
- function directionErrors( status ) {
2334
- var msg;
2335
-
2336
- switch ( status ) {
2337
- case "NOT_FOUND":
2338
- case "ZERO_RESULTS":
2339
- msg = wpslLabels.noDirectionsFound;
2340
- break;
2341
- case "OVER_QUERY_LIMIT":
2342
- msg = wpslLabels.queryLimit;
2343
- break;
2344
- default:
2345
- msg = wpslLabels.generalError;
2346
- break;
2347
- }
2348
-
2349
- alert( msg );
2350
- }
2351
-
2352
- $( "#wpsl-stores" ).on( "click", ".wpsl-store-details", function() {
2353
- var i, len,
2354
- $parentLi = $( this ).parents( "li" ),
2355
- storeId = $parentLi.data( "store-id" );
2356
-
2357
- // Check if we should show the 'more info' details.
2358
- if ( wpslSettings.moreInfoLocation == "info window" ) {
2359
- for ( i = 0, len = markersArray.length; i < len; i++ ) {
2360
- if ( markersArray[i].storeId == storeId ) {
2361
- google.maps.event.trigger( markersArray[i], "click" );
2362
- }
2363
- }
2364
- } else {
2365
-
2366
- // Check if we should set the 'more info' item to active or not.
2367
- if ( $parentLi.find( ".wpsl-more-info-listings" ).is( ":visible" ) ) {
2368
- $( this ).removeClass( "wpsl-active-details" );
2369
- } else {
2370
- $( this ).addClass( "wpsl-active-details" );
2371
- }
2372
-
2373
- $parentLi.siblings().find( ".wpsl-store-details" ).removeClass( "wpsl-active-details" );
2374
- $parentLi.siblings().find( ".wpsl-more-info-listings" ).hide();
2375
- $parentLi.find( ".wpsl-more-info-listings" ).toggle();
2376
- }
2377
-
2378
- /*
2379
- * If we show the store listings under the map, we do want to jump to the
2380
- * top of the map to focus on the opened infowindow
2381
- */
2382
- if ( wpslSettings.templateId != "default" || wpslSettings.moreInfoLocation == "store listings" ) {
2383
- return false;
2384
- }
2385
- });
2386
-
2387
- /**
2388
- * Create the styled dropdown filters.
2389
- *
2390
- * Inspired by https://github.com/patrickkunka/easydropdown
2391
- *
2392
- * @since 1.2.24
2393
- * @returns {void}
2394
- */
2395
- function createDropdowns() {
2396
- var maxDropdownHeight = Number( wpslSettings.maxDropdownHeight );
2397
-
2398
- $( ".wpsl-dropdown" ).each( function( index ) {
2399
- var active, maxHeight, $this = $( this );
2400
-
2401
- $this.$dropdownWrap = $this.wrap( "<div class='wpsl-dropdown'></div>" ).parent();
2402
- $this.$selectedVal = $this.val();
2403
- $this.$dropdownElem = $( "<div><ul/></div>" ).appendTo( $this.$dropdownWrap );
2404
- $this.$dropdown = $this.$dropdownElem.find( "ul" );
2405
- $this.$options = $this.$dropdownWrap.find( "option" );
2406
-
2407
- // Hide the original <select> and remove the css class.
2408
- $this.hide().removeClass( "wpsl-dropdown" );
2409
-
2410
- // Loop over the options from the <select> and move them to a <li> instead.
2411
- $.each( $this.$options, function() {
2412
- if ( $( this ).val() == $this.$selectedVal ) {
2413
- active = 'class="wpsl-selected-dropdown"';
2414
- } else {
2415
- active = '';
2416
- }
2417
-
2418
- $this.$dropdown.append( "<li data-value=" + $( this ).val() + " " + active + ">" + $( this ).text() + "</li>" );
2419
- });
2420
-
2421
- $this.$dropdownElem.before( "<span data-value=" + $this.find( ":selected" ).val() + " class='wpsl-selected-item'>" + $this.find( ":selected" ).text() + "</span>" );
2422
- $this.$dropdownItem = $this.$dropdownElem.find( "li" );
2423
-
2424
- // Listen for clicks on the 'wpsl-dropdown' div.
2425
- $this.$dropdownWrap.on( "click", function( e ) {
2426
-
2427
- // Check if we only need to close the current open dropdown.
2428
- if ( $( this ).hasClass( "wpsl-active" ) ) {
2429
- $( this ).removeClass( "wpsl-active" );
2430
-
2431
- return;
2432
- }
2433
-
2434
- closeAllDropdowns();
2435
-
2436
- $( this ).toggleClass( "wpsl-active" );
2437
- maxHeight = 0;
2438
-
2439
- // Either calculate the correct height for the <ul>, or set it to 0 to hide it.
2440
- if ( $( this ).hasClass( "wpsl-active" ) ) {
2441
- $this.$dropdownItem.each( function( index ) {
2442
- maxHeight += $( this ).outerHeight();
2443
- });
2444
-
2445
- $this.$dropdownElem.css( "height", maxHeight + 2 + "px" );
2446
- } else {
2447
- $this.$dropdownElem.css( "height", 0 );
2448
- }
2449
-
2450
- // Check if we need to enable the scrollbar in the dropdown filter.
2451
- if ( maxHeight > maxDropdownHeight ) {
2452
- $( this ).addClass( "wpsl-scroll-required" );
2453
- $this.$dropdownElem.css( "height", ( maxDropdownHeight ) + "px" );
2454
- }
2455
-
2456
- e.stopPropagation();
2457
- });
2458
-
2459
- // Listen for clicks on the individual dropdown items.
2460
- $this.$dropdownItem.on( "click", function( e ) {
2461
-
2462
- // Set the correct value as the selected item.
2463
- $this.$dropdownWrap.find( $( ".wpsl-selected-item" ) ).html( $( this ).text() ).attr( "data-value", $( this ).attr( "data-value" ) );
2464
-
2465
- // Apply the class to the correct item to make it bold.
2466
- $this.$dropdownItem.removeClass( "wpsl-selected-dropdown" );
2467
- $( this ).addClass( "wpsl-selected-dropdown" );
2468
-
2469
- closeAllDropdowns();
2470
-
2471
- e.stopPropagation();
2472
- });
2473
- });
2474
-
2475
- $( document ).click( function() {
2476
- closeAllDropdowns();
2477
- });
2478
- }
2479
-
2480
- /**
2481
- * Close all the dropdowns.
2482
- *
2483
- * @since 1.2.24
2484
- * @returns {void}
2485
- */
2486
- function closeAllDropdowns() {
2487
- $( ".wpsl-dropdown" ).removeClass( "wpsl-active" );
2488
- $( ".wpsl-dropdown div" ).css( "height", 0 );
2489
- }
2490
-
2491
- /**
2492
- * Check if the user submitted a search through a search widget.
2493
- *
2494
- * @since 2.1.0
2495
- * @returns {void}
2496
- */
2497
- function checkWidgetSubmit() {
2498
- if ( $( ".wpsl-search" ).hasClass( "wpsl-widget" ) ) {
2499
- $( "#wpsl-search-btn" ).trigger( "click" );
2500
- $( ".wpsl-search" ).removeClass( "wpsl-widget" );
2501
- }
2502
- }
2503
-
2504
- /**
2505
- * Check if we need to run the code to prevent Google Maps
2506
- * from showing up grey when placed inside one or more tabs.
2507
- *
2508
- * @since 2.2.10
2509
- * @return {void}
2510
- */
2511
- function maybeApplyTabFix() {
2512
- var mapNumber, len;
2513
-
2514
- if ( _.isArray( wpslSettings.mapTabAnchor ) ) {
2515
- for ( mapNumber = 0, len = mapsArray.length; mapNumber < len; mapNumber++ ) {
2516
- fixGreyTabMap( mapsArray[mapNumber], wpslSettings.mapTabAnchor[mapNumber], mapNumber );
2517
- }
2518
- } else if ( $( "a[href='#" + wpslSettings.mapTabAnchor + "']" ).length ) {
2519
- fixGreyTabMap( map, wpslSettings.mapTabAnchor );
2520
- }
2521
- }
2522
-
2523
- /**
2524
- * This code prevents the map from showing a large grey area if
2525
- * the store locator is placed in a tab, and that tab is actived.
2526
- *
2527
- * The default map anchor is set to 'wpsl-map-tab', but you can
2528
- * change this with the 'wpsl_map_tab_anchor' filter.
2529
- *
2530
- * Note: If the "Attempt to auto-locate the user" option is enabled,
2531
- * and the user quickly switches to the store locator tab, before the
2532
- * Geolocation timeout is reached, then the map is sometimes centered in the ocean.
2533
- *
2534
- * I haven't really figured out why this happens. The only option to fix this
2535
- * is to simply disable the "Attempt to auto-locate the user" option if
2536
- * you use the store locator in a tab.
2537
- *
2538
- * @since 2.2.10
2539
- * @param {object} currentMap The map object from the current map
2540
- * @param {string} mapTabAnchor The anchor used in the tab that holds the map
2541
- * @param (int) mapNumber Map number
2542
- * @link http://stackoverflow.com/questions/9458215/google-maps-not-working-in-jquery-tabs
2543
- * @returns {void}
2544
- */
2545
- function fixGreyTabMap( currentMap, mapTabAnchor, mapNumber ) {
2546
- var mapZoom, mapCenter, maxZoom, bounds, tabMap,
2547
- returnBool = Number( wpslSettings.mapTabAnchorReturn ) ? true : false,
2548
- $wpsl_tab = $( "a[href='#" + mapTabAnchor + "']" );
2549
-
2550
- if ( typeof currentMap.maxZoom !== "undefined" ) {
2551
- maxZoom = currentMap.maxZoom;
2552
- } else {
2553
- maxZoom = Number( wpslSettings.autoZoomLevel );
2554
- }
2555
-
2556
- /*
2557
- * We need to do this to prevent the map from flashing if
2558
- * there's only a single marker on the first click on the tab.
2559
- */
2560
- if ( typeof mapNumber !== "undefined" && mapNumber == 0 ) {
2561
- $wpsl_tab.addClass( "wpsl-fitbounds" );
2562
- }
2563
-
2564
- $wpsl_tab.on( "click", function() {
2565
- setTimeout( function() {
2566
- if ( typeof currentMap.map !== "undefined" ) {
2567
- bounds = currentMap.bounds;
2568
- tabMap = currentMap.map;
2569
- } else {
2570
- tabMap = currentMap;
2571
- }
2572
-
2573
- mapZoom = tabMap.getZoom();
2574
- mapCenter = tabMap.getCenter();
2575
-
2576
- google.maps.event.trigger( tabMap, "resize" );
2577
-
2578
- if ( !$wpsl_tab.hasClass( "wpsl-fitbounds" ) ) {
2579
-
2580
- //Make sure fitBounds doesn't zoom past the max zoom level.
2581
- attachBoundsChangedListener( tabMap, maxZoom );
2582
-
2583
- tabMap.setZoom( mapZoom );
2584
- tabMap.setCenter( mapCenter );
2585
-
2586
- if ( typeof bounds !== "undefined" ) {
2587
- tabMap.fitBounds( bounds );
2588
- } else {
2589
- fitBounds();
2590
- }
2591
-
2592
- $wpsl_tab.addClass( "wpsl-fitbounds" );
2593
- }
2594
- }, 50 );
2595
-
2596
- return returnBool;
2597
- });
2598
- }
2599
-
2600
- /**
2601
- * Add the bounds_changed event listener to the map object
2602
- * to make sure we don't zoom past the max zoom level.
2603
- *
2604
- * @since 2.2.10
2605
- * @param object The map object to attach the event listener to
2606
- * @returns {void}
2607
- */
2608
- function attachBoundsChangedListener( map, maxZoom ) {
2609
- google.maps.event.addListenerOnce( map, "bounds_changed", function() {
2610
- google.maps.event.addListenerOnce( map, "idle", function() {
2611
- if ( this.getZoom() > maxZoom ) {
2612
- this.setZoom( maxZoom );
2613
- }
2614
- });
2615
- });
2616
- }
2617
-
2618
- /**
2619
- * Handle keyboard submits when the autocomplete option is enabled.
2620
- *
2621
- * If we don't do this, then the search will break the second time
2622
- * the user makes a search, selects the item with the keyboard
2623
- * and submits it with the enter key.
2624
- *
2625
- * @since 2.2.20
2626
- * @returns {void}
2627
- */
2628
- function keyboardAutoCompleteSubmit() {
2629
- $( "#wpsl-search-input" ).keypress( function( e ) {
2630
-
2631
- if ( e.which == 13 ) {
2632
- resetSearchResults();
2633
- codeAddress( infoWindow );
2634
-
2635
- return false;
2636
- }
2637
- });
2638
- }
2639
-
2640
- /**
2641
- * Reset all elements before a search is made.
2642
- *
2643
- * @since 2.2.20
2644
- * @returns {void}
2645
- */
2646
- function resetSearchResults() {
2647
- var keepStartMarker = false;
2648
-
2649
- $( "#wpsl-result-list ul" ).empty();
2650
- $( "#wpsl-stores" ).show();
2651
- $( ".wpsl-direction-before, .wpsl-direction-after" ).remove();
2652
- $( "#wpsl-direction-details" ).hide();
2653
-
2654
- resetMap = false;
2655
-
2656
- // Force the open InfoBox info window to close.
2657
- closeInfoBoxWindow();
2658
-
2659
- deleteOverlays( keepStartMarker );
2660
- deleteStartMarker();
2661
- }
2662
-
1
+ var wpsl = wpsl || {};
2
+
3
+ wpsl.gmaps = {};
4
+
5
+ /**
6
+ * This is only used to init the map after the
7
+ * user agreed to load Google Maps in combination
8
+ * with the Borlabs Cookie plugin.
9
+ *
10
+ * @since 2.2.22
11
+ * @returns {void}
12
+ */
13
+ function initWpslMap() {
14
+ var mapsLoaded;
15
+
16
+ mapsLoaded = setInterval( function() {
17
+ if ( typeof google === 'object' && typeof google.maps === 'object' ) {
18
+ clearInterval( mapsLoaded );
19
+
20
+ jQuery( ".wpsl-gmap-canvas" ).each( function( mapIndex ) {
21
+ var mapId = jQuery( this ).attr( "id" );
22
+
23
+ wpsl.gmaps.init( mapId, mapIndex );
24
+ });
25
+ }
26
+ }, 500 );
27
+ }
28
+
29
+ jQuery( document ).ready( function( $ ) {
30
+ var geocoder, map, directionsDisplay, directionsService, autoCompleteLatLng,
31
+ activeWindowMarkerId, infoWindow, markerClusterer, startMarkerData, startAddress,
32
+ openInfoWindow = [],
33
+ markersArray = [],
34
+ mapsArray = [],
35
+ markerSettings = {},
36
+ directionMarkerPosition = {},
37
+ mapDefaults = {},
38
+ resetMap = false,
39
+ streetViewAvailable = false,
40
+ autoLoad = ( typeof wpslSettings !== "undefined" ) ? wpslSettings.autoLoad : "",
41
+ userGeolocation = {},
42
+ statistics = {
43
+ enabled: ( typeof wpslSettings.collectStatistics !== "undefined" ) ? true : false,
44
+ addressComponents: ''
45
+ };
46
+
47
+ /**
48
+ * Set the underscore template settings.
49
+ *
50
+ * Defining them here prevents other plugins
51
+ * that also use underscore / backbone, and defined a
52
+ * different _.templateSettings from breaking the
53
+ * rendering of the store locator template.
54
+ *
55
+ * @link http://underscorejs.org/#template
56
+ * @requires underscore.js
57
+ * @since 2.0.0
58
+ */
59
+ _.templateSettings = {
60
+ evaluate: /\<\%(.+?)\%\>/g,
61
+ interpolate: /\<\%=(.+?)\%\>/g,
62
+ escape: /\<\%-(.+?)\%\>/g
63
+ };
64
+
65
+ /**
66
+ * Initialize Google Maps with the correct settings.
67
+ *
68
+ * @since 1.0.0
69
+ * @param {string} mapId The id of the map div
70
+ * @param {number} mapIndex Number of the map
71
+ * @returns {void}
72
+ */
73
+ wpsl.gmaps.init = function( mapId, mapIndex ) {
74
+ var mapOptions, mapDetails, settings, infoWindow, latLng,
75
+ bounds, mapData, zoomLevel,
76
+ defaultZoomLevel = Number( wpslSettings.zoomLevel ),
77
+ maxZoom = Number( wpslSettings.autoZoomLevel );
78
+
79
+ // Get the settings that belongs to the current map.
80
+ settings = getMapSettings( mapIndex );
81
+
82
+ /*
83
+ * This is the value from either the settings page,
84
+ * or the zoom level set through the shortcode.
85
+ */
86
+ zoomLevel = Number( settings.zoomLevel );
87
+
88
+ /*
89
+ * If they are not equal, then the zoom value is set through the shortcode.
90
+ * If this is the case, then we use that as the max zoom level.
91
+ */
92
+ if ( zoomLevel !== defaultZoomLevel ) {
93
+ maxZoom = zoomLevel;
94
+ }
95
+
96
+ // Create a new infoWindow, either with the infobox libray or use the default one.
97
+ infoWindow = newInfoWindow();
98
+
99
+ geocoder = new google.maps.Geocoder();
100
+ directionsDisplay = new google.maps.DirectionsRenderer();
101
+ directionsService = new google.maps.DirectionsService();
102
+
103
+ // Set the map options.
104
+ mapOptions = {
105
+ zoom: zoomLevel,
106
+ center: settings.startLatLng,
107
+ mapTypeId: google.maps.MapTypeId[ settings.mapType.toUpperCase() ],
108
+ mapTypeControl: Number( settings.mapTypeControl ) ? true : false,
109
+ streetViewControl: Number( settings.streetView ) ? true : false,
110
+ gestureHandling: settings.gestureHandling,
111
+ zoomControlOptions: {
112
+ position: google.maps.ControlPosition[ settings.controlPosition.toUpperCase() + '_TOP' ]
113
+ }
114
+ };
115
+
116
+ /**
117
+ * When the gestureHandling is set to cooperative and the scrollWheel
118
+ * options is also set, then the gestureHandling value is ingored.
119
+ *
120
+ * To fix this we only include the scrollWheel options when 'cooperative' isn't used.
121
+ */
122
+ if ( settings.gestureHandling !== 'cooperative' ) {
123
+ mapOptions.scrollwheel = Number( settings.scrollWheel ) ? true : false;
124
+ }
125
+
126
+ // Get the correct marker path & properties.
127
+ markerSettings = getMarkerSettings();
128
+
129
+ map = new google.maps.Map( document.getElementById( mapId ), mapOptions );
130
+
131
+ // Check if we need to apply a map style.
132
+ maybeApplyMapStyle( settings.mapStyle );
133
+
134
+ if ( ( typeof window[ "wpslMap_" + mapIndex ] !== "undefined" ) && ( typeof window[ "wpslMap_" + mapIndex ].locations !== "undefined" ) ) {
135
+ bounds = new google.maps.LatLngBounds(),
136
+ mapData = window[ "wpslMap_" + mapIndex ].locations;
137
+
138
+ // Loop over the map data, create the infowindow object and add each marker.
139
+ $.each( mapData, function( index ) {
140
+ latLng = new google.maps.LatLng( mapData[index].lat, mapData[index].lng );
141
+ addMarker( latLng, mapData[index].id, mapData[index], false, infoWindow );
142
+ bounds.extend( latLng );
143
+ });
144
+
145
+ // If we have more then one location on the map, then make sure to not zoom to far.
146
+ if ( mapData.length > 1 ) {
147
+ // Make sure we don't zoom to far when fitBounds runs.
148
+ attachBoundsChangedListener( map, maxZoom );
149
+
150
+ // Make all the markers fit on the map.
151
+ map.fitBounds( bounds );
152
+ }
153
+
154
+ /*
155
+ * If we need to apply the fix for the map showing up grey because
156
+ * it's used in a tabbed nav multiple times, then collect the active maps.
157
+ *
158
+ * See the fixGreyTabMap function.
159
+ */
160
+ if ( _.isArray( wpslSettings.mapTabAnchor ) ) {
161
+ mapDetails = {
162
+ map: map,
163
+ bounds: bounds,
164
+ maxZoom: maxZoom
165
+ };
166
+
167
+ mapsArray.push( mapDetails );
168
+ }
169
+ }
170
+
171
+ // Only run this part if the store locator exist and we don't just have a basic map.
172
+ if ( $( "#wpsl-gmap" ).length ) {
173
+
174
+ if ( wpslSettings.autoComplete == 1 ) {
175
+ activateAutocomplete();
176
+ }
177
+
178
+ /*
179
+ * Not the most optimal solution, but we check the useragent if we should enable the styled dropdowns.
180
+ *
181
+ * We do this because several people have reported issues with the styled dropdowns on
182
+ * iOS and Android devices. So on mobile devices the dropdowns will be styled according
183
+ * to the browser styles on that device.
184
+ */
185
+ if ( !checkMobileUserAgent() && $( ".wpsl-dropdown" ).length && wpslSettings.enableStyledDropdowns == 1 ) {
186
+ createDropdowns();
187
+ } else {
188
+ $( "#wpsl-search-wrap select" ).show();
189
+
190
+ if ( checkMobileUserAgent() ) {
191
+ $( "#wpsl-wrap" ).addClass( "wpsl-mobile" );
192
+ } else {
193
+ $( "#wpsl-wrap" ).addClass( "wpsl-default-filters" );
194
+ }
195
+ }
196
+
197
+ // Check if we need to autolocate the user, or autoload the store locations.
198
+ if ( !$( ".wpsl-search" ).hasClass( "wpsl-widget" ) ) {
199
+ if ( wpslSettings.autoLocate == 1 ) {
200
+ checkGeolocation( settings.startLatLng, infoWindow );
201
+ } else if ( wpslSettings.autoLoad == 1 ) {
202
+ showStores( settings.startLatLng, infoWindow );
203
+ }
204
+ }
205
+
206
+ // Move the mousecursor to the store search field if the focus option is enabled.
207
+ if ( wpslSettings.mouseFocus == 1 && !checkMobileUserAgent() ) {
208
+ $( "#wpsl-search-input" ).focus();
209
+ }
210
+
211
+ // Bind store search button.
212
+ searchLocationBtn( infoWindow );
213
+
214
+ // Add the 'reload' and 'find location' icon to the map.
215
+ mapControlIcons( settings, map, infoWindow );
216
+
217
+ // Check if the user submitted a search through a search widget.
218
+ checkWidgetSubmit();
219
+ }
220
+
221
+ // Bind the zoom_changed listener.
222
+ zoomChangedListener();
223
+ };
224
+
225
+ // Only continue if a map is present.
226
+ if ( $( ".wpsl-gmap-canvas" ).length ) {
227
+ $( "<img />" ).attr( "src", wpslSettings.url + "img/ajax-loader.gif" );
228
+
229
+ /*
230
+ * The [wpsl] shortcode can only exist once on a page,
231
+ * but the [wpsl_map] shortcode can exist multiple times.
232
+ *
233
+ * So to make sure we init all the maps we loop over them.
234
+ */
235
+ $( ".wpsl-gmap-canvas" ).each( function( mapIndex ) {
236
+ var mapId = $( this ).attr( "id" );
237
+
238
+ wpsl.gmaps.init( mapId, mapIndex );
239
+ });
240
+
241
+ /*
242
+ * Check if we are dealing with a map that's placed in a tab,
243
+ * if so run a fix to prevent the map from showing up grey.
244
+ */
245
+ maybeApplyTabFix();
246
+ }
247
+
248
+
249
+ /**
250
+ * Activate the autocomplete for the store search.
251
+ *
252
+ * @since 2.2.0
253
+ * @link https://developers.google.com/maps/documentation/javascript/places-autocomplete
254
+ * @returns {void}
255
+ */
256
+ function activateAutocomplete() {
257
+ var input, autocomplete, place,
258
+ options = {};
259
+
260
+ // Handle autocomplete queries submitted by the user using the 'enter' key.
261
+ keyboardAutoCompleteSubmit();
262
+
263
+ /**
264
+ * Check if we need to set the geocode component restrictions.
265
+ * This is automatically included when a fixed map region is
266
+ * selected on the WPSL settings page.
267
+ */
268
+ if ( typeof wpslSettings.geocodeComponents !== "undefined" && !$.isEmptyObject( wpslSettings.geocodeComponents ) ) {
269
+ options.componentRestrictions = wpslSettings.geocodeComponents;
270
+
271
+ /**
272
+ * If the postalCode is included in the autocomplete together with '(regions)' ( which is included ),
273
+ * then it will break it. So we have to remove it.
274
+ */
275
+ options.componentRestrictions = _.omit( options.componentRestrictions, 'postalCode' );
276
+ }
277
+
278
+ // Check if we need to restrict the autocomplete data.
279
+ if ( typeof wpslSettings.autoComp