Skip to content

Geolocation & Shipping Zones

Flow ID: SY-20 | Module(s): eshop, core | Complexity: Medium Last Updated: 2026-04-04

Business Overview

The geolocation and shipping zones system manages country and county/region data used throughout the platform for address forms, transporter availability, checkout shipping calculations, and store location display. The AdvCountriesCounties library loads all geographic data at controller initialization and provides it as structured lookups for both the storefront and admin panel.

What the system provides:

  • Country and county data with multi-language support (MUI)
  • Filtered views based on registry-configured available countries
  • Dropdown-ready data for address forms (countries, counties, phone codes)
  • Integration with transporter availability checks
  • PSCache for query performance

Where it is used:

  • Checkout address forms (shipping and billing)
  • Admin order management (address display and editing)
  • Transporter availability calculations
  • Store location labeling
  • Reporting filters

Architecture

AdvCountriesCounties (Library)
  |
  +--> constructor -> initializeData()
         |
         +--> Load models: country_model, county_model
         +--> Load registry
         +--> Read AVAILABLE_COUNTRIES from registry (pipe-delimited array)
         +--> PSCache: country_model::countries($countryCodes) -> setUpCountries()
         +--> PSCache: county_model::counties()               -> setUpCounties()

  +--> countries()                   all countries indexed by alpha_2
  +--> availableCountries()          filtered by AVAILABLE_COUNTRIES registry
  +--> countriesDropDown($lang)      {alpha_2 => name} for select elements
  +--> availableCountriesDropDown()  filtered dropdown
  +--> countiesDropDown($lang)       {country => {county_alpha => name}} nested
  +--> availableCountiesDropDown()   filtered nested dropdown
  +--> phoneCodes()                  {alpha_2 => phone_code}
  +--> availablePhoneCodes()         filtered phone codes
  +--> getCountryRawDataCodes()      raw country object by alpha_2
  +--> getCountyRawDataCodes()       raw county object by county_alpha (searches all countries)

Core Classes

ClassFilePurpose
AdvCountriesCountiesecommercen/libraries/AdvCountriesCounties.phpBase library with all lookup methods
CountriesCountiesapplication/libraries/CountriesCounties.phpClient-overridable subclass
Adv_country_modelecommercen/eshop/models/Adv_country_model.phpCountry database queries
Country_modelapplication/modules/eshop/models/Country_model.phpClient-overridable country model
Adv_county_modelecommercen/eshop/models/Adv_county_model.phpCounty database queries
County_modelapplication/modules/eshop/models/County_model.phpClient-overridable county model

Instantiation

The library is instantiated in both base controllers, making it available on every page:

php
// In Adv_front_controller constructor:
$this->countriesCounties = new CountriesCounties();

// In Adv_admin_controller constructor:
$this->countriesCounties = new CountriesCounties();

Code Flow

1. Initialization

initializeData() runs in the constructor:

  1. Load models: country_model and county_model
  2. Load registry library
  3. Read available countries: registry->getArray('AVAILABLE_COUNTRIES', 'ESHOP', '') returns an array like ['GR', 'CY', 'DE']
  4. Flip to lookup: array_flip($countries) for O(1) availability checks
  5. Load countries via PSCache: pscache->model('country_model', 'countries', [[], $countries], $ttl) -- fetches all countries filtered by available codes, with L2 cache TTL
  6. Load counties via PSCache: pscache->model('county_model', 'counties', [], $ttl) -- fetches all counties

2. Country Data Structure

setUpCountries() processes the flat query result into a structured format:

countries['GR'] = {
    alpha_2: 'GR',
    alpha_3: 'GRC',
    iso_cc: 300,
    phone_code: '+30',
    captions: {
        el: 'Ελλάδα',
        en: 'Greece',
        de: 'Griechenland'
    }
}

Each country row from the MUI join produces one language entry in captions. The lang and name properties are removed after merging into captions.

3. County Data Structure

setUpCounties() nests counties under their parent country:

counties['GR']['AT'] = {
    country_alpha_2: 'GR',
    county_alpha: 'AT',
    captions: {
        el: 'Αττική',
        en: 'Attica'
    }
}

4. Dropdown Methods

All dropdown methods return simple key-value arrays suitable for HTML <select> elements:

  • countriesDropDown($lang): ['GR' => 'Greece', 'CY' => 'Cyprus', ...]
  • countiesDropDown($lang): ['GR' => ['AT' => 'Attica', 'TH' => 'Thessaloniki', ...], ...]
  • phoneCodes(): ['GR' => '+30', 'CY' => '+357', ...]

The "available" variants filter by the AVAILABLE_COUNTRIES registry setting.

5. County Ordering

Counties are ordered by a configurable priority list (counties_order config item) in descending FIELD() order, then alphabetically by name. This allows merchants to put their most common delivery regions first.


Data Model

Table: country

ColumnTypeRole
alpha_2char(2)PK, ISO 3166-1 alpha-2 code (e.g., GR)
alpha_3char(3)ISO 3166-1 alpha-3 code (e.g., GRC)
iso_ccintISO 3166-1 numeric code
phone_codevarcharInternational dialing code (e.g., +30)

Table: country_mui

ColumnTypeRole
alpha_2char(2)FK to country.alpha_2
langvarcharLanguage code
namevarcharCountry name in specified language

Table: county

ColumnTypeRole
county_alphavarcharPK, region/county code (e.g., AT for Attica)
country_alpha_2char(2)FK to country.alpha_2

Table: county_mui

ColumnTypeRole
county_alphavarcharFK to county.county_alpha
langvarcharLanguage code
county_namevarcharCounty name in specified language

Configuration

Registry Settings

GroupKeyPurpose
ESHOPAVAILABLE_COUNTRIESPipe-delimited list of enabled country alpha_2 codes (e.g., GR|CY|DE)

Application Config

Config itemPurpose
cache_l2_default_expiresPSCache TTL for geographic data queries
counties_orderArray of county codes to prioritize in dropdown ordering

Caching

Geographic data is cached via PSCache at two levels:

  • Country data: pscache->model('country_model', 'countries', ...) with L2 TTL
  • County data: pscache->model('county_model', 'counties', ...) with L2 TTL

Cache is automatically refreshed when TTL expires. No manual cache invalidation is needed for geographic data changes (though PSCache can be cleared if immediate updates are required).


Client Extension Points

  1. Override the library: Create CountriesCounties in application/libraries/ extending AdvCountriesCounties. Override methods to add custom filtering (e.g., exclude counties not served by the merchant's transporters).

  2. Override country model: Extend Country_model to add custom ordering or filtering logic (e.g., priority sort for primary market).

  3. Override county model: Extend County_model to add custom county data or override the ordering.

  4. Configure available countries: Update ESHOP.AVAILABLE_COUNTRIES in the registry via admin settings to control which countries appear in checkout forms and transporter calculations.

  5. Configure county ordering: Set counties_order in application/config/app.php to prioritize specific regions in dropdowns.

  6. Store label customization: The buildStoreLabel() helper in ecommercen/helpers/store_helper.php uses CountriesCounties for address formatting. Override the pattern parameter to change store address display.


Business Rules

RuleDescription
Available-country filteringOnly countries listed in ESHOP.AVAILABLE_COUNTRIES registry appear in checkout and transporter checks
Multi-language captionsCountry and county names are stored per language; the $lang parameter selects the display language
English fallbackDropdown methods fall back to English (en) when a requested language caption is not available
Eager loadingAll geographic data is loaded once per request in the controller constructor -- no lazy loading
PSCache performanceDatabase queries are cached via PSCache to avoid per-request database hits
Universal availabilityThe library is instantiated in both front and admin base controllers, making it available on every page load
County nestingCounties are always accessed through their parent country code, supporting multiple countries with overlapping region names