WordPress: How to display nearby attractions and amenities using Yelp Nearby

👋 Ciprian on Monday, November 1, 2021 in Blog
Last modified on Monday, November 1, 2021

Learn JavaScript by example. Code snippets, how-to's and tutorials. Try now!

This tutorial will show you how to display nearby attractions and amenities based on geographical coordinates (latitude and longitude) using Yelp Nearby. At the time of writing this tutorial, Yelp is using the Fusion API v3.

Get your Yelp API key here and make sure you follow the official display recommendations.

Yelp Nearby

The code snippets below can be easily integrated in a theme or a plugin. A great example of real-life application would be a property theme or a real estate plugin.

Code Structure

Let’s start with the required files and let’s assume you’ll integrate it with your plugin. Include the Yelp functions in your main plugin’s file:

<?php
require_once 'yelp-functions.php';
require_once 'yelp-oauth.php';
require_once 'yelp-nearby.php';

The yelp-functions.php file contains the scripts and styles required by Yelp along with any hooks and filters you might need. In this example, I have used a shortcode. In my opinion, creating an editor block does not justify the overhead and the amount of code.

<?php
add_shortcode('yelp-nearby', 'wpyelp_yelp_nearby');

function wpyelp_yelp_enqueue() {
    wp_register_script('yelp', plugins_url('/yelp.js', __FILE__), [], '1.0.0', true);
}

add_action('wp_enqueue_scripts', 'wpyelp_yelp_enqueue');

The yelp-oauth.php` is the authentication library. Keep it as it is below and replace the API key with your own.

Note that, in my example below, I have the key saved as an option:

$API_KEY = get_option('wpyelp_yelp_api');

<?php
/**
 * Yelp Fusion API code sample.
 *
 * This program demonstrates the capability of the Yelp Fusion API
 * by using the Business Search API to query for businesses by a
 * search term and location, and the Business API to query additional
 * information about the top result from the search query.
 *
 * Please refer to http://www.yelp.com/developers/v3/documentation
 * for the API documentation.
 *
 * Sample usage of the program:
 * `php sample.php --term="dinner" --location="San Francisco, CA"`
 */

// API key placeholders that must be filled in by users.
// You can find it on
// https://www.yelp.com/developers/v3/manage_app

$API_KEY = NULL;

// API constants, you shouldn't have to change these.
$API_HOST = "https://api.yelp.com";
$SEARCH_PATH = "/v3/businesses/search";
$BUSINESS_PATH = "/v3/businesses/";  // Business ID will come after slash.

/**
 * Makes a request to the Yelp API and returns the response
 *
 * @param    $host    The domain host of the API
 * @param    $path    The path of the API after the domain.
 * @param    $url_params    Array of query-string parameters.
 * @return   The JSON response from the request
 */
function wpyelp_yelp_request($host, $path, $url_params = []) {
    $API_KEY = get_option('wpyelp_yelp_api');

    // Send Yelp API Call
    try {
        $curl = curl_init();
        if (FALSE === $curl)
            throw new Exception('Failed to initialize');

        $url = $host . $path . "?" . http_build_query($url_params);
        curl_setopt_array($curl, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_ENCODING => "",
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST => "GET",
            CURLOPT_HTTPHEADER => [
                "authorization: Bearer " . $API_KEY,
                "cache-control: no-cache",
            ],
        ]);

        $response = curl_exec($curl);

        if (FALSE === $response) {
            throw new Exception(curl_error($curl), curl_errno($curl));
        }
        $http_status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
        if (200 != $http_status) {
            throw new Exception($response, $http_status);
        }

        curl_close($curl);
    } catch(Exception $e) {
        trigger_error(sprintf('Curl failed with error #%d: %s', $e->getCode(), $e->getMessage()), E_USER_ERROR);
    }

    return $response;
}

/**
 * Query the Search API by a search term and location
 *
 * @param    $term        The search term passed to the API
 * @param    $location    The search location passed to the API
 * @return   The JSON response from the request
 */
function wpyelp_yelp_search($term, $location) {
    $url_params = [];

    $url_params['term'] = $term;
    $url_params['location'] = $location;
    $url_params['limit'] = 5;
    $url_params['sort_by'] = 'distance';

    return wpyelp_yelp_request($GLOBALS['API_HOST'], $GLOBALS['SEARCH_PATH'], $url_params);
}

/**
 * Query the Business API by business_id
 *
 * @param    $business_id    The ID of the business to query
 * @return   The JSON response from the request
 */
function wpyelp_yelp_get_business($business_id) {
    $business_path = $GLOBALS['BUSINESS_PATH'] . urlencode($business_id);

    return wpyelp_yelp_request($GLOBALS['API_HOST'], $business_path);
}

/**
 * Queries the API by the input values from the user
 *
 * @param    $term        The search term to query
 * @param    $location    The location of the business to query
 */
function wpyelp_yelp_query_api($term, $location) {
    $response = json_decode(wpyelp_yelp_search($term, $location));
    $business_id = $response->businesses[0]->id;

    return $response;
}

The last file is the actual Yelp Nearby view template, yelp-nearby.php. First of all, my example below uses a sample of business types that we are interested in:

$yelp_data = [
    'education',
    'food',
    'health',
    'shopping',
    'restaurants',
    'transport',
];

Also, I am using 2 custom meta fields to store latitude and longitude, as seen here:

get_post_meta($post_id, 'latitude', true);
get_post_meta($post_id, 'longitude', true);

Here’s the full yelp-nearby.php code:

<?php
function wpyelp_yelp_nearby() {
    <?php
    global $post;

    $post_id = $post->ID;

    $wpyelp_yelp_api = get_option('wpyelp_yelp_api');

    $yelp_categories = [
        'active' => ['name' => 'Active Life', 'icon' => ''],
        'arts' => ['name' => 'Arts & Entertainment', 'icon' => ''],
        'auto' => ['name' => 'Automotive', 'icon' => ''],
        'beautysvc' => ['name' => 'Beauty & Spas', 'icon' => ''],
        'education' => ['name' => 'Education', 'icon' => ''],
        'eventservices' => ['name' => 'Event Planning & Services', 'icon' => ''],
        'financialservices' => ['name' => 'Financial Services', 'icon' => ''],
        'food' => ['name' => 'Food', 'icon' => ''],
        'health' => ['name' => 'Health & Medical', 'icon' => ''],
        'homeservices' => ['name' => 'Home Services ', 'icon' => ''],
        'hotelstravel' => ['name' => 'Hotels & Travel', 'icon' => ''],
        'localflavor' => ['name' => 'Local Flavor', 'icon' => ''],
        'localservices' => ['name' => 'Local Services', 'icon' => ''],
        'massmedia' => ['name' => 'Mass Media', 'icon' => ''],
        'nightlife' => ['name' => 'Nightlife', 'icon' => ''],
        'pets' => ['name' => 'Pets', 'icon' => ''],
        'professional' => ['name' => 'Professional Services', 'icon' => ''],
        'publicservicesgovt' => ['name' => 'Public Services & Government', 'icon' => ''],
        'realestate' => ['name' => 'Real Estate', 'icon' => ''],
        'religiousorgs' => ['name' => 'Religious Organizations', 'icon' => ''],
        'restaurants' => ['name' => 'Restaurants', 'icon' => ''],
        'shopping' => ['name' => 'Shopping', 'icon' => ''],
        'transport' =>  ['name' => 'Transportation', 'icon' => ''],
    ];


    $yelp_data = [
        'education',
        'food',
        'health',
        'shopping',
        'restaurants',
        'transport',
    ];

    $yelp_dist_unit = 'kilometers';
    $prop_location = get_post_meta($post_id, 'latitude', true) . ',' . get_post_meta($post_id, 'longitude', true);

    $dist_unit = 1.1515;
    $unit_text = 'mi';
    if ((string) $yelp_dist_unit === 'kilometers') {
        $dist_unit = 1.609344;
        $unit_text = 'km';
    }
    if (!empty($wpyelp_yelp_api)) {
        wp_enqueue_script('yelp');

        $out = '<div class="yelp-nearby">
            <h4 class="listing-section-title">
                What\'s Nearby?
                <br><small>Powered by <img src="' . plugins_url('/yelp-logo.png', __FILE__) . '" alt="Yelp"></small>
            </h4>';

            foreach ($yelp_data as $value) {
                $term_id = $value;
                $term_name = $yelp_categories[$term_id]['name'];
                $response = wpyelp_yelp_query_api($term_id, $prop_location);

                if (isset($response->businesses)) {
                    $businesses = $response->businesses;
                } else {
                    $businesses = [$response];
                }

                $distance = false;
                $current_lat = '';
                $current_lng = '';

                if (isset($response->region->center)) {
                    $current_lat = $response->region->center->latitude;
                    $current_lng = $response->region->center->longitude;
                    $distance = true;
                }

                if ((int) sizeof($businesses) !== 0) {
                    $out .= '<h4>' . $term_name . '</h4>';

                    foreach ($businesses as $data) {
                        $location_distance = '';

                        if ($distance && isset($data->coordinates)) {
                            $location_lat = $data->coordinates->latitude;
                            $location_lng = $data->coordinates->longitude;
                            $theta = $current_lng - $location_lng;
                            $dist = sin(deg2rad($current_lat)) * sin(deg2rad($location_lat)) + cos(deg2rad($current_lat)) * cos(deg2rad($location_lat)) * cos(deg2rad($theta));
                            $dist = acos($dist);
                            $dist = rad2deg($dist);
                            $miles = $dist * 60 * $dist_unit;

                            $location_distance = '<small>(' . round($miles, 2) . ' ' . $unit_text . ')</small>';
                        }

                        $out .= '<div class="wp-block-columns yelp-nearby-listing">
                            <div class="wp-block-column" style="flex-basis:60%">' . $data->name . ' ' . $location_distance . '</div>
                            <div class="wp-block-column" style="flex-basis:40%">
                                <span class="yelp-rating-wrap">
                                    <span class="yelp-stars" data-stars="' . $data->rating . '">
                                </span>
                                <small> ' . $data->review_count . ' reviews</small>
                            </div>
                        </div>';
                    }
                }
            }
        $out .= '</div>';

        echo $out;
    }
}

This is the CSS styling and it’s pretty small. I am leveraging WordPress’ block editor for the column styles. Note that in my yelp.js file I am using Font Awesome 5 icons as SVG. Feel free to enqueue the Font Awesome styles or replace the SVG with another library.

.wp-block-columns.yelp-nearby-listing {
    margin-bottom: 0;
}

.yelp-category .yelp-cat-listing li {
    font-size: 14px;
    font-weight: 400;
    line-height: 1.25;
    margin-bottom: 4px;
    list-style: none;
}

Finally, here’s the yelp.js file. Again, pretty small, and it can be integrated with your plugin (or theme).

/**
 * onDOMLoaded routines
 */
document.addEventListener('DOMContentLoaded', () => {
    if (document.querySelector('.yelp-rating-wrap')) {
        [].forEach.call(document.querySelectorAll('.yelp-stars'), (stars) => {
            let rating = stars.dataset.stars;

            // Round to nearest half
            rating = Math.round(rating * 2) / 2;
            let output = [],
                i;

            // Append all the filled whole stars
            for (i = rating; i >= 1; i--) {
                output.push('<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="svg-inline--fa fa-star fa-w-18 fa-fw" data-icon="star" data-prefix="fas" viewBox="0 0 576 512"><defs/><path fill="#f8b42b" d="M259.3 17.8L194 150.2 47.9 171.5a32 32 0 00-17.7 54.6l105.7 103-25 145.5a32 32 0 0046.4 33.7L288 439.6l130.7 68.7a32 32 0 0046.4-33.7l-25-145.5 105.7-103a32 32 0 00-17.7-54.6L382 150.2 316.7 17.8a32 32 0 00-57.4 0z"/></svg>');
            }

            // If there is a half a star, append it
            if (i === .5) {
                output.push('<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="svg-inline--fa fa-star-half-alt fa-w-17 fa-fw" data-icon="star-half-alt" data-prefix="fas" viewBox="0 0 536 512"><defs/><path fill="#f8b42b" d="M508.6 171.5l-146.4-21.3-65.4-132.4A31.8 31.8 0 00267.9 0a31.6 31.6 0 00-28.6 17.8l-65.5 132.4-146.4 21.3A32 32 0 009.7 226l105.9 103-25 145.4a32 32 0 0046.4 33.8l131-68.7 131 68.7a32 32 0 0046.5-33.7L420.3 329l105.9-103a32 32 0 00-17.7-54.6zM386.8 294.7l-18.1 17.6 4.3 25 19.5 113.4L290.4 397 268 385.3V68.1l51 103.3 11.2 22.7 25 3.6 114.3 16.6-82.7 80.4z"/></svg>');
            }

            // Fill the empty stars
            for (i = (5 - rating); i >= 1; i--) {
                output.push('<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="svg-inline--fa fa-star fa-w-18 fa-fw" data-icon="star" data-prefix="fal" viewBox="0 0 576 512"><defs/><path fill="#f8b42b" d="M528.1 171.5L382 150.2 316.7 17.8a32 32 0 00-57.4 0L194 150.2 47.9 171.5a32 32 0 00-17.7 54.6l105.7 103-25 145.5a32 32 0 0046.4 33.7L288 439.6l130.7 68.7a32 32 0 0046.4-33.7l-25-145.5 105.7-103a32 32 0 00-17.7-54.6zM405.8 317.9l27.8 162L288 403.5 142.5 480l27.8-162L52.5 203.1l162.7-23.6L288 32l72.8 147.5 162.7 23.6-117.7 114.8z"/></svg>');
            }

            stars.innerHTML = output.join('');
        });
    }
});

Related: Read how Yelp refreshed their logo.

Remember to keep an eye on your Yelp API usage limits, and use caching to avoid going over the limit.

Yelp API Usage Limits

Buy me a coffee to support my work!

👋 Added by Ciprian on Monday, November 1, 2021 in Blog. Last modified on Monday, November 1, 2021.

Leave a Reply

You have to agree to the comment policy.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Lighthouse 3.6 has arrived!
Privacy Policy