feat: auto-enrich IPs with country flags across all admin pages
DB-IP plugin onAfterRender scans admin HTML for IP addresses in <code> tags, looks up geolocation, prepends country flag emoji and adds city/region/country hover tooltip. Caches lookups per request, skips private/loopback IPs and already-enriched elements.
This commit is contained in:
@@ -23,6 +23,7 @@ class DBIP extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
return [
|
||||
'onAfterInitialise' => 'onAfterInitialise',
|
||||
'onAfterRender' => 'onAfterRender',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -80,4 +81,92 @@ class DBIP extends CMSPlugin implements SubscriberInterface
|
||||
|
||||
DBIPHelper::downloadCityDb($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan rendered admin HTML for IP addresses and enrich with geo flags + tooltips.
|
||||
*/
|
||||
public function onAfterRender(): void
|
||||
{
|
||||
$app = $this->getApplication();
|
||||
|
||||
if (!$app->isClient('administrator'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ($app->getDocument()->getType() !== 'html')
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$body = $app->getBody();
|
||||
|
||||
if (empty($body))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$ipv4 = '(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)';
|
||||
$ipv6 = '(?:[0-9a-fA-F]{1,4}:){2,7}[0-9a-fA-F]{1,4}(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))?';
|
||||
$pattern = '#<code(?:\s[^>]*)?>(' . $ipv4 . '|' . $ipv6 . ')</code>#';
|
||||
|
||||
$cache = [];
|
||||
|
||||
$newBody = preg_replace_callback($pattern, function ($m) use (&$cache) {
|
||||
$fullMatch = $m[0];
|
||||
$ip = $m[1];
|
||||
|
||||
// Skip private/loopback
|
||||
if (filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE) === false)
|
||||
{
|
||||
return $fullMatch;
|
||||
}
|
||||
|
||||
// Already enriched (has a title attribute)
|
||||
if (strpos($fullMatch, 'title=') !== false)
|
||||
{
|
||||
return $fullMatch;
|
||||
}
|
||||
|
||||
if (!isset($cache[$ip]))
|
||||
{
|
||||
$cache[$ip] = DBIPHelper::lookup($ip);
|
||||
}
|
||||
|
||||
$geo = $cache[$ip];
|
||||
|
||||
if ($geo === null || empty($geo['country_code']))
|
||||
{
|
||||
return $fullMatch;
|
||||
}
|
||||
|
||||
$cc = strtoupper($geo['country_code']);
|
||||
$flag = self::countryFlag($cc);
|
||||
|
||||
$parts = array_filter([$geo['city'], $geo['region'], $geo['country_name']]);
|
||||
$tooltip = htmlspecialchars(implode(', ', $parts), \ENT_QUOTES, 'UTF-8');
|
||||
$escaped = htmlspecialchars($ip, \ENT_QUOTES, 'UTF-8');
|
||||
|
||||
return $flag . ' <code title="' . $tooltip . '" style="cursor:help;">' . $escaped . '</code>';
|
||||
}, $body);
|
||||
|
||||
if ($newBody !== null && $newBody !== $body)
|
||||
{
|
||||
$app->setBody($newBody);
|
||||
}
|
||||
}
|
||||
|
||||
private static function countryFlag(string $cc): string
|
||||
{
|
||||
if (\strlen($cc) !== 2)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
$cc = strtoupper($cc);
|
||||
$first = mb_chr(0x1F1E6 + \ord($cc[0]) - \ord('A'));
|
||||
$second = mb_chr(0x1F1E6 + \ord($cc[1]) - \ord('A'));
|
||||
|
||||
return $first . $second;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user