site-accueil-insa/matomo/plugins/UserCountry/LocationProvider.php

557 lines
18 KiB
PHP

<?php
/**
* Matomo - free/libre analytics platform
*
* @link https://matomo.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\UserCountry;
use Exception;
use Piwik\Common;
use Piwik\IP;
use Piwik\Option;
use Piwik\Piwik;
use Piwik\Plugin;
use Piwik\Plugins\UserCountry\LocationProvider\DefaultProvider;
use Piwik\Plugin\Manager as PluginManager;
use Piwik\Plugins\UserCountry\LocationProvider\DisabledProvider;
use Piwik\Tracker\Cache;
use Piwik\Tracker\TrackerConfig;
/**
* @see plugins/UserCountry/functions.php
*/
require_once PIWIK_INCLUDE_PATH . '/plugins/UserCountry/functions.php';
/**
* The base class of all LocationProviders.
*
* LocationProviders attempt to determine a visitor's location using
* visit information. All LocationProviders require a visitor's IP address, some
* require more, such as the browser language.
*/
abstract class LocationProvider
{
const NOT_INSTALLED = 0;
const INSTALLED = 1;
const BROKEN = 2;
const CURRENT_PROVIDER_OPTION_NAME = 'usercountry.location_provider';
const GEOGRAPHIC_COORD_PRECISION = 3;
const CONTINENT_CODE_KEY = 'continent_code';
const CONTINENT_NAME_KEY = 'continent_name';
const COUNTRY_CODE_KEY = 'country_code';
const COUNTRY_NAME_KEY = 'country_name';
const REGION_CODE_KEY = 'region_code';
const REGION_NAME_KEY = 'region_name';
const CITY_NAME_KEY = 'city_name';
const AREA_CODE_KEY = 'area_code';
const LATITUDE_KEY = 'lat';
const LONGITUDE_KEY = 'long';
const POSTAL_CODE_KEY = 'postal_code';
const ISP_KEY = 'isp';
const ORG_KEY = 'org';
/**
* An array of all provider instances. Access it through static methods.
*
* @var array
*/
public static $providers = null;
/**
* Returns location information based on visitor information.
*
* The result of this function will be an array. The array can store some or all of
* the following information:
*
* - Continent Code: The code of the visitor's continent.
* (array key is self::CONTINENT_CODE_KEY)
* - Continent Name: The name of the visitor's continent.
* (array key is self::CONTINENT_NAME_KEY)
* - Country Code: The code of the visitor's country.
* (array key is self::COUNTRY_CODE_KEY)
* - Country Name: The name of the visitor's country.
* (array key is self::COUNTRY_NAME_KEY)
* - Region Code: The code of the visitor's region.
* (array key is self::REGION_CODE_KEY)
* - Region Name: The name of the visitor's region.
* (array key is self::REGION_NAME_KEY)
* - City Name: The name of the visitor's city.
* (array key is self::CITY_NAME_KEY)
* - Area Code: The visitor's area code.
* (array key is self::AREA_CODE_KEY)
* - Latitude: The visitor's latitude.
* (array key is self::LATITUDE_KEY)
* - Longitude: The visitor's longitude.
* (array key is self::LONGITUDE_KEY)
* - Postal Code: The visitor's postal code.
* (array key is self::POSTAL_CODE_KEY)
* - ISP: The visitor's ISP.
* (array key is self::ISP_KEY)
* - Org: The company/organization of the visitor's IP.
* (array key is self::ORG_KEY)
*
* All LocationProviders will attempt to return the country of the visitor.
*
* @param array $info What this must contain depends on the specific provider
* implementation. All providers require an 'ip' key mapped
* to the visitor's IP address.
* @return array|false
*/
abstract public function getLocation($info);
/**
* Returns true if this provider is available for use, false if otherwise.
*
* @return bool
*/
abstract public function isAvailable();
/**
* Returns true if this provider is working, false if otherwise.
*
* @return bool
*/
abstract public function isWorking();
/**
* Returns information about this location provider. Contains an id, title & description:
*
* array(
* 'id' => 'geoip2php',
* 'title' => '...',
* 'description' => '...'
* );
*
* @return array
*/
abstract public function getInfo();
/**
* Returns an array mapping location result keys w/ bool values indicating whether
* that information is supported by this provider. If it is not supported, that means
* this provider either cannot get this information, or is not configured to get it.
*
* @return array eg. array(self::CONTINENT_CODE_KEY => true,
* self::CONTINENT_NAME_KEY => true,
* self::ORG_KEY => false)
* The result is not guaranteed to have keys for every type of location
* info.
*/
abstract public function getSupportedLocationInfo();
/**
* Renders Configuration or Setup View to be attached to the provider list
*
* @return string
*/
public function renderConfiguration()
{
return '';
}
/**
* Renders SetUp Guide, which will be shown above Geolocation admin, if there is no working provider
*
* @return string
*/
public function renderSetUpGuide()
{
return '';
}
/**
* Method called when a provider gets activated.
*/
public function activate()
{
}
/**
* Returns if location provider should be shown.
*/
public function isVisible()
{
return true;
}
/**
* Returns a message that should be shown as diagnostics warning if provider is used
*
* @return null|string
*/
public function getUsageWarning(): ?string
{
return null;
}
/**
* Returns every available provider instance.
*
* @return LocationProvider[]
*/
public static function getAllProviders()
{
if (is_null(self::$providers)) {
self::$providers = array();
$plugins = PluginManager::getInstance()->getPluginsLoadedAndActivated();
foreach ($plugins as $plugin) {
foreach (self::getLocationProviders($plugin) as $instance) {
self::$providers[] = $instance;
}
}
}
return self::$providers;
}
/**
* Get all lo that are defined by the given plugin.
*
* @param Plugin $plugin
* @return LocationProvider[]
*/
protected static function getLocationProviders(Plugin $plugin)
{
$locationProviders = $plugin->findMultipleComponents('LocationProvider', 'Piwik\\Plugins\\UserCountry\\LocationProvider');
$instances = [];
foreach ($locationProviders as $locationProvider) {
$instances[] = new $locationProvider();
}
return $instances;
}
/**
* Returns all provider instances that are 'available'. An 'available' provider
* is one that is available for use. They may not necessarily be working.
*
* @return array
*/
public static function getAvailableProviders()
{
$result = array();
foreach (self::getAllProviders() as $provider) {
if ($provider->isAvailable()) {
$result[] = $provider;
}
}
return $result;
}
/**
* Returns an array mapping provider IDs w/ information about the provider,
* for each location provider.
*
* The following information is provided for each provider:
* 'id' - The provider's unique string ID.
* 'title' - The provider's title.
* 'description' - A description of how the location provider works.
* 'status' - Either self::NOT_INSTALLED, self::INSTALLED or self::BROKEN.
* 'statusMessage' - If the status is self::BROKEN, then the message describes why.
* 'location' - A pretty formatted location of the current IP address
* (IP::getIpFromHeader()).
*
* An example result:
* array(
* 'geoip2php' => array('id' => 'geoip2php',
* 'title' => '...',
* 'desc' => '...',
* 'status' => GeoIp2::BROKEN,
* 'statusMessage' => '...',
* 'location' => '...')
* 'geoip_serverbased' => array(...)
* )
*
* @param string $newline What to separate lines with in the pretty locations.
* @param bool $includeExtra Whether to include ISP/Org info in formatted location.
* @return array
*/
public static function getAllProviderInfo($newline = "\n", $includeExtra = false)
{
$allInfo = array();
foreach (self::getAllProviders() as $provider) {
$info = $provider->getInfo();
$status = self::INSTALLED;
$location = false;
$statusMessage = false;
$availableOrMessage = $provider->isAvailable();
if ($availableOrMessage !== true) {
$status = self::NOT_INSTALLED;
if (is_string($availableOrMessage)) {
$statusMessage = $availableOrMessage;
}
} else {
$workingOrError = $provider->isWorking();
if ($workingOrError === true) // if the implementation is configured correctly, get the location
{
$locInfo = array('ip' => IP::getIpFromHeader(),
'lang' => Common::getBrowserLanguage(),
'disable_fallbacks' => true);
$location = $provider->getLocation($locInfo);
$location = self::prettyFormatLocation($location, $newline, $includeExtra);
} else // otherwise set an error message describing why
{
$status = self::BROKEN;
$statusMessage = $workingOrError;
}
}
$info['status'] = $status;
$info['statusMessage'] = $statusMessage;
$info['location'] = $location;
$info['isVisible'] = $provider->isVisible();
$info['usageWarning'] = $provider->getUsageWarning();
$allInfo[$info['id']] = $info;
}
uasort($allInfo, function($a, $b) {
if ($a['order'] == $b['order']) {
return strcmp($a['id'], $b['id']);
}
return $a['order'] - $b['order'];
});
return $allInfo;
}
/**
* Returns the ID of the currently used location provider.
*
* The used provider is stored in the 'usercountry.location_provider' option.
*
* This function should not be called by the Tracker.
*
* @return string
*/
public static function getCurrentProviderId()
{
try {
$optionValue = Option::get(self::CURRENT_PROVIDER_OPTION_NAME);
} catch (\Exception $e) {
$optionValue = false;
}
return $optionValue === false ? self::getDefaultProviderId() : $optionValue;
}
/**
* Returns the provider instance of the current location provider.
*
* This function should not be called by the Tracker.
*
* @return \Piwik\Plugins\UserCountry\LocationProvider|null
*/
public static function getCurrentProvider()
{
return self::getProviderById(self::getCurrentProviderId());
}
/**
* Sets the provider to use when tracking.
*
* @param string $providerId The ID of the provider to use.
* @return \Piwik\Plugins\UserCountry\LocationProvider The new current provider.
* @throws Exception If the provider ID is invalid.
*/
public static function setCurrentProvider($providerId)
{
$provider = self::getProviderById($providerId);
if (empty($provider)) {
throw new Exception(
"Invalid provider ID '$providerId'. The provider either does not exist or is not available");
}
$provider->activate();
Option::set(self::CURRENT_PROVIDER_OPTION_NAME, $providerId);
Cache::clearCacheGeneral();
return $provider;
}
/**
* Returns the default provider id to use
*
* @return string
*/
public static function getDefaultProviderId()
{
if (!!TrackerConfig::getConfigValue('enable_default_location_provider')) {
return DefaultProvider::ID;
}
return DisabledProvider::ID;
}
/**
* Returns a provider instance by ID or false if the ID is invalid or unavailable.
*
* @param string $providerId
* @return \Piwik\Plugins\UserCountry\LocationProvider|null
*/
public static function getProviderById($providerId)
{
foreach (self::getAllProviders() as $provider) {
if ($provider->getId() == $providerId && $provider->isAvailable()) {
return $provider;
}
}
return null;
}
public function getId()
{
$info = $this->getInfo();
return $info['id'];
}
/**
* Tries to fill in any missing information in a location result.
*
* This method will try to set the continent code, continent name and country code
* using other information.
*
* Note: This function must always be called by location providers in getLocation.
*
* @param array $location The location information to modify.
*/
public function completeLocationResult(&$location)
{
// fill in continent code if country code is present
if (empty($location[self::CONTINENT_CODE_KEY])
&& !empty($location[self::COUNTRY_CODE_KEY])
) {
$countryCode = strtolower($location[self::COUNTRY_CODE_KEY]);
$location[self::CONTINENT_CODE_KEY] = Common::getContinent($countryCode);
}
// fill in continent name if continent code is present
if (empty($location[self::CONTINENT_NAME_KEY])
&& !empty($location[self::CONTINENT_CODE_KEY])
) {
$continentCode = strtolower($location[self::CONTINENT_CODE_KEY]);
$location[self::CONTINENT_NAME_KEY] = continentTranslate($continentCode);
}
// fill in country name if country code is present
if (empty($location[self::COUNTRY_NAME_KEY])
&& !empty($location[self::COUNTRY_CODE_KEY])
) {
$countryCode = strtolower($location[self::COUNTRY_CODE_KEY]);
$location[self::COUNTRY_NAME_KEY] = countryTranslate($countryCode);
}
// deal w/ improper latitude/longitude & round proper values
if (!empty($location[self::LATITUDE_KEY])) {
if (is_numeric($location[self::LATITUDE_KEY])) {
$location[self::LATITUDE_KEY] = round($location[self::LATITUDE_KEY], self::GEOGRAPHIC_COORD_PRECISION);
} else {
unset($location[self::LATITUDE_KEY]);
}
}
if (!empty($location[self::LONGITUDE_KEY])) {
if (is_numeric($location[self::LONGITUDE_KEY])) {
$location[self::LONGITUDE_KEY] = round($location[self::LONGITUDE_KEY], self::GEOGRAPHIC_COORD_PRECISION);
} else {
unset($location[self::LONGITUDE_KEY]);
}
}
}
/**
* Returns a prettified location result.
*
* @param array|false $locationInfo
* @param string $newline The line separator (ie, \n or <br/>).
* @param bool $includeExtra Whether to include ISP/Organization info.
* @return string
*/
public static function prettyFormatLocation($locationInfo, $newline = "\n", $includeExtra = false)
{
if ($locationInfo === false) {
return Piwik::translate('General_Unknown');
}
// add latitude/longitude line
$lines = array();
if (!empty($locationInfo[self::LATITUDE_KEY])
&& !empty($locationInfo[self::LONGITUDE_KEY])
) {
$lines[] = '(' . $locationInfo[self::LATITUDE_KEY] . ', ' . $locationInfo[self::LONGITUDE_KEY] . ')';
}
// add city/state line
$cityState = array();
if (!empty($locationInfo[self::CITY_NAME_KEY])) {
$cityState[] = $locationInfo[self::CITY_NAME_KEY];
}
if (!empty($locationInfo[self::REGION_CODE_KEY])) {
$cityState[] = $locationInfo[self::REGION_CODE_KEY];
} else if (!empty($locationInfo[self::REGION_NAME_KEY])) {
$cityState[] = $locationInfo[self::REGION_NAME_KEY];
}
if (!empty($cityState)) {
$lines[] = implode(', ', $cityState);
}
// add postal code line
if (!empty($locationInfo[self::POSTAL_CODE_KEY])) {
$lines[] = $locationInfo[self::POSTAL_CODE_KEY];
}
// add country line
if (!empty($locationInfo[self::COUNTRY_NAME_KEY])) {
$lines[] = $locationInfo[self::COUNTRY_NAME_KEY];
} else if (!empty($locationInfo[self::COUNTRY_CODE_KEY])) {
$lines[] = $locationInfo[self::COUNTRY_CODE_KEY];
}
// add extra information (ISP/Organization)
if ($includeExtra) {
$lines[] = '';
$unknown = Piwik::translate('General_Unknown');
$org = !empty($locationInfo[self::ORG_KEY]) ? $locationInfo[self::ORG_KEY] : $unknown;
$lines[] = "Org: $org";
$isp = !empty($locationInfo[self::ISP_KEY]) ? $locationInfo[self::ISP_KEY] : $unknown;
$lines[] = "ISP: $isp";
}
return implode($newline, $lines);
}
/**
* Returns an IP address from an array that was passed into getLocation. This
* will return an IPv4 address or IPv6 address.
*
* @param array $info Must have 'ip' key.
* @return string|null
*/
protected function getIpFromInfo($info)
{
$ip = \Matomo\Network\IP::fromStringIP($info['ip']);
if ($ip instanceof \Matomo\Network\IPv6 && $ip->isMappedIPv4()) {
return $ip->toIPv4String();
} else {
return $ip->toString();
}
}
}