maximus Posted March 16 Posted March 16 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 15 1
HMCB Posted April 11 Posted April 11 Wow. This is incredible. Thank you for how thorough it appears to be. 1
maximus Posted June 28 Author Posted June 28 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. 1 1
BrendonKoz Posted October 7 Posted October 7 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.
maximus Posted October 7 Author Posted October 7 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!
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now