skip to Main Content

We’re building a directory of automotive-related businesses in 3 countries – USA, Canada and Mexico.

I created a custom post type ‘Listings’ and tried to move on with the following.

For various SEO purposes we need specific URL structures as follows:

1) Individual listing page

Nothing too fancy – domain.com/usa/listing-slug

2) Per-state archives

Nothing too fancy as well – domain.com/usa/texas

3) Per-city archives

This one is more interesting, we need the following structure:

  • domain.com/usa/cleveland/oh
  • domain.com/usa/cleveland/nd

Where the 1st one is archive for Cleveland, Ohio and the other one for Cleveland, Nevada.

4) Per-ZIP archives

Pretty usual – domain.com/usa/05657

5) Mixed location-category archives

  • domain.com/usa/cleveland/oh/car-parts
  • domain.com/usa/ohio/car-parts
  • domain.com/usa/05657/car-parts

6) Custom content (templates) for all above archive pages, except maybe ZIP

We’re trying to somewhat mimic this website.

If you check, let’s say, the same examples for Cleveland, Ohio and Cleveland, Nevada: http://www.familydaysout.com/kids-things-to-do-usa/cleveland/oh and http://www.familydaysout.com/kids-things-to-do-usa/cleveland/nd you’ll see what I mean – these pages have custom content (descriptions) in the beginning.

Above are per-city custom content examples. The same exists for per-state http://www.familydaysout.com/kids-things-to-do-usa/ohio and mixed:
http://www.familydaysout.com/kids-things-to-do-usa/ohio/major-theme-parks
http://www.familydaysout.com/kids-things-to-do-usa/cleveland/oh/major-theme-parks
http://www.familydaysout.com/kids-things-to-do-usa/5601/major-theme-parks

I got to the point where I can auto-populate location data when adding a new listing, so when I paste a full address, e.g. “21200 Saint Clair Avenue Euclid, Ohio 44117” and save I get this:

automatically populated custom fields

As you can see, country, state, city and ZIP are all there. Category (Car Parts) is also there as a listing category.

Now how can I use these values to construct URLs and pages/templates for archives? Should I automatically convert custom field values to custom taxonomies and go from there? I know I can do this with https://codex.wordpress.org/Plugin_API/Action_Reference/save_post and https://codex.wordpress.org/Function_Reference/wp_set_object_terms

I know custom taxonomies can have descriptions and I know how to output these descriptions. Besides that, taxonomies can be hierarchical, so I can add Ohio and Nevada as parents and each Cleveland as a child for them.

So I’m at the point where I managed to collect and store all the info bits, but not sure how to start manipulating them to achieve the above model.

Q1: Where should I store location data (country, state, city, zip) to be able to use it in URLs as described above? Custom fields or taxonomy terms?

Q2: How do I build archive pages/templates as described above in a way that I can customize templates for them?

Any other helpful tips will be highly appreciated.

3

Answers


  1. As per you requirements , you can achieve this by creation of categories as described below :

    domain.com/usa/cleveland/oh and domain.com/usa/cleveland/nd

    1. Cleveland, Ohio (/usa/cleveland/oh)
    2. Cleveland, Nevada (/usa/cleveland/nd)

    So create a parent category as “USA” and then child category as “Cleveland” and under that one more child category as “Ohio” and its slug as “oh”.

    So hierarchy should be USA->Cleveland->Ohio and in slugs it should be usa->cleveland->oh

    Similarly, USA->Cleveland->Nevada and slugs as usa->cleveland->nd

    Now , For the posts to be displayed at URL :

    1. domain.com/category/usa = provide them “USA” category
    2. domain.com/category/usa/cleveland/ = provide them “Cleveland”
      category
    3. domain.com/category/usa/cleveland/oh = Provide them “Ohio”
      category

    Now you only need to remove the category from URL . So for that write the below code in the .htaccess file located in the root folder.

    RewriteRule . /wordpress/index.php [L]
    
    Login or Signup to reply.
  2. I think there are 2 ways to solve your problem
    First one is to use mod_rewrite to transfer you suggested formatted URLs to associate template php file and handle your request there the way you want.
    For this following link might help. Google for more https://www.addedbytes.com/articles/for-beginners/url-rewriting-for-beginners/

    The 2nd approach would be follow the wordpress page template hierarchy and create template pages accordingly and handle similar requests in template php. For details explanation refer to https://www.smashingmagazine.com/2015/06/wordpress-custom-page-templates/

    and a quick page template hierarchy to understand which page template will execute for particular url refer to https://media-mediatemple.netdna-ssl.com/wp-content/uploads/2015/05/01-wp-template-hierarchy-opt.jpg

    Login or Signup to reply.
  3. After searching around, I think I can help you now.

    1. Register location taxonomy to create base archive slug:

    function wpso36667674_register_location_taxonomy() {
        $labels = [
            'name'          => _x('Locations', 'Taxonomy General Name', 'wpso'),
            'singular_name' => _x('Location', 'Taxonomy Singular Name', 'wpso'),
            'all_items'     => __('All Locations', 'wpso'),
        ];
        $args = [
            'labels'        => $labels,
            'public'        => true,
            'hierarchical'  => true,
            'rewrite'       => ['hierarchical' => true],
        ];
        register_taxonomy('location', 'listing', $args);
    }
    add_action('init', 'wpso36667674_register_location_taxonomy', 0);
    

    We have to change the terms’ slugs which are automatically generated by WordPress to sanitized terms’ names so that the URLs’ structure look as you wished for:

    function wpso36667674_filter_term_link($term_link, $term, $taxonomy) {
        $term_link = rtrim($term_link, '/');
        $pos = strrpos($term_link, '/');
        $term_link = substr($term_link, 0, $pos + 1) . sanitize_title($term->name);
        return $term_link;
    }
    add_filter('term_link', 'wpso36667674_filter_term_link', 0, 3);
    

    Caution: Do not enter terms’ slugs manually when adding new terms, let WordPress generate it for us or we will not be able to query terms’ posts contents correctly. The reason is the two URLs: location/usa/ohio/cleveland and location/usa/idaho/cleveland give us the same posts because WordPress only uses cleveland as term for querying posts’ contents without caring about the parent term.

    2. Register listing post type like this:

    function wpso36667674_register_listing_post_type() {
        $labels = [
            'name'          => _x('Listings', 'Taxonomy General Name', 'wpso'),
            'singular_name' => _x('Listing', 'Taxonomy Singular Name', 'wpso'),
            'all_items'     => __('All Listings', 'wpse'),
        ];
        $args = [
            'labels'        => $labels,
            'public'        => true,
            'menu_icon'     => 'dashicons-location-alt',
            'menu_position' => 6,
            'supports'      => ['title', 'editor', 'thumbnail', 'custom-fields'],
            'taxonomies'    => ['location'],
            'rewrite'       => ['slug' => '%country%/%state%/%city%/%zip%'],
        ];
        register_post_type('listing', $args);
    }
    add_action('init', 'wpso36667674_register_listing_post_type', 0);
    

    We’re not sure whether the listing has state|city|zip or not, so we have to use substitutes (%country%/%state%/%city%/%zip%). Then replace it with each listing meta data (From my experience with WordPress, you should store location data in each listing):

    function wpso36667674_filter_post_link($post_link, $post, $leave_name = false, $sample = false) {
        $zip = get_post_meta($post->ID, 'geolocation_postcode', true);
        $city = get_post_meta($post->ID, 'geolocation_city_short', true);
        $state = get_post_meta($post->ID, 'geolocation_state_short', true);
        $country = get_post_meta($post->ID, 'geolocation_country_short', true);
        $post_link = str_replace('%zip%', $zip, $post_link);
        $post_link = str_replace('%city%', $city, $post_link);
        $post_link = str_replace('%state%', $state, $post_link);
        $post_link = str_replace('%country%', $country, $post_link);
        $post_link = preg_replace('/[^:](/{2,})/', '/', $post_link);  // Remove multiple forward slashes except http://.
        return $post_link;
    }
    add_filter('post_type_link', 'wpso36667674_filter_post_link', 0, 4);
    

    Note: If your use uppercase short names, you must convert it to lowercase when modifying URLs’ structure.

    3. Add rewrite rules to make our custom URLs work correctly.

    // Add rewrite rules for location taxonomy.
    function wpso36667674_add_location_rewrite_rules() {
        add_rewrite_rule('^location/([^/]+)/?$', 'index.php?taxonomy=location&term=$matches[1]', 'top');
        add_rewrite_rule('^location/([^/]+)/([^/]+)/?$', 'index.php?taxonomy=location&term=$matches[2]', 'top');
        add_rewrite_rule('^location/([^/]+)/([^/]+)/([^/]+)/?$', 'index.php?taxonomy=location&term=$matches[3]', 'top');
    }
    add_action('init', 'wpso36667674_add_location_rewrite_rules', 0);
    
    // Add rewrite rules for listings.
    function wpso36667674_add_listing_rewrite_rules() {
        add_rewrite_rule('^usa/([^/]+)/?$', 'index.php?post_type=listing&name=$matches[1]', 'top');
        add_rewrite_rule('^usa/([^/]+)/([^/]+)/?$', 'index.php?post_type=listing&name=$matches[2]', 'top');
        add_rewrite_rule('^usa/([^/]+)/([^/]+)/([^/]+)/?$', 'index.php?post_type=listing&name=$matches[3]', 'top');
        add_rewrite_rule('^usa/([^/]+)/([^/]+)/([^/]+)/([^/]+)/?$', 'index.php?post_type=listing&name=$matches[4]', 'top');
        add_rewrite_rule('^mexico/([^/]+)/?$', 'index.php?post_type=listing&name=$matches[1]', 'top');
        add_rewrite_rule('^mexico/([^/]+)/([^/]+)/?$', 'index.php?post_type=listing&name=$matches[2]', 'top');
        add_rewrite_rule('^mexico/([^/]+)/([^/]+)/([^/]+)/?$', 'index.php?post_type=listing&name=$matches[3]', 'top');
        add_rewrite_rule('^mexico/([^/]+)/([^/]+)/([^/]+)/([^/]+)/?$', 'index.php?post_type=listing&name=$matches[4]', 'top');
        add_rewrite_rule('^canada/([^/]+)/?$', 'index.php?post_type=listing&name=$matches[1]', 'top');
        add_rewrite_rule('^canada/([^/]+)/([^/]+)/?$', 'index.php?post_type=listing&name=$matches[2]', 'top');
        add_rewrite_rule('^canada/([^/]+)/([^/]+)/([^/]+)/?$', 'index.php?post_type=listing&name=$matches[3]', 'top');
        add_rewrite_rule('^canada/([^/]+)/([^/]+)/([^/]+)/([^/]+)/?$', 'index.php?post_type=listing&name=$matches[4]', 'top');    
    }
    add_action('init', 'wpso36667674_add_listing_rewrite_rules', 0);
    

    4. Return back the correct terms’ slugs:

    As I said, by changing usa/idaho/cleveland-idaho to usa/idaho/cleveland in permalinks, queried posts for usa/idaho/cleveland are same as usa/ohio/cleveland. So we must help WordPress know what is the correct term slug to use for querying posts’ contents:

    function wpso36667674_modify_requested_url($query) {
        if ( $query->query_vars['taxonomy'] === 'location' ) {
            $slugs = array_filter( explode('/', $query->request) );
            $term = array_pop($slugs) . '-' . array_pop($slugs);
            if ( get_term_by('slug', $term, 'location') ) {
                $query->query_vars['term'] = $term;
            }
        }
    }
    add_action('parse_request', 'wpso36667674_modify_requested_url');
    

    Thanks to @Jevuska for suggesting to use parse_request.

    5. Create a custom template for each archive location:

    Thanks to WordPress Template Hierarchy, we can do it easily.

    Example:

    For location/usa/ohio, the template will be taxonomy-location-ohio.php.

    But remember that we must use the actual terms’ slugs which generated by WordPress, not what we modified in URLs.

    Example:

    If Cleveland in Ohio was added before Cleveland in Idaho, the term slug of the first is cleveland and the second will be cleveland-idaho.

    Then, template for the first is taxonomy-location-cleveland.php and the second is taxonomy-location-cleveland-idaho.php.

    That’s all! All code you can put directly inside functions.php file. Remember to flush the permalink settings too.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search