class IpGeoLocGlobal in IP Geolocation Views & Maps 8
Class IpGeoLocGlobal.
Hierarchy
- class \Drupal\ip_geoloc\Services\IpGeoLocGlobal
Expanded class hierarchy of IpGeoLocGlobal
3 files declare their use of IpGeoLocGlobal
- IpGeoLocPluginStyleLeaflet.php in src/
Plugin/ views/ style/ IpGeoLocPluginStyleLeaflet.php - IpGeoLocPluginStyleMap.php in src/
Plugin/ views/ style/ IpGeoLocPluginStyleMap.php - IpGeoLocSubscriber.php in src/
EventSubscriber/ IpGeoLocSubscriber.php
1 string reference to 'IpGeoLocGlobal'
1 service uses IpGeoLocGlobal
File
- src/
Services/ IpGeoLocGlobal.php, line 16
Namespace
Drupal\ip_geoloc\ServicesView source
class IpGeoLocGlobal {
protected $logger;
protected $ipGeolocSession;
protected $stringTranslation;
protected $messenger;
protected $moduleHandler;
protected $config;
/**
* Constructs a new IpGeoLocGlobal object. Adds dependency injection.
*/
public function __construct(LoggerChannelFactoryInterface $logger, IpGeoLocSession $ipGeolocSession, TranslationInterface $stringTranslation, MessengerInterface $messenger, ModuleHandler $moduleHandler, ConfigFactoryInterface $config_factory) {
$this->logger = $logger;
$this->ipGeolocSession = $ipGeolocSession;
$this->stringTranslation = $stringTranslation;
$this->messenger = $messenger;
$this->moduleHandler = $moduleHandler;
$this->config = $config_factory
->get('ip_geoloc.settings');
}
/**
* Log errors via the watchdog.
*/
public function logErrors() {
if ($error = $this->ipGeolocSession
->getSessionValue('error')) {
// @todo How do we treat repeated 'user declined to share location' errors?
// @TODO migrate debug functoin
$this->logger
->get('IPGV&M')
->notice($error);
// ip_geoloc_debug('IPGV&M, ' . ip_address() . ': ' . $error, 'warning');.
$this->ipGeolocSession
->setSessionValue('error', NULL);
}
}
/**
* Returns whether this was the first click of the session.
*
* @return bool
* TRUE if it was, i.e. if there has been no position check before.
*/
public function isFirstClick() {
$last_position_check = $this->ipGeolocSession
->getSessionValue('last_position_check');
return empty($last_position_check);
}
/**
* Reinitialises the supplied location array.
*/
public function reinitLocation(&$location, $reverse_geocode_client_timeout) {
$location = [
'fixed_address' => isset($location['fixed_address']) ? (int) $location['fixed_address'] : NULL,
'regions' => isset($location['regions']) ? $location['regions'] : NULL,
];
// Calls below are synchronous, $location is filled upon return.
if ($this
->useSmartIpIfEnabled($location) || $this
->useGeoipApiIfEnabled($location)) {
if ($reverse_geocode_client_timeout) {
$this->logger
->get('IPGV&M')
->notice('Location timeout (waited %sec s). Fallback: %address.', [
'%sec' => number_format($reverse_geocode_client_timeout, 1),
'%address' => isset($location['formatted_address']) ? $location['formatted_address'] : '',
], WATCHDOG_NOTICE);
}
}
else {
$this
->debug($this->stringTranslation
->translate('Smart IP and GeoIP API fallbacks NOT enabled.'));
}
}
/**
* Use Smart IP (if enabled) to retrieve lat/long and address info.
*
* Note that smart_ip_get_location() will invoke
* hook_smart_ip_get_location_alter($location), which we use to format the
* address.
*
* @param array $location
* If $location['ip_address'] isn't filled out the current user's IP address will be used.
*
* @return bool
* TRUE upon success, FALSE otherwise.
*/
public function useSmartIpIfEnabled(array &$location) {
$config = \Drupal::config('ip_geoloc.settings');
$smart_ip = $this->config
->get('ip_geoloc_smart_ip_as_backup') ? $this->config
->get('ip_geoloc_smart_ip_as_backup') : FALSE;
if ($smart_ip) {
if (function_exists('smart_ip_get_location')) {
if (empty($location['ip_address'])) {
$location['ip_address'] = ip_address();
}
$fixed_address = isset($location['fixed_address']) ? $location['fixed_address'] : 0;
$region = isset($location['region']) ? $location['region'] : 0;
// See also: ip_geoloc_smart_ip_get_location_alter().
$location = [
'provider' => 'smart_ip',
'fixed_address' => $fixed_address,
'region' => $region,
] + smart_ip_get_location($location['ip_address']);
return TRUE;
}
$this
->debug($this->stringTranslation
->translate('IPGV&M: Smart IP configured as a backup, but is not enabled.'));
}
// $location['formatted_address'] = '';.
return FALSE;
}
/**
* Module GeoIP API does not expose a hook, but it does expose an API.
*
* @param array $location
* Ff $location['ip_address'] isn't filled out the current user's
* IP address will be used.
*
* @return bool
* TRUE upon success, FALSE otherwise.
*/
public function useGeoipApiIfEnabled(array &$location) {
if (!function_exists('geoip_city')) {
return FALSE;
}
$location['provider'] = 'geoip';
if (empty($location['ip_address'])) {
$location['ip_address'] = ip_address();
}
$geoip_location = (array) geoip_city($location['ip_address']);
if (reset($geoip_location)) {
// Where different, convert GeoIP names to our equivalents.
$geoip_location['country'] = isset($geoip_location['country_name']) ? $geoip_location['country_name'] : '';
unset($geoip_location['country_name']);
$location = array_merge($geoip_location, $location);
ip_geoloc_format_address($location);
}
$this
->debug($this->stringTranslation
->translate('IPGV&M: GeoIP API retrieved: !location', [
'!location' => ip_geoloc_pretty_print($location),
]));
return TRUE;
}
/**
* Return whether a the visitor's location is due for an update.
*
* Updates are only performed on selected configured pages.
* An update is due when more than a configurable number of seconds have
* elapsed. If that number is set to zero, then the user's location will be
* requested until at least the location's country is known, which is
* normally immediately at the start of the session.
*
* @param array $location
* Array of location components.
*
* @return bool
* TRUE if an update is due.
*/
public function checkLocation(array $location = NULL) {
if (!$this->config
->get('ip_geoloc_google_to_reverse_geocode') ? $this->config
->get('ip_geoloc_google_to_reverse_geocode') : FALSE) {
return FALSE;
}
$current_path = \Drupal::service('path.current')
->getPath();
$path_alias = \Drupal::service('path.alias_manager')
->getAliasByPath($current_path);
$path_matcher = \Drupal::service('path.matcher');
$include_pages = $this->config
->get('ip_geoloc_include_pages') ? $this->config
->get('ip_geoloc_include_pages') : '*';
if (!$path_matcher
->matchPath($path_alias, $include_pages)) {
return FALSE;
}
$exclude_pages = $this->config
->get('ip_geoloc_exclude_pages') ? $this->config
->get('ip_geoloc_exclude_pages') : '*';
if ($path_matcher
->matchPath($path_alias, $exclude_pages)) {
return FALSE;
}
$roles_to_reverse_geocode = $this->config
->get('ip_geoloc_roles_to_reverse_geocode') ? $this->config
->get('ip_geoloc_roles_to_reverse_geocode') : [
DRUPAL_ANONYMOUS_RID,
DRUPAL_AUTHENTICATED_RID,
];
$roles_applicable = array_intersect($roles_to_reverse_geocode, array_keys(user_role_names()));
if (empty($roles_applicable)) {
return FALSE;
}
$interval = (int) $this->config
->get('ip_geoloc_location_check_interval') ? $this->config
->get('ip_geoloc_location_check_interval') : IP_GEOLOC_LOCATION_CHECK_INTERVAL;
if ($interval == 0) {
return !isset($location['latitude']);
}
$last_position_check = $this->ipGeolocSession
->getSessionValue('last_position_check');
if (isset($last_position_check)) {
$time_elapsed = time() - $last_position_check;
if ($time_elapsed < $interval) {
$this
->debug($this->stringTranslation
->translate('IPGV&M: next update scheduled for first click after %seconds seconds (unless overridden or on excluded page).', [
'%seconds' => $interval - $time_elapsed,
]));
return FALSE;
}
}
return TRUE;
}
/**
* Handle timeout of the Google Maps reverse-geocode callback, if enabled.
*
* This is based on $position_pending_since being set to the current time when
* the service was initiated.
*/
public function reverseGeocodeTimeout() {
$pending_since = $this->ipGeolocSession
->getSessionValue('position_pending_since');
if (isset($pending_since)) {
$time_elapsed = microtime(TRUE) - $pending_since;
if ($time_elapsed > IP_GEOLOC_CALLBACK_TIMEOUT) {
$this
->debug($this->stringTranslation
->translate('IPGV&M timeout: the last reverse-geocode request was @sec s ago.', [
'@sec' => number_format($time_elapsed, 1),
]));
$this->ipGeolocSession
->setSessionValue('position_pending_since', NULL);
return $time_elapsed;
}
}
return FALSE;
}
/**
* Poor man's address formatter.
*
* It doesn't take local format conventions into account. Luckily this is only
* called as a fallback when lat/long could not be established or the Google
* reverse-geocode function returned an error.
*
* @param array $location
* Array of location components.
*/
public function formatAddress(array &$location) {
$location['formatted_address'] = isset($location['city']) ? $location['city'] : '';
if (!empty($location['region'])) {
$location['formatted_address'] .= ' ' . $location['region'];
}
if (!empty($location['postal_code']) && $location['postal_code'] != '-') {
$location['formatted_address'] .= ' ' . $location['postal_code'] . ',';
}
if (!empty($location['country'])) {
$location['formatted_address'] .= ' ' . $location['country'];
}
$location['formatted_address'] = trim($location['formatted_address']);
}
/**
* Fleshes out the $ip_geoloc_address array.
*
* This is based on the additional data provided in the $google_address array.
* This may involve tweaking of the 'latitude' and 'longitude' entries so that
* they remain consistent with the street address components.
*
* @param array $google_address
* Array of address components as returned by Google service.
* @param array $ip_geoloc_address
* The $google_address in flattened form.
*
* @return bool
* TRUE, unless google_address or ip_geoloc_address are empty
*/
public function flattenGoogleAddress(array $google_address, array &$ip_geoloc_address) {
if (is_array($google_address) && is_array($google_address['address_components']) && is_array($ip_geoloc_address)) {
$ip_geoloc_address['provider'] = 'google';
foreach ($google_address['address_components'] as $component) {
$long_name = $component['long_name'];
if (!empty($long_name)) {
$type = $component['types'][0];
$ip_geoloc_address[$type] = $long_name;
if ($type == 'country' && !empty($component['short_name'])) {
$ip_geoloc_address['country_code'] = $component['short_name'];
}
}
}
$ip_geoloc_address['formatted_address'] = $google_address['formatted_address'];
// The following may be slightly different from the original lat,long passed
// into ip_geoloc_reverse_geocode().
$ip_geoloc_address['latitude'] = $google_address['geometry']['location']['lat'];
$ip_geoloc_address['longitude'] = $google_address['geometry']['location']['lng'];
return TRUE;
}
return FALSE;
}
/**
* Print the location array nicely.
*
* @param array $location
* Array of location components.
*
* @return string
* The location array formatted as string.
*/
public function prettyPrint(array $location) {
$t = '';
foreach ($location as $label => $value) {
if (!empty($value)) {
$t .= SafeMarkup::checkPlain($label) . ': <strong>' . SafeMarkup::checkPlain($value) . '</strong> ';
}
}
return empty($t) ? t('nothing') : $t;
}
/**
* Returns the path to the configured marker directory.
*/
public function markerDirectory() {
$path = drupal_get_path('module', 'ip_geoloc');
$marker_directory = $this->config
->get('ip_geoloc_marker_directory');
$marker_folder = "{$path}/" . $this->moduleHandler
->moduleExists('leaflet') ? 'amarkers' : 'markers';
return $marker_directory ? $marker_directory : $marker_folder;
}
/**
* Return the height and width of the markers in the selected set.
*
* @return string
* For example '32 x 42' or '21 x 34'
*/
public function markerDimensions() {
$dimensions = $this->config
->get('ip_geoloc_marker_dimensions');
if (empty($dimensions)) {
$directory = $this
->markerDirectory();
$dimensions = strpos($directory, '/amarkers') ? '32 x 42' : '21 x 34';
}
return $dimensions;
}
/**
* Return available marker colors for use in a select drop-down. List is compiled based on available .png files in ip_geoloc/markers dir.
*
* @return array
* Array of color names indexed by machine names
*/
public function markerColors() {
$color_list =& drupal_static(__FUNCTION__);
if (!isset($color_list)) {
$color_list = [
'' => '<' . t('default') . '>',
0 => '<' . t('no marker') . '>',
];
if ($directory_handle = opendir($this
->markerDirectory())) {
while (($filename = readdir($directory_handle)) !== FALSE) {
if ($ext_pos = strrpos($filename, '.png')) {
$color = Unicode::substr($filename, 0, $ext_pos);
// Ok... relies on translations done elsewhere.
// @TODO dependency injection
// $color_list[$color] = t($color);
$color_list[$color] = $color;
}
}
closedir($directory_handle);
}
asort($color_list);
}
return $color_list;
}
/**
* Return available OpenLayers marker layers for use in a select drop-down.
*
* @return array
* An array indexed by marker layer number (1..n)
*/
public function openlayersMarkerLayers() {
$num_location_marker_layers = $this->config
->get('ip_geoloc_num_location_marker_layers') ? $this->config
->get('ip_geoloc_num_location_marker_layers') : IP_GEOLOC_DEF_NUM_MARKER_LAYERS;
$marker_layers = [];
for ($layer = 1; $layer <= $num_location_marker_layers; $layer++) {
$marker_layers[$layer] = $this->stringTranslation
->translate('Marker layer') . " #{$layer}";
}
return $marker_layers;
}
/**
* Determines if a value is within the supplied numeric or alphabetical range.
*
* String comparison is based on the ASCII/UTF8 order, so is case-sensitive.
*
* @param string $value
* The value to check in $range.
* @param string $range
* Of the form '1.5--4.5' (range is inclusive of end points)
* @param mixed $view_args
* The value to check in $range.
*
* @return bool
* TRUE if the value is in range
*/
public function isInRange($value, $range, $view_args = NULL) {
if (!isset($value) || !isset($range)) {
return FALSE;
}
// Defensive programming to make sure we have a string.
if (is_array($range)) {
$range = reset($range);
}
$from_to = explode(IP_GEOLOC_RANGE_SEPARATOR1, $range);
if (count($from_to) < 2) {
$from_to = explode(IP_GEOLOC_RANGE_SEPARATOR2, $range);
}
if (($from = _ip_geoloc_extract_value($from_to[0], $view_args)) === NULL) {
return FALSE;
}
if (count($from_to) == 1) {
// Single value.
return trim($value) == trim($from);
}
if (($to = _ip_geoloc_extract_value($from_to[1], $view_args)) === NULL) {
return FALSE;
}
if ($from == '' && $to == '') {
// Range separator without values.
return TRUE;
}
if ($from != '' && $to != '') {
return $value >= $from && $value <= $to;
}
if ($from != '') {
return $value >= $from;
}
return $value <= $to;
}
/**
* Extracts a Views argument value from the supplied string.
*
* @param string $string
* The string to parse.
* @param array $view_args
* The View arguments.
*
* @return string
* The extracted value.
*/
public function extractValue($string, array $view_args) {
if (preg_match('/^!([0-9])/', $string, $matches)) {
$arg = $matches[1];
return isset($view_args[$arg - 1]) ? $view_args[$arg - 1] : arg($arg);
}
return $string;
}
/**
* FAPI validation of a range element.
*
* We want to cover numeric and alphabetic ranges, as well as the special
* replacement strings !1, !2 ... So we can't be very strict.
*/
public function rangeWidgetValidate($element, &$form_state) {
$range = $element['#value'];
$from_to = explode(IP_GEOLOC_RANGE_SEPARATOR1, $range);
if (count($from_to) < 2) {
$from_to = explode(IP_GEOLOC_RANGE_SEPARATOR2, $range);
}
if (count($from_to) < 2) {
// Not a range but a single value. This is ok. If we knew we were checking
// for a number we would pass the input through is_numeric(), but we don't.
}
else {
$from = trim($from_to[0]);
$to = trim($from_to[1]);
if (preg_match('/^![0-9]/', $from) || preg_match('/^![0-9]/', $to)) {
return;
}
$ok = TRUE;
// If either $from or $to is numeric then assume numeric range and apply
// validation accordingly.
if (is_numeric($from) || is_numeric($to)) {
// If one end is numeric, then the other must also be, or be empty.
$ok = empty($from) && empty($to) || empty($from) && is_numeric($to) || empty($to) && is_numeric($from) || is_numeric($from) && is_numeric($to) && $from <= $to;
}
elseif (!empty($from) && !empty($to)) {
// Alphabetic range validation.
$ok = $from <= $to;
}
if (!$ok) {
form_error($element, t('Invalid range.'));
}
}
}
/**
* Returns an array of libraries as entered on the config page.
*/
public function getFontIconLibs() {
$libs = [];
for ($i = 1; $i <= IP_GEOLOC_MAX_NUM_FONT_ICON_LIBS; $i++) {
$file = $this->config
->get("ip_geoloc_font_icon_lib{$i}");
if (!empty($file)) {
$libs[$i] = $file;
}
}
$known_install = 'sites/all/libraries/font-awesome/css/font-awesome.min.css';
if (empty($libs) && file_exists($known_install)) {
$libs[1] = $known_install;
}
return $libs;
}
/**
* Returns whether debug is on for the current user.
*/
public function debugFlag() {
global $user;
$current_user = \Drupal::service('current_user');
$ip_geoloc_debug = $this->config
->get('ip_geoloc_debug');
$user_names = explode(',', $ip_geoloc_debug);
foreach ($user_names as $user_name) {
$user_name = mb_strtolower(trim($user_name));
$match = isset($user->name) ? $user_name == mb_strtolower(trim($user->name)) : $user_name == 'anon' || $user_name == 'anonymous';
if ($match) {
return TRUE;
}
}
return FALSE;
}
/**
* Special debug function: messages selected user names only.
*
* @param string $message
* The message string to bue output as a debug message.
* @param string $type
* Defaults to 'status'.
*
* @return array|null
* A multidimensional array with keys corresponding to the set message types.
* If there are no messages set, the function returns NULL.
*/
public function debug($message, $type = 'status') {
if ($this
->debugFlag()) {
return $this->messenger
->addMessage($message, $type, FALSE);
}
}
/**
* Returns true if the previous page was reloaded.
*/
public function samePath() {
if (empty($_SERVER['HTTP_REFERER'])) {
return FALSE;
}
$referer = $_SERVER['HTTP_REFERER'];
global $base_url;
if (strpos($referer, $base_url) === 0) {
$prev_path = Unicode::substr($referer, drupal_strlen($base_url) + 1);
if (empty($prev_path) && drupal_is_front_page()) {
return TRUE;
}
return $prev_path == current_path() || $prev_path == drupal_get_path_alias();
}
return FALSE;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
IpGeoLocGlobal:: |
protected | property | ||
IpGeoLocGlobal:: |
protected | property | ||
IpGeoLocGlobal:: |
protected | property | ||
IpGeoLocGlobal:: |
protected | property | ||
IpGeoLocGlobal:: |
protected | property | ||
IpGeoLocGlobal:: |
protected | property | ||
IpGeoLocGlobal:: |
public | function | Return whether a the visitor's location is due for an update. | |
IpGeoLocGlobal:: |
public | function | Special debug function: messages selected user names only. | |
IpGeoLocGlobal:: |
public | function | Returns whether debug is on for the current user. | |
IpGeoLocGlobal:: |
public | function | Extracts a Views argument value from the supplied string. | |
IpGeoLocGlobal:: |
public | function | Fleshes out the $ip_geoloc_address array. | |
IpGeoLocGlobal:: |
public | function | Poor man's address formatter. | |
IpGeoLocGlobal:: |
public | function | Returns an array of libraries as entered on the config page. | |
IpGeoLocGlobal:: |
public | function | Returns whether this was the first click of the session. | |
IpGeoLocGlobal:: |
public | function | Determines if a value is within the supplied numeric or alphabetical range. | |
IpGeoLocGlobal:: |
public | function | Log errors via the watchdog. | |
IpGeoLocGlobal:: |
public | function | Return available marker colors for use in a select drop-down. List is compiled based on available .png files in ip_geoloc/markers dir. | |
IpGeoLocGlobal:: |
public | function | Return the height and width of the markers in the selected set. | |
IpGeoLocGlobal:: |
public | function | Returns the path to the configured marker directory. | |
IpGeoLocGlobal:: |
public | function | Return available OpenLayers marker layers for use in a select drop-down. | |
IpGeoLocGlobal:: |
public | function | Print the location array nicely. | |
IpGeoLocGlobal:: |
public | function | FAPI validation of a range element. | |
IpGeoLocGlobal:: |
public | function | Reinitialises the supplied location array. | |
IpGeoLocGlobal:: |
public | function | Handle timeout of the Google Maps reverse-geocode callback, if enabled. | |
IpGeoLocGlobal:: |
public | function | Returns true if the previous page was reloaded. | |
IpGeoLocGlobal:: |
public | function | Module GeoIP API does not expose a hook, but it does expose an API. | |
IpGeoLocGlobal:: |
public | function | Use Smart IP (if enabled) to retrieve lat/long and address info. | |
IpGeoLocGlobal:: |
public | function | Constructs a new IpGeoLocGlobal object. Adds dependency injection. |