Visitor

Even before the session starts and before the first database request, something is already known about the visitor — from the request headers. Where they connected from, what they are browsing on, and what language they speak. These three things are returned by the Agent* family of methods.

They all share one important trait: the data comes from outside, from the client. It is used to decide what to show, but it cannot be trusted as verified information — anyone can forge a header.

All three methods are for the root script. They must not be called inside a cacheable module: the response will be “baked” into the cache and served to visitors it does not belong to. Modules receive this information already in the form of a selected template group, page language, or an explicitly passed parameter.

Visitor IP

$ip = MELBIS()->AgentIp();

The method returns the visitor’s address taking into account that the site may be behind a proxy or CDN — forwarding headers are parsed, not just the direct connection address. Use this for logs, anti-fraud, and region detection instead of accessing $_SERVER['REMOTE_ADDR'] directly.

The method does not depend on the session and works regardless of whether DefineSession has been called.

Device

$device = MELBIS()->AgentDevice();

The method returns one of three strings — phone, tablet, or desktop — and is needed where the storefront is served in different versions from the same address: it is used to select a template group before calling Run().

Detection happens in two steps. First, the browser itself is queried: Chromium over HTTPS sends the client hint Sec-CH-UA-Mobile, which is not a guess but the browser’s own report about itself. If the hint is absent — Safari, Firefox, and search bots do not send it — the User-Agent is parsed against sets of keywords: tablet patterns first, then phone patterns. If nothing matches — desktop.

The tablet is intentionally separated from the phone: whether to treat it as a mobile version or a full version is a project decision, not an engine decision.

$device = MELBIS()->AgentDevice();
if ( $device == 'phone' ) MELBIS()->TemplateSet('mobile');

MELBIS()->Run($entry_point, $entry_param);

How template groups work and why two versions of a storefront can live on the same domain is covered in the “Template Groups” section.

The keyword sets can be overridden if needed — entirely or one at a time — before the first call to AgentDevice:

MELBIS()->DefineAgentKeys([
    'tablet'    => '/ipad|tablet|playbook|silk|kindle|nexus 7/i'
    ]);

The key must be phone or tablet, and the value must be a complete regular expression including delimiters and flags. An unknown key or an invalid pattern stops the parser with an error: otherwise a typo in a regular expression would silently send all mobile traffic to the full version. The Sec-CH-UA-Mobile hint is not configurable — there is nothing to improve about a good signal.

The result is computed once per request, so calling the method again costs nothing.

When serving different markup from the same address, add the Vary: User-Agent header to the response — this tells search engines and intermediate caches that the page content depends on the device.

Browser Language

$lang = MELBIS()->AgentLanguage(['ru', 'uk'], 'uk');

The method parses the Accept-Language header and returns the language the visitor prefers most — as a two-letter code. The first argument is the list of languages the storefront supports; the second is what to return if none of them match the visitor’s preferences. Without arguments, the method returns the single most preferred language, whatever it may be.

It is used in the same place as AgentDevice — in the root script, to greet a new visitor in their language: either by inserting the appropriate language prefix into the URL or by calling LanguageSet directly.

Why you cannot simply take the first two characters of the header. The browser sends not one language but a list with weights:

uk-UA,uk;q=0.9,ru;q=0.8,en-US;q=0.7,en;q=0.6

The weight q (from 0 to 1, default 1) defines the preference — the order of elements formally means nothing, although browsers typically write them in descending order. With the header en-US,ru;q=0.9,uk;q=0.8, the naive “first two characters” approach yields en, and a visitor who explicitly requested Russian as their second choice gets the default language instead. AgentLanguage removes everything the storefront does not support from the list and picks the highest weight among what remains.

The method also handles three more things on its own: it reduces regional variants to the base language (uk-UA and uk are both uk, and the higher weight is taken), it skips q=0 — which is an explicit rejection of a language, not a weak preference — and it ignores * and garbage values.

The header is parsed once per request, so repeated calls with different language lists cost nothing.

The header may be absent entirely — search bots and some API clients do not send it. In that case the method returns the default value, and this is the normal path, not an error: a bot should receive the primary language version, not the result of guessing.