src/includes/classes/Core/Utils/Ip.php
protected function geoData(string $ip)
    {
        # Valid IP. Do we have one?

        if (!($ip = $this->c::mbTrim(mb_strtolower($ip)))) {
            return false; // Not possible.
        }
        # Check object cache. Did we already do this?

        if (($geo = &$this->cacheKey(__FUNCTION__, $ip)) !== null) {
            return $geo; // Already cached this.
        }
        # Check filesystem cache. Did we already do this?

        if (!$this->App->Config->©fs_paths['©cache_dir']) {
            throw $this->c::issue('Missing cache directory.');
        }
        $ip_sha1               = sha1($ip); // Needed below.
        $cache_dir             = $this->App->Config->©fs_paths['©cache_dir'].'/ip-geo-data/'.$this->c::sha1ModShardId($ip_sha1, true);
        $cache_dir_permissions = $this->App->Config->©fs_permissions['©transient_dirs'];
        $cache_file            = $cache_dir.'/'.$ip_sha1.'.json';

        if (is_file($cache_file) && filemtime($cache_file) >= strtotime('-30 days')) {
            $cache      = json_decode(file_get_contents($cache_file));
            return $geo = is_object($cache) ? $cache : false;
        }
        # Initialize request-related variables.

        $region = $country = ''; // Initialize.

        # Query geoPlugin service.

        $response = $this->c::remoteRequest(
            'GET::http://www.geoplugin.net/json.gp?ip='.urlencode($ip),
            ['max_con_secs' => 5, 'max_stream_secs' => 5]
        );
        if (!$response || !is_object($json = json_decode($response))) {
            // Fill object cache, but do not create a cache file.
            return $geo = false; // Connection failure.
        }
        # Parse response; i.e., try to fill geo data.

        if (!empty($json->geoplugin_regionCode)) {
            $region = (string) $json->geoplugin_regionCode;
            $region = $this->c::mbStrPad($region, 2, '0', STR_PAD_LEFT);
            $region = mb_strtoupper($region);
        }
        if (!empty($json->geoplugin_countryCode)) {
            $country = (string) $json->geoplugin_countryCode;
            $country = mb_strtoupper($country);
        }
        # Fill object cache; i.e., `\StdClass` or `false`.

        $geo = (object) compact('region', 'country');

        if (mb_strlen($geo->region) !== 2 || mb_strlen($geo->country) !== 2) {
            $geo = false; // Invalid (or insufficient) data.
        }
        # Cache geo data; i.e., `\StdClass` or `false`.

        if (!is_dir($cache_dir)) {
            mkdir($cache_dir, $cache_dir_permissions, true);
        }
        file_put_contents($cache_file, json_encode($geo));

        # Return, else `false` on failure.

        return $geo; // `\StdClass` or `false`.
    }
protected function geoData(string $ip)
    {
        # Valid IP. Do we have one?

        if (!($ip = $this->c::mbTrim(mb_strtolower($ip)))) {
            return false; // Not possible.
        }
        # Check object cache. Did we already do this?

        if (($geo = &$this->cacheKey(__FUNCTION__, $ip)) !== null) {
            return $geo; // Already cached this.
        }
        # Check filesystem cache. Did we already do this?

        if (!$this->App->Config->©fs_paths['©cache_dir']) {
            throw $this->c::issue('Missing cache directory.');
        }
        $ip_sha1               = sha1($ip); // Needed below.
        $cache_dir             = $this->App->Config->©fs_paths['©cache_dir'].'/ip-geo-data/'.$this->c::sha1ModShardId($ip_sha1, true);
        $cache_dir_permissions = $this->App->Config->©fs_permissions['©transient_dirs'];
        $cache_file            = $cache_dir.'/'.$ip_sha1.'.json';

        if (is_file($cache_file) && filemtime($cache_file) >= strtotime('-30 days')) {
            $cache      = json_decode(file_get_contents($cache_file));
            return $geo = is_object($cache) ? $cache : false;
        }
        # Initialize request-related variables.

        $region = $country = ''; // Initialize.

        # Query geoPlugin service.

        $response = $this->c::remoteRequest(
            'GET::http://www.geoplugin.net/json.gp?ip='.urlencode($ip),
            ['max_con_secs' => 5, 'max_stream_secs' => 5]
        );
        if (!$response || !is_object($json = json_decode($response))) {
            // Fill object cache, but do not create a cache file.
            return $geo = false; // Connection failure.
        }
        # Parse response; i.e., try to fill geo data.

        if (!empty($json->geoplugin_regionCode)) {
            $region = (string) $json->geoplugin_regionCode;
            $region = $this->c::mbStrPad($region, 2, '0', STR_PAD_LEFT);
            $region = mb_strtoupper($region);
        }
        if (!empty($json->geoplugin_countryCode)) {
            $country = (string) $json->geoplugin_countryCode;
            $country = mb_strtoupper($country);
        }
        # Fill object cache; i.e., `\StdClass` or `false`.

        $geo = (object) compact('region', 'country');

        if (mb_strlen($geo->region) !== 2 || mb_strlen($geo->country) !== 2) {
            $geo = false; // Invalid (or insufficient) data.
        }
        # Cache geo data; i.e., `\StdClass` or `false`.

        if (!is_dir($cache_dir)) {
            mkdir($cache_dir, $cache_dir_permissions, true);
        }
        file_put_contents($cache_file, json_encode($geo));

        # Return, else `false` on failure.

        return $geo; // `\StdClass` or `false`.
    }
Geo data for IP address.

@since 150424 Initial release.

@param string $ip An IP address.

@return \StdClass|bool Geo data for IP.