David Stockdale's Scrapcode

Fixed/Replaced Comment Country Counter

This is the fifth post I have made about automatically counting the number of comments from various countries.

Unfortunately the service I used to make use of has allowed their API to fall apart.

Fortunately I was easily able to find a replacement for it!

APILayer provides a free to use API that only requires you to sign up and then get your API Key from their dashboard.

Once you have that this simple code can be added to the “functions.php” file of your child theme:

/**
 * Adds the "cron_hook" action hook.
 * This is then called by a cron job.
 */
add_action( 'cron_hook', 'location_list_shortcode' );

/**
 * Adds the "location_list" shortcode.
 * [location_list]
 */
add_shortcode('location_list', 'location_list_shortcode');

/**
 * Lists locations comments came from (by IP), and stores as a single row in `locations.location`.
 * Uses APILayer IPAPI: https://api.ipapi.com/api/{ip}?access_key=... :contentReference[oaicite:2]{index=2}
 */
function location_list_shortcode() {
    // 1) Put your IPAPI access key somewhere safe (wp-config.php is ideal), e.g.:
    define('IPAPI_ACCESS_KEY', '<YOUR API KEY HERE>');
    if (!defined('IPAPI_ACCESS_KEY') || !IPAPI_ACCESS_KEY) {
        return 'IPAPI access key not configured.';
    }

    $comments = get_comments();
    if (empty($comments)) {
        return 'No comments found.';
    }

    $countries = [];

    foreach ($comments as $comment) {
        $comment_ip = $comment->comment_author_IP;

        // Skip invalid / private / reserved IPs (removal may break API)
        if (!filter_var($comment_ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
            continue;
        }

        // Cache country lookups per IP to reduce API calls
        $cache_key = 'ipapi_country_' . md5($comment_ip);
        $country_name = get_transient($cache_key);

        if ($country_name === false) {
            // Request only country_name to reduce payload size :contentReference[oaicite:3]{index=3}
            $url = add_query_arg(
                [
                    'access_key' => IPAPI_ACCESS_KEY,
                    'fields'     => 'country_name', // fields option :contentReference[oaicite:4]{index=4}
                ],
                'https://api.ipapi.com/api/' . rawurlencode($comment_ip) // endpoint format :contentReference[oaicite:5]{index=5}
            );

            $response = wp_remote_get($url, [
                'timeout' => 10,
            ]);

            if (is_wp_error($response)) {
                continue;
            }

            $code = wp_remote_retrieve_response_code($response);
            $body = wp_remote_retrieve_body($response);

            if ($code !== 200 || !$body) {
                continue;
            }

            $data = json_decode($body, true);
            if (!is_array($data)) {
                continue;
            }

            // On success, the API returns the requested field(s) :contentReference[oaicite:6]{index=6}
            $country_name = isset($data['country_name']) ? trim((string)$data['country_name']) : '';

            // Cache for a day (tune as you like)
            set_transient($cache_key, $country_name, DAY_IN_SECONDS);
        }

        if (!empty($country_name)) {
            $countries[] = $country_name;
        }
    }

    if (empty($countries)) {
        return 'No country data available.';
    }

    // Count occurrences per country
    $counts = array_count_values($countries);

    // Build "Country = N, Country2 = M" string (same style as your original)
    $parts = [];
    foreach ($counts as $country => $count) {
        $parts[] = $country . ' = ' . (int)$count;
    }
    $resultz = implode(', ', $parts);

    // Update the 1 row in the locations table (keeping your existing behavior)
    global $wpdb;
    $sql  = "UPDATE `locations` SET `location`=%s WHERE `location` IS NOT NULL;";
    $prep = $wpdb->prepare($sql, $resultz);

    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta($prep);

    return $resultz;
}

/**
 * Adds the "read_locations" shortcode.
 * [read_locations]
 */
add_shortcode('read_locations', 'read_locations');
/**
 * Reads the rows of the "locations" table.
 */
function read_locations() {
	global $wpdb;
    return (string) $wpdb->get_var("SELECT `location` FROM `locations` LIMIT 1");
}

/**
 * Adds the "total_locations" shortcode.
 * [total_locations]
 */
add_shortcode('total_locations', 'total_locations');
/**
 * Adds up the numbers returned by the "read locations" shortcode.
 */
function total_locations() {
	$string = do_shortcode('[read_locations]');
	
	preg_match_all('!\d+!', $string, $matches);
	
	return("Total comments: " . array_sum($matches[0]));
}

Then you can have a cron job that calls the cron hook every 6 hours or once per day to update your numbers.

Smooth and easy, just the way I like it!

Leave a Reply