Jump to content

Recommended Posts

Posted

Hey everyone,

I've been building a e-commerce project and needed to show personalized content based on visitor location — shipping availability, regional pricing, state-level compliance notices. Nothing like this existed in the PW ecosystem, so I built it.

What it does

Detects country, region and city from the visitor IP using MaxMind GeoLite2 databases (free). Result is cached in session. Exposes $geoip as a wire variable — available in every template automatically, just like $page or $user.

// That's it. No setup, no require, just use it.
if ($geoip->inCountry('US')) {
    echo $page->us_content;
}

API

// Boolean checks — accept single value or array
$geoip->inCountry('US')
$geoip->inCountry(['US', 'CA', 'GB'])
$geoip->inRegion('GA')                  // ISO 3166-2 subdivision code
$geoip->inRegion(['GA', 'NJ', 'NY'])
$geoip->inCity('Atlanta')

// Inline conditional with optional fallback
echo $geoip->showIf('countryCode', 'US', $page->us_block, $page->global_block);
echo $geoip->showIf('regionCode', ['GA', 'NJ', 'NY'], $page->northeast_promo);
echo $geoip->showIf('continent', 'Europe', $page->eu_gdpr_notice);

// Single field
$geoip->getField('countryCode')   // "US"
$geoip->getField('regionCode')    // "GA"
$geoip->getField('city')          // "Atlanta"
$geoip->getField('timezone')      // "America/New_York"

// Full array
$geo = $geoip->detect();
// ip, country, countryCode, continent, region, regionCode,
// city, zip, lat, lon, timezone, corrected, status

Combining conditions

// Country + region
if ($geoip->inCountry('US') && $geoip->inRegion('CA')) {
    echo $page->california_prop65_notice;
}

// Logged-in + location
if ($user->isLoggedIn() && $geoip->inCountry('US')) {
    echo $page->us_member_block;
}

// Time-of-day in visitor's timezone
$tz   = $geoip->getField('timezone') ?: 'UTC';
$hour = (int) (new DateTime('now', new DateTimeZone($tz)))->format('H');
if ($geoip->inCountry('US') && $hour >= 9 && $hour < 17) {
    echo 'Our US office is open right now.';
}

// Pre-select shipping dropdown (Vivino-style)
$selectedCountry = $geoip->getField('countryCode') ?: 'US';
$selectedState   = $geoip->getField('regionCode')  ?: '';

User location correction

Frontend widget lets visitors fix incorrectly detected location. Stored per-IP in DB, applied on subsequent requests. You can also build your own UI — just POST to /?geoip_action=correct with country_code, region_code, city.

Setup

Composer package and databases live in site/assets/GeoIP/ — not in the module directory, so they survive updates.

cd /path/to/site/assets/GeoIP/ && composer require geoip2/geoip2

Then drop GeoLite2-City.mmdb (or GeoLite2-Country.mmdb) in the same folder. Free download from maxmind.com. The module config page shows the exact path and command for your server.

Admin panel

Setup → GeoIP — lookup log with country/region/city, corrections manager, manual IP lookup tool.


GitHub: https://github.com/mxmsmnv/GeoIP
License: MIT
Requires: ProcessWire 3.0.200+, PHP 8.2+

Feedback welcome — especially if you're doing anything geo-based with ProcessWire.

Maxim

  • Like 15
  • Thanks 1
  • 4 weeks later...
  • 2 months later...
  • 2 weeks later...
Posted

Yes. Composer is only needed to build the `vendor/` folder; the module does not need Composer on the server at runtime.

You can run this locally:

cd /path/to/site/assets/GeoIP/
composer require geoip2/geoip2

Then upload the whole `site/assets/GeoIP/` folder to the server, including:

site/assets/GeoIP/vendor/autoload.php
site/assets/GeoIP/GeoLite2-City.mmdb

The module looks for the autoloader at:

site/assets/GeoIP/vendor/autoload.php

So as long as that file and the GeoLite2 database are there, it should work without Composer installed on the hosting server.

  • Like 1
  • Thanks 1
  • 3 months later...
Posted

I see that this module's configuration defaults it to an autoloader. Although you've done quite a bit to make it as efficient as possible, since the lookups are fairly expensive, would it not make more sense to have the GeoIP module manually loaded, do the check, and store its result to the user for subsequent browsing?

I might be incorrectly understanding how the module loads its resources, though.

Posted

Good question. The autoload flag here only registers the $geoip API variable and the request hooks. It does not open the MaxMind database, load the Composer autoloader, or perform a lookup during init().

Detection is lazy and runs only when detect(), getField(), inCountry(), etc. are called, when an admin performs a manual lookup, or when a deferred widget requests its fragment. The result is then cached in memory for the request and, optionally, in the ProcessWire session.

So manually loading the module would mainly remove the global API variable and widget endpoints without avoiding the expensive work — that work is already deferred until it is actually needed.

I realized this was not clear enough in the documentation, so I clarified the performance model and separated detection, frontend endpoints/widgets, and runtime paths into focused traits in version 1.3.3. Thanks for raising it!

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...