├── .gitignore ├── README.md ├── app ├── API │ ├── AllLocationsShortcode.php │ ├── FormShortcode.php │ ├── FormWidget.php │ └── SingleLocationShortcode.php ├── Bootstrap.php ├── Dependencies │ ├── AdminDependencies.php │ ├── DependencyBase.php │ ├── DependencyFactory.php │ └── PublicDependencies.php ├── Events │ ├── RegisterAdminEvents.php │ └── RegisterPublicEvents.php ├── Form │ └── SearchForm.php ├── Helpers.php ├── Integrations │ ├── ACF │ │ └── AdvancedCustomFields.php │ └── IntegrationFactory.php ├── Listeners │ ├── AJAXAdminListenerBase.php │ ├── AJAXListenerBase.php │ ├── APIAllLocations.php │ ├── APILocationSearch.php │ ├── GetMetaFieldsForPostType.php │ ├── HistoryClear.php │ ├── HistorySearch.php │ ├── LocationSearch.php │ ├── QuickEdit.php │ └── ResetPostTypeSettings.php ├── Migrations │ ├── Activation.php │ ├── CreateTables.php │ ├── DefaultOptions.php │ ├── MapStyles.php │ ├── RequiredOptions.php │ ├── map_options │ │ ├── autocomplete-options.php │ │ ├── cluster-renderer.php │ │ └── map-options.php │ └── map_styles │ │ ├── 1-subtle_grayscale.php │ │ ├── 2-blue_water.php │ │ ├── 3-apple_maps_esque.php │ │ ├── 4-old_map.php │ │ ├── 5-shades_of_gray.php │ │ └── 6-night_rider.php ├── Post │ ├── AdminMap.php │ ├── QuickEditLink.php │ └── Singular.php ├── Repositories │ ├── FieldRepository.php │ ├── ImportRepository.php │ ├── MapStyles.php │ ├── PostRepository.php │ ├── SearchHistoryRepository.php │ └── SettingsRepository.php ├── Services │ ├── CSVDownload │ │ ├── ExportCsvDownload.php │ │ └── HistoryCsvDownload.php │ ├── ExportTemplates │ │ ├── TemplateCreator.php │ │ ├── TemplateDestroyer.php │ │ └── TemplateRequest.php │ ├── Helpers │ │ ├── RemoteAddress.php │ │ ├── StateListing.php │ │ └── States │ │ │ ├── Ca.php │ │ │ ├── Mx.php │ │ │ ├── StateInterface.php │ │ │ └── Us.php │ ├── Import │ │ ├── CSV │ │ │ ├── CSVImport.php │ │ │ └── Row.php │ │ ├── Events │ │ │ └── RegisterImportEvents.php │ │ ├── Exceptions │ │ │ ├── GoogleAPIException.php │ │ │ ├── GoogleQueryLimitException.php │ │ │ ├── GoogleRequestDeniedException.php │ │ │ └── ImportCompleteException.php │ │ ├── GoogleMapGeocode.php │ │ ├── Listeners │ │ │ ├── ApiTest.php │ │ │ ├── ColumnMapper.php │ │ │ ├── FileUploader.php │ │ │ ├── FinishImport.php │ │ │ ├── GetCSVRow.php │ │ │ ├── Import.php │ │ │ ├── ImportAJAXListenerBase.php │ │ │ ├── ImportListenerBase.php │ │ │ ├── ImportTemplateRemove.php │ │ │ ├── ImportTemplateSave.php │ │ │ ├── RedoImport.php │ │ │ ├── RemoveImport.php │ │ │ └── UndoImport.php │ │ └── PostImporter.php │ ├── LocationSearch │ │ ├── JsonResponseFactory.php │ │ ├── LocationResultPresenter.php │ │ ├── LocationSearch.php │ │ ├── LocationSearchValidator.php │ │ ├── ResultsInfoPresenter.php │ │ └── SaveSearch.php │ └── Validation │ │ └── NonceValidator.php ├── Settings │ └── Settings.php ├── SimpleLocator.php └── WPData │ ├── Fields │ ├── FieldBase.php │ └── FormFields.php │ ├── MetaFields.php │ ├── PostTypes.php │ ├── RegisterApiEndpoints.php │ └── UploadFilter.php ├── assets ├── banner-772x250.png ├── css │ ├── simple-locator-admin.css │ └── simple-locator.css ├── csv_template.csv ├── fonts │ ├── simplelocator.eot │ ├── simplelocator.svg │ ├── simplelocator.ttf │ └── simplelocator.woff ├── icon-128x128.png ├── images │ ├── datepicker-arrow-next.png │ ├── datepicker-arrow-prev.png │ ├── loading-settings.gif │ ├── loading-spinner.svg │ ├── loading-white.gif │ ├── loading.gif │ ├── map-marker-blue.svg │ ├── map-marker.svg │ ├── map-pin-blue.png │ ├── map-pin-red.png │ ├── screenshot-2.png │ └── screenshot.png ├── js │ ├── markerclusterer.min.js │ ├── simple-locator-admin-maps.js │ ├── simple-locator-admin-search-history.js │ ├── simple-locator-admin.js │ ├── simple-locator-admin.min.js │ ├── simple-locator-non-ajax-results.js │ ├── simple-locator.js │ ├── simple-locator.min.js │ └── source │ │ ├── admin │ │ ├── simple-locator-admin.default-map.js │ │ ├── simple-locator-admin.export-templates.js │ │ ├── simple-locator-admin.factory.js │ │ ├── simple-locator-admin.general-settings.js │ │ ├── simple-locator-admin.import-column-map.js │ │ ├── simple-locator-admin.import-import.js │ │ ├── simple-locator-admin.import-test.js │ │ ├── simple-locator-admin.import-upload.js │ │ ├── simple-locator-admin.listing-map.js │ │ ├── simple-locator-admin.map-display.js │ │ ├── simple-locator-admin.modals.js │ │ ├── simple-locator-admin.post-edit.js │ │ ├── simple-locator-admin.post-type.js │ │ ├── simple-locator-admin.quick-edit.js │ │ ├── simple-locator-admin.results-display.js │ │ └── simple-locator-admin.search-history.js │ │ ├── simple-locator.all-locations.js │ │ ├── simple-locator.default-map.js │ │ ├── simple-locator.errors.js │ │ ├── simple-locator.factory.js │ │ ├── simple-locator.form.js │ │ ├── simple-locator.functions-deprecated.js │ │ ├── simple-locator.geocoder.js │ │ ├── simple-locator.geolocation.js │ │ ├── simple-locator.infowindow-open.js │ │ ├── simple-locator.places-autocomplete.js │ │ ├── simple-locator.results-list.js │ │ ├── simple-locator.results-map-non-ajax.js │ │ ├── simple-locator.results-map.js │ │ ├── simple-locator.single-location.js │ │ └── simple-locator.utilities.js ├── screenshot-1.png ├── screenshot-10.png ├── screenshot-2.png ├── screenshot-3.png ├── screenshot-4.png ├── screenshot-5.png ├── screenshot-6.png ├── screenshot-7.png ├── screenshot-8.png ├── screenshot-9.png └── scss │ ├── _admin-loading.scss │ ├── _admin-location-map.scss │ ├── _admin-quick-edit.scss │ ├── _alerts.scss │ ├── _buttons.scss │ ├── _datepicker.scss │ ├── _form-results.scss │ ├── _form.scss │ ├── _icons.scss │ ├── _maps.scss │ ├── _meta-fields.scss │ ├── _mixins.scss │ ├── _modals.scss │ ├── _pagination.scss │ ├── _settings-default-map.scss │ ├── _settings-general.scss │ ├── _settings-import-previous.scss │ ├── _settings-import-templates.scss │ ├── _settings-import.scss │ ├── _settings-map-options.scss │ ├── _settings-posttypes.scss │ ├── _settings-results-fields.scss │ ├── _settings-search-history.scss │ ├── _variables.scss │ ├── _widget.scss │ ├── simple-locator-admin.scss │ └── simple-locator.scss ├── composer.json ├── gulpfile.js ├── languages ├── index.php ├── pt_PT.mo ├── pt_PT.po ├── wpsimplelocator-de_DE.mo ├── wpsimplelocator-de_DE.po ├── wpsimplelocator-fr_FR.mo ├── wpsimplelocator-fr_FR.po ├── wpsimplelocator-nl_NL.mo ├── wpsimplelocator-nl_NL.po ├── wpsimplelocator.mo └── wpsimplelocator.pot ├── package.json ├── readme.txt ├── simplelocator.php ├── templates └── search-form.php └── views ├── post-meta ├── error-modal.php └── location-meta.php ├── search-results.php ├── settings ├── defaultmap.php ├── export-posts.php ├── general.php ├── import-0.php ├── import-1.php ├── import-2.php ├── import-3.php ├── import-errors.php ├── map.php ├── posttype.php ├── result-field-custom-selection.php ├── resultsfields.php ├── search-history.php └── settings.php ├── singular-post.php └── widget-options.php /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | assets/js/wpsl-locator-ck.js 3 | /languages/wpsimplelocator-es_ES.mo 4 | /languages/wpsimplelocator-es_ES.po 5 | /vendor/* 6 | composer.lock -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Locator for WordPress 2 | 3 | 4 | ## Overview 5 | 6 | Simple locator is ideal for developers who are looking for a way to implement location search for any post type within WordPress. Simple locator is different in that it allows the selection of any existing custom fields for use as the geocoded data sources. If there are no existing data sources, the plugin ships with a "location" post type with fields ready to go. 7 | 8 | Best suited for WordPress developers looking for a customizable way of adding location search. 9 | 10 | Learn more at [locatewp.com](http://locatewp.com) and download from the [WordPress Plugin Repository](https://wordpress.org/plugins/simple-locator/). 11 | 12 | #### Requirements 13 | 14 | Simple Locator requires WordPress version 3.8+ and PHP version 5.4+. Simple Locator version 1.1.5 (no longer maintained) is compatible with PHP version 5.3.2+. 15 | 16 | 17 | #### Demo 18 | [View the Demo](http://locatewp.com) 19 | 20 | ![Screenshot](https://raw.githubusercontent.com/kylephillips/wp-simple-locator/master/assets/images/screenshot-2.png) 21 | 22 | 23 | #### Installation - from the WordPress Repository 24 | 1. Upload simple-locator to the wp-content/plugins/ directory 25 | 2. Activate the plugin through the Plugins menu in WordPress 26 | 3. Click on the Locations Menu item to begin adding locations. 27 | 4. To change the post type, geocoded fields, or map styles, visit Settings > Simple Locator 28 | 29 | #### Installation - from the GitHub Project 30 | 1. cd into the wp-content/plugins directory 31 | 2. Clone the project: ```git clone https://github.com/kylephillips/wp-simple-locator.git simple-locator``` 32 | 3. ```cd``` into the ```simple-locator``` directory and run ```composer install``` to download & install the plugin dependencies. [More on composer](https://getcomposer.org) 33 | 4. Activate the plugin through the Plugins menu in WordPress 34 | 35 | 36 | #### Usage 37 | To display the locator, include the shortcode ```[wp_simple_locator]```. See available options and customization on the [plugin website](http://locatewp.com) 38 | 39 | 40 | #### Filters 41 | Full form output and query customization is available through the plugin filters. See the [plugin website](http://locatewp.com) for full examples and documentation. By using the various filters, it is possible to add a fully-customized search form, using any number of custom criteria. 42 | 43 | 44 | ```simple_locator_form_filter($output, $distances, $taxonomies, $widget)``` 45 | Customize the form HTML output. Custom fields may be added to the form, and accessed via the post field filter and query filters. 46 | 47 | 48 | ```simple_locator_result($output, $result, $count)``` 49 | Customize the HTML output of each result in the list view. 50 | 51 | 52 | ```simple_locator_infowindow($infowindow, $result)``` 53 | Customize the HTML output for the Google Maps Info Window for each result. 54 | 55 | 56 | ```simple_locator_post_fields()``` 57 | Include custom fields in the form POST data, for use in custom query filters. Fields should be passed as an array of field names that correspond to the custom input names added in the ```simple_locator_form_filter``` filter. 58 | 59 | 60 | ```simple_locator_sql_select($sql)``` 61 | Customize the SELECT statement in the search query. 62 | 63 | 64 | ```simple_locator_sql_join($sql)``` 65 | Customize JOINS in the search query. 66 | 67 | ```simple_locator_sql_where($sql)``` 68 | Customize the WHERE clauses in the search query. 69 | -------------------------------------------------------------------------------- /app/API/AllLocationsShortcode.php: -------------------------------------------------------------------------------- 1 | options = shortcode_atts([ 30 | 'perpage' => '', // If the list is included pagination is available 31 | 'mapheight' => '', 32 | 'noresultstext' => __('No locations found.', 'simple-locator'), 33 | 'includelist' => '', // Set to true to display the list beneath the map 34 | 'showall' => '', // Text to display in list header 35 | 'orderby' => '', // Defaults to default distance as set in plugin settings. Passed to wp_query as orderby parameter (ex: post_title) 36 | 'order' => 'asc', // If orderby set, may be 'asc' or 'desc' 37 | 'ids' => '' 38 | ], $options); 39 | $this->setTaxonomyArgs($options); 40 | } 41 | 42 | /** 43 | * Enqueue the single view script & add localized data 44 | */ 45 | private function enqueueScripts() 46 | { 47 | wp_enqueue_script('google-maps'); 48 | wp_enqueue_script('simple-locator'); 49 | } 50 | 51 | /** 52 | * Setup Taxonomy Arguments 53 | */ 54 | private function setTaxonomyArgs($options) 55 | { 56 | if ( !is_array($options) ) return; 57 | $tax_args = []; 58 | $taxonomies = get_taxonomies(); 59 | foreach ( $taxonomies as $tax ){ 60 | if ( array_key_exists($tax, $options) ) $tax_args[$tax] = explode(',', $options[$tax]); 61 | } 62 | if ( !empty($tax_args) ) $this->args['taxfilter'] = $tax_args; 63 | } 64 | 65 | /** 66 | * The View 67 | */ 68 | public function renderView($options) 69 | { 70 | $this->setOptions($options); 71 | $this->enqueueScripts(); 72 | $output = '
options['showall'] !== '' ) $output .= '="' . $this->options['showall'] . '"'; 74 | $output .= ' class="wpsl-map"'; 75 | 76 | // Add data-attributes to handle taxonomy arguments 77 | if ( isset($this->args['taxfilter']) ) { 78 | foreach ( $this->args['taxfilter'] as $taxonomy => $terms ) { 79 | $output .= ' data-taxfilter-' . $taxonomy . '="' . implode(',', $terms) . '"'; 80 | } 81 | } 82 | 83 | if ( $this->options['orderby'] !== '' ) $output .= ' data-orderby="' . esc_attr($this->options['orderby']) . '"'; 84 | $output .= ( $this->options['order'] == 'asc' ) ? ' data-order="asc"' : ' data-order="desc"'; 85 | if ( $this->options['ids'] !== '' ) $output .= ' data-post-ids="' . esc_attr($this->options['ids']) . '"'; 86 | if ( $this->options['mapheight'] !== '' ) $output .= ' style="height:' . intval($this->options['mapheight']) . 'px;"'; 87 | if ( $this->options['noresultstext'] !== '' ) $output .= ' data-no-results-text="' . esc_attr($this->options['noresultstext']) . '"'; 88 | if ( $this->options['includelist'] == 'true' ) $output .= ' data-include-listing="true"'; 89 | if ( $this->options['perpage'] !== '' ) $output .= ' data-per-page="' . intval($this->options['perpage']) . '"'; 90 | 91 | $output .= '>
'; 92 | return $output; 93 | } 94 | } -------------------------------------------------------------------------------- /app/API/SingleLocationShortcode.php: -------------------------------------------------------------------------------- 1 | post_repo = new PostRepository; 39 | $this->settings_repo = new SettingsRepository; 40 | add_shortcode('wp_simple_locator_map', [$this, 'renderView']); 41 | add_shortcode('simple_locator_single', [$this, 'renderView']); 42 | } 43 | 44 | /** 45 | * Shortcode Options 46 | */ 47 | private function setOptions($options) 48 | { 49 | $this->options = shortcode_atts([ 50 | 'post' => get_the_id(), 51 | 'additionalfields' => 'hide' 52 | ], $options); 53 | } 54 | 55 | /** 56 | * Set the location data for use in map 57 | */ 58 | private function setLocationData() 59 | { 60 | $this->location_data = $this->post_repo->getLocationData($this->options['post']); 61 | } 62 | 63 | /** 64 | * Enqueue the single view script & add localized data 65 | */ 66 | private function enqueueScripts() 67 | { 68 | if ( (isset($this->location_data['latitude'])) && (isset($this->location_data['longitude'])) ){ 69 | wp_enqueue_script('google-maps'); 70 | wp_enqueue_script('simple-locator'); 71 | } 72 | } 73 | 74 | /** 75 | * The View 76 | */ 77 | public function renderView($options) 78 | { 79 | $this->setOptions($options); 80 | $this->setLocationData(); 81 | $this->enqueueScripts(); 82 | 83 | include ( \SimpleLocator\Helpers::view('singular-post') ); 84 | return $out; 85 | } 86 | } -------------------------------------------------------------------------------- /app/Bootstrap.php: -------------------------------------------------------------------------------- 1 | init(); 14 | $this->registerPublicEvents(); 15 | $this->registerAdminEvents(); 16 | add_filter( 'plugin_action_links_' . 'simple-locator/simplelocator.php', [$this, 'settingsLink'] ); 17 | add_action( 'plugins_loaded', [$this, 'addLocalization'] ); 18 | } 19 | 20 | /** 21 | * Initialize 22 | */ 23 | public function init() 24 | { 25 | new \SimpleLocator\Migrations\Activation; 26 | new \SimpleLocator\Dependencies\DependencyFactory; 27 | new \SimpleLocator\WPData\PostTypes; 28 | new \SimpleLocator\WPData\MetaFields; 29 | new \SimpleLocator\WPData\UploadFilter; 30 | new \SimpleLocator\WPData\RegisterApiEndpoints; 31 | new \SimpleLocator\Settings\Settings; 32 | new \SimpleLocator\API\FormShortcode; 33 | new \SimpleLocator\API\SingleLocationShortcode; 34 | new \SimpleLocator\API\AllLocationsShortcode; 35 | new \SimpleLocator\Post\Singular; 36 | new \SimpleLocator\Listeners\LocationSearch; 37 | new \SimpleLocator\Integrations\IntegrationFactory; 38 | new \SimpleLocator\Post\AdminMap; 39 | new \SimpleLocator\Post\QuickEditLink; 40 | add_action( 'widgets_init', [$this, 'formWidget']); 41 | } 42 | 43 | /** 44 | * Register Public Application Events 45 | */ 46 | private function registerPublicEvents() 47 | { 48 | new \SimpleLocator\Events\RegisterPublicEvents; 49 | } 50 | 51 | /** 52 | * Register Admin Application Events 53 | */ 54 | private function registerAdminEvents() 55 | { 56 | if ( !is_admin() ) return; 57 | new \SimpleLocator\Events\RegisterAdminEvents; 58 | new \SimpleLocator\Services\Import\Events\RegisterImportEvents; 59 | } 60 | 61 | /** 62 | * Add a link to the settings on the plugin page 63 | */ 64 | public function settingsLink($links) 65 | { 66 | $settings_link = '' . __('Settings', 'simple-locator') . ''; 67 | $help_link = '' . __('FAQ', 'simple-locator') . ''; 68 | array_unshift($links, $help_link); 69 | array_unshift($links, $settings_link); 70 | return $links; 71 | } 72 | 73 | /** 74 | * Register the Widget 75 | */ 76 | public function formWidget() 77 | { 78 | register_widget( 'SimpleLocator\API\FormWidget' ); 79 | } 80 | 81 | /** 82 | * Localization Domain 83 | */ 84 | public function addLocalization() 85 | { 86 | load_plugin_textdomain( 87 | 'simple-locator', 88 | false, 89 | dirname( dirname( plugin_basename( __FILE__ ) ) ) . '/languages' ); 90 | } 91 | 92 | } -------------------------------------------------------------------------------- /app/Dependencies/DependencyBase.php: -------------------------------------------------------------------------------- 1 | plugin_dir = \SimpleLocator\Helpers::plugin_url(); 44 | $this->styles_repo = new MapStyles; 45 | $this->settings_repo = new SettingsRepository(); 46 | $this->setPostType(); 47 | } 48 | 49 | /** 50 | * Set the Post Type from Options 51 | * @since 1.0.6 52 | */ 53 | protected function setPostType() 54 | { 55 | $labels = get_option('wpsl_posttype_labels'); 56 | $this->post_type = ( isset($labels['name']) ) ? $labels['name'] : 'location'; 57 | } 58 | 59 | /** 60 | * Set the Plugin Version for dependency versioning 61 | */ 62 | protected function setVersion() 63 | { 64 | global $simple_locator_version; 65 | $this->version = $simple_locator_version; 66 | } 67 | 68 | /** 69 | * Register the Google Maps Script 70 | * Only Enqueue when needed 71 | */ 72 | protected function addGoogleMaps() 73 | { 74 | if ( !is_admin() && !$this->settings_repo->outputMapsApi() ) return; 75 | if ( is_admin() && !$this->settings_repo->outputGMapsAdmin() ) return; 76 | $maps_url = 'https://maps.google.com/maps/api/js?'; 77 | $maps_url .= ( get_option('wpsl_google_api_key') ) ? 'key=' . get_option('wpsl_google_api_key') . '&' : ''; 78 | $maps_url .= '&libraries=places'; 79 | wp_register_script( 80 | 'google-maps', 81 | $maps_url 82 | ); 83 | } 84 | 85 | /** 86 | * Get Map Style Data 87 | * for use in settings page display of google maps 88 | */ 89 | protected function mapStyleData() 90 | { 91 | return $this->styles_repo->getAllStyles(); 92 | } 93 | } -------------------------------------------------------------------------------- /app/Dependencies/DependencyFactory.php: -------------------------------------------------------------------------------- 1 | $value ){ 29 | ${$var} = $value; 30 | } 31 | } 32 | $template = locate_template('simple-locator/' . $file . '.php'); 33 | $template = ( $template == '' ) ? dirname(dirname(__FILE__)) . '/templates/' . $file . '.php' : $template; 34 | ob_start(); 35 | include ( $template ); 36 | $output = ob_get_contents(); 37 | ob_end_clean(); 38 | 39 | return $output; 40 | } 41 | 42 | /** 43 | * Check URL Format 44 | */ 45 | public static function checkURL($url) 46 | { 47 | $parsed = parse_url($url); 48 | if (empty($parsed['scheme'])) $url = 'http://' . ltrim($url, '/'); 49 | return $url; 50 | } 51 | 52 | /** 53 | * Find and replace a url given a string 54 | * Searches string for hrefs, checks urls, adds http if not present 55 | */ 56 | public static function replaceURLs($string) 57 | { 58 | $urls = preg_match_all('/href=('|"|\'|")(.+?)('|"|\'|")/', $string, $matches); 59 | 60 | foreach($matches[2] as $key=>$m){ 61 | $matches[2][$key] = 'href="' . self::checkURL($m) . '"'; 62 | } 63 | foreach($matches[0] as $key=>$m){ 64 | $string = str_replace($m, $matches[2][$key], $string); 65 | } 66 | 67 | return $string; 68 | } 69 | 70 | /** 71 | * Get Post Excerpt By ID 72 | */ 73 | public static function excerptByID($id) 74 | { 75 | $post = get_post($id); 76 | if( has_excerpt($post->ID) ){ 77 | $the_excerpt = $post->post_excerpt; 78 | return apply_filters('the_content', $the_excerpt); 79 | } 80 | 81 | $the_excerpt = $post->post_content; 82 | $the_excerpt = preg_split('/\b/', $the_excerpt, 20 * 2+1); 83 | $excerpt_waste = array_pop($the_excerpt); 84 | $the_excerpt = implode($the_excerpt) . '...'; 85 | $the_excerpt = substr($the_excerpt, 0, strripos($the_excerpt, " ")); 86 | 87 | return apply_filters('the_content', $the_excerpt); 88 | } 89 | 90 | /** 91 | * Get the current URL 92 | */ 93 | public static function currentUrl() 94 | { 95 | global $wp; 96 | return home_url(add_query_arg(array(), $wp->request)); 97 | } 98 | } -------------------------------------------------------------------------------- /app/Integrations/ACF/AdvancedCustomFields.php: -------------------------------------------------------------------------------- 1 | settings_repo = new SettingsRepository; 18 | add_action( 'acf/save_post', [$this, 'saveMapFieldCoordinates'], 10); 19 | } 20 | 21 | /** 22 | * Save map field coordinates to the selected latitude/longitude fields 23 | * @param int $post_id 24 | * @see https://wordpress.org/support/topic/possible-to-search-distance-between-posts-user 25 | */ 26 | public function saveMapFieldCoordinates($post_id) 27 | { 28 | if ( empty( $_POST['acf']) ) return; 29 | $option = get_option('wpsl_acf_map_field'); 30 | if ( !$option ) return; 31 | if ( !isset($_POST['acf'][$option]) ) return; 32 | $map_field = get_field($option, $post_id); 33 | if ( isset($map_field['lat']) && isset($map_field['lng']) ){ 34 | update_post_meta($post_id, $this->settings_repo->getGeoField('lat'), $map_field['lat']); 35 | update_post_meta($post_id, $this->settings_repo->getGeoField('lng'), $map_field['lng']); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /app/Integrations/IntegrationFactory.php: -------------------------------------------------------------------------------- 1 | build(); 11 | } 12 | 13 | /** 14 | * Build up the Integrations 15 | */ 16 | private function build() 17 | { 18 | new AdvancedCustomFields; 19 | } 20 | } -------------------------------------------------------------------------------- /app/Listeners/AJAXAdminListenerBase.php: -------------------------------------------------------------------------------- 1 | nonce_validator = new NonceValidator; 16 | $this->validateNonce(); 17 | } 18 | 19 | /** 20 | * Validate the Nonce 21 | */ 22 | protected function validateNonce() 23 | { 24 | try { 25 | $this->nonce_validator->validate($_POST['nonce'], 'wpsl_locator-locator-nonce'); 26 | } catch ( \Exception $e ){ 27 | return $this->error($e->getMessage()); 28 | } 29 | } 30 | 31 | /** 32 | * Send a Success Response 33 | */ 34 | protected function success($message) 35 | { 36 | return wp_send_json(['status'=>'success', 'message'=>$message]); 37 | die(); 38 | } 39 | 40 | /** 41 | * Send an Error Response 42 | */ 43 | protected function error($error) 44 | { 45 | return wp_send_json(['status'=>'error', 'message'=>$error]); 46 | die(); 47 | } 48 | } -------------------------------------------------------------------------------- /app/Listeners/AJAXListenerBase.php: -------------------------------------------------------------------------------- 1 | respond(['status' => 'success', 'message' => $message]); 25 | } 26 | 27 | /** 28 | * Send an Error Response 29 | */ 30 | protected function error($message) 31 | { 32 | $this->respond(['status' => 'error', 'message' => $message]); 33 | } 34 | } -------------------------------------------------------------------------------- /app/Listeners/APIAllLocations.php: -------------------------------------------------------------------------------- 1 | post_repo = new PostRepository; 29 | $this->request = $request; 30 | } 31 | 32 | /** 33 | * Get all locations 34 | */ 35 | public function getLocations() 36 | { 37 | $this->locations = $this->post_repo->allLocations($this->request); 38 | return $this->locations; 39 | } 40 | } -------------------------------------------------------------------------------- /app/Listeners/APILocationSearch.php: -------------------------------------------------------------------------------- 1 | request = $request; 43 | $this->location_search = new LocationSearch; 44 | $this->search_validator = new LocationSearchValidator; 45 | $this->response_factory = new JsonResponseFactory; 46 | $this->search_store = new SaveSearch; 47 | } 48 | 49 | /** 50 | * Perform the Search and get the results 51 | */ 52 | public function getResults() 53 | { 54 | $this->validate(); 55 | $this->location_search->search($this->request); 56 | $response = $this->response_factory->build( 57 | $this->location_search->getResults(), 58 | $this->location_search->getResultCount(), 59 | $this->location_search->getTotalResultCount(), 60 | $this->request 61 | ); 62 | $this->saveSearch(); 63 | return $response; 64 | } 65 | 66 | /** 67 | * Validate the Form Data 68 | */ 69 | private function validate() 70 | { 71 | try { 72 | $this->search_validator->validate($this->request); 73 | } catch ( \Exception $e ){ 74 | throw new \Exception($e->getMessage()); 75 | } 76 | } 77 | 78 | /** 79 | * Store the Search 80 | */ 81 | private function saveSearch() 82 | { 83 | if ( !get_option('wpsl_save_searches') ) return; 84 | if ( isset($this->request['allow_empty_address']) ) return; 85 | $this->search_store->save($this->request); 86 | } 87 | } -------------------------------------------------------------------------------- /app/Listeners/GetMetaFieldsForPostType.php: -------------------------------------------------------------------------------- 1 | field_repo = new FieldRepository; 32 | $this->setData(); 33 | $this->validateNonce(); 34 | $this->getFields(); 35 | } 36 | 37 | /** 38 | * Sanitize and set the user-submitted data 39 | */ 40 | private function setData() 41 | { 42 | $show_hidden = ( $_GET['show_hidden'] == 'true' ) ? true : false; 43 | $include_wpsl = ( $_GET['include_wpsl'] == 'true' ) ? true : false; 44 | $this->data = [ 45 | 'nonce' => sanitize_text_field($_GET['nonce']), 46 | 'post_type' => sanitize_text_field($_GET['post_type']), 47 | 'include_wpsl' => $include_wpsl, 48 | 'show_hidden' => $show_hidden 49 | ]; 50 | } 51 | 52 | /** 53 | * Validate Nonce 54 | */ 55 | private function validateNonce() 56 | { 57 | if ( ! wp_verify_nonce( $this->data['nonce'], 'wpsl_locator-locator-nonce' ) ){ 58 | $this->sendResponse(['status'=>'error', 'message' => 'Incorrect Form Field']); 59 | } 60 | } 61 | 62 | /** 63 | * Get the fields for the post type 64 | */ 65 | private function getFields() 66 | { 67 | $fields = $this->field_repo->displayFieldOptions($this->data['post_type'], $this->data['show_hidden'], $this->data['include_wpsl']); 68 | $taxonomies = get_object_taxonomies($this->data['post_type'], 'objects'); 69 | $response = ['status' => 'success', 'fields' => $fields, 'taxonomies' => $taxonomies]; 70 | $this->sendResponse($response); 71 | } 72 | 73 | /** 74 | * Send the Response 75 | * @param response array 76 | * @return JSON response 77 | */ 78 | private function sendResponse($response) 79 | { 80 | return wp_send_json($response); 81 | } 82 | } -------------------------------------------------------------------------------- /app/Listeners/HistoryClear.php: -------------------------------------------------------------------------------- 1 | url = sanitize_text_field($_POST['page']); 15 | $this->clearLog(); 16 | $this->redirect(); 17 | } 18 | 19 | /** 20 | * Clear the log 21 | */ 22 | private function clearLog() 23 | { 24 | global $wpdb; 25 | $table = $wpdb->prefix . 'simple_locator_history'; 26 | $wpdb->query("TRUNCATE TABLE $table"); 27 | } 28 | 29 | /** 30 | * Redirect to new URL 31 | */ 32 | private function redirect() 33 | { 34 | header('Location:' . $this->url); 35 | } 36 | } -------------------------------------------------------------------------------- /app/Listeners/HistorySearch.php: -------------------------------------------------------------------------------- 1 | setURL(); 15 | $this->redirect(); 16 | } 17 | 18 | /** 19 | * Build the URL to Redirect to 20 | */ 21 | private function setURL() 22 | { 23 | $this->url = sanitize_text_field($_POST['page']); 24 | $this->setSearchTerm(); 25 | } 26 | 27 | /** 28 | * Set Order parameters 29 | */ 30 | private function setSearchTerm() 31 | { 32 | if ( !isset($_POST['search_term']) || !isset($_POST['date_start']) || !isset($_POST['date_end']) ) return; 33 | $q = sanitize_text_field($_POST['search_term']); 34 | $date_start = strtotime(sanitize_text_field($_POST['date_start'])); 35 | $date_end = strtotime(sanitize_text_field($_POST['date_end'])); 36 | $per_page = ( intval(sanitize_text_field($_POST['per_page'])) ); 37 | $this->url .= '&q=' . $q; 38 | $this->url .= '&date_start=' . $date_start; 39 | $this->url .= '&date_end=' . $date_end; 40 | $this->url .= '&per_page=' . $per_page; 41 | $this->url .= '&p=1'; 42 | } 43 | 44 | /** 45 | * Redirect to new URL 46 | */ 47 | private function redirect() 48 | { 49 | header('Location:' . $this->url); 50 | } 51 | } -------------------------------------------------------------------------------- /app/Listeners/QuickEdit.php: -------------------------------------------------------------------------------- 1 | settings = new SettingsRepository; 16 | $this->validate(); 17 | $this->save(); 18 | } 19 | 20 | /** 21 | * Validate the Data 22 | */ 23 | private function validate() 24 | { 25 | if ( !isset($_GET['id']) || $_GET['id'] == '' ) return $this->error(__('A post ID was not provided.', 'simple-locator')); 26 | } 27 | 28 | /** 29 | * Save the Data 30 | */ 31 | private function save() 32 | { 33 | $latitude_field = $this->settings->getGeoField('lat'); 34 | $longitude_field = $this->settings->getGeoField('lng'); 35 | $fields = $_GET; 36 | $post_id = intval(sanitize_text_field($_GET['id'])); 37 | $allowed_fields = ['wpsl_address', 'wpsl_address_two', 'wpsl_city', 'wpsl_state', 'wpsl_zip', 'wpsl_country', 'wpsl_phone', 'wpsl_custom_geo', 'wpsl_website', $latitude_field, $longitude_field]; 38 | foreach ( $fields as $key => $value ){ 39 | if ( $key == 'action' || $key == 'id' ) continue; 40 | if ( $key == 'custom_geo' && $value == 'false' ) $value = 'false'; 41 | if ( $key == 'custom_geo' && $value == 'true' ) $value = 'true'; 42 | $meta_key = 'wpsl_' . $key; 43 | if ( !in_array($meta_key, $allowed_fields) ) continue; 44 | $meta_value = sanitize_text_field($value); 45 | update_post_meta($post_id, $meta_key, $meta_value); 46 | } 47 | $this->success(__('The location was successfully saved.', 'simple-locator')); 48 | } 49 | } -------------------------------------------------------------------------------- /app/Listeners/ResetPostTypeSettings.php: -------------------------------------------------------------------------------- 1 | reset(); 12 | } 13 | 14 | /** 15 | * Remove the Post type option(s) 16 | */ 17 | private function reset() 18 | { 19 | delete_option('wpsl_post_type'); 20 | delete_option('wpsl_posttype_labels'); 21 | delete_option('wpsl_field_type'); 22 | delete_option('wpsl_lng_field'); 23 | delete_option('wpsl_lat_field'); 24 | update_option('wpsl_hide_default', 'false'); 25 | flush_rewrite_rules(false); 26 | $this->success(__('Post Type successfully reset', 'simple-locator')); 27 | } 28 | } -------------------------------------------------------------------------------- /app/Migrations/Activation.php: -------------------------------------------------------------------------------- 1 | version = $simple_locator_version; 23 | $this->setVersion(); 24 | $this->migrateTables(); 25 | new RequiredOptions; 26 | register_activation_hook( dirname(dirname( dirname(__FILE__) )) . '/simplelocator.php', [$this, 'install'] ); 27 | } 28 | 29 | /** 30 | * Activation Hook 31 | */ 32 | public function install() 33 | { 34 | $types = new PostTypes; 35 | $options = new DefaultOptions; 36 | $types->register(); 37 | flush_rewrite_rules(); 38 | $this->migrateMaps(); 39 | } 40 | 41 | /** 42 | * Set the Plugin Version 43 | */ 44 | private function setVersion() 45 | { 46 | update_option('wpsl_version', $this->version); 47 | } 48 | 49 | /** 50 | * Migrate Map Styles 51 | */ 52 | private function migrateMaps() 53 | { 54 | new MapStyles; 55 | } 56 | 57 | /** 58 | * Table Migration 59 | */ 60 | private function migrateTables() 61 | { 62 | new CreateTables; 63 | } 64 | } -------------------------------------------------------------------------------- /app/Migrations/CreateTables.php: -------------------------------------------------------------------------------- 1 | searchTable(); 12 | } 13 | 14 | /** 15 | * Create the search records table if it doesn't exist 16 | */ 17 | private function searchTable() 18 | { 19 | $table_installed = get_option('wpsl_search_table_installed'); 20 | if ( $table_installed ) return; 21 | 22 | global $wpdb; 23 | $tablename = $wpdb->prefix . 'simple_locator_history'; 24 | if ( $wpdb->get_var('SHOW TABLES LIKE "' . $tablename . '"') != $tablename ) : 25 | $sql = 'CREATE TABLE ' . $tablename . '( 26 | id INTEGER(10) UNSIGNED AUTO_INCREMENT, 27 | time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 28 | user_ip VARCHAR(20), 29 | search_lat VARCHAR(20), 30 | search_lng VARCHAR(20), 31 | search_term VARCHAR(100), 32 | search_term_formatted VARCHAR(100), 33 | distance VARCHAR(20), 34 | PRIMARY KEY (id) )'; 35 | require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); 36 | dbDelta($sql); 37 | update_option('wpsl_search_table_installed', true); 38 | endif; 39 | } 40 | } -------------------------------------------------------------------------------- /app/Migrations/DefaultOptions.php: -------------------------------------------------------------------------------- 1 | setOptions(); 12 | } 13 | 14 | public function setOptions() 15 | { 16 | if ( !get_option('wpsl_output_css') ) update_option('wpsl_output_css', 'true'); 17 | if ( !get_option('wpsl_map_pin') ) update_option('wpsl_map_pin', ''); 18 | if ( !get_option('wpsl_map_styles_type') ) update_option('wpsl_map_styles_type', 'none'); 19 | if ( !get_option('wpsl_map_styles_choice') ) update_option('wpsl_map_styles_choice', ''); 20 | if ( !get_option('wpsl_singular_data') ) update_option('wpsl_singular_data', 'true'); 21 | if ( !get_option('wpsl_show_hidden') ) update_option('wpsl_show_hidden', 'false'); 22 | if ( !get_option('wpsl_gmaps_api') ) update_option('wpsl_gmaps_api', 'true'); 23 | if ( !get_option('wpsl_gmaps_api_admin') ) update_option('wpsl_gmaps_api_admin', 'true'); 24 | if ( !get_option('wpsl_measurement_unit') ) update_option('wpsl_measurement_unit', 'miles'); 25 | if ( !get_option('wpsl_geo_button') ){ 26 | update_option('wpsl_geo_button', array( 27 | 'enabled' => '', 28 | 'text'=> __('Use my location', 'simple-locator') 29 | )); 30 | } 31 | if ( !get_option('wpsl_default_map') ){ 32 | update_option('wpsl_default_map', array( 33 | 'show' => 'false', 34 | 'latitude' => '33.786637', 35 | 'longitude' => '-84.383160', 36 | 'zoom' => '15', 37 | 'user_location' => 'false' 38 | )); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /app/Migrations/MapStyles.php: -------------------------------------------------------------------------------- 1 | setDirectory(); 24 | $this->loopFiles(); 25 | } 26 | 27 | /** 28 | * Set the directory 29 | */ 30 | private function setDirectory() 31 | { 32 | $this->directory = dirname(__FILE__) . '/map_styles'; 33 | } 34 | 35 | /** 36 | * Set the File ID 37 | * @param object - DirectoryInterator Object 38 | */ 39 | private function setFileID($file) 40 | { 41 | $filename = $file->getFilename(); 42 | $file_id = explode('-', $filename); 43 | $this->file_id = $file_id[0]; 44 | } 45 | 46 | /** 47 | * Loop through the maps and create them 48 | * @todo check if post exists 49 | */ 50 | private function loopFiles() 51 | { 52 | $files = new \DirectoryIterator($this->directory); 53 | foreach ($files as $file){ 54 | if ( !$file->isDot() ){ 55 | $this->setFileID($file); 56 | $this->importPost($file); 57 | } 58 | } 59 | } 60 | 61 | /** 62 | * Import the Map 63 | * @param object - file to import 64 | */ 65 | private function importPost($file) 66 | { 67 | if ( !$this->mapExists() ) include($this->directory . '/' . $file); 68 | } 69 | 70 | /** 71 | * Check if Map Post Exists before import 72 | */ 73 | private function mapExists() 74 | { 75 | $map_query = new \WP_Query([ 76 | 'post_type' => 'wpslmaps', 77 | 'posts_per_page' => -1, 78 | 'meta_key' => 'wpsl_map_id', 79 | 'meta_value' => $this->file_id 80 | ]); 81 | $exists = ( $map_query->have_posts() ) ? true : false; 82 | wp_reset_postdata(); 83 | return $exists; 84 | } 85 | } -------------------------------------------------------------------------------- /app/Migrations/RequiredOptions.php: -------------------------------------------------------------------------------- 1 | setOptions(); 12 | } 13 | 14 | public function setOptions() 15 | { 16 | if ( !get_option('wpsl_post_type') ) update_option('wpsl_post_type', 'location'); 17 | if ( !get_option('wpsl_field_type') ) update_option('wpsl_field_type', 'wpsl'); 18 | if ( !get_option('wpsl_lat_field') ) update_option('wpsl_lat_field', 'wpsl_latitude'); 19 | if ( !get_option('wpsl_lng_field') ) update_option('wpsl_lng_field', 'wpsl_longitude'); 20 | if ( !get_option('wpsl_posttype_labels') ){ 21 | update_option('wpsl_posttype_labels', [ 22 | 'name' => 'location', 23 | 'label' => __('Locations', 'simple-locator'), 24 | 'singular'=> __('Location', 'simple-locator'), 25 | 'add_new_item'=> __('Add Location', 'simple-locator'), 26 | 'edit_item' => __('Edit Location', 'simple-locator'), 27 | 'view_item' => __('View Location', 'simple-locator'), 28 | 'slug' => __('location', 'simple-locator'), 29 | 'menu_icon' => 'dashicons-post-status', 30 | 'menu_position' => 6 31 | ]); 32 | } 33 | if ( !get_option('wpsl_results_fields_formatted') ){ 34 | update_option('wpsl_results_fields_formatted', array( 35 | 'output' => "[post_title]\r\n" . __('Distance', 'simple-locator') . ": [distance]\r\n[wpsl_address]\r\n[wpsl_city], [wpsl_state] [wpsl_zip]\r\n[wpsl_phone]\r\n[wpsl_website]\r\n[show_on_map]", 36 | 'limit' => -1 37 | )); 38 | } 39 | if ( !get_option('wpsl_results_fields_formatted_default') ){ 40 | update_option('wpsl_results_fields_formatted_default', [ 41 | 'output' => "[post_title]\r\n[wpsl_address]\r\n[wpsl_city], [wpsl_state] [wpsl_zip]\r\n[wpsl_phone]\r\n[wpsl_website]\r\n[show_on_map]", 42 | 'limit' => -1 43 | ]); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /app/Migrations/map_options/autocomplete-options.php: -------------------------------------------------------------------------------- 1 | 3 | new google.maps.Marker({ 4 | label: { text: String(count), color: "white", fontSize: "12px" }, 5 | position, 6 | zIndex: Number(google.maps.Marker.MAX_ZINDEX) + count, 7 | icon: { 8 | path: "M-20,0a20,20 0 1,0 40,0a20,20 0 1,0 -40,0", 9 | fillColor: "#000000", 10 | fillOpacity: 1, 11 | anchor: new google.maps.Point(0,0), 12 | strokeWeight: 2, 13 | strokeColor: "#ffffff", 14 | scale: .8, 15 | } 16 | }), 17 | }; -------------------------------------------------------------------------------- /app/Migrations/map_options/map-options.php: -------------------------------------------------------------------------------- 1 | __('Subtle Gray', 'simple-locator'), 8 | 'post_status' => 'publish', 9 | 'post_type' => 'wpslmaps', 10 | 'ping_status' => 'closed', 11 | 'comment_status' => 'closed', 12 | 'post_content' => '[{"featureType":"landscape","stylers":[{"saturation":-100},{"lightness":65},{"visibility":"on"}]},{"featureType":"poi","stylers":[{"saturation":-100},{"lightness":51},{"visibility":"simplified"}]},{"featureType":"road.highway","stylers":[{"saturation":-100},{"visibility":"simplified"}]},{"featureType":"road.arterial","stylers":[{"saturation":-100},{"lightness":30},{"visibility":"on"}]},{"featureType":"road.local","stylers":[{"saturation":-100},{"lightness":40},{"visibility":"on"}]},{"featureType":"transit","stylers":[{"saturation":-100},{"visibility":"simplified"}]},{"featureType":"administrative.province","stylers":[{"visibility":"off"}]},{"featureType":"water","elementType":"labels","stylers":[{"visibility":"on"},{"lightness":-25},{"saturation":-100}]},{"featureType":"water","elementType":"geometry","stylers":[{"hue":"#ffff00"},{"lightness":-25},{"saturation":-97}]}]' 13 | ); 14 | $post = wp_insert_post($map); 15 | add_post_meta($post, 'wpsl_map_id', $this->file_id); -------------------------------------------------------------------------------- /app/Migrations/map_styles/2-blue_water.php: -------------------------------------------------------------------------------- 1 | __('Blue Water', 'simple-locator'), 8 | 'post_status' => 'publish', 9 | 'post_type' => 'wpslmaps', 10 | 'ping_status' => 'closed', 11 | 'comment_status' => 'closed', 12 | 'post_content' => '[{"featureType":"water","stylers":[{"color":"#46bcec"},{"visibility":"on"}]},{"featureType":"landscape","stylers":[{"color":"#f2f2f2"}]},{"featureType":"road","stylers":[{"saturation":-100},{"lightness":45}]},{"featureType":"road.highway","stylers":[{"visibility":"simplified"}]},{"featureType":"road.arterial","elementType":"labels.icon","stylers":[{"visibility":"off"}]},{"featureType":"administrative","elementType":"labels.text.fill","stylers":[{"color":"#444444"}]},{"featureType":"transit","stylers":[{"visibility":"off"}]},{"featureType":"poi","stylers":[{"visibility":"off"}]}]' 13 | ); 14 | $post = wp_insert_post($map); 15 | add_post_meta($post, 'wpsl_map_id', $this->file_id); -------------------------------------------------------------------------------- /app/Migrations/map_styles/3-apple_maps_esque.php: -------------------------------------------------------------------------------- 1 | __('Apple Maps-esque', 'simple-locator'), 8 | 'post_status' => 'publish', 9 | 'post_type' => 'wpslmaps', 10 | 'ping_status' => 'closed', 11 | 'comment_status' => 'closed', 12 | 'post_content' => '[{"featureType":"water","elementType":"geometry","stylers":[{"color":"#a2daf2"}]},{"featureType":"landscape.man_made","elementType":"geometry","stylers":[{"color":"#f7f1df"}]},{"featureType":"landscape.natural","elementType":"geometry","stylers":[{"color":"#d0e3b4"}]},{"featureType":"landscape.natural.terrain","elementType":"geometry","stylers":[{"visibility":"off"}]},{"featureType":"poi.park","elementType":"geometry","stylers":[{"color":"#bde6ab"}]},{"featureType":"poi","elementType":"labels","stylers":[{"visibility":"off"}]},{"featureType":"poi.medical","elementType":"geometry","stylers":[{"color":"#fbd3da"}]},{"featureType":"poi.business","stylers":[{"visibility":"off"}]},{"featureType":"road","elementType":"geometry.stroke","stylers":[{"visibility":"off"}]},{"featureType":"road","elementType":"labels","stylers":[{"visibility":"off"}]},{"featureType":"road.highway","elementType":"geometry.fill","stylers":[{"color":"#ffe15f"}]},{"featureType":"road.highway","elementType":"geometry.stroke","stylers":[{"color":"#efd151"}]},{"featureType":"road.arterial","elementType":"geometry.fill","stylers":[{"color":"#ffffff"}]},{"featureType":"road.local","elementType":"geometry.fill","stylers":[{"color":"black"}]},{"featureType":"transit.station.airport","elementType":"geometry.fill","stylers":[{"color":"#cfb2db"}]}]' 13 | ); 14 | $post = wp_insert_post($map); 15 | add_post_meta($post, 'wpsl_map_id', $this->file_id); -------------------------------------------------------------------------------- /app/Migrations/map_styles/4-old_map.php: -------------------------------------------------------------------------------- 1 | __('Old Map', 'simple-locator'), 8 | 'post_status' => 'publish', 9 | 'post_type' => 'wpslmaps', 10 | 'ping_status' => 'closed', 11 | 'comment_status' => 'closed', 12 | 'post_content' => '[{"featureType":"administrative","stylers":[{"visibility":"off"}]},{"featureType":"poi","stylers":[{"visibility":"simplified"}]},{"featureType":"road","elementType":"labels","stylers":[{"visibility":"simplified"}]},{"featureType":"water","stylers":[{"visibility":"simplified"}]},{"featureType":"transit","stylers":[{"visibility":"simplified"}]},{"featureType":"landscape","stylers":[{"visibility":"simplified"}]},{"featureType":"road.highway","stylers":[{"visibility":"off"}]},{"featureType":"road.local","stylers":[{"visibility":"on"}]},{"featureType":"road.highway","elementType":"geometry","stylers":[{"visibility":"on"}]},{"featureType":"water","stylers":[{"color":"#abbaa4"}]},{"featureType":"transit.line","elementType":"geometry","stylers":[{"color":"#3f518c"}]},{"featureType":"road.highway","stylers":[{"color":"#ad9b8d"}]}]' 13 | ); 14 | $post = wp_insert_post($map); 15 | add_post_meta($post, 'wpsl_map_id', $this->file_id); -------------------------------------------------------------------------------- /app/Migrations/map_styles/5-shades_of_gray.php: -------------------------------------------------------------------------------- 1 | __('Shades of Grey', 'simple-locator'), 8 | 'post_status' => 'publish', 9 | 'post_type' => 'wpslmaps', 10 | 'ping_status' => 'closed', 11 | 'comment_status' => 'closed', 12 | 'post_content' => '[{"featureType":"water","elementType":"geometry","stylers":[{"color":"#000000"},{"lightness":17}]},{"featureType":"landscape","elementType":"geometry","stylers":[{"color":"#000000"},{"lightness":20}]},{"featureType":"road.highway","elementType":"geometry.fill","stylers":[{"color":"#000000"},{"lightness":17}]},{"featureType":"road.highway","elementType":"geometry.stroke","stylers":[{"color":"#000000"},{"lightness":29},{"weight":0.2}]},{"featureType":"road.arterial","elementType":"geometry","stylers":[{"color":"#000000"},{"lightness":18}]},{"featureType":"road.local","elementType":"geometry","stylers":[{"color":"#000000"},{"lightness":16}]},{"featureType":"poi","elementType":"geometry","stylers":[{"color":"#000000"},{"lightness":21}]},{"elementType":"labels.text.stroke","stylers":[{"visibility":"on"},{"color":"#000000"},{"lightness":16}]},{"elementType":"labels.text.fill","stylers":[{"saturation":36},{"color":"#000000"},{"lightness":40}]},{"elementType":"labels.icon","stylers":[{"visibility":"off"}]},{"featureType":"transit","elementType":"geometry","stylers":[{"color":"#000000"},{"lightness":19}]},{"featureType":"administrative","elementType":"geometry.fill","stylers":[{"color":"#000000"},{"lightness":20}]},{"featureType":"administrative","elementType":"geometry.stroke","stylers":[{"color":"#000000"},{"lightness":17},{"weight":1.2}]}]' 13 | ); 14 | $post = wp_insert_post($map); 15 | add_post_meta($post, 'wpsl_map_id', $this->file_id); -------------------------------------------------------------------------------- /app/Migrations/map_styles/6-night_rider.php: -------------------------------------------------------------------------------- 1 | __('Night Rider', 'simple-locator'), 8 | 'post_status' => 'publish', 9 | 'post_type' => 'wpslmaps', 10 | 'ping_status' => 'closed', 11 | 'comment_status' => 'closed', 12 | 'post_content' => '[{"featureType":"administrative","elementType":"geometry.fill","stylers":[{"color":"#1e242b"},{"lightness":"5"}]},{"featureType":"administrative","elementType":"geometry.stroke","stylers":[{"color":"#1e242b"},{"saturation":"0"},{"lightness":"30"}]},{"featureType":"administrative","elementType":"labels","stylers":[{"color":"#1e242b"},{"lightness":"30"}]},{"featureType":"administrative","elementType":"labels.text.stroke","stylers":[{"visibility":"off"}]},{"featureType":"administrative.province","elementType":"geometry.stroke","stylers":[{"color":"#1e242b"},{"lightness":"20"},{"weight":"1.00"}]},{"featureType":"administrative.neighborhood","elementType":"labels.text.fill","stylers":[{"lightness":"-20"}]},{"featureType":"administrative.land_parcel","elementType":"labels.text.fill","stylers":[{"lightness":"-20"}]},{"featureType":"landscape","elementType":"geometry","stylers":[{"color":"#1e242b"}]},{"featureType":"landscape","elementType":"labels","stylers":[{"color":"#1e242b"},{"lightness":"30"}]},{"featureType":"landscape","elementType":"labels.text.stroke","stylers":[{"visibility":"off"}]},{"featureType":"poi","elementType":"geometry","stylers":[{"color":"#1e242b"},{"lightness":"5"}]},{"featureType":"poi","elementType":"labels","stylers":[{"color":"#1e242b"},{"lightness":"30"}]},{"featureType":"poi","elementType":"labels.text.stroke","stylers":[{"visibility":"off"}]},{"featureType":"road","elementType":"geometry","stylers":[{"visibility":"simplified"},{"color":"#1e242b"},{"lightness":"15"}]},{"featureType":"road","elementType":"labels","stylers":[{"visibility":"off"}]},{"featureType":"transit","elementType":"geometry","stylers":[{"color":"#1e242b"},{"lightness":"6"}]},{"featureType":"transit","elementType":"labels","stylers":[{"color":"#1e242b"},{"lightness":"30"}]},{"featureType":"transit","elementType":"labels.text.stroke","stylers":[{"visibility":"off"}]},{"featureType":"water","elementType":"geometry","stylers":[{"color":"#010306"}]},{"featureType":"water","elementType":"labels.text.stroke","stylers":[{"visibility":"off"}]}]' 13 | ); 14 | $post = wp_insert_post($map); 15 | add_post_meta($post, 'wpsl_map_id', $this->file_id); -------------------------------------------------------------------------------- /app/Post/AdminMap.php: -------------------------------------------------------------------------------- 1 | settings = new SettingsRepository; 31 | if ( !$this->settings->includeAdminListMap() ) return; 32 | $this->presenter = new LocationResultPresenter; 33 | $this->post_type = $this->settings->getLocationPostType(); 34 | $location_columns = $this->post_type . '_columns'; 35 | $location_custom_columns = $this->post_type . '_posts_custom_column'; 36 | add_filter("manage_edit-$location_columns", [$this, 'addColumn']); 37 | add_action("manage_$location_custom_columns", [$this, 'setColumnData'], 10, 2); 38 | } 39 | 40 | /** 41 | * Add a hidden column to store the map data in for each post 42 | */ 43 | public function addColumn($columns) 44 | { 45 | $columns['wpsl-coordinates'] = apply_filters('simple_locator_admin_map_column_title', __('Location', 'simple-locator')); 46 | return $columns; 47 | } 48 | 49 | /** 50 | * Add the custom column data 51 | */ 52 | public function setColumnData($column, $post_id) 53 | { 54 | if ( $column !== 'wpsl-coordinates' ) return; 55 | $meta = get_post_meta($post_id); 56 | if ( !isset($meta['wpsl_latitude']) || !isset($meta['wpsl_longitude']) ) return; 57 | $latitude = $meta['wpsl_latitude'][0]; 58 | $longitude = $meta['wpsl_longitude'][0]; 59 | $edit_link = get_edit_post_link($post_id); 60 | $view_link = get_the_permalink($post_id); 61 | if ( $latitude == '' || $longitude == '' ) return; 62 | $infowindow = $this->getInfowindow($post_id, $latitude, $longitude); 63 | 64 | echo '
' . $infowindow . '
'; 65 | $link = ''; 66 | echo $link . apply_filters('simple_locator_admin_map_column_content', __('View on Map', 'simple-locator')) . ''; 67 | } 68 | 69 | /** 70 | * Get the formatted infowindow 71 | */ 72 | private function getInfowindow($post_id, $latitude, $longitude) 73 | { 74 | $location_post = get_post($post_id); 75 | $location_post->id = $post_id; 76 | $location_post->title = $location_post->post_title; 77 | $location_post->latitude = $latitude; 78 | $location_post->longitude = $longitude; 79 | $formatted_post = $this->presenter->present($location_post, 0); 80 | return $formatted_post['infowindow']; 81 | } 82 | } -------------------------------------------------------------------------------- /app/Post/QuickEditLink.php: -------------------------------------------------------------------------------- 1 | settings = new SettingsRepository; 36 | $this->getSettings(); 37 | add_filter('post_row_actions', [$this, 'addLink'], 10, 2); 38 | } 39 | 40 | /** 41 | * Set Object Vars (called in constructor to limit DB queries) 42 | */ 43 | private function getSettings() 44 | { 45 | $this->post_type = $this->settings->getLocationPostType(); 46 | $hide_locator_fields = get_option('wpsl_hide_default_fields'); 47 | if ( $hide_locator_fields == 'true' ) $this->hide_locator_fields = true; 48 | $this->geocode_fields['lat'] = $this->settings->getGeoField('lat'); 49 | $this->geocode_fields['lng'] = $this->settings->getGeoField('lng'); 50 | } 51 | 52 | /** 53 | * Add the quick edit link to the post row actions 54 | */ 55 | public function addLink($actions, $post) 56 | { 57 | if ( $post->post_type !== $this->post_type ) return $actions; 58 | if ( $this->hide_locator_fields ) return $actions; 59 | $post_type_object = get_post_type_object($this->post_type); 60 | 61 | $meta = get_post_meta($post->ID, '', true); 62 | $address = ( isset($meta['wpsl_address']) && $meta['wpsl_address'] !== '' ) ? $meta['wpsl_address'][0] : ''; 63 | $address_two = ( isset($meta['wpsl_address_two']) && $meta['wpsl_address_two'] !== '' ) ? $meta['wpsl_address_two'][0] : ''; 64 | $city = ( isset($meta['wpsl_city']) && $meta['wpsl_city'] !== '' ) ? $meta['wpsl_city'][0] : ''; 65 | $state = ( isset($meta['wpsl_state']) && $meta['wpsl_state'] !== '' ) ? $meta['wpsl_state'][0] : ''; 66 | $country = ( isset($meta['wpsl_country']) && $meta['wpsl_country'] !== '' ) ? $meta['wpsl_country'][0] : ''; 67 | $zip = ( isset($meta['wpsl_zip']) && $meta['wpsl_zip'] !== '' ) ? $meta['wpsl_zip'][0] : ''; 68 | $phone = ( isset($meta['wpsl_phone']) && $meta['wpsl_phone'] !== '' ) ? $meta['wpsl_phone'][0] : ''; 69 | $latitude = ( isset($meta[$this->geocode_fields['lat']]) && $meta[$this->geocode_fields['lat']] !== '' ) ? $meta[$this->geocode_fields['lat']][0] : ''; 70 | $longitude = ( isset($meta[$this->geocode_fields['lng']]) && $meta[$this->geocode_fields['lng']] !== '' ) ? $meta[$this->geocode_fields['lng']][0] : ''; 71 | $website = ( isset($meta['wpsl_website']) && $meta['wpsl_website'] !== '' ) ? $meta['wpsl_website'][0] : ''; 72 | 73 | $link = ''; 74 | $title = apply_filters('simple_locator_quick_edit_title', __('Edit Location', 'simple-locator'), $post); 75 | $actions['location-quick-edit'] = $link . $title . ''; 76 | return $actions; 77 | } 78 | } -------------------------------------------------------------------------------- /app/Post/Singular.php: -------------------------------------------------------------------------------- 1 | settings_repo = new SettingsRepository; 37 | $this->setPostType(); 38 | $this->setOptions(); 39 | $this->filterContent(); 40 | } 41 | 42 | /** 43 | * Set the Post Type 44 | */ 45 | private function setPostType() 46 | { 47 | $this->post_type = $this->settings_repo->getLocationPostType(); 48 | } 49 | 50 | /** 51 | * Set the View Options 52 | */ 53 | private function setOptions() 54 | { 55 | $this->options['additionalfields'] = false; 56 | } 57 | 58 | /** 59 | * Filter the Content 60 | */ 61 | private function filterContent() 62 | { 63 | add_filter('the_content', [$this, 'addFilteredContent']); 64 | } 65 | 66 | /** 67 | * Set the location data for use in map and on page 68 | */ 69 | private function setLocationData() 70 | { 71 | $this->location_data['additionalfields'] = 'show'; 72 | $this->location_data['title'] = get_the_title(); 73 | $this->location_data['latitude'] = get_post_meta( get_the_id(), get_option('wpsl_lat_field'), true ); 74 | $this->location_data['longitude'] = get_post_meta( get_the_id(), get_option('wpsl_lng_field'), true ); 75 | if ( $this->post_type == 'location' ){ 76 | $this->location_data['address'] = get_post_meta( get_the_id(), 'wpsl_address', true); 77 | $this->location_data['city'] = get_post_meta( get_the_id(), 'wpsl_city', true); 78 | $this->location_data['state'] = get_post_meta( get_the_id(), 'wpsl_state', true); 79 | $this->location_data['zip'] = get_post_meta( get_the_id(), 'wpsl_zip', true); 80 | $this->location_data['phone'] = get_post_meta( get_the_id(), 'wpsl_phone', true); 81 | $this->location_data['website'] = get_post_meta( get_the_id(), 'wpsl_website', true); 82 | $this->location_data['additionalinfo'] = get_post_meta( get_the_id(), 'wpsl_additionalinfo', true); 83 | } 84 | } 85 | 86 | /** 87 | * Apply filter to the content if single view of location post 88 | */ 89 | public function addFilteredContent($content) 90 | { 91 | if ( ( is_singular($this->post_type) ) && ( get_option('wpsl_singular_data') == 'true') ){ 92 | if ( !is_main_query() ) return; 93 | $this->setLocationData(); 94 | $this->enqueueScript(); 95 | $output = $this->addmap(); 96 | $content = $output . $content; 97 | } 98 | return $content; 99 | } 100 | 101 | /** 102 | * Add the map to the output 103 | */ 104 | private function addmap() 105 | { 106 | include( \SimpleLocator\Helpers::view('singular-post') ); 107 | return $out; 108 | } 109 | 110 | /** 111 | * Enqueue the simple locator scripts & add localized data to them 112 | */ 113 | private function enqueueScript() 114 | { 115 | if ( (isset($this->location_data['latitude'])) && (isset($this->location_data['longitude'])) ){ 116 | wp_enqueue_script('google-maps'); 117 | wp_enqueue_script('simple-locator'); 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /app/Repositories/ImportRepository.php: -------------------------------------------------------------------------------- 1 | transient = get_transient('wpsl_import_file'); 15 | } 16 | 17 | /** 18 | * Is there an incomplete import? 19 | * @return int 20 | */ 21 | public function incomplete() 22 | { 23 | if ( isset($this->transient['complete']) && !$this->transient['complete'] ){ 24 | return $this->transient['row_count'] - $this->transient['complete_rows'] - count($this->transient['error_rows']); 25 | } 26 | return false; 27 | } 28 | 29 | /** 30 | * Get the import transient 31 | */ 32 | public function transient() 33 | { 34 | return $this->transient; 35 | } 36 | 37 | /** 38 | * Get imported post IDs from an import 39 | * @param int $id - The Import ID 40 | */ 41 | public function getImportedPostIDs($id) 42 | { 43 | $meta = get_post_meta($id, 'wpsl_import_data', true); 44 | return $meta['post_ids']; 45 | } 46 | 47 | /** 48 | * Get Import Data for a specific import 49 | * @param int $id - The Import ID 50 | */ 51 | public function getImportData($id) 52 | { 53 | return get_post_meta($id, 'wpsl_import_data', true); 54 | } 55 | 56 | /** 57 | * Get import templates 58 | */ 59 | public function getImportTemplates($id = null) 60 | { 61 | $posts = null; 62 | $args = ['post_type' => 'wpslimporttemplate', 'posts_per_page' => -1]; 63 | if ( $id ) $args['p'] = $id; 64 | $q = new \WP_Query($args); 65 | if ( $q->have_posts() ) : $c = 0; while ( $q->have_posts() ) : $q->the_post(); 66 | $posts[$c] = new \stdClass; 67 | $posts[$c]->ID = get_the_id(); 68 | $posts[$c]->title = get_the_title(); 69 | $import_data = $this->getImportData(get_the_id()); 70 | $posts[$c]->import_post_type = $import_data['post_type']; 71 | $posts[$c]->import_columns = $import_data['columns']; 72 | $posts[$c]->import_status = $import_data['import_status']; 73 | $posts[$c]->import_skip_first = $import_data['skip_first']; 74 | $posts[$c]->import_skip_geocode = $import_data['skip_geocode']; 75 | $posts[$c]->import_duplicate_handling = $import_data['duplicate_handling']; 76 | $posts[$c]->import_missing_handling = $import_data['missing_handling']; 77 | $posts[$c]->import_missing_handling_label = __('Skip', 'simple-locator'); 78 | $posts[$c]->import_taxonomy_separator = $import_data['taxonomy_separator']; 79 | 80 | if ( $import_data['missing_handling'] == 'draft' ) $posts[$c]->import_missing_handling_label = __('Change Status to Draft', 'simple-locator'); 81 | if ( $import_data['missing_handling'] == 'trash' ) $posts[$c]->import_missing_handling_label = __('Move Post to Trash', 'simple-locator'); 82 | if ( $import_data['missing_handling'] == 'delete' ) $posts[$c]->import_missing_handling_label = __('Delete Post Completely', 'simple-locator'); 83 | $c++; endwhile; endif; 84 | wp_reset_postdata(); 85 | return ( $id && is_array($posts) ) ? $posts[0] : $posts; 86 | } 87 | } -------------------------------------------------------------------------------- /app/Repositories/MapStyles.php: -------------------------------------------------------------------------------- 1 | 'wpslmaps', 17 | 'posts_per_page' => -1 18 | ]); 19 | if ( $map_query->have_posts() ) : $i = 0; while ( $map_query->have_posts() ) : $map_query->the_post(); 20 | $styles[$i]['title'] = get_the_title(); 21 | $styles[$i]['styles'] = get_the_content(); 22 | $styles[$i]['id'] = get_the_id(); 23 | $styles[$i]['selected'] = ( get_option('wpsl_map_styles_choice') == get_the_id() ) ? true : false; 24 | $i++; 25 | endwhile; endif; wp_reset_postdata(); 26 | return $styles; 27 | } 28 | 29 | /** 30 | * Get Styles for JS Localization 31 | */ 32 | public function getLocalizedStyles() 33 | { 34 | $style_type = get_option('wpsl_map_styles_type'); 35 | if ( $style_type == 'none' ) return ''; 36 | if ( $style_type == 'choice' ) return $this->getSelectedStyles(); 37 | return ( get_option('wpsl_map_styles') ) ? json_decode(get_option('wpsl_map_styles')) : ''; 38 | } 39 | 40 | /** 41 | * Get the selected style 42 | */ 43 | private function getSelectedStyles() 44 | { 45 | global $post; 46 | $original_post = $post; 47 | $style_query = new \WP_Query([ 48 | 'post_type' => 'wpslmaps', 49 | 'p' => get_option('wpsl_map_styles_choice'), 50 | 'posts_per_page' => 1 51 | ]); 52 | $style_content = null; 53 | if ( $style_query->have_posts() ) : while ( $style_query->have_posts() ) : $style_query->the_post(); 54 | $style_content = json_decode(get_the_content()); 55 | endwhile; endif; 56 | $post = $original_post; 57 | wp_reset_postdata(); 58 | return $style_content; 59 | } 60 | } -------------------------------------------------------------------------------- /app/Services/CSVDownload/HistoryCsvDownload.php: -------------------------------------------------------------------------------- 1 | search_history_repo = new SearchHistoryRepository; 22 | $this->getResults(); 23 | $this->generateCsv(); 24 | } 25 | 26 | /** 27 | * Get the Search History based on post params 28 | */ 29 | private function getResults() 30 | { 31 | $this->search_history_repo->setSearch('POST', false); 32 | $this->results = $this->search_history_repo->getResults(); 33 | } 34 | 35 | /** 36 | * Generate the CSV 37 | */ 38 | private function generateCsv() 39 | { 40 | $csv = Writer::createFromFileObject(new \SplTempFileObject()); 41 | 42 | // Header Row 43 | $csv->insertOne([ 44 | __('Time', 'simple-locator'), 45 | __('User IP', 'simple-locator'), 46 | __('Search Latitude', 'simple-locator'), 47 | __('Search Longitude', 'simple-locator'), 48 | __('Search Term', 'simple-locator'), 49 | __('Search Term Formatted', 'simple-locator'), 50 | __('Distance', 'simple-locator') 51 | ]); 52 | 53 | foreach ( $this->results as $result ){ 54 | $csv->insertOne([ 55 | $result->time, 56 | $result->user_ip, 57 | $result->search_lat, 58 | $result->search_lng, 59 | $result->search_term, 60 | $result->search_term_formatted, 61 | $result->distance 62 | ]); 63 | } 64 | 65 | $filename = __('location-search-history', 'simple-locator') . '.csv'; 66 | $csv->output($filename); 67 | } 68 | } -------------------------------------------------------------------------------- /app/Services/ExportTemplates/TemplateCreator.php: -------------------------------------------------------------------------------- 1 | template = [ 30 | 'name' => $template_name, 31 | 'standard_columns' => $standard_columns, 32 | 'custom_columns' => $custom_columns, 33 | 'taxonomies' => $taxonomies, 34 | 'include_header' => $include_header, 35 | 'column_names' => $column_names, 36 | 'taxonomy_separator' => $taxonomy_separator, 37 | 'filename' => $filename 38 | ]; 39 | $this->store(); 40 | } 41 | 42 | private function store() 43 | { 44 | $templates = get_option('wpsl_export_templates', []); 45 | $templates[] = $this->template; 46 | update_option('wpsl_export_templates', $templates); 47 | } 48 | } -------------------------------------------------------------------------------- /app/Services/ExportTemplates/TemplateDestroyer.php: -------------------------------------------------------------------------------- 1 | deleteTemplate(); 9 | return wp_send_json(['status' => 'success']); 10 | } 11 | 12 | private function deleteTemplate() 13 | { 14 | $template_key = sanitize_text_field($_GET['templateKey']); 15 | $templates = get_option('wpsl_export_templates', []); 16 | if ( !isset($templates[$template_key]) ) return; 17 | unset($templates[$template_key]); 18 | update_option('wpsl_export_templates', $templates); 19 | } 20 | } -------------------------------------------------------------------------------- /app/Services/ExportTemplates/TemplateRequest.php: -------------------------------------------------------------------------------- 1 | getTemplates(); 12 | } 13 | 14 | public function getTemplates() 15 | { 16 | $templates = get_option('wpsl_export_templates'); 17 | if ( isset($_GET['templateKey']) && isset($templates[$_GET['templateKey']]) ) $templates = $templates[sanitize_text_field($_GET['templateKey'])]; 18 | return wp_send_json([ 19 | 'status' => 'success', 20 | 'templates' => $templates 21 | ]); 22 | } 23 | } -------------------------------------------------------------------------------- /app/Services/Helpers/RemoteAddress.php: -------------------------------------------------------------------------------- 1 | getIpAddressFromProxy(); 45 | if ($ip) { 46 | return $ip; 47 | } 48 | 49 | // direct IP address 50 | if (isset($_SERVER['REMOTE_ADDR'])) { 51 | return $_SERVER['REMOTE_ADDR']; 52 | } 53 | 54 | return ''; 55 | } 56 | 57 | /** 58 | * Attempt to get the IP address for a proxied client 59 | * 60 | * @see http://tools.ietf.org/html/draft-ietf-appsawg-http-forwarded-10#section-5.2 61 | * @return false|string 62 | */ 63 | protected function getIpAddressFromProxy() 64 | { 65 | if (!$this->useProxy 66 | || (isset($_SERVER['REMOTE_ADDR']) && !in_array($_SERVER['REMOTE_ADDR'], $this->trustedProxies)) 67 | ) { 68 | return false; 69 | } 70 | 71 | $header = $this->proxyHeader; 72 | if (!isset($_SERVER[$header]) || empty($_SERVER[$header])) { 73 | return false; 74 | } 75 | 76 | // Extract IPs 77 | $ips = explode(',', $_SERVER[$header]); 78 | // trim, so we can compare against trusted proxies properly 79 | $ips = array_map('trim', $ips); 80 | // remove trusted proxy IPs 81 | $ips = array_diff($ips, $this->trustedProxies); 82 | 83 | // Any left? 84 | if (empty($ips)) { 85 | return false; 86 | } 87 | 88 | // Since we've removed any known, trusted proxy servers, the right-most 89 | // address represents the first IP we do not know about -- i.e., we do 90 | // not know if it is a proxy server, or a client. As such, we treat it 91 | // as the originating IP. 92 | // @see http://en.wikipedia.org/wiki/X-Forwarded-For 93 | $ip = array_pop($ips); 94 | return $ip; 95 | } 96 | 97 | // [...] 98 | } -------------------------------------------------------------------------------- /app/Services/Helpers/StateListing.php: -------------------------------------------------------------------------------- 1 | states(); 18 | 19 | // First try the country code 20 | $country_from_code = $this->countryCode($request['country'], null); 21 | if ( $country_from_code ) { 22 | $class = 'SimpleLocator\Services\Helpers\States\\' . ucfirst($country_from_code); 23 | if ( class_exists($class) ) return (new $class)->states(); 24 | } 25 | 26 | // Then try the name 27 | $country_from_name = $this->countryCode(null, $request['country']); 28 | if ( $country_from_name ){ 29 | $class = 'SimpleLocator\Services\Helpers\States\\' . ucfirst($country_from_name); 30 | if ( class_exists($class) ) return (new $class)->states(); 31 | } 32 | 33 | throw new \Exception(__('Country not found', 'simple-locator')); 34 | } 35 | 36 | /** 37 | * Loop through country codes (allows searching by code or name) 38 | */ 39 | public function countryCode($code = null, $name = null) 40 | { 41 | if ( !$code && !$name ) return null; 42 | $countries = [ 43 | 'us' => 'United States', 44 | 'mx' => 'Mexico', 45 | 'ca' => 'Canada' 46 | ]; 47 | foreach ( $countries as $country_code => $country_name ){ 48 | if ( $code && $country_code == strtolower($code) ) return $country_code; 49 | if ( $name && strtolower($country_name) == strtolower($name) ) return $country_code; 50 | } 51 | return false; 52 | } 53 | } -------------------------------------------------------------------------------- /app/Services/Helpers/States/Ca.php: -------------------------------------------------------------------------------- 1 | [ 13 | 'code' => 'ab', 14 | 'name' => 'Alberta', 15 | 'territory' => false, 16 | ], 17 | 'bc' => [ 18 | 'code' => 'bc', 19 | 'name' => 'British Columbia', 20 | 'territory' => false, 21 | ], 22 | 'mb' => [ 23 | 'code' => 'mb', 24 | 'name' => 'Manitoba', 25 | 'territory' => false, 26 | ], 27 | 'nb' => [ 28 | 'code' => 'nb', 29 | 'name' => 'New Brunswick', 30 | 'territory' => false, 31 | ], 32 | 'nl' => [ 33 | 'code' => 'nl', 34 | 'name' => 'Newfoundland and Labrador', 35 | 'territory' => false, 36 | ], 37 | 'ns' => [ 38 | 'code' => 'ns', 39 | 'name' => 'Nova Scotia', 40 | 'territory' => false, 41 | ], 42 | 'nt' => [ 43 | 'code' => 'nt', 44 | 'name' => 'Northwest Territories', 45 | 'territory' => false, 46 | ], 47 | 'nu' => [ 48 | 'code' => 'nu', 49 | 'name' => 'Nunavut', 50 | 'territory' => false, 51 | ], 52 | 'on' => [ 53 | 'code' => 'on', 54 | 'name' => 'Ontario', 55 | 'territory' => false, 56 | ], 57 | 'pe' => [ 58 | 'code' => 'pe', 59 | 'name' => 'Prince Edward Island', 60 | 'territory' => false, 61 | ], 62 | 'qc' => [ 63 | 'code' => 'qc', 64 | 'name' => 'Quebec', 65 | 'territory' => false, 66 | ], 67 | 'sk' => [ 68 | 'code' => 'sk', 69 | 'name' => 'Saskatchewan', 70 | 'territory' => false, 71 | ], 72 | 'yt' => [ 73 | 'code' => 'yt', 74 | 'name' => 'Yukon', 75 | 'territory' => false, 76 | ] 77 | ]; 78 | return apply_filters('simple_locator_state_listing_ca', $states); 79 | } 80 | } -------------------------------------------------------------------------------- /app/Services/Helpers/States/StateInterface.php: -------------------------------------------------------------------------------- 1 | file = get_transient('wpsl_import_file'); 26 | } 27 | 28 | /** 29 | * Get the columns for a given row 30 | */ 31 | public function getRow($row) 32 | { 33 | $this->setFile(); 34 | $this->setMacFormatting(); 35 | $csv = Reader::createFromPath($this->file['file']); 36 | $res = $csv->fetchOne($row); 37 | 38 | if ( !$res ){ 39 | throw new \Exception(__('Row not found', 'simple-locator')); 40 | } 41 | return $res; 42 | } 43 | 44 | /** 45 | * Set Mac Formatting 46 | */ 47 | private function setMacFormatting() 48 | { 49 | if ( isset($this->file['mac']) && $this->file['mac'] ){ 50 | if (!ini_get("auto_detect_line_endings")) { 51 | ini_set("auto_detect_line_endings", '1'); 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /app/Services/Import/Exceptions/GoogleAPIException.php: -------------------------------------------------------------------------------- 1 | get('https://maps.googleapis.com/maps/api/geocode/json', [ 35 | 'query' => [ 36 | 'address' => $address, 37 | 'key' => $apikey 38 | ] 39 | ]); 40 | $response_status = $response->getReasonPhrase(); 41 | 42 | if ( $response_status == 'OVER_QUERY_LIMIT' ){ 43 | throw new GoogleQueryLimitException(__('Your API limit has been reached. Try again in 24 hours.', 'simple-locator')); 44 | } 45 | 46 | if ( $response_status == 'REQUEST_DENIED' ){ 47 | throw new GoogleRequestDeniedException(__('Google Maps Error', 'simple-locator') . ': ' . $json['error_message']); 48 | } 49 | 50 | if ( $response_status !== 'OK' ) { 51 | throw new GoogleAPIException(__('Google Maps Error', 'simple-locator') . ': ' . $response_status); 52 | return false; 53 | } 54 | 55 | $results = $response->getBody()->getContents(); 56 | $results = json_decode($results, true); 57 | 58 | if ( isset($results['error_message']) ){ 59 | throw new GoogleAPIException(sprintf(__('%s', 'simple-locator'), $results['error_message'])); 60 | } 61 | 62 | $this->coordinates = [ 63 | 'lat' => $results['results'][0]['geometry']['location']['lat'], 64 | 'lng' => $results['results'][0]['geometry']['location']['lng'] 65 | ]; 66 | return true; 67 | } 68 | 69 | /** 70 | * Coordinates Getter 71 | */ 72 | public function getCoordinates() 73 | { 74 | return $this->coordinates; 75 | } 76 | 77 | /** 78 | * Error getter 79 | */ 80 | public function getError() 81 | { 82 | return $this->error; 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /app/Services/Import/Listeners/ApiTest.php: -------------------------------------------------------------------------------- 1 | geocoder = new GoogleMapGeocode; 16 | $this->runTest(); 17 | return wp_send_json(['status' => 'testing', 'message' => 'testing']); 18 | } 19 | 20 | private function runTest() 21 | { 22 | $address = '1600+Amphitheatre+Parkway,+Mountain+View,+CA'; 23 | try { 24 | $this->geocoder->geocode($address); 25 | $this->respond('success', __('Google Maps Geocoder test successful using the provided API key.', 'simple-locator')); 26 | } catch ( \Exception $e ){ 27 | $this->respond('error', __('Google response: ', 'simple-locator') . $e->getMessage()); 28 | } 29 | } 30 | 31 | private function respond($status, $message) 32 | { 33 | return wp_send_json(['status' => $status, 'message' => $message]); 34 | } 35 | } -------------------------------------------------------------------------------- /app/Services/Import/Listeners/ColumnMapper.php: -------------------------------------------------------------------------------- 1 | setFields(); 20 | $this->saveTransient(); 21 | } 22 | 23 | /** 24 | * Sanitize and set the fields 25 | */ 26 | private function setFields() 27 | { 28 | $fields = $_POST['wpsl_import_field']; 29 | $post_fields = ['title', 'content', 'excerpt', 'status', 'publish_date', 'publish_date_gmt', 'modified_date', 'modified_date_gmt', 'slug']; 30 | foreach ( $fields as $key => $field ){ 31 | $this->fields[$key] = new \StdClass(); 32 | $this->fields[$key]->csv_column = intval(sanitize_text_field($field['csv_column'])); 33 | $this->fields[$key]->field = sanitize_text_field($field['field']); 34 | $this->fields[$key]->type = sanitize_text_field($field['type']); 35 | $this->fields[$key]->unique = ( isset($field['unique']) && $field['unique'] == 1 ) ? true : false; 36 | $field_type = 'post_meta'; 37 | if ( in_array($field['field'], $post_fields) ) $field_type = 'post_field'; 38 | if ( strpos($field['field'], 'taxonomy') !== false ) $field_type = 'taxonomy'; 39 | $this->fields[$key]->field_type = $field_type; 40 | } 41 | } 42 | 43 | /** 44 | * Save the Map to the Transient 45 | */ 46 | private function saveTransient() 47 | { 48 | $transient = get_transient('wpsl_import_file'); 49 | $transient['columns'] = $this->fields; 50 | $transient['import_status'] = ( isset($_POST['wpsl_import_status']) && $_POST['wpsl_import_status'] == 'draft' ) ? 'draft' : 'publish'; 51 | $transient['skip_first'] = false; 52 | $transient['skip_geocode'] = ( isset($_POST['wpsl_skip_geocoding']) && $_POST['wpsl_skip_geocoding'] == '1' ) ? true : false; 53 | if ( isset($_POST['wpsl_first_row_header']) ){ 54 | $transient['skip_first'] = true; 55 | $transient['row_count'] = $transient['row_count'] - 1; 56 | } 57 | $transient['auto_parent'] = ( isset($_POST['wpsl_auto_parent']) && $_POST['wpsl_auto_parent'] == 1 ) ? true : false; 58 | $transient['taxonomy_separator'] = sanitize_text_field($_POST['wpsl_import_taxonomy_separator']); 59 | $transient['duplicate_handling'] = sanitize_text_field($_POST['wpsl_import_duplicate_handling']); 60 | $transient['missing_handling'] = sanitize_text_field($_POST['wpsl_import_missing_handling']); 61 | set_transient('wpsl_import_file', $transient, 1 * YEAR_IN_SECONDS); 62 | $this->success('3'); 63 | } 64 | } -------------------------------------------------------------------------------- /app/Services/Import/Listeners/FinishImport.php: -------------------------------------------------------------------------------- 1 | post_repo = new PostRepository; 21 | $this->getTransient(); 22 | $this->saveImport(); 23 | $this->handleMissing(); 24 | $this->response(); 25 | } 26 | 27 | /** 28 | * Get the Transient 29 | */ 30 | private function getTransient() 31 | { 32 | $this->transient = get_transient('wpsl_import_file'); 33 | $this->transient['complete'] = true; 34 | set_transient('wpsl_import_file', $this->transient, 1 * YEAR_IN_SECONDS); 35 | } 36 | 37 | /** 38 | * Save the Import 39 | */ 40 | private function saveImport() 41 | { 42 | $title = __('Import on ', 'simple-locator') . date_i18n( 'Y-m-d H:m:s', time() ); 43 | $importpost = [ 44 | 'post_title' => $title, 45 | 'post_status' => 'publish', 46 | 'post_type' => 'wpslimport' 47 | ]; 48 | $post_id = wp_insert_post($importpost); 49 | add_post_meta($post_id, 'wpsl_import_data', $this->transient); 50 | } 51 | 52 | /** 53 | * Handle Missing Rows 54 | */ 55 | private function handleMissing() 56 | { 57 | $action = $this->transient['missing_handling']; 58 | if ( $action == 'skip' ) return; 59 | $has_unique = false; 60 | foreach ( $this->transient['columns'] as $column ){ 61 | if ( $column->unique ) $has_unique = true; 62 | } 63 | if ( !$has_unique ) return; 64 | $import_ids = $this->transient['post_ids']; 65 | $import_post_type = $this->transient['post_type']; 66 | $missing_ids = $this->post_repo->getMissingPostsFromImport($import_ids, $import_post_type); 67 | $this->post_repo->updateMissingPostsFromImport($missing_ids, $action); 68 | } 69 | 70 | /** 71 | * Send the Response 72 | */ 73 | private function response() 74 | { 75 | return wp_send_json([ 76 | 'status' => 'success', 77 | 'import_count' => $this->transient['complete_rows'], 78 | 'error_count'=> count($this->transient['error_rows']), 79 | 'errors' => $this->transient['error_rows'] 80 | ]); 81 | } 82 | } -------------------------------------------------------------------------------- /app/Services/Import/Listeners/GetCSVRow.php: -------------------------------------------------------------------------------- 1 | row = new Row; 18 | $this->getRow(); 19 | } 20 | 21 | /** 22 | * Get the column data for a specific row 23 | */ 24 | private function getRow() 25 | { 26 | $row = ( !isset($_POST['rowcount']) || !is_numeric($_POST['rowcount']) ) ? 0 : intval(sanitize_text_field($_POST['rowcount'])); 27 | 28 | try { 29 | $columns = $this->row->getRow($row); 30 | } catch ( \Exception $e ){ 31 | $this->error($e->getMessage); 32 | } 33 | 34 | $this->respond(['status'=>'success', 'columns' => $columns, 'row_count' => $this->totalRowCount()]); 35 | } 36 | 37 | /** 38 | * Get total number of rows 39 | */ 40 | private function totalRowCount() 41 | { 42 | $transient = get_transient('wpsl_import_file'); 43 | return $transient['row_count']; 44 | } 45 | } -------------------------------------------------------------------------------- /app/Services/Import/Listeners/Import.php: -------------------------------------------------------------------------------- 1 | setTransient(); 27 | $this->setImportClass(); 28 | $this->doImport(); 29 | } 30 | 31 | /** 32 | * Get the transient and set property 33 | */ 34 | private function setTransient() 35 | { 36 | $this->transient = get_transient('wpsl_import_file'); 37 | } 38 | 39 | /** 40 | * Instantiate import class based on type of import 41 | */ 42 | private function setImportClass() 43 | { 44 | $import_type = $this->transient['import_type']; 45 | if ( $import_type == 'text/csv' ) return $this->import_class = new \SimpleLocator\Services\Import\CSV\CSVImport; 46 | } 47 | 48 | /** 49 | * Do the Import 50 | */ 51 | private function doImport() 52 | { 53 | $offset = intval(sanitize_text_field($_POST['offset'])); 54 | $request_number = intval(sanitize_text_field($_POST['imports_per_request'])); 55 | 56 | try { 57 | $ids = $this->import_class->doImport($offset, $request_number, $this->transient); 58 | } catch (\SimpleLocator\Services\Import\Exceptions\ImportCompleteException $e ) { 59 | $this->respond(['status' => 'complete']); 60 | } catch ( \Exception $e ){ 61 | $this->error($e->getMessage()); 62 | } 63 | $this->updatePostIDs($ids); 64 | $this->sendResponse(); 65 | } 66 | 67 | /** 68 | * Update New Post IDs in Transient 69 | */ 70 | private function updatePostIDs($ids) 71 | { 72 | $transient = get_transient('wpsl_import_file'); 73 | $transient['post_ids'] = array_unique(array_merge($transient['post_ids'], $ids)); 74 | set_transient('wpsl_import_file', $transient, 1 * YEAR_IN_SECONDS); 75 | } 76 | 77 | /** 78 | * Send Response 79 | */ 80 | private function sendResponse() 81 | { 82 | $this->respond([ 83 | 'status' => 'success', 84 | 'failed_count' => $this->import_class->getFailedCount(), 85 | 'import_count' => $this->import_class->getImportCount(), 86 | 'complete_rows' => $this->import_class->getCompleteCount(), 87 | 'total_rows' => $this->transient['row_count'] 88 | ]); 89 | } 90 | } -------------------------------------------------------------------------------- /app/Services/Import/Listeners/ImportAJAXListenerBase.php: -------------------------------------------------------------------------------- 1 | nonce_validator = new NonceValidator; 17 | $this->validateNonce(); 18 | } 19 | 20 | /** 21 | * Validate the Nonce 22 | */ 23 | protected function validateNonce() 24 | { 25 | try { 26 | $this->nonce_validator->validate($_POST['nonce'], 'wpsl_locator-locator-nonce'); 27 | } catch ( \Exception $e ){ 28 | return $this->error($e->getMessage()); 29 | } 30 | } 31 | 32 | /** 33 | * Send a Response 34 | */ 35 | protected function respond($json) 36 | { 37 | wp_send_json($json); 38 | die(); 39 | } 40 | 41 | /** 42 | * Send an Error response 43 | */ 44 | protected function error($error) 45 | { 46 | wp_send_json(['status' => 'error', 'message' => $error]); 47 | die(); 48 | } 49 | } -------------------------------------------------------------------------------- /app/Services/Import/Listeners/ImportListenerBase.php: -------------------------------------------------------------------------------- 1 | nonce_validator = new NonceValidator; 16 | $this->validateNonce(); 17 | } 18 | 19 | /** 20 | * Validate the Nonce 21 | */ 22 | protected function validateNonce() 23 | { 24 | try { 25 | $this->nonce_validator->validate($_POST['nonce'], 'wpsl-import-nonce'); 26 | } catch ( \Exception $e ){ 27 | return $this->error($e->getMessage()); 28 | } 29 | } 30 | 31 | /** 32 | * Check Capabilities 33 | */ 34 | protected function validateUser() 35 | { 36 | if ( !current_user_can('delete_others_posts') ) return $this->error(__('You do not have the necessary capabilities to undo an import. Contact your site administrator to perform this action.', 'simple-locator')); 37 | } 38 | 39 | /** 40 | * Redirect to next step on success 41 | */ 42 | protected function success($step) 43 | { 44 | $url = admin_url('options-general.php?page=wp_simple_locator&tab=import&step=' . $step); 45 | return header('Location:' . $url); 46 | } 47 | 48 | /** 49 | * Redirect to current step with error 50 | */ 51 | protected function error($error) 52 | { 53 | $url = admin_url('options-general.php?page=wp_simple_locator&tab=import&error=' . $error); 54 | header('Location:' . $url); 55 | die(); 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /app/Services/Import/Listeners/ImportTemplateRemove.php: -------------------------------------------------------------------------------- 1 | validateUser(); 19 | $this->setID(); 20 | $this->removeImport(); 21 | $this->success(null); 22 | } 23 | 24 | /** 25 | * Set the Import ID & Post IDs 26 | */ 27 | private function setID() 28 | { 29 | $this->template_id = ( isset($_POST['template_remove_id']) ) ? intval(sanitize_text_field($_POST['template_remove_id'])) : 0; 30 | } 31 | 32 | /** 33 | * Delete the Posts 34 | */ 35 | private function removeImport() 36 | { 37 | wp_delete_post($this->template_id, true); 38 | } 39 | 40 | /** 41 | * Redirect back on success 42 | */ 43 | protected function success($step) 44 | { 45 | $url = admin_url('options-general.php?page=wp_simple_locator&tab=import&success=' . __('Import template successfully removed.', 'simple-locator')); 46 | return header('Location:' . $url); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /app/Services/Import/Listeners/ImportTemplateSave.php: -------------------------------------------------------------------------------- 1 | validateUser(); 16 | $this->validate(); 17 | $this->getImport(); 18 | $this->save(); 19 | $this->returnSuccess(); 20 | } 21 | 22 | /** 23 | * Validate the data 24 | */ 25 | private function validate() 26 | { 27 | if ( !isset($_POST['template_name']) || $_POST['template_name'] == '' ) return $this->error(__('A template name is required.', 'simple-locator')); 28 | if ( !isset($_POST['template_id']) || $_POST['template_id'] == '' ) return $this->error(__('An import ID is required.', 'simple-locator')); 29 | } 30 | 31 | /** 32 | * Get the import 33 | */ 34 | private function getImport() 35 | { 36 | $post = null; 37 | $q = new \WP_Query([ 38 | 'post_type' => 'wpslimport', 39 | 'posts_per_page' => 1, 40 | 'fields' => 'ids', 41 | 'p' => intval(sanitize_text_field($_POST['template_id'])) 42 | ]); 43 | if ( $q->have_posts() ) : 44 | $post = $q->posts; 45 | endif; wp_reset_postdata(); 46 | if ( !$post ) return $this->error(__('The specified import could not be found.', 'simple-locator')); 47 | $this->import_post_id = $post[0]; 48 | } 49 | 50 | /** 51 | * Save the template 52 | */ 53 | private function save() 54 | { 55 | $import_data = get_post_meta($this->import_post_id, 'wpsl_import_data', true); 56 | $title = sanitize_text_field($_POST['template_name']); 57 | $template = [ 58 | 'post_title' => $title, 59 | 'post_status' => 'publish', 60 | 'post_type' => 'wpslimporttemplate' 61 | ]; 62 | $post_id = wp_insert_post($template); 63 | add_post_meta($post_id, 'wpsl_import_data', $import_data); 64 | } 65 | 66 | /** 67 | * Redirect back on success 68 | */ 69 | private function returnSuccess() 70 | { 71 | $url = admin_url('options-general.php?page=wp_simple_locator&tab=import&success=' . __('Import template successfully saved.', 'simple-locator')); 72 | return header('Location:' . $url); 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /app/Services/Import/Listeners/RedoImport.php: -------------------------------------------------------------------------------- 1 | import_repo = new ImportRepository; 28 | $this->setID(); 29 | $this->resetTransient(); 30 | $this->success(3); 31 | } 32 | 33 | /** 34 | * Set the Import ID & Post IDs 35 | */ 36 | private function setID() 37 | { 38 | $this->import_id = ( isset($_POST['redo_import_id']) ) ? intval(sanitize_text_field($_POST['redo_import_id'])) : 0; 39 | } 40 | 41 | /** 42 | * Delete the Posts 43 | */ 44 | private function resetTransient() 45 | { 46 | $import_data = $this->import_repo->getImportData($this->import_id); 47 | $import_data['error_rows'] = []; 48 | $import_data['complete_rows'] = 0; 49 | $import_data['last_imported'] = 0; 50 | $import_data['post_ids'] = []; 51 | $import_data['complete'] = false; 52 | $import_data['last_imported_time'] = time(); 53 | set_transient('wpsl_import_file', $import_data, 1 * YEAR_IN_SECONDS); 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /app/Services/Import/Listeners/RemoveImport.php: -------------------------------------------------------------------------------- 1 | validateUser(); 19 | $this->setID(); 20 | $this->removeImport(); 21 | $this->success(null); 22 | } 23 | 24 | /** 25 | * Set the Import ID & Post IDs 26 | */ 27 | private function setID() 28 | { 29 | $this->import_id = ( isset($_POST['remove_import_id']) ) ? intval(sanitize_text_field($_POST['remove_import_id'])) : 0; 30 | } 31 | 32 | /** 33 | * Delete the Posts 34 | */ 35 | private function removeImport() 36 | { 37 | wp_delete_post($this->import_id, true); 38 | } 39 | 40 | /** 41 | * Redirect back on success 42 | */ 43 | protected function success($step) 44 | { 45 | $url = admin_url('options-general.php?page=wp_simple_locator&tab=import&success=' . __('Import successfully removed.', 'simple-locator')); 46 | return header('Location:' . $url); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /app/Services/Import/Listeners/UndoImport.php: -------------------------------------------------------------------------------- 1 | validateUser(); 33 | $this->import_repo = new ImportRepository; 34 | $this->setIDs(); 35 | $this->deletePosts(); 36 | $this->success(null); 37 | } 38 | 39 | /** 40 | * Set the Import ID & Post IDs 41 | */ 42 | private function setIDs() 43 | { 44 | $this->import_id = ( isset($_POST['undo_import_id']) ) ? intval(sanitize_text_field($_POST['undo_import_id'])) : 0; 45 | $this->post_ids = $this->import_repo->getImportedPostIDs($this->import_id); 46 | } 47 | 48 | /** 49 | * Delete the Posts 50 | */ 51 | private function deletePosts() 52 | { 53 | foreach($this->post_ids as $id){ 54 | wp_delete_post($id, true); 55 | } 56 | wp_delete_post($this->import_id, true); 57 | } 58 | 59 | /** 60 | * Redirect back on success 61 | */ 62 | protected function success($step) 63 | { 64 | $url = admin_url('options-general.php?page=wp_simple_locator&tab=import&success=' . __('Import successfully undone. All post data has been removed.', 'simple-locator')); 65 | return header('Location:' . $url); 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /app/Services/LocationSearch/LocationSearchValidator.php: -------------------------------------------------------------------------------- 1 | getIP(); 22 | 23 | global $wpdb; 24 | $table = $wpdb->prefix . 'simple_locator_history'; 25 | $wpdb->insert( 26 | $table, 27 | array( 28 | 'user_ip' => $ip, 29 | 'search_lat' => $latitude, 30 | 'search_lng' => $longitude, 31 | 'search_term' => $address, 32 | 'search_term_formatted' => $formatted_address, 33 | 'distance' => $distance 34 | ), 35 | array( 36 | '%s', 37 | '%s', 38 | '%s', 39 | '%s', 40 | '%s', 41 | '%s' 42 | ) 43 | ); 44 | } 45 | 46 | /** 47 | * Get the user's IP 48 | */ 49 | private function getIP() 50 | { 51 | $ipaddress = ''; 52 | if (getenv('HTTP_CLIENT_IP')) 53 | $ipaddress = getenv('HTTP_CLIENT_IP'); 54 | else if(getenv('HTTP_X_FORWARDED_FOR')) 55 | $ipaddress = getenv('HTTP_X_FORWARDED_FOR'); 56 | else if(getenv('HTTP_X_FORWARDED')) 57 | $ipaddress = getenv('HTTP_X_FORWARDED'); 58 | else if(getenv('HTTP_FORWARDED_FOR')) 59 | $ipaddress = getenv('HTTP_FORWARDED_FOR'); 60 | else if(getenv('HTTP_FORWARDED')) 61 | $ipaddress = getenv('HTTP_FORWARDED'); 62 | else if(getenv('REMOTE_ADDR')) 63 | $ipaddress = getenv('REMOTE_ADDR'); 64 | else 65 | $ipaddress = 'UNKNOWN'; 66 | return $ipaddress; 67 | } 68 | } -------------------------------------------------------------------------------- /app/Services/Validation/NonceValidator.php: -------------------------------------------------------------------------------- 1 | ' . $field['label'] . ''; 14 | $out .= 'attributes($field); 16 | $out .= ' />'; 17 | } 18 | if ( $field['type'] == 'select' ){ 19 | $out .= ''; 20 | $out .= ''; 28 | } 29 | if ( $field['type'] == 'textarea' ){ 30 | $out .= ''; 31 | $out .= ''; 34 | } 35 | return $out; 36 | } 37 | 38 | /** 39 | * Add Attributes to the Output 40 | */ 41 | protected function attributes($field) 42 | { 43 | $out = ''; 44 | foreach ( $field['attributes'] as $attr ){ 45 | if ( is_array($attr) ) $out .= ' ' . $attr['attr'] . '="' . $attr['value'] . '"'; 46 | if ( !is_array($attr) ) $out .= ' ' . $attr; 47 | } 48 | return $out; 49 | } 50 | } -------------------------------------------------------------------------------- /app/WPData/MetaFields.php: -------------------------------------------------------------------------------- 1 | form_fields = new FormFields; 29 | add_action( 'admin_init', [$this, 'setFields']); 30 | add_action( 'add_meta_boxes', [$this, 'metaBox']); 31 | add_action( 'save_post', [$this, 'savePost']); 32 | } 33 | 34 | /** 35 | * Set the Fields for use in custom meta 36 | */ 37 | public function setFields() 38 | { 39 | $this->fields = $this->form_fields->order(); 40 | $this->fields[] = 'custom_geo'; 41 | $this->fields[] = 'latitude'; 42 | $this->fields[] = 'longitude'; 43 | $this->fields[] = 'custom_geo'; 44 | } 45 | 46 | /** 47 | * Register the Meta Box 48 | */ 49 | public function metaBox() 50 | { 51 | add_meta_box( 52 | 'wpsl-meta-box', 53 | apply_filters('simple_locator_meta_fields_title', __('Location Information', 'simple-locator')), 54 | [$this, 'displayMeta'], 55 | $this->getPostType(), 56 | 'normal', 57 | 'high' 58 | ); 59 | } 60 | 61 | /** 62 | * Meta Boxes for Output 63 | */ 64 | public function displayMeta($post) 65 | { 66 | $this->setData($post); 67 | include( \SimpleLocator\Helpers::view('post-meta/location-meta') ); 68 | } 69 | 70 | /** 71 | * Set the Field Data 72 | */ 73 | private function setData($post) 74 | { 75 | foreach ( $this->fields as $key => $field ) 76 | { 77 | $this->meta[$field] = get_post_meta( $post->ID, 'wpsl_' . $field, true ); 78 | } 79 | } 80 | 81 | /** 82 | * Save the custom post meta 83 | */ 84 | public function savePost( $post_id ) 85 | { 86 | if ( get_post_type($post_id) == $this->getPostType() ) : 87 | if( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return $post_id; 88 | if( !isset( $_POST['wpsl_meta_box_nonce'] ) || !wp_verify_nonce( $_POST['wpsl_meta_box_nonce'], 'my_wpsl_meta_box_nonce' ) ) return $post_id; 89 | 90 | // Save Custom Fields 91 | foreach ( $this->fields as $key => $field ) 92 | { 93 | $fieldName = 'wpsl_' . $field; 94 | if ( isset($_POST[$fieldName]) && $_POST[$fieldName] !== "" ) 95 | update_post_meta( $post_id, $fieldName, esc_attr( $_POST[$fieldName] ) ); 96 | if ( isset($_POST[$fieldName]) && $_POST[$fieldName] == "" ) 97 | delete_post_meta( $post_id, $fieldName ); 98 | } 99 | endif; 100 | } 101 | 102 | /** 103 | * Get the Location Post Type 104 | */ 105 | private function getPostType() 106 | { 107 | $posttype = get_option('wpsl_post_type'); 108 | $hide_meta = get_option('wpsl_hide_default_fields'); 109 | if ( $hide_meta == 'true' ) return ' '; 110 | return ( $posttype !== "" ) ? $posttype : 'location'; 111 | } 112 | } -------------------------------------------------------------------------------- /app/WPData/RegisterApiEndpoints.php: -------------------------------------------------------------------------------- 1 | 'GET', 22 | 'callback' => [$this, 'searchLocations'], 23 | 'permission_callback' => '__return_true' 24 | ]); 25 | register_rest_route( 'simplelocator/v2', '/locations/', [ 26 | 'methods' => 'GET', 27 | 'callback' => [$this, 'getLocations'], 28 | 'permission_callback' => '__return_true' 29 | ]); 30 | register_rest_route( 'simplelocator/v2', '/states/', [ 31 | 'methods' => 'GET', 32 | 'callback' => [$this, 'getStates'], 33 | 'permission_callback' => '__return_true' 34 | ]); 35 | register_rest_route( 'simplelocator/v2', '/export-templates/', [ 36 | 'methods' => 'GET', 37 | 'callback' => [$this, 'getExportTemplates'], 38 | 'permission_callback' => '__return_true' 39 | ]); 40 | } 41 | 42 | /** 43 | * Get locations from a search request 44 | */ 45 | public function searchLocations(\WP_REST_Request $request) 46 | { 47 | $search = new APILocationSearch($request->get_query_params()); 48 | try { 49 | $results = $search->getResults(); 50 | return $results; 51 | } catch ( \Exception $e ){ 52 | return [ 53 | 'status' => 'error', 54 | 'message' => $e->getMessage() 55 | ]; 56 | } 57 | } 58 | 59 | /** 60 | * Get all locations 61 | */ 62 | public function getLocations(\WP_REST_Request $request) 63 | { 64 | $locations = new APIAllLocations($request->get_query_params()); 65 | return $locations->getLocations(); 66 | } 67 | 68 | /** 69 | * Get all states 70 | */ 71 | public function getStates(\WP_REST_Request $request) 72 | { 73 | try { 74 | return (new StateListing)->getStates($request->get_query_params()); 75 | } catch ( \Exception $e ){ 76 | return [ 77 | 'status' => 'error', 78 | 'message' => $e->getMessage() 79 | ]; 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /app/WPData/UploadFilter.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /assets/fonts/simplelocator.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/fonts/simplelocator.ttf -------------------------------------------------------------------------------- /assets/fonts/simplelocator.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/fonts/simplelocator.woff -------------------------------------------------------------------------------- /assets/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/icon-128x128.png -------------------------------------------------------------------------------- /assets/images/datepicker-arrow-next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/images/datepicker-arrow-next.png -------------------------------------------------------------------------------- /assets/images/datepicker-arrow-prev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/images/datepicker-arrow-prev.png -------------------------------------------------------------------------------- /assets/images/loading-settings.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/images/loading-settings.gif -------------------------------------------------------------------------------- /assets/images/loading-spinner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | spinner 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/images/loading-white.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/images/loading-white.gif -------------------------------------------------------------------------------- /assets/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/images/loading.gif -------------------------------------------------------------------------------- /assets/images/map-marker-blue.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /assets/images/map-marker.svg: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /assets/images/map-pin-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/images/map-pin-blue.png -------------------------------------------------------------------------------- /assets/images/map-pin-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/images/map-pin-red.png -------------------------------------------------------------------------------- /assets/images/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/images/screenshot-2.png -------------------------------------------------------------------------------- /assets/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/images/screenshot.png -------------------------------------------------------------------------------- /assets/js/simple-locator-admin-maps.js: -------------------------------------------------------------------------------- 1 | jQuery(function(n){n(document).ready(function(){}),n(document).on("change","#wpsl_map_styles_type",function(){n(this).val()}),n(document).on("change","#wpsl_custom_map_options",function(){n(this).is(":checked")?n("#wpsl_map_options").show():n("#wpsl_map_options").hide()})}); -------------------------------------------------------------------------------- /assets/js/simple-locator-admin-search-history.js: -------------------------------------------------------------------------------- 1 | var WPSL_SearchHistoryMap=function(o,e){var n=this,t=jQuery;return n.locations=o,n.container=e,n.bindEvents=function(){t("[data-wpsl-history-per-page]").on("change",function(){t(this).parents("form").submit()})},n.loadmap=function(){var o,e=wpsl_locator_searchhistory.mapstyles,t=wpsl_locator_searchhistory.mappin?wpsl_locator_searchhistory.mappin:"",a=new google.maps.LatLngBounds,r=n.locations,s={mapTypeId:"roadmap",mapTypeControl:!1,zoom:8,styles:e,scrollwheel:!1,panControl:!1},i=new google.maps.InfoWindow,l=new google.maps.Map(document.getElementById(n.container),s);for(o=0;o",r[o].infowindow+="

",r[o].infowindow+=wpsl_locator_searchhistory.searchTerm+": "+r[o].search_term,r[o].infowindow+="
"+wpsl_locator_searchhistory.searchTermFormatted+": "+r[o].search_term_formatted,r[o].infowindow+="
"+wpsl_locator_searchhistory.userIp+": "+r[o].user_ip,r[o].infowindow+="
"+wpsl_locator_searchhistory.distance+": "+r[o].distance,r[o].infowindow+="

",google.maps.event.addListener(c,"click",function(o,e){return function(){i.setContent(r[e].infowindow),i.open(l,o)}}(c,o)),l.fitBounds(a);var d=google.maps.event.addListener(l,"idle",function(){r.length<2&&l.setZoom(13),google.maps.event.removeListener(d)})}var m=google.maps.event.addListener(l,"bounds_changed",function(o){google.maps.event.removeListener(m)})},n.bindEvents()}; -------------------------------------------------------------------------------- /assets/js/simple-locator-non-ajax-results.js: -------------------------------------------------------------------------------- 1 | var NonAjaxResults=function(){var t=this,e=jQuery;return t.results=e("*[data-wpsl-result]"),t.resultData=[],t.init=function(){t.setResultData()},t.setResultData=function(){e.each(t.results,function(a,o){t.resultData[a]={latitude:e(this).attr("data-lat"),longitude:e(this).attr("data-lng"),permalink:e(this).attr("data-permalink"),title:e(this).attr("data-title")}}),t.loadMap()},t.loadMap=function(){markers=[];var a,o=t.resultData,n=wpsl_locator.mapstyles,l=wpsl_locator.mappin?wpsl_locator.mappin:"",s=new google.maps.LatLngBounds,r={mapTypeId:"roadmap",mapTypeControl:!1,zoom:8,styles:n,panControl:!1},i=new google.maps.InfoWindow,p=e(wpsl_locator_options.mapcont)[0];e(p).show();var g=new google.maps.Map(p,r);for(a=0;a"+o[e].title+'

'+wpsl_locator.viewlocation+"

"),i.open(g,t)}}(m,a)),markers.push(m),g.fitBounds(s);var d=google.maps.event.addListener(g,"idle",function(){o.length<2&&g.setZoom(13),google.maps.event.removeListener(d)})}var c=google.maps.event.addListener(g,"bounds_changed",function(t){google.maps.event.removeListener(c)});googlemap=g},t.init()}; -------------------------------------------------------------------------------- /assets/js/source/admin/simple-locator-admin.factory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Admin Functionality for Simple Locator 3 | * @package Simple Locator 4 | * @author Kyle Phillips - https://github.com/kylephillips 5 | * 6 | */ 7 | 8 | jQuery(document).ready(function(){ 9 | new SimpleLocatorAdmin.Factory; 10 | }); 11 | 12 | var SimpleLocatorAdmin = SimpleLocatorAdmin || {}; 13 | 14 | // API Endpoints 15 | SimpleLocatorAdmin.endpoints = { 16 | 17 | } 18 | 19 | /** 20 | * Primary Simple Locator Class 21 | */ 22 | SimpleLocatorAdmin.Factory = function() 23 | { 24 | var self = this; 25 | var $ = jQuery; 26 | 27 | self.init = function() 28 | { 29 | new SimpleLocatorAdmin.Modals; 30 | new SimpleLocatorAdmin.PostEdit; 31 | new SimpleLocatorAdmin.GeneralSettings; 32 | new SimpleLocatorAdmin.PostType; 33 | new SimpleLocatorAdmin.MapDisplay; 34 | new SimpleLocatorAdmin.ResultsDisplay; 35 | new SimpleLocatorAdmin.DefaultMap; 36 | new SimpleLocatorAdmin.SearchHistory; 37 | new SimpleLocatorAdmin.ImportTest; 38 | new SimpleLocatorAdmin.ImportUpload; 39 | new SimpleLocatorAdmin.ImportColumnMap; 40 | new SimpleLocatorAdmin.Import; 41 | new SimpleLocatorAdmin.ListingMap; 42 | new SimpleLocatorAdmin.ExportTemplates; 43 | new SimpleLocatorAdmin.QuickEdit; 44 | } 45 | 46 | return self.init(); 47 | } 48 | 49 | /** 50 | * Global function catches Google API Errors 51 | * @link https://developers.google.com/maps/documentation/javascript/events#auth-errors 52 | */ 53 | var editScreenGoogleApiError = false; 54 | function gm_authFailure(e){ 55 | editScreenGoogleApiError = true; 56 | } 57 | -------------------------------------------------------------------------------- /assets/js/source/admin/simple-locator-admin.import-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test that the Google API key has the geocode service enabled 3 | * @package simple-locator 4 | */ 5 | var SimpleLocatorAdmin = SimpleLocatorAdmin || {}; 6 | SimpleLocatorAdmin.ImportTest = function() 7 | { 8 | var self = this; 9 | var $ = jQuery; 10 | 11 | self.selectors = { 12 | button : 'data-simple-locator-import-test-button', 13 | message : 'data-simple-locator-import-test-message' 14 | } 15 | 16 | self.bindEvents = function() 17 | { 18 | $(document).on('click', '[' + self.selectors.button+ ']', function(e){ 19 | e.preventDefault(); 20 | self.test(); 21 | }) 22 | } 23 | 24 | /** 25 | * Test 26 | */ 27 | self.test = function() 28 | { 29 | $.ajax({ 30 | url: ajaxurl, 31 | type: 'post', 32 | datatype: 'json', 33 | data: { 34 | action: 'wpslimporttest', 35 | nonce: wpsl_locator.locatorNonce 36 | }, 37 | success: function(data){ 38 | if ( data.status === 'testing' ){ 39 | console.log(data); 40 | return; 41 | } 42 | if ( data.status === 'success' ){ 43 | $('[' + self.selectors.message + ']').text(data.message).addClass('wpsl-alert'); 44 | return; 45 | } 46 | if (data.status === 'error') { 47 | $('[' + self.selectors.message + ']').text(data.message).addClass('wpsl-error'); 48 | return; 49 | } 50 | } 51 | }); 52 | } 53 | 54 | 55 | return self.bindEvents(); 56 | } -------------------------------------------------------------------------------- /assets/js/source/admin/simple-locator-admin.modals.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Modal Windows 3 | * 4 | * @package Simple Locator 5 | * @author Kyle Phillips - https://github.com/kylephillips 6 | * 7 | * To use, include a modal backdrop and modal content window with the appropriate data-attributes 8 | * The data attributes should match the value of the toggle buttons data-modal-toggle attribute 9 | */ 10 | var SimpleLocatorAdmin = SimpleLocatorAdmin || {}; 11 | SimpleLocatorAdmin.Modals = function() 12 | { 13 | var plugin = this; 14 | var $ = jQuery; 15 | 16 | plugin.activeBtn = ''; 17 | plugin.activeModal = ''; 18 | plugin.modalOpen = false; 19 | 20 | plugin.selectors = { 21 | toggleBtn : '[data-wpsl-modal-toggle]', 22 | backdrop : '[data-wpsl-modal-backdrop]', 23 | closeBtn : '[data-wpsl-modal-close]' 24 | } 25 | 26 | plugin.bindEvents = function() 27 | { 28 | $(document).on('click', plugin.selectors.toggleBtn, function(e){ 29 | e.preventDefault(); 30 | plugin.activeBtn = $(this); 31 | plugin.openModal(); 32 | }); 33 | $(document).on('click', plugin.selectors.closeBtn, function(e){ 34 | e.preventDefault(); 35 | plugin.closeModals(); 36 | }); 37 | $(document).on('open-wpsl-modal-manual', function(e, modal){ 38 | plugin.activeModal = $('*[data-wpsl-modal="' + modal + '"]'); 39 | console.log(plugin.activeModal); 40 | plugin.openModal(); 41 | }); 42 | $(document).on('close-wpsl-modal-manual', function(e){ 43 | plugin.closeModals(); 44 | }); 45 | $(document).on('click', plugin.selectors.backdrop, function(e){ 46 | plugin.closeModals(); 47 | }); 48 | } 49 | 50 | /** 51 | * Open the Modal Window 52 | */ 53 | plugin.openModal = function() 54 | { 55 | if ( plugin.modalOpen ){ 56 | plugin.closeModals(); 57 | return; 58 | } 59 | if ( $(plugin.activeBtn).length > 0 ){ 60 | var modal = $(plugin.activeBtn).attr('data-wpsl-modal-toggle'); 61 | plugin.activeModal = $('*[data-wpsl-modal="' + modal + '"]'); 62 | } 63 | $(plugin.activeModal).addClass('active'); 64 | plugin.modalOpen = true; 65 | $(document).trigger('open-wpsl-modal', [plugin.activeBtn, plugin.activeModal]); 66 | } 67 | 68 | /** 69 | * Close the Modal Window 70 | */ 71 | plugin.closeModals = function() 72 | { 73 | plugin.modalOpen = false; 74 | $('[data-wpsl-modal]').removeClass('active'); 75 | $(document).trigger('close-wpsl-modal', [plugin.activeBtn, plugin.activeModal]); 76 | } 77 | 78 | return plugin.bindEvents(); 79 | } -------------------------------------------------------------------------------- /assets/js/source/admin/simple-locator-admin.results-display.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Results Display Settings Page 3 | * @package simple-locator 4 | */ 5 | var SimpleLocatorAdmin = SimpleLocatorAdmin || {}; 6 | SimpleLocatorAdmin.ResultsDisplay = function() 7 | { 8 | var self = this; 9 | var $ = jQuery; 10 | 11 | self.bindEvents = function() 12 | { 13 | $(document).ready(function(){ 14 | self.enableDatePicker(); 15 | }); 16 | $(document).on('click', '.wpsl-field-add', function(e){ 17 | e.preventDefault(); 18 | self.addResultField(); 19 | }); 20 | $(document).on('click', '.wpsl-post-field-add', function(e){ 21 | e.preventDefault(); 22 | self.addPostField(); 23 | }); 24 | } 25 | 26 | self.addResultField = function() 27 | { 28 | var field = '[' + $('#wpsl-fields').val() + ']'; 29 | tinymce.activeEditor.execCommand('mceInsertContent', false, field); 30 | } 31 | 32 | self.addPostField = function() 33 | { 34 | var field = '[' + $('#wpsl-post-fields').val() + ']'; 35 | tinymce.activeEditor.execCommand('mceInsertContent', false, field); 36 | } 37 | 38 | self.enableDatePicker = function() 39 | { 40 | $('[data-date-picker]').datepicker({ 41 | beforeShow: function(input, inst){ 42 | $('#ui-datepicker-div').addClass('wpsl-datepicker'); 43 | } 44 | }); 45 | } 46 | 47 | return self.bindEvents(); 48 | } -------------------------------------------------------------------------------- /assets/js/source/admin/simple-locator-admin.search-history.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Search History Map 3 | */ 4 | var SimpleLocatorAdmin = SimpleLocatorAdmin || {}; 5 | SimpleLocatorAdmin.SearchHistory = function() 6 | { 7 | var self = this; 8 | var $ = jQuery; 9 | 10 | self.locations; 11 | 12 | self.selectors = { 13 | container : 'data-simple-locator-search-history-map', 14 | perPage : 'data-simple-locator-history-per-page' 15 | } 16 | 17 | self.bindEvents = function() 18 | { 19 | $(document).ready(function(){ 20 | if ( $('[' + self.selectors.container + ']').length < 1 ) return; 21 | self.locations = search_history_locations; 22 | self.loadMap(); 23 | }); 24 | $('[' + self.selectors.perPage + ']').on('change', function(){ 25 | $(this).parents('form').submit(); 26 | }); 27 | } 28 | 29 | /** 30 | * Load the Map 31 | */ 32 | self.loadMap = function() 33 | { 34 | var mapstyles = wpsl_locator_searchhistory.mapstyles; 35 | var mappin = ( wpsl_locator_searchhistory.mappin ) ? wpsl_locator_searchhistory.mappin : ''; 36 | var bounds = new google.maps.LatLngBounds(); 37 | var locations = self.locations; 38 | var container = $('[' + self.selectors.container + ']'); 39 | 40 | var mapOptions = { 41 | mapTypeId: 'roadmap', 42 | mapTypeControl: false, 43 | zoom: 8, 44 | styles: mapstyles, 45 | scrollwheel: false, 46 | panControl : false 47 | } 48 | 49 | var infoWindow = new google.maps.InfoWindow(), marker, i; 50 | var map = new google.maps.Map( container[0], mapOptions ); 51 | 52 | // Loop through array of markers & place each one on the map 53 | for( i = 0; i < locations.length; i++ ) { 54 | var position = new google.maps.LatLng(locations[i].latitude, locations[i].longitude); 55 | bounds.extend(position); 56 | 57 | var marker = new google.maps.Marker({ 58 | position: position, 59 | map: map, 60 | title: locations[i].search_term, 61 | icon: mappin 62 | }); 63 | 64 | locations[i].infowindow = '

' + locations[i].date + '

'; 65 | locations[i].infowindow += '

'; 66 | locations[i].infowindow += wpsl_locator_searchhistory.searchTerm + ': ' + locations[i].search_term; 67 | locations[i].infowindow += '
' + wpsl_locator_searchhistory.searchTermFormatted + ': ' + locations[i].search_term_formatted; 68 | locations[i].infowindow += '
' + wpsl_locator_searchhistory.userIp + ': ' + locations[i].user_ip; 69 | locations[i].infowindow += '
' + wpsl_locator_searchhistory.distance + ': ' + locations[i].distance; 70 | locations[i].infowindow += '

'; 71 | 72 | // Info window for each marker 73 | google.maps.event.addListener(marker, 'click', (function(marker, i){ 74 | return function() { 75 | infoWindow.setContent(locations[i].infowindow); 76 | infoWindow.open(map, marker); 77 | } 78 | })(marker, i)); 79 | 80 | // Center the Map 81 | map.fitBounds(bounds); 82 | var listener = google.maps.event.addListener(map, "idle", function() { 83 | if ( locations.length < 2 ) { 84 | map.setZoom(13); 85 | } 86 | google.maps.event.removeListener(listener); 87 | }); 88 | } 89 | 90 | // Fit the map bounds to all the pins 91 | var boundsListener = google.maps.event.addListener((map), 'bounds_changed', function(event) { 92 | google.maps.event.removeListener(boundsListener); 93 | }); 94 | 95 | } 96 | 97 | return self.bindEvents(); 98 | } -------------------------------------------------------------------------------- /assets/js/source/simple-locator.default-map.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Show a Default Map if the option is selected 3 | * @package simple-locator 4 | */ 5 | var SimpleLocator = SimpleLocator || {}; 6 | SimpleLocator.DefaultMap = function() 7 | { 8 | var self = this; 9 | var $ = jQuery; 10 | 11 | self.mapIndex; 12 | 13 | self.selectors = { 14 | map : 'data-simple-locator-default-enabled' 15 | } 16 | 17 | self.bindEvents = function() 18 | { 19 | $(document).ready(function(){ 20 | if ( wpsl_locator.default_user_center === 'true' ) return; // See geolocation for user-centered automatic maps 21 | self.queueDefaultMaps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Queue the default map 27 | */ 28 | self.queueDefaultMaps = function(errors) 29 | { 30 | var maps = $('[' + self.selectors.map + ']'); 31 | $.each(maps, function(){ 32 | var map = $(this); 33 | self.setMapIndex(map); 34 | self.loadDefault(map); 35 | }); 36 | } 37 | 38 | /** 39 | * Set the map index for a map 40 | */ 41 | self.setMapIndex = function(map) 42 | { 43 | var wrappers = $('[' + SimpleLocator.selectors.resultsWrapper + ']'); 44 | var currentWrapper = $(map).parents('[' + SimpleLocator.selectors.resultsWrapper + ']'); 45 | self.mapIndex = $(wrappers).index(currentWrapper); 46 | } 47 | 48 | /** 49 | * Load the default map 50 | */ 51 | self.loadDefault = function(map) 52 | { 53 | var latitude = wpsl_locator.default_latitude; 54 | var longitude = wpsl_locator.default_longitude; 55 | $(map).removeClass('loading'); 56 | 57 | var center = new google.maps.LatLng(latitude, longitude); 58 | var mapOptions = { 59 | center: center, 60 | zoom: parseInt(wpsl_locator.default_zoom), 61 | styles: wpsl_locator.mapstyles 62 | } 63 | // Override options if custom options are set 64 | if ( wpsl_locator.custom_map_options === '1' ) mapOptions = wpsl_locator.map_options; 65 | mapOptions.center = center; 66 | SimpleLocator.maps[self.mapIndex] = new google.maps.Map(map[0],mapOptions); 67 | } 68 | 69 | return self.bindEvents(); 70 | } -------------------------------------------------------------------------------- /assets/js/source/simple-locator.errors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Error Handling 3 | * @package simple-locator 4 | */ 5 | var SimpleLocator = SimpleLocator || {}; 6 | SimpleLocator.Errors = function() 7 | { 8 | var self = this; 9 | var $ = jQuery; 10 | 11 | self.error; 12 | self.form; 13 | self.formContainer; 14 | 15 | self.bindEvents = function() 16 | { 17 | $(document).on('simple-locator-error', function(e, form, message){ 18 | self.form = form; 19 | self.formContainer = $(form).parents('[' + SimpleLocator.selectors.formContainer + ']'); 20 | self.toggleError(message); 21 | $(document).trigger('simple-locator-error-message', [form, message]); 22 | self.clearMap(); 23 | wpsl_error(message, self.form); 24 | }); 25 | } 26 | 27 | self.toggleError = function(message) 28 | { 29 | $(self.formContainer).removeClass('loading').addClass('has-error'); 30 | $(self.formContainer).find('[' + SimpleLocator.selectors.formError + ']').text(message).show(); 31 | } 32 | 33 | /** 34 | * Remove all the markers from the map 35 | */ 36 | self.clearMap = function() 37 | { 38 | var wrappers = $('[' + SimpleLocator.selectors.resultsWrapper + ']'); 39 | var mapIndex = $(self.formContainer).index(wrappers); 40 | if ( !SimpleLocator.markers[mapIndex] ) SimpleLocator.markers[mapIndex] = []; 41 | for (var i = 0; i < SimpleLocator.markers[mapIndex].length; i++){ 42 | SimpleLocator.markers[mapIndex][i].setMap(null); 43 | } 44 | SimpleLocator.markers[mapIndex] = []; 45 | if ( wpsl_locator.includeuserpin === '' ) return; 46 | if ( !SimpleLocator.userPin[mapIndex] ) return; 47 | SimpleLocator.userPin[mapIndex].setMap(null); 48 | SimpleLocator.userPin[mapIndex] = null; 49 | } 50 | 51 | return self.bindEvents(); 52 | } -------------------------------------------------------------------------------- /assets/js/source/simple-locator.factory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Primary Simple Locator Initialization 3 | * @package Simple Locator 4 | * @author Kyle Phillips - https://github.com/kylephillips 5 | * 6 | * Document Events: 7 | * simple-locator-error[form, message] 8 | * simple-locator-address-geocoded[results, form] 9 | * simple-locator-autocomplete-changed[place, form] 10 | * simple-locator-form-success[results, form] 11 | * simple-locator-infowindow-opened[mapIndex, markerIndex] 12 | * simple-locator-geolocation-available-set[available] 13 | * simple-locator-geolocation-success[form] 14 | * simple-locator-marker-clicked[marker, markerIndex, form, locationId] 15 | * simple-locator-results-rendered[listIndex, form] 16 | */ 17 | 18 | jQuery(document).ready(function(){ 19 | new SimpleLocator.Factory; 20 | }); 21 | 22 | var SimpleLocator = SimpleLocator || {}; 23 | 24 | // DOM Selectors 25 | SimpleLocator.selectors = { 26 | resultsWrapper : 'data-simple-locator-results-wrapper', 27 | form : 'data-simple-locator-form', 28 | formContainer : 'data-simple-locator-form-container', 29 | formError : 'data-simple-locator-form-error', 30 | results : 'data-simple-locator-results', 31 | map : 'data-simple-locator-map', 32 | mapNonAjax : 'data-simple-locator-map-non-ajax', 33 | inputAddress : 'data-simple-locator-input-address', 34 | inputLatitude : 'data-simple-locator-input-latitude', 35 | inputLongitude : 'data-simple-locator-input-longitude', 36 | inputUnit : 'data-simple-locator-input-unit', 37 | inputDistance : 'data-simple-locator-input-distance', 38 | inputFormattedLocation : 'data-simple-locator-input-formatted-location', 39 | inputGeocode : 'data-simple-locator-input-geocode', 40 | inputLimit : 'data-simple-locator-input-limit', 41 | submitButton : 'data-simple-locator-submit', 42 | infoWindowLink : 'data-simple-locator-open-infowindow', 43 | geoButton : 'data-simple-locator-geolocation-button', 44 | ajaxForm : 'data-simple-locator-ajax-form', 45 | paginationButton : 'data-simple-locator-pagination', 46 | pageJumpForm : 'data-simple-locator-page-jump-form' 47 | } 48 | 49 | // API Endpoints 50 | SimpleLocator.endpoints = { 51 | search : wpsl_locator.rest_url + '/search', 52 | locations : wpsl_locator.rest_url + '/locations' 53 | } 54 | 55 | // Map Objects 56 | SimpleLocator.maps = []; 57 | 58 | // Map Markers 59 | SimpleLocator.markers = []; 60 | SimpleLocator.userPin = []; 61 | SimpleLocator.formData = []; 62 | SimpleLocator.infoWindow; 63 | SimpleLocator.utilities; 64 | 65 | SimpleLocator.mapservice = wpsl_locator.mapservice; 66 | 67 | /** 68 | * Primary Simple Locator Class 69 | */ 70 | SimpleLocator.Factory = function() 71 | { 72 | var self = this; 73 | var $ = jQuery; 74 | 75 | self.init = function() 76 | { 77 | SimpleLocator.utilities = new SimpleLocator.Utilities; 78 | new SimpleLocator.Geocoder; 79 | new SimpleLocator.Geolocation; 80 | new SimpleLocator.PlacesAutocomplete; 81 | new SimpleLocator.DefaultMap; 82 | new SimpleLocator.ResultsMapNonAjax; 83 | new SimpleLocator.SingleLocation; 84 | new SimpleLocator.AllLocations; 85 | new SimpleLocator.Form; 86 | new SimpleLocator.ResultsMap; 87 | new SimpleLocator.ResultsList; 88 | new SimpleLocator.InfoWindowOpen; 89 | new SimpleLocator.Errors; 90 | } 91 | 92 | return self.init(); 93 | } -------------------------------------------------------------------------------- /assets/js/source/simple-locator.functions-deprecated.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Callback functions available to users 3 | * Place in Theme scripts to perform actions after map has rendered 4 | * Theme scripts should be enqueued with a script dependency for 'simple-locator' 5 | * Deprecated as of version 2 in place of events. 6 | */ 7 | 8 | // Deprecated global vars replaced with scoped variables 9 | var active_form = ''; 10 | var formatted_address = ''; 11 | var googlemaps_response = ''; 12 | 13 | // Replaced with data-simple-locator-open-infowindow attribute on link 14 | function openInfoWindow(id){ 15 | google.maps.event.trigger(SimpleLocator.markers[0][id], 'click'); 16 | var lat = SimpleLocator.markers[0][id].getPosition().lat(); 17 | var lng = SimpleLocator.markers[0][id].getPosition().lng(); 18 | var position = new google.maps.LatLng(lat,lng); 19 | SimpleLocator.maps[0].panTo(position); 20 | SimpleLocator.maps[0].fitBounds(position); 21 | SimpleLocator.maps[0].setZoom(12); 22 | return false; 23 | } 24 | 25 | // Runs before form has been submitted/after click 26 | // Replaced with event: simple-locator-before-submit[form] 27 | function wpsl_before_submit(active_form, formelements){} 28 | 29 | // Runs after map & results render 30 | // Replaced with two events: simple-locator-map-rendered[mapIndex, form], simple-locator-results-rendered[listIndex, form] 31 | function wpsl_after_render(active_form){} 32 | 33 | // Runs on click event on a map marker 34 | // Replaced with event: simple-locator-marker-clicked[marker, index, form, post_id] 35 | function wpsl_click_marker(marker, i, active_form, post_id){} 36 | 37 | // Runs if no results were returned from the query 38 | // Replaced with event: simple-locator-error[error, form, message] 39 | function wpsl_no_results(location, active_form){} 40 | 41 | // Runs on form error 42 | // Replaced with event: simple-locator-error[error, form, message] 43 | function wpsl_error(message, active_form){} 44 | 45 | // Runs immediately on form success, pre-render of map/results 46 | // Replaced with event: simple-locator-form-success[data, form] 47 | function wpsl_success(resultcount, results, active_form){} 48 | 49 | // Returns the Google Maps Response 50 | function wpsl_googlemaps_response(){ 51 | return googlemaps_response; 52 | } 53 | 54 | // Runs after locations map has rendered 55 | // Replaced with event: simple-locator-all-locations-rendered[map] 56 | function wpsl_all_locations_rendered(map){} 57 | 58 | // Runs after clicking on a marker in all locations map 59 | // Replaced with event: simple-locator-all-locations-marker-clicked[marker, infoWindow] 60 | function wpsl_all_locations_marker_clicked(marker, infoWindow){} 61 | 62 | function wpsl_googlemaps_response(){ 63 | return googlemaps_response; 64 | } -------------------------------------------------------------------------------- /assets/js/source/simple-locator.geocoder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Geocode an Address 3 | * @package simple-locator 4 | */ 5 | var SimpleLocator = SimpleLocator || {}; 6 | SimpleLocator.Geocoder = function() 7 | { 8 | var self = this; 9 | var $ = jQuery; 10 | 11 | self.form; 12 | self.searchTerm; 13 | self.results = []; 14 | 15 | /** 16 | * Get coordinates and formatted for a provided address 17 | * @param address string 18 | * @param form object 19 | * @return array 20 | */ 21 | self.getCoordinates = function(form) 22 | { 23 | self.form = form; 24 | self.searchTerm = $(self.form).find('[' + SimpleLocator.selectors.inputAddress + ']').val(); 25 | var allowempty = $(self.form).attr('data-simple-locator-form-allow-empty'); 26 | if ( typeof allowempty !== 'undefined' && allowempty == 'true' ){ 27 | if ( typeof self.searchTerm == 'undefined' || self.searchTerm == '' ){ 28 | $(document).trigger('simple-locator-address-geocoded', [self.results, self.form]); 29 | return; 30 | } 31 | } 32 | if ( SimpleLocator.mapservice === 'google' ) self.queryGoogleMaps(); 33 | } 34 | 35 | /** 36 | * Query Google 37 | */ 38 | self.queryGoogleMaps = function() 39 | { 40 | geocoder = new google.maps.Geocoder(); 41 | geocoder.geocode({ 42 | 'address' : self.searchTerm 43 | }, function(results, status){ 44 | if ( status == google.maps.GeocoderStatus.OK ){ 45 | googlemaps_response = results; // deprecated 46 | self.results['latitude'] = results[0].geometry.location.lat(); 47 | self.results['longitude'] = results[0].geometry.location.lng(); 48 | self.results['formatted_address'] = results[0].formatted_address; 49 | if ( wpsl_locator.jsdebug === '1' ){ 50 | console.log('Google Geocode Response'); 51 | console.log(self.results); 52 | } 53 | $(document).trigger('simple-locator-address-geocoded', [self.results, self.form]); 54 | } else { 55 | $(document).trigger('simple-locator-error', [self.form, wpsl_locator.notfounderror]); 56 | self.removeLoading(); 57 | } 58 | }); 59 | } 60 | 61 | /** 62 | * Remove Loading 63 | */ 64 | self.removeLoading = function() 65 | { 66 | $(self.form).parents('[' + SimpleLocator.selectors.formContainer + ']').removeClass('loading'); 67 | } 68 | } -------------------------------------------------------------------------------- /assets/js/source/simple-locator.infowindow-open.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Open an infowindow when clicking a link 3 | * @package simple-locator 4 | */ 5 | var SimpleLocator = SimpleLocator || {}; 6 | SimpleLocator.InfoWindowOpen = function() 7 | { 8 | var self = this; 9 | var $ = jQuery; 10 | 11 | self.mapIndex; 12 | self.markerIndex; 13 | 14 | self.bindEvents = function() 15 | { 16 | $(document).on('click', '[' + SimpleLocator.selectors.infoWindowLink + ']', function(e){ 17 | e.preventDefault(); 18 | self.setIndexes($(this)); 19 | self.openInfoWindow(); 20 | }); 21 | } 22 | 23 | /** 24 | * Set the map index 25 | */ 26 | self.setIndexes = function(link) 27 | { 28 | var activeList = $(link).parents('[' + SimpleLocator.selectors.resultsWrapper + ']'); 29 | var lists = $('[' + SimpleLocator.selectors.resultsWrapper + ']'); 30 | self.mapIndex = $(activeList).index(lists); 31 | self.markerIndex = parseInt($(link).attr(SimpleLocator.selectors.infoWindowLink)); 32 | } 33 | 34 | /** 35 | * Open the infowindow 36 | */ 37 | self.openInfoWindow = function() 38 | { 39 | google.maps.event.trigger(SimpleLocator.markers[self.mapIndex][self.markerIndex], 'click'); 40 | $(document).trigger('simple-locator-infowindow-opened', [self.mapIndex, self.markerIndex]); 41 | } 42 | 43 | return self.bindEvents(); 44 | } -------------------------------------------------------------------------------- /assets/js/source/simple-locator.places-autocomplete.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Enable Google Maps Places Autocomplete on Fields 3 | * @package simple-locator 4 | */ 5 | var SimpleLocator = SimpleLocator || {}; 6 | SimpleLocator.PlacesAutocomplete = function() 7 | { 8 | var self = this; 9 | var $ = jQuery; 10 | 11 | self.selectors = { 12 | input : 'data-simple-locator-autocomplete' 13 | } 14 | 15 | self.bindEvents = function() 16 | { 17 | $(document).ready(function(){ 18 | self.enableAutocomplete(); 19 | }); 20 | } 21 | 22 | self.enableAutocomplete = function() 23 | { 24 | var inputs = $('[' + self.selectors.input + ']'); 25 | $.each(inputs, function(i, v){ 26 | var options = {}; 27 | if ( wpsl_locator.custom_autocomplete ) options = wpsl_locator.autocomplete_options; 28 | var autocomplete = new google.maps.places.Autocomplete(this, options); 29 | var submitBtn = $(this).parents('[' + SimpleLocator.selectors.form + ']').find('[' + SimpleLocator.selectors.submitButton + ']'); 30 | var form = $(this).parents('form'); 31 | google.maps.event.addListener(autocomplete, 'place_changed', function(){ 32 | $(document).trigger('simple-locator-autocomplete-changed', [autocomplete.getPlace(), form]); 33 | }); 34 | }); 35 | } 36 | 37 | return self.bindEvents(); 38 | } -------------------------------------------------------------------------------- /assets/js/source/simple-locator.single-location.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Display a single location map 3 | * @package simple-locator 4 | */ 5 | var SimpleLocator = SimpleLocator || {}; 6 | SimpleLocator.SingleLocation = function() 7 | { 8 | var self = this; 9 | var $ = jQuery; 10 | 11 | self.selectors = { 12 | mapContainer : 'data-simple-locator-single-map', 13 | latitude : 'data-latitude', 14 | longitude : 'data-longitude', 15 | title : 'data-title' 16 | } 17 | 18 | self.bindEvents = function() 19 | { 20 | $(document).ready(function(){ 21 | self.loadAllMaps(); 22 | }); 23 | } 24 | 25 | self.loadAllMaps = function() 26 | { 27 | var maps = $('[' + self.selectors.mapContainer + ']'); 28 | $.each(maps, function(){ 29 | self.loadMap($(this)); 30 | }); 31 | } 32 | 33 | /** 34 | * Load the map 35 | */ 36 | self.loadMap = function(container) 37 | { 38 | var latitude = parseFloat($(container).attr(self.selectors.latitude)); 39 | var longitude = parseFloat($(container).attr(self.selectors.longitude)); 40 | var title = $(container).attr(self.selectors.title); 41 | var mappin = ( wpsl_locator.mappinsingle ) ? wpsl_locator.mappinsingle : ''; 42 | var position = new google.maps.LatLng( latitude, longitude ); 43 | var options = { 44 | zoom: 12, 45 | styles: wpsl_locator.mapstyles 46 | }; 47 | 48 | // Override options if custom options are set 49 | if ( wpsl_locator.custom_map_options === '1' ) options = wpsl_locator.map_options; 50 | options.center = position; 51 | 52 | var map = new google.maps.Map(container[0], options); 53 | var marker = new google.maps.Marker({ 54 | position: position, 55 | map: map, 56 | icon: mappin, 57 | title: title 58 | }); 59 | $(container).removeClass('loading'); 60 | $(document).trigger('simple-locator-single-map-rendered', [map, marker, container]); 61 | } 62 | 63 | return self.bindEvents(); 64 | } -------------------------------------------------------------------------------- /assets/js/source/simple-locator.utilities.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility Functions 3 | * @package simple-locator 4 | */ 5 | var SimpleLocator = SimpleLocator || {}; 6 | SimpleLocator.Utilities = function() 7 | { 8 | var self = this; 9 | var $ = jQuery; 10 | 11 | /** 12 | * Get the currently displayed radius/distance of a map 13 | * @return int 14 | */ 15 | self.getMapRadius = function(map) 16 | { 17 | var bounds = map.getBounds(); 18 | var center = bounds.getCenter(); 19 | var ne = bounds.getNorthEast(); 20 | var r = 3963.0; // radius of earth in miles 21 | 22 | var lat1 = center.lat() / 57.2958; 23 | var lon1 = center.lng() / 57.2958; 24 | var lat2 = ne.lat() / 57.2958; 25 | var lon2 = ne.lng() / 57.2958; 26 | 27 | var dis = r * Math.acos(Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1)); 28 | return dis; 29 | } 30 | 31 | /** 32 | * Get a map center point 33 | * @return array [latitude, longitude] 34 | */ 35 | self.getMapCenterPoint = function(map) 36 | { 37 | var bounds = map.getBounds(); 38 | var center = bounds.getCenter(); 39 | var latlng = [center.lat(), center.lng()]; 40 | return latlng; 41 | } 42 | 43 | /** 44 | * Cluster markers if set to do so 45 | */ 46 | self.clusterMarkers = function(map, markers) 47 | { 48 | var options = { 49 | map: map, 50 | markers: markers 51 | } 52 | if ( typeof wpsl_locator.cluster_renderer !== 'undefined' && wpsl_locator.cluster_renderer !== '' ) options.renderer = wpsl_locator.cluster_renderer; 53 | if ( wpsl_locator.marker_clusters === '1' && typeof markerClusterer.MarkerClusterer !== 'undefined' ){ 54 | const markerCluster = new markerClusterer.MarkerClusterer(options); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /assets/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/screenshot-1.png -------------------------------------------------------------------------------- /assets/screenshot-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/screenshot-10.png -------------------------------------------------------------------------------- /assets/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/screenshot-2.png -------------------------------------------------------------------------------- /assets/screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/screenshot-3.png -------------------------------------------------------------------------------- /assets/screenshot-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/screenshot-4.png -------------------------------------------------------------------------------- /assets/screenshot-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/screenshot-5.png -------------------------------------------------------------------------------- /assets/screenshot-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/screenshot-6.png -------------------------------------------------------------------------------- /assets/screenshot-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/screenshot-7.png -------------------------------------------------------------------------------- /assets/screenshot-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/screenshot-8.png -------------------------------------------------------------------------------- /assets/screenshot-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/assets/screenshot-9.png -------------------------------------------------------------------------------- /assets/scss/_admin-loading.scss: -------------------------------------------------------------------------------- 1 | .wpsl-icon-spinner { 2 | position: relative; 3 | top: 5px; 4 | width: 40px; 5 | vertical-align: top; 6 | // display: none; 7 | } 8 | 9 | .wpsl-icon-spinner-image { 10 | animation: wpslrotate linear 1.5s infinite; 11 | position: absolute; 12 | line-height: .1; 13 | } 14 | 15 | @keyframes wpslrotate { 16 | 0% { 17 | transform: rotate(0deg); 18 | transform-origin: center center; 19 | } 20 | 50% { 21 | transform: rotate(180deg); 22 | transform-origin: center center; 23 | } 24 | 100% { 25 | transform: rotate(360deg); 26 | transform-origin: center center; 27 | } 28 | } -------------------------------------------------------------------------------- /assets/scss/_admin-location-map.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Map in location listing (post list table) 3 | // -------------------------------------------------- 4 | 5 | // Hide the column data is stored in 6 | th.column-wpsl-coordinates, 7 | td.column-wpsl-coordinates { 8 | // display: none; 9 | } 10 | 11 | // The post listing map 12 | .wpsl-post-listing-map { 13 | background-color: $white; 14 | margin: 2em 0; 15 | height: 400px; 16 | } -------------------------------------------------------------------------------- /assets/scss/_admin-quick-edit.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Location Quick Edit 3 | // -------------------------------------------------- 4 | .simple-locator-quick-edit-alert { 5 | background-color: $red; 6 | color: $white; 7 | padding: .5em; 8 | margin-bottom: .5em; 9 | display: none; 10 | } 11 | 12 | .simple-locator-quick-edit-spinner { 13 | display: none; 14 | float: right; 15 | width: 30px; 16 | height: 30px; 17 | margin-right: 5px; 18 | position: relative; 19 | top: 2px; 20 | img { 21 | width: 24px; 22 | height: 24px; 23 | } 24 | } 25 | 26 | .simple-locator-quick-edit { 27 | h3 { 28 | color: #555; 29 | font-size: 12px; 30 | text-transform: uppercase; 31 | margin: 0 0 1em 0; 32 | } 33 | .inner { 34 | display: flex; 35 | justify-content: space-between; 36 | margin-bottom: 1em; 37 | } 38 | .map { 39 | width: 49%; 40 | background-color: rgba($black, .03); 41 | } 42 | .fields { 43 | width: 49%; 44 | @include clearfix; 45 | .field { 46 | position: relative; 47 | padding: 5px 0 5px 150px; 48 | label { 49 | position: absolute; 50 | left: 0; 51 | top: 10px; 52 | } 53 | input, select { 54 | width: 100%; 55 | } 56 | } 57 | } 58 | .buttons { 59 | @include clearfix; 60 | button { 61 | float: right; 62 | &:first-child { 63 | float: left; 64 | } 65 | } 66 | .button-primary { 67 | margin-left: 5px; 68 | } 69 | } // .buttons 70 | } // .dealer-quick-edit -------------------------------------------------------------------------------- /assets/scss/_alerts.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Alerts 3 | // -------------------------------------------------- 4 | .wpsl-error, 5 | .wpsl-info { 6 | display: block; 7 | padding: .75rem 1.25rem; 8 | border-radius: 3px; 9 | margin: 0; 10 | margin-bottom: 10px; 11 | background-color: #f2dede; 12 | color: #a94442; 13 | border: 1px solid #ebcccc; 14 | } 15 | 16 | .wpsl-info { 17 | background-color: rgba($blue, .2); 18 | border-color: rgba($blue, .15); 19 | color: $black; 20 | padding: .75em; 21 | } -------------------------------------------------------------------------------- /assets/scss/_buttons.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Buttons 3 | // -------------------------------------------------- 4 | .button-danger { 5 | text-decoration: none; 6 | display: inline-block; 7 | background: transparent; 8 | background-color: $red; 9 | border: 1px solid $red; 10 | border-radius: 2px; 11 | line-height: 30px; 12 | padding: 0px 10px; 13 | cursor: pointer; 14 | color: $white; 15 | box-shadow: inset 0px 1px 0px 0px rgba(255, 255, 255, .3); 16 | &:hover, &:active, &:focus { 17 | background: transparent; 18 | background-color: darken($red, 8%); 19 | border-color: darken($red, 8%);; 20 | color: $white; 21 | } 22 | &:disabled { 23 | opacity: .5; 24 | } 25 | } -------------------------------------------------------------------------------- /assets/scss/_datepicker.scss: -------------------------------------------------------------------------------- 1 | // 2 | // JQuery UI Datepicker for Nested Pages 3 | // -------------------------------------------------- 4 | .wpsl-datepicker.ui-datepicker { 5 | background-color: $white; 6 | font-size: 90%; 7 | box-shadow: 0px 0px 3px 0px rgba(0,0,0,.4); 8 | border-radius: 3px; 9 | padding: 0; 10 | width: auto; 11 | 12 | // Prev & Next 13 | .ui-datepicker-next, .ui-datepicker-prev { 14 | position: absolute; 15 | color: $white; 16 | top: 0px; 17 | right: 0px; 18 | cursor: pointer; 19 | display: block; 20 | width: 35px; 21 | height: 35px; 22 | font-size: 0; 23 | background: url('../images/datepicker-arrow-next.png'); 24 | background-position: center; 25 | opacity: 1; 26 | transition: opacity 200ms ease; 27 | &:hover { 28 | opacity: .5; 29 | transition: opacity 200ms ease; 30 | } 31 | } 32 | 33 | .ui-datepicker-calendar { 34 | margin: 0; 35 | } 36 | 37 | .ui-datepicker-prev { 38 | right: auto; 39 | left: 0px; 40 | background: url('../images/datepicker-arrow-prev.png'); 41 | } 42 | 43 | .ui-datepicker-header { 44 | padding: 0; 45 | } 46 | 47 | // Month & Year 48 | .ui-datepicker-title { 49 | background-color: darken($white, 90%); 50 | text-align: center; 51 | color: $white; 52 | height: 35px; 53 | line-height: 35px; 54 | border-radius: 3px 3px 0 0; 55 | margin: 0; 56 | } 57 | 58 | // Calendar Table 59 | .ui-datepicker-calendar { 60 | border-collapse: collapse; 61 | table { 62 | width: auto; 63 | } 64 | th, td { 65 | text-align: center; 66 | width: 30px; 67 | height: 30px; 68 | } 69 | // Days 70 | th { 71 | background-color: darken($white, 10%); 72 | text-transform: uppercase; 73 | font-size: 11px; 74 | letter-spacing: 1px; 75 | padding: 0; 76 | } 77 | td { 78 | border-top: 1px solid darken($white, 8%); 79 | border-left: 1px solid darken($white, 8%); 80 | padding: 0; 81 | &:first-child { 82 | border-left: 0; 83 | } 84 | } 85 | a { 86 | text-align: center; 87 | padding: 0; 88 | text-decoration: none; 89 | color: $black; 90 | display: block; 91 | background-color: $white; 92 | transition: all 200ms ease; 93 | width: 30px; 94 | height: 30px; 95 | line-height: 30px; 96 | &.ui-state-active { 97 | background-color: darken($white, 90%); 98 | color: $white; 99 | } 100 | &:hover { 101 | background-color: $black; 102 | color: $white; 103 | transition: all 200ms ease; 104 | } 105 | } 106 | } 107 | 108 | } -------------------------------------------------------------------------------- /assets/scss/_form-results.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Form Results 3 | // -------------------------------------------------- 4 | .wpsl-results { 5 | clear: both; 6 | ul { 7 | @include unstyled(); 8 | @include clearfix(); 9 | li { 10 | float: left; 11 | width: 46%; 12 | clear: both; 13 | margin:0 0 20px 0 !important; 14 | list-style: none !important; 15 | &:nth-child(even){ 16 | float: right; 17 | clear: none; 18 | } 19 | } 20 | } 21 | .gm-style-iw h4 { 22 | margin: 0 0 .5em 0; 23 | } 24 | } // .wpsl-results -------------------------------------------------------------------------------- /assets/scss/_form.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Form 3 | // -------------------------------------------------- 4 | .simple-locator-form { 5 | form { 6 | margin-bottom: 20px; 7 | } 8 | label { 9 | display: block; 10 | } 11 | .address-input { 12 | float: left; 13 | width: 60%; 14 | input[type='text'], 15 | input[type='tel']{ 16 | width: 100%; 17 | box-sizing: border-box; 18 | } 19 | } 20 | .distance { 21 | float: right; 22 | clear: none; 23 | width: 35%; 24 | select { 25 | width: 100%; 26 | } 27 | } 28 | .submit { 29 | clear: both; 30 | padding-top: 20px; 31 | margin-bottom: 20px; 32 | } 33 | .geo_button_cont { 34 | display: inline-block; 35 | vertical-align: top; 36 | } 37 | &.loading { 38 | .wpsl-icon-spinner { 39 | display: inline-block; 40 | } 41 | } 42 | } 43 | 44 | .wpsl-taxonomy-filters { 45 | clear: both; 46 | } 47 | 48 | .wpsl-icon-spinner { 49 | position: relative; 50 | top: 5px; 51 | width: 40px; 52 | vertical-align: top; 53 | display: none; 54 | &.visible { 55 | display: inline-block; 56 | margin-left: 5px; 57 | } 58 | } 59 | 60 | .wpsl-icon-spinner-image { 61 | animation: wpslrotate linear 1.5s infinite; 62 | position: absolute; 63 | line-height: .1; 64 | } 65 | 66 | @keyframes wpslrotate { 67 | 0% { 68 | transform: rotate(0deg); 69 | transform-origin: center center; 70 | } 71 | 50% { 72 | transform: rotate(180deg); 73 | transform-origin: center center; 74 | } 75 | 100% { 76 | transform: rotate(360deg); 77 | transform-origin: center center; 78 | } 79 | } -------------------------------------------------------------------------------- /assets/scss/_maps.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Maps 3 | // -------------------------------------------------- 4 | .wpsl-map { 5 | clear: both; 6 | width: 100%; 7 | height: 250px; 8 | margin: 20px 0px; 9 | img { 10 | width: auto; 11 | max-width: none !important; 12 | } 13 | &.loading { 14 | display: none; 15 | } 16 | } -------------------------------------------------------------------------------- /assets/scss/_meta-fields.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Meta Fields for Post Type 3 | // -------------------------------------------------- 4 | .wpsl-meta label { 5 | display: block; 6 | } 7 | .wpsl-meta input[type='text'], .wpsl-meta select{ 8 | width: 100%; 9 | height: 40px; 10 | } 11 | .wpsl-meta textarea { 12 | width: 100%; 13 | height: 80px; 14 | } 15 | .wpsl-meta hr { 16 | clear: both; 17 | background: transparent; 18 | border: 0; 19 | } 20 | 21 | /* Clearfix */ 22 | .wpsl-meta, .wpsl-meta .latlng { 23 | zoom:1; 24 | } 25 | .wpsl-meta:before, .wpsl-meta:after, .wpsl-meta .latlng:before, .wpsl-meta .latlng:after { 26 | content: " "; /* 1 */ 27 | display: table; /* 2 */ 28 | } 29 | .wpsl-meta:after, .wpsl-meta .latlng:after { 30 | clear: both; 31 | } 32 | 33 | /* Settings */ 34 | 35 | /* Fields */ 36 | .wpsl-meta p { 37 | float: left; 38 | } 39 | .wpsl-meta p.full { 40 | float: none; 41 | clear: both; 42 | } 43 | .wpsl-meta p.city { 44 | width: 50%; 45 | margin-right: 5%; 46 | } 47 | .wpsl-meta p.state { 48 | width: 20%; 49 | margin-right: 5%; 50 | } 51 | .wpsl-meta p.zip { 52 | width: 20%; 53 | float: right; 54 | } 55 | .wpsl-meta p.half { 56 | width: 48%; 57 | float: left; 58 | } 59 | .wpsl-meta p.right { 60 | float: right; 61 | } 62 | 63 | /* Latitude & Longitude */ 64 | .wpsl-meta .latlng { 65 | clear: both; 66 | background-color: rgba(0,0,0,.05); 67 | padding: 15px; 68 | margin: 20px 0; 69 | border-radius: 6px; 70 | max-width: none; 71 | } 72 | .wpsl-meta input:disabled { 73 | cursor: not-allowed; 74 | } 75 | .wpsl-meta .latlng span { 76 | display: block; 77 | font-style: oblique; 78 | border-radius: 3px; 79 | } 80 | .wpsl-meta .latlng p { 81 | float: left; 82 | width: 48%; 83 | } 84 | .wpsl-meta .latlng p.lat { 85 | float: right; 86 | } 87 | 88 | #wpslmap { 89 | display: none; 90 | clear: both; 91 | height: 300px; 92 | background-color: rgba(0,0,0,.05); 93 | } -------------------------------------------------------------------------------- /assets/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Mixins 3 | // -------------------------------------------------- 4 | 5 | @mixin clearfix { 6 | zoom:1; 7 | &:before, 8 | &:after { 9 | content: " "; /* 1 */ 10 | display: table; /* 2 */ 11 | } 12 | &:after { 13 | clear: both; 14 | } 15 | } 16 | 17 | @mixin unstyled { 18 | list-style-type:none; 19 | margin:0; 20 | padding:0; 21 | } 22 | 23 | .pull-right { 24 | float:right; 25 | } 26 | 27 | @mixin translate($x, $y) { 28 | -webkit-transform: translate($x, $y); 29 | -ms-transform: translate($x, $y); // IE9 only 30 | -o-transform: translate($x, $y); 31 | transform: translate($x, $y); 32 | } 33 | 34 | @mixin transition-transform($transition...) { 35 | -webkit-transition: -webkit-transform $transition; 36 | -moz-transition: -moz-transform $transition; 37 | -o-transition: -o-transform $transition; 38 | transition: transform $transition; 39 | } -------------------------------------------------------------------------------- /assets/scss/_modals.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Modals 3 | // -------------------------------------------------- 4 | .wpsl-modal-backdrop { 5 | position: fixed; 6 | width: 100%; 7 | height: 100%; 8 | background-color: rgba(0,0,0,.85); 9 | top: 0; 10 | left: 0; 11 | z-index: 1000; 12 | visibility: hidden; 13 | opacity: 0; 14 | transition: all 500ms ease; 15 | &.active { 16 | visibility: visible; 17 | opacity: 1; 18 | transition: all 200ms ease; 19 | } 20 | } 21 | 22 | .wpsl-modal-content { 23 | z-index: 1001; 24 | position: fixed; 25 | top: 50%; 26 | top: -200%; 27 | left: 50%; 28 | width: 50%; 29 | background-color: $white; 30 | border-radius: 2px; 31 | transform: translateX(-50%); 32 | transition: all 500ms ease; 33 | &.small { 34 | width: 400px; 35 | } 36 | &.active { 37 | top: 50px; 38 | transition: all 200ms ease; 39 | } 40 | } 41 | 42 | .wpsl-modal-content-body { 43 | padding: 1em; 44 | max-height: 300px; 45 | overflow: auto; 46 | @include clearfix; 47 | &.no-padding { 48 | padding: 0; 49 | } 50 | } 51 | 52 | .wpsl-modal-content-footer { 53 | @include clearfix; 54 | padding: .7em; 55 | border-top: 1px solid rgba(#000, .1); 56 | .button-primary { 57 | float: right; 58 | } 59 | } 60 | 61 | 62 | @media (max-width: 767px) 63 | { 64 | h3 { 65 | line-height: 1.3; 66 | } 67 | .wpsl-modal-content { 68 | width: 90%; 69 | &.small { 70 | width: 90%; 71 | } 72 | &.active { 73 | top: 20px; 74 | } 75 | } 76 | .wpsl-modal-content-body { 77 | height: 300px; 78 | overflow-y: scroll; 79 | -webkit-overflow-scrolling: touch; 80 | overflow-x: hidden; 81 | } 82 | .wpsl-modal-video { 83 | width: 100%; 84 | &.modal-content.active { 85 | padding: 0; 86 | } 87 | } 88 | } 89 | 90 | @media print { 91 | .wpsl-modal-backdrop, 92 | .wpsl-modal-content { 93 | display: none; 94 | } 95 | } -------------------------------------------------------------------------------- /assets/scss/_pagination.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Pagination 3 | // -------------------------------------------------- 4 | .wpsl-pagination-button { 5 | float: left; 6 | &.button-previous { 7 | margin-right: 10px; 8 | } 9 | } 10 | 11 | .wpsl-results-current-count { 12 | opacity: .5; 13 | } 14 | 15 | .wpsl-form-page-selection { 16 | clear: both; 17 | padding-top: 1em; 18 | font-style: oblique; 19 | opacity: .5; 20 | } 21 | 22 | .wpsl-pagination { 23 | @include clearfix; 24 | } 25 | 26 | .wpsl-jump-to-page-form { 27 | float: right; 28 | .current-page, 29 | .total-pages { 30 | display: inline-block; 31 | margin: 0; 32 | } 33 | button[type="submit"]{ 34 | display: inline-block; 35 | float: none; 36 | margin-left: 8px; 37 | } 38 | input[type="tel"]{ 39 | display: inline-block; 40 | width: 50px; 41 | text-align: center; 42 | margin: 0 3px; 43 | } 44 | } -------------------------------------------------------------------------------- /assets/scss/_settings-default-map.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Default Map Settings 3 | // -------------------------------------------------- 4 | .wpsl-settings .row .wpsl-default-map { 5 | .search-form { 6 | @include clearfix; 7 | position: relative; 8 | padding: 8px 90px 8px 8px; 9 | background-color: $gray-lighter; 10 | border: 1px solid $border; 11 | } 12 | input[type="text"]{ 13 | float: left; 14 | height: 28px; 15 | margin: 0; 16 | } 17 | button { 18 | position: absolute; 19 | width: 77px; 20 | right: 8px; 21 | } 22 | } 23 | 24 | #wpsl-default { 25 | clear: both; 26 | height: 250px; 27 | width: 100%; 28 | } -------------------------------------------------------------------------------- /assets/scss/_settings-import-previous.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Previous Imports on Step One 3 | // -------------------------------------------------- 4 | .wpsl-previous-imports { 5 | 6 | .import { 7 | background-color: $white; 8 | border-top: 1px solid darken($white, 12%); 9 | &.first { 10 | border-top: 0; 11 | } 12 | } // .import 13 | 14 | .import-title { 15 | @include clearfix; 16 | padding: 8px; 17 | h4 { 18 | margin: 0; 19 | position: relative; 20 | top: 3px; 21 | } 22 | a { 23 | float: right; 24 | position: relative; 25 | z-index: 1; 26 | } 27 | } 28 | 29 | .import-body { 30 | display: none; 31 | padding: 8px; 32 | border-top: 1px solid darken($white, 12%); 33 | @include clearfix; 34 | } 35 | 36 | .import-meta { 37 | float: left; 38 | width: 45%; 39 | } 40 | 41 | .wpsl-import-save-template { 42 | float: right; 43 | margin-top: 1em; 44 | width: 45%; 45 | background-color: darken($white, 6%); 46 | padding: 1em; 47 | button { 48 | display: block; 49 | } 50 | input[type="text"]{ 51 | width: 100%; 52 | margin-bottom: 10px; 53 | } 54 | } 55 | 56 | .import-footer { 57 | padding: 8px 0; 58 | clear: both; 59 | .button-danger { 60 | margin-right: 10px; 61 | } 62 | p { 63 | margin: 0; 64 | } 65 | } 66 | 67 | } // .wpsl-previous-imports -------------------------------------------------------------------------------- /assets/scss/_settings-import-templates.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Import Templates on Step One 3 | // -------------------------------------------------- 4 | .wpsl-import-template-list { 5 | .row.import-template { 6 | 7 | .title { 8 | @include clearfix; 9 | padding: 8px; 10 | // border-top: 1px solid darken($white, 12%); 11 | .button { 12 | float: right; 13 | position: relative; 14 | top: -5px; 15 | } 16 | p { 17 | margin: 0; 18 | padding-top: 5px; 19 | } 20 | } 21 | &.first .title { 22 | border-top: 0; 23 | } 24 | } // .row 25 | h4 { 26 | margin: 0; 27 | } 28 | .details { 29 | padding: 5px; 30 | display: none; 31 | .columns { 32 | width: 100%; 33 | border-collapse: collapse; 34 | } 35 | th, td { 36 | text-align: left; 37 | padding: 5px; 38 | } 39 | th { 40 | background-color: darken($white, 50%); 41 | font-weight: normal; 42 | color: $white; 43 | } 44 | tbody tr:nth-child(even){ 45 | background-color: darken($white, 6%); 46 | } 47 | } 48 | .summary { 49 | background-color: darken($white, 6%); 50 | padding: 5px; 51 | li { 52 | @include clearfix; 53 | } 54 | } 55 | } // .wpsl-import-template-list -------------------------------------------------------------------------------- /assets/scss/_settings-map-options.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Map Style Choices 3 | // -------------------------------------------------- 4 | .wpsl-settings { 5 | .map-styles-header { 6 | padding: 1em; 7 | h3 { 8 | margin: 0 0 8px 0; 9 | } 10 | p { 11 | margin: 0; 12 | } 13 | } 14 | } 15 | .wpsl-settings-map-style-choice { 16 | ul { 17 | @include unstyled; 18 | @include clearfix; 19 | padding: 1em 1em 1em 0; 20 | max-height: 400px; 21 | overflow: auto; 22 | } 23 | li { 24 | float: left; 25 | width: 48%; 26 | margin: 0 0 2em 0; 27 | box-sizing: border-box; 28 | clear: both; 29 | &:nth-child(even){ 30 | float: right; 31 | clear: none; 32 | } 33 | .map { 34 | display: block; 35 | width: 100%; 36 | height: 150px; 37 | background-repeat: no-repeat; 38 | background-size: 40px; 39 | background-position: center; 40 | box-sizing: border-box; 41 | transition: all 200ms ease; 42 | } 43 | h4 { 44 | margin: 0; 45 | background-color: $gray-lighter; 46 | border: 1px solid $border-light; 47 | border-bottom: 0; 48 | padding: 5px; 49 | text-align: center; 50 | font-weight: normal; 51 | text-transform: uppercase; 52 | letter-spacing: .05em; 53 | font-size: .9em; 54 | } 55 | .choose-style { 56 | display: block; 57 | text-align: center; 58 | background-color: darken(#fff, 50%); 59 | padding: 8px 0; 60 | color: #fff; 61 | text-decoration: none; 62 | transition: all 200ms ease; 63 | box-sizing: border-box; 64 | &:focus, &:active { 65 | outline: none; 66 | box-shadow: none; 67 | } 68 | &:hover { 69 | background-color: #0074a2; 70 | } 71 | } 72 | &.active { 73 | .map { 74 | border: 4px solid #0074a2; 75 | border-bottom: 0; 76 | transition: all 200ms ease; 77 | } 78 | .choose-style { 79 | border: 4px solid #0074a2; 80 | border-top: 0; 81 | border-bottom: 0; 82 | background-color: #0074a2; 83 | } 84 | } 85 | } 86 | } // wpsl-map-style-choices 87 | .wpsl-settings-map-style-custom { 88 | padding-top: 1em; 89 | .wpsl-alert { 90 | margin: 0; 91 | } 92 | } 93 | 94 | 95 | -------------------------------------------------------------------------------- /assets/scss/_settings-posttypes.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Post Type Settings 3 | // -------------------------------------------------- 4 | .latlng { 5 | background-color: rgba($black, .08); 6 | padding: 20px; 7 | margin-top: 20px; 8 | @include clearfix; 9 | } 10 | 11 | .wpsl-show-hidden { 12 | display: block; 13 | margin-bottom: 20px; 14 | } 15 | 16 | .wpsl-left-field, 17 | .wpsl-right-field { 18 | float: left; 19 | width: 45%; 20 | select { 21 | width: 100%; 22 | } 23 | label { 24 | margin: 0; 25 | } 26 | } 27 | 28 | .wpsl-degree-info { 29 | clear: both; 30 | padding-top: 15px; 31 | margin: 0; 32 | font-style: oblique; 33 | } 34 | 35 | .wpsl-right-field { 36 | float: right; 37 | } 38 | 39 | .wpsl-posttype-settings-labels { 40 | .post-type-row { 41 | display: table; 42 | width: 100%; 43 | padding: .5em; 44 | border-top: 1px solid $border-light; 45 | box-sizing: border-box; 46 | &:first-child { 47 | border: 0; 48 | } 49 | } 50 | label { 51 | display: table-cell; 52 | width: 30%; 53 | } 54 | .post-type-field { 55 | display: table-cell; 56 | width: 70%; 57 | } 58 | } 59 | 60 | .wpsl-red { 61 | color: $red; 62 | } 63 | 64 | .wpsl-acf-map-setting { 65 | @extend .latlng; 66 | h3 { 67 | font-size: 16px; 68 | margin-top: 0; 69 | } 70 | select { 71 | width: 100%; 72 | } 73 | } 74 | 75 | 76 | -------------------------------------------------------------------------------- /assets/scss/_settings-results-fields.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Settings - Results Fields 3 | // -------------------------------------------------- 4 | .wpsl-results-field-selector { 5 | background-color: rgba($black, .05); 6 | border: 1px solid $border-light; 7 | padding: 8px; 8 | @include clearfix; 9 | .left, .right { 10 | width: 48%; 11 | position: relative; 12 | float: left; 13 | padding-right: 55px; 14 | box-sizing: border-box; 15 | @include clearfix; 16 | label { 17 | display: block; 18 | } 19 | select { 20 | float: left; 21 | width: 100%; 22 | } 23 | button { 24 | position: absolute; 25 | width: 50px; 26 | right: 0; 27 | bottom: 0; 28 | } 29 | } 30 | .right { 31 | float: right; 32 | } 33 | } 34 | 35 | .wpsl-limit-setting { 36 | input[type="text"]{ 37 | width: 100%; 38 | } 39 | } 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /assets/scss/_settings-search-history.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Search History 3 | // -------------------------------------------------- 4 | .wpsl-search-history-actions { 5 | background-color: $gray-lighter; 6 | padding: 1em; 7 | box-sizing: border-box; 8 | @include clearfix; 9 | form, .button { 10 | float: left; 11 | } 12 | form.right { 13 | float: right; 14 | } 15 | .button { 16 | margin-right: 1em; 17 | } 18 | } 19 | .wpsl-search-history-pagination { 20 | li { 21 | display: inline-block; 22 | margin-left: .5em; 23 | &:first-child { 24 | margin: 0; 25 | } 26 | } 27 | &.bottom { 28 | border-top: 1px solid $border-light; 29 | padding: 1em; 30 | @include clearfix; 31 | ul { 32 | margin: 0; 33 | float: right; 34 | } 35 | } 36 | } 37 | .wpsl-search-history-table-header { 38 | clear: both; 39 | border-top: 1px solid $border-light; 40 | @include clearfix; 41 | padding: 1em; 42 | h3 { 43 | display: inline-block; 44 | vertical-align: top; 45 | margin: 0; 46 | line-height: 30px; 47 | font-size: 1em; 48 | } 49 | select { 50 | float: right; 51 | margin: 0; 52 | } 53 | .wpsl-search-history-pagination { 54 | float: right; 55 | margin-left: 10px; 56 | ul { 57 | margin: 0; 58 | } 59 | } 60 | } 61 | .wpsl-search-history-form { 62 | background-color: $white; 63 | position: relative; 64 | @include clearfix; 65 | label { 66 | display: block; 67 | } 68 | .inputs { 69 | @include clearfix; 70 | padding: 1em 100px 1em 1em; 71 | } 72 | .keyword { 73 | float: left; 74 | width: 30%; 75 | box-sizing: border-box; 76 | } 77 | input[type="text"]{ 78 | width: 100%; 79 | } 80 | .date-range { 81 | float: left; 82 | width: 70%; 83 | box-sizing: border-box; 84 | padding-left: 1em; 85 | input[type="text"]{ 86 | float: left; 87 | width: 48%; 88 | max-width: 120px; 89 | &:last-child { 90 | margin-left: 5px; 91 | // float: right; 92 | } 93 | } 94 | } 95 | 96 | input[type="submit"]{ 97 | position: absolute; 98 | right: 1em; 99 | display: block; 100 | width: 80px; 101 | bottom: 1em; 102 | } 103 | h4 { 104 | padding: 0; 105 | margin-top: 0; 106 | } 107 | } // .wpsl-search-history-form 108 | 109 | #wpsl-search-history-map { 110 | height: 300px; 111 | background-color: $white; 112 | } 113 | 114 | .wpsl-search-history-table { 115 | background-color: $white; 116 | width: 100%; 117 | border: 0; 118 | border-collapse: collapse; 119 | th, td { 120 | font-size: .9em; 121 | text-align: left; 122 | border-top: 1px solid darken($white, 8%); 123 | border-left: 1px solid darken($white, 8%); 124 | padding: 5px; 125 | &:first-child { 126 | border-left: 0; 127 | } 128 | } 129 | th { 130 | padding: 1em 5px; 131 | border-top: 0; 132 | border-left-color: rgba($black, .9); 133 | background-color: rgba($black, .8); 134 | color: $white; 135 | line-height: 1.2; 136 | &:last-child { 137 | border-left: 0; 138 | } 139 | } 140 | } -------------------------------------------------------------------------------- /assets/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Variables 3 | // -------------------------------------------------- 4 | $white : #ffffff; 5 | $black : #000000; 6 | $red : #e14d43; 7 | $blue: #0073aa; 8 | $border: #e1e1e1; 9 | $border-light: #e5e5e5; 10 | $gray-lighter: #f9f9f9; // WP BG -------------------------------------------------------------------------------- /assets/scss/_widget.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Widget Specific Styles 3 | // -------------------------------------------------- 4 | .simple-locator-widget { 5 | 6 | .simple-locator-form { 7 | .address-input, .distance { 8 | float: none; 9 | width: 100%; 10 | } 11 | .address-input { 12 | margin-bottom: 10px; 13 | } 14 | } 15 | 16 | .wpsl-results ul li { 17 | float: none; 18 | width: 100%; 19 | } 20 | 21 | .geo_button_cont { 22 | margin: 0; 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /assets/scss/simple-locator-admin.scss: -------------------------------------------------------------------------------- 1 | @import "_variables.scss"; 2 | @import "_mixins.scss"; 3 | 4 | // Componenets 5 | @import "_modals.scss"; 6 | @import "_icons.scss"; 7 | @import "_buttons.scss"; 8 | @import "_datepicker.scss"; 9 | @import "_admin-loading.scss"; 10 | @import "_alerts.scss"; 11 | 12 | // PT meta fields 13 | @import "_meta-fields.scss"; 14 | 15 | // Settings 16 | @import "_settings-map-options.scss"; 17 | @import "_settings-default-map.scss"; 18 | @import "_settings-posttypes.scss"; 19 | @import "_settings-general.scss"; 20 | @import "_settings-results-fields.scss"; 21 | @import "_settings-import.scss"; 22 | @import "_settings-import-previous.scss"; 23 | @import "_settings-import-templates.scss"; 24 | @import "_settings-search-history.scss"; 25 | 26 | // Additional Views 27 | @import "_admin-location-map.scss"; 28 | @import "_admin-quick-edit.scss"; -------------------------------------------------------------------------------- /assets/scss/simple-locator.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Simple Locator Front-End Styles 3 | // -------------------------------------------------- 4 | @import "_variables.scss"; 5 | @import "_mixins.scss"; 6 | @import "_alerts.scss"; 7 | @import "_form.scss"; 8 | @import "_form-results.scss"; 9 | @import "_widget.scss"; 10 | @import "_maps.scss"; 11 | @import "_pagination.scss"; -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kylephillips/simplelocator", 3 | "description": "Simple Locator for WordPress", 4 | "keywords": ["wordpress", "plugin", "store locator"], 5 | "homepage": "http://locatewp.com", 6 | "license": "GPL", 7 | "authors": [ 8 | { 9 | "name": "Kyle Phillips", 10 | "email": "support@nestedpages.com", 11 | "homepage": "https://github.com/kylephillips" 12 | } 13 | ], 14 | "type": "wordpress-plugin", 15 | "require": { 16 | "php": ">=5.3.2", 17 | "composer/installers": "v1.0.6", 18 | "league/csv": "7.0.*@dev", 19 | "guzzlehttp/guzzle": "~6.0" 20 | }, 21 | "autoload": { 22 | "psr-4" : { 23 | "SimpleLocator\\" : "app" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /languages/index.php: -------------------------------------------------------------------------------- 1 | Simple Locator plugin requires'.$flag.' version '.$version.' or greater.

','Plugin Activation Error', array( 'response'=>200, 'back_link'=>TRUE ) ); 40 | } 41 | 42 | if ( !class_exists('Bootstrap') ) : 43 | define('SIMPLELOCATOR_DIR', __DIR__); 44 | define('SIMPLELOCATOR_URI', __FILE__); 45 | wpsimplelocator_check_versions(); 46 | require('vendor/autoload.php'); 47 | require_once('app/SimpleLocator.php'); 48 | SimpleLocator::init(); 49 | endif; -------------------------------------------------------------------------------- /templates/search-form.php: -------------------------------------------------------------------------------- 1 | 14 |
15 | 16 | 17 | 18 |
> 19 | 20 | 21 | 22 | 23 |
24 | 25 | /> 26 |
27 |
28 | 29 | 32 |
33 | 34 |
35 | 38 | 39 |
40 |
<?php _e('Loading Indicator', 'simple-locator'); ?>
41 |
42 | 43 |
'; 51 | echo $out; 52 | endif; 53 | ?> 54 | 55 |
56 |
57 | 58 | '; -------------------------------------------------------------------------------- /views/post-meta/error-modal.php: -------------------------------------------------------------------------------- 1 | 6 |
7 |
8 |
9 |

10 |
11 | 15 |
-------------------------------------------------------------------------------- /views/post-meta/location-meta.php: -------------------------------------------------------------------------------- 1 | form_fields->order(); 4 | ?> 5 |
6 | meta[$method]) ) ? $this->meta[$method] : null; 10 | $field = ( method_exists($this->form_fields, $method) ) 11 | ? $this->form_fields->$method($value, $post->ID) // Included Field 12 | : $this->form_fields->customField($method, $value, $post->ID); // Custom Field 13 | 14 | 15 | if ( $method == 'map' ) : 16 | $html .= '
'; 17 | continue; 18 | endif; 19 | 20 | if ( $method == 'latlng' ) : 21 | $latitude = ( isset($this->meta['latitude']) ) ? $this->meta['latitude'] : null; 22 | $longitude = ( isset($this->meta['longitude']) ) ? $this->meta['longitude'] : null; 23 | $html .= '
'; 24 | $html .= '' . __('Geocode values will update on save. Fields are for display purpose only.', 'simple-locator') . ''; 25 | $html .= '

'; 26 | $html .= ''; 27 | $html .= ''; 28 | $html .= '

'; 29 | $html .= '

'; 30 | $html .= ''; 31 | $html .= ''; 32 | $html .= '

'; 33 | $html .= '
'; 34 | continue; 35 | endif; 36 | 37 | $html .= '

' . $this->form_fields->output($field, $value) . '

'; 42 | } 43 | echo $html; 44 | ?> 45 | 46 |
47 | -------------------------------------------------------------------------------- /views/search-results.php: -------------------------------------------------------------------------------- 1 | search_data['results']) < 1 ) ? false : true; 7 | $output = '
'; 10 | 11 | // No Results 12 | if ( !$has_results ) $output .= $this->results_info->noResultsFoundError(); 13 | 14 | // Results 15 | if ( $has_results ) : 16 | $output .= $this->results_info->resultsHeader(); 17 | $output .= '

' . $this->results_info->newSearchLink() . '

'; 18 | $output .= $this->results_info->currentResultCounts(); 19 | 20 | $output .= '
request['mapheight']) && $this->request['mapheight'] !== "" ) $output .= 'style="height:' . $this->request['mapheight'] . 'px;"'; 22 | $output .= ' data-latitude="' . $this->request['latitude'] . '"'; 23 | $output .= ' data-longitude="' . $this->request['longitude'] . '"'; 24 | $output .= '>
'; 25 | 26 | $results_output = '
'; 27 | foreach($this->search_data['results'] as $result) : 28 | $results_output .= $result['output']; 29 | endforeach; 30 | $results_output .= '
'; 31 | $output .= apply_filters('simple_locator_non_ajax_results_output', $results_output, $this->request, $this->search_data); 32 | 33 | $output .= '
'; 34 | $output .= $this->results_info->pagination('back'); 35 | $output .= $this->results_info->pagination('next'); 36 | $output .= $this->results_info->goToPage(); 37 | $output .= $this->results_info->pagePosition(); 38 | $output .= '
'; 39 | endif; 40 | 41 | $output .= '
'; -------------------------------------------------------------------------------- /views/settings/defaultmap.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |

4 | 5 |
6 |
7 |

8 |

9 |
10 |
11 | 12 |
13 |
14 | 15 | 34 | 35 |
36 |
37 |

38 |

39 |
40 |
41 | 43 | 44 |
45 |
46 | 47 |
-------------------------------------------------------------------------------- /views/settings/import-0.php: -------------------------------------------------------------------------------- 1 | '; 8 | elseif ( !$apikey ) : 9 | echo '

' . __('Import functionality requires a Google Maps Server API key. The key must have the Geocoding API enabled, and your server\'s IP address must be whitelisted in the Google Developer Console. To enter an API key, visit the Simple Locator general settings tab.', 'simple-locator') . '

'; 10 | else : 11 | // Steps 12 | if ( isset($_GET['step']) && in_array($_GET['step'], array('1', '2', '3')) ) { 13 | include 'import-' . sanitize_text_field($_GET['step']) . '.php'; 14 | } else { 15 | include 'import-1.php'; 16 | } 17 | endif; 18 | ?> 19 | -------------------------------------------------------------------------------- /views/settings/import-errors.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/simple-locator/a3642546de47a78f5d9781ba2fb86ad8d21c6c0c/views/settings/import-errors.php -------------------------------------------------------------------------------- /views/settings/result-field-custom-selection.php: -------------------------------------------------------------------------------- 1 | field_repo->getFieldsForPostType($this->post_type); 3 | $post_type = get_post_type_object($this->post_type); 4 | $image_sizes = get_intermediate_image_sizes(); 5 | ?> 6 |
7 |
8 | 9 | 20 | 21 |
22 |
23 | 24 | 32 | 33 |
34 |
-------------------------------------------------------------------------------- /views/settings/resultsfields.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |

6 |

7 |
8 |
9 | settings_repo->resultsFormatting(), 'wpsl_results_fields_formatted', [ 13 | 'media_buttons' => false, 14 | 'textarea_name' => 'wpsl_results_fields_formatted[output]', 15 | 'tabindex' => 1, 16 | 'textarea_rows' => 12, 17 | 'teeny' => true, 18 | 'wpautop' => true 19 | ]); ?> 20 |
21 |
22 |
23 |
24 |

25 |

26 |
27 |
28 | settings_repo->resultsFormatting('default'), 'wpsl_results_fields_formatted_default', [ 32 | 'media_buttons' => false, 33 | 'textarea_name' => 'wpsl_results_fields_formatted_default[output]', 34 | 'tabindex' => 1, 35 | 'textarea_rows' => 12, 36 | 'teeny' => true, 37 | 'wpautop' => true 38 | ]); ?> 39 |
40 |
41 |
42 |
43 |

44 |

45 |
46 |
47 | 48 | 49 |
50 |
51 |
-------------------------------------------------------------------------------- /views/settings/settings.php: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 | 17 | 18 | 19 |
20 | 25 |
26 | 35 |
-------------------------------------------------------------------------------- /views/singular-post.php: -------------------------------------------------------------------------------- 1 | location_data['latitude'] !== "") && ($this->location_data['longitude'] !== "") ) : 6 | $out = '
'; 7 | endif; 8 | 9 | // It's the built in location post type, safe to use fields 10 | if ( ($this->settings_repo->getLocationPostType() == 'location') && (isset($this->location_data['additionalfields'])) && ($this->location_data['additionalfields'] == 'show') ) : 11 | 12 | $out .= '
'; 13 | 14 | // Address 15 | $out .= '

'; 16 | if ( isset($this->location_data['address']) && $this->location_data['address'] !== "" ) 17 | $out .= $this->location_data['address']; 18 | if ( isset($this->location_data['city']) && $this->location_data['city'] !== "" ) 19 | $out .= '
' . $this->location_data['city']; 20 | if ( isset($this->location_data['state']) && $this->location_data['state'] !== "" ) 21 | $out .= ', ' . $this->location_data['state']; 22 | if ( isset($this->location_data['zip']) && $this->location_data['zip'] !== "" ) 23 | $out .= ' ' . $this->location_data['zip']; 24 | $out .= '

'; 25 | 26 | // Website 27 | if ( (isset($this->location_data['website'])) && ($this->location_data['website'] !== "") ){ 28 | $url = \SimpleLocator\Helpers::checkURL($this->location_data['website']); 29 | $out .= '

'; 30 | $out .= __('Website', 'simple-locator') . ':'; 31 | $out .= ' ' . $this->location_data['website'] . ''; 32 | $out .= '

'; 33 | } 34 | 35 | // Phone 36 | if ( isset($this->location_data['phone']) && $this->location_data['phone'] !== "" ){ 37 | $out .= '

'; 38 | $out .= __('Phone', 'simple-locator') . ': ' . $this->location_data['phone']; 39 | $out .= '

'; 40 | } 41 | 42 | // Additional info 43 | if ( isset($this->location_data['additionalinfo']) && $this->location_data['additionalinfo'] !== "" ){ 44 | $out .= '

'; 45 | $out .= $this->location_data['additionalinfo']; 46 | $out .= '

'; 47 | } 48 | 49 | $out .= '
'; 50 | 51 | endif; // Post type 52 | 53 | -------------------------------------------------------------------------------- /views/widget-options.php: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 |

5 |

6 | 7 | 8 |

9 |

10 | 11 | 12 |

13 |

14 | 15 | 16 |

17 |

18 | 19 | 20 |

--------------------------------------------------------------------------------