Create a WordPress Location-Based Search:
Creating a location-based search feature for a WordPress website involves integrating geolocation functionality and customizing your search system to filter results based on user-provided or detected location data. Here’s a general guide to help you achieve this:
1. Using a plugin:
1. Choose a Geolocation Plugin: Start by choosing a suitable WordPress plugin that offers geolocation functionality. Some popular options include “WP Store Locator,” “GeoDirectory,” and “Locatoraid.”
2. Install and Activate the Plugin: Search for your chosen geolocation plugin in the WordPress plugin repository, install it, and activate it.
3. Configure the Plugin: Most geolocation plugins will have settings to configure how your location-based search works. You’ll need to set parameters such as map styles, location data input methods (manual entry or automatic detection), radius distance, and other relevant options.
4. Add Locations: If your website involves displaying specific locations (e.g., stores, events, properties), you’ll need to add these locations using the plugin’s interface. Typically, you’ll provide the location name, address, and potentially additional information like hours of operation or contact details.
5. Integrate Geolocation with Search: Depending on the plugin you’re using, it might have built-in shortcodes or widgets that you can use to display a search form with location-based options. If not, you might need to customize your theme’s templates or use a page builder to create a custom search page.
6. Customize the Search Form: Create a search form that includes a location input field along with other search criteria you want to offer. The location input could be a text field where users can type in their location or use a map-based picker if your plugin supports it.
7. Filter Search Results: Modify your website’s search functionality to consider the location parameter when querying your database for results. You’ll need to integrate the plugin’s geolocation functionality into your search queries. This might involve using hooks, filters, or custom code depending on your site’s setup and the chosen plugin.
8. Display Results: Once you’ve gathered location-based search results from your database, display them on your search results page. Depending on your design, you might want to show the results as a list, grid, or on a map.
9. Implement Maps (if needed): If your plugin supports it, you can integrate interactive maps into your search results page. This can help users visualize the location of search results more effectively.
10. Test Thoroughly: Testing is crucial to ensure that the location-based search functionality works as intended. Test it on various devices, browsers, and with different search scenarios to catch any potential issues.
Remember that this is a general guide, and the exact steps might vary based on the plugin you choose and your website’s specific requirements. Make sure to refer to the documentation of the geolocation plugin you’re using for more detailed instructions.
2. Custom Code
I’ve done this a few times in the past but yesterday I decided to write these notes for a colleague to show how to construct a location based search for a new WordPress site he is developing. Essentially to create a location search, in this case for Property sales, we need to override the standard WordPress search with our own custom query. The following is split into sections. It’s written mostly in note form but it should be easy enough to follow. If you have any questions by all means comment and I’ll answer as best as I can.
Data
Create a new custom post type called ‘property’ which will serve as the location for the data keeping it away from the other content on the site.
Custom fields
I use Advanced Custom Fields for all of my projects these days but by all means use your own plugin or method. As long as the data is stored in wp_postmeta then this tutorial is still valid. Create something similar to the following fields in your system. These aren’t really referenced specifically later on but are a good start for search fields for this sort of site.
- Beds (select, fixed number of options)
- Baths (select, fixed number of options)
- Price (number field)
- Address (separate fields unless you want to split them manually)
- Type (select, predefined, multiselect?)
- Status (select, predefined… under offer, sold, available?)
Custom data
On saving a new/exsiting property post type item you need to geocode the address and save the resulting latitude and longitude into the wp_postmeta table. Use the following page to help you with this:
https://codex.wordpress.org/Plugin_API/Action_Reference/save_post
Use the ‘book’ example looking for the ‘property’ post type almost verbatim. It will show you how to trigger a PHP function on the saving of a new or existing item of content using the correct post type. You then need to Geocode (convert to latitude and longitude) the address string from the POST array and save in postmeta per above. The following page will help you with that.
https://www.andrew-kirkpatrick.com/2011/10/google-geocoding-api-with-php/
If you aren’t familiar with how to save to postmeta then the following will help:
update_post_meta($post_id, 'latitude', $latitude);
Build the search form sidebar
This can be done either in raw PHP in sidebar.php (or somewhere in the theme) or as a widget (latter method preferred for sidebars as makes it portable). If widgets then the following will help significantly as the code sample is more or less exactly what you need:
https://codex.wordpress.org/Widgets_API
Copy the code from the section titled ‘Example’. This will register a widget on the system which you can drag into one or more sidebars in Appearance > Widgets
Setting up a sidebar is here:
https://codex.wordpress.org/Function_Reference/register_sidebar
If you are using the Bones theme (my preference) then it has several set up out of the box. A sidebar has a name which you use to put the contents onto the site. The code is simply:
dynamic_sidebar($sidebar_name);
It will echo by default and outputs nothing if no widgets so nice and easy really
Build the search form
You can do in HTML/CSS as normal hard coding the values of the search or pulling from $_GET which could be where you are advised to send the data through (means you can directly link via URL to a search result rather than POST which is less portable). The form action to use for a search in WP is /?s= which will activate the search template. ‘s’ is the querystring parameter for a search term. You can perform a search without it.. A handsome chappie wrote a code sample you can use:
https://www.sean-barton.co.uk/2012/05/wordpress-search-keyword
Perform the search
search.php in your theme powers the search results as you might know. The query itself can be done anywhere so use this for layout purposes. There should be ‘the loop’ in this file as normal which is designed for search results. Before ‘if (have_posts()): the_post();’ add a PHP function call or your own to something you have defined in functions.php or simply dump your search code into search.php.. there is no ‘wrong’ way to do it.
The trigger for the search is a WP function ‘query_posts()’. It will override the array of data that ‘has_posts’ has to work with and you can use the native output loop/functions to display the results.
Constructing a custom search would be a case of building an array and passing to query_posts. You can get most of the search done this way and then split into a bit of SQL perhaps for the location based side of things. I shall go into that later.
The resource you need to map the majority of the fields would be:
https://codex.wordpress.org/Class_Reference/WP_Query
WP Query is the class which WP uses at the back of query_posts but using the latter function sets up globals which make WordPress behave differently in ‘the loop’. For searching on data stored in postmeta you would need to use ‘meta_query’, taxonomies use ‘tax_query’ and general stuff like search you just pass as attributes to the array. Examples as follows which you can break down and use if you like:
function cf_custom_search_query() {
global $wp_query; //not sure we need this
$arr = array();
$author = cf_get('author'); //cf_get is a wrapper on if (isset(something)) return something; else return false;
$genre = cf_get('genre');
$price_from = cf_get('price-from');
$price_to = cf_get('price-to');
$paged = get_query_var('paged'); //wp function. copy verbatim
$arr['post_type'] = 'property'; //post type slub
$arr['post_status'] = 'publish'; //published only as opposed to draft or private
$arr['paged'] = $paged; //sets the page number to show.. just leave this as is
$condition = array();
foreach ($_REQUEST as $key=>$value) {
if (substr($key, 0, 5) == 'cond-') {
$condition[] = substr($key, 5); //for multi select checkboxes
}
}
if ($val = cf_get('s')) {
$arr['s'] = $val; //s is the actual search term
}
$meta_query = array();
if ($price_from || $price_to || $author) { //meta query is for items in post_meta. we can only pass a single array although the array can have multiple conditions
if ($price_from && $price_to) {
$meta_query[] = array(
'key' => '_price',
'value' => array($price_from, $price_to),
'type' => 'DECIMAL',
'compare' => 'BETWEEN'
);
} else if ($price_from) {
$meta_query[] = array(
'key' => '_price',
'value' => $price_from,
'type' => 'DECIMAL',
'compare' => '>='
);
} else if ($price_to) {
$meta_query[] = array(
'key' => '_price',
'value' => $price_to,
'type' => 'DECIMAL',
'compare' => '<='
);
}
if ($author) {
$meta_query[] = array(
'key' => 'author',
'value' => $author,
'type' => 'CHAR',
'compare' => 'LIKE'
);
}
if ($meta_query) {
$arr['meta_query'] = $meta_query;
}
}
if ($genre && $genre != '-') { //this is a custom taxonomy. so we pass the category slug (or an array of slugs) from a category.
$arr['tax_query'][] = array(
'taxonomy' => 'product_cat',
'field' => 'slug',
'terms' => $genre,
);
}
if ($condition) { //as above. another custom taxonomy
$arr['tax_query'][] = array(
'taxonomy' => 'condition',
'field' => 'slug',
'terms' => $condition,
);
}
if (!$sort = cf_get('sort_order')) {
$sort = cf_session('sort_order', 'price_low');
}
if ($sort) {
if ($sort == 'price_low' || $sort == 'price_high') {
$arr['orderby'] = 'meta_value_num';
$arr['meta_key'] = '_price';
$arr['order'] = 'ASC';
if ($sort == 'price_high') {
$arr['order'] = 'DESC';
}
} else {
$arr['orderby'] = 'title';
$arr['order'] = 'DESC';
if ($sort == 'abc') {
$arr['order'] = 'ASC';
}
}
$_SESSION['sort_order'] = $sort; //so it's saved for next time if they go elsewhere and come back
}
query_posts($arr); //performs the query and sets up the globals
}
Performing a location based search
On search we have ‘s’ or ‘location’ depending on how you built your form. Up to you which field you use but perform the same Geocode on the fly as you did for the save_post step above. This will give you a latitude and longitude to search on. To get into the actual SQL of a search you would want to implement something like the following:
https://codex.wordpress.org/Plugin_API/Filter_Reference/posts_where //to add to WHERE
https://codex.wordpress.org/Plugin_API/Filter_Reference/posts_join //to add to FROM
A handy usage conversation to help with the above:
https://wordpress.stackexchange.com/questions/75381/using-dynamic-conditions-in-posts-where-filter
This is a good resource giving you all of the filters you can use to get access to the SQL of the main query:
https://codex.wordpress.org/Plugin_API/Filter_Reference/posts_clauses
Make sure to wrap your add_filter and remove_filter calls in is_search() which means you only require the LAT/LONG calculation to be done in search based queries.. or just only add/remove the filter in your search function defined in functions.php or search.php and only called on a search.. making sure to remove once used.
if (is_search()) {
add_filter('posts_where', 'my_posts_where');
//.. and so on
//perform search function
remove_filter('posts_where', 'my_posts_where');
//.. and so on
}
The remove is just housekeeping, however, if you don’t remove the filter(s) then the next time you run query_posts in that same page load the same filters will be added which might mess with the result unexpectedly. To put this together you need to join in two instances of wp_postmeta or $wpdb->postmeta (in case wp_ is not the table prefix) making sure to alias uniquely:
add_filter('posts_join', 'my_posts_join');
function my_posts_join($sql) {
$sql .= ' JOIN ' . $wpdb->postmeta . ' my_join1 ON (' . $wpdb->posts . '.ID = my_join1.post_id AND my_join1.meta_key="latitude");
return $sql;
}
And in posts_where:
add_filter('posts_where', 'my_posts_where');
function my_posts_where($sql) {
$sql .= ' AND my_join1.value = "blah" '; //always start AND because it will be appended onto the main query and you can use the aliases defined in the post_join above
return $sql;
}
You can use the geocode values for the location entered via $_GET and then write the SQL to return the correct result based of something like this example:
https://developers.google.com/maps/articles/phpsqlsearch_v3
The section titled ‘Finding Locations with MySQL’ gives the query and some useful advice.
Get a brew and put your feet up!