- All plugins need to have a service that acts as a plugin manager. Create a new file in your module's src directory called GeoLocatorManager.php. This will hold the GeoLocatorManager class.
- Create the GeoLocatorManager class by extending the \Drupal\Core\Plugin\DefaultPluginManager class:
<?php
namespace Drupal\geoip;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
class GeoLocatorManager extends DefaultPluginManager {
}
- When creating a new plugin type, it is recommended that the plugin manager provides a set of defaults for new plugins, in case an item is missing from the definition:
<?php
namespace Drupal\geoip;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
class GeoLocatorManager extends DefaultPluginManager {
/**
* Default values for each plugin.
*
* @var array
*/
protected $defaults = [
'label' => '',
'description' => '',
'weight' => 0,
];
}
- Next, we will need to override the \Drupal\Core\Plugin|DefaultPluginManager class constructor to define the module handler and cache backend:
<?php
namespace Drupal\geoip;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
class GeoLocatorManager extends DefaultPluginManager {
/**
* Default values for each plugin.
*
* @var array
* /
protected $defaults = [
'label' => '',
'description' => '',
'weight' => 0,
];
/**
* Constructs a new GeoLocatorManager object.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent::__construct(
'Plugin/GeoLocator',
$namespaces,
$module_handler,
'Drupal\geoip\Plugin\GeoLocator\GeoLocatorInterface',
'Drupal\geoip\Annotation\GeoLocator'
);
$this->setCacheBackend($cache_backend, 'geolocator_plugins');
}
}
We override the constructor so that we can specify a specific cache key. This allows plugin definitions to be cached and cleared properly; otherwise, our plugin manager will continuously read the disk to find plugins.
- The next step will be to create a geoip.services.yml file in our module's root directory. This will describe our plugin manager to Drupal, allowing a plugin discovery:
services:
plugin.manager.geolocator:
class: Drupal\geoip\GeoLocatorManager
parent: default_plugin_manager
Drupal utilizes services and dependency injection. By defining our class as a service, we are telling the application container how to initiate our class. We can use the parent definition to tell the container to use the same arguments as the default_plugin_manager definition.
- All annotation-based plugins must provide a class, which serves as the annotation definition. Create GeoLocator.php in src/Annotation to provide the GeoLocator annotation class:
<?php
namespace Drupal\geoip\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines a GeoLocator annotation object.
*
* @Annotation
*/
class GeoLocator extends Plugin {
/**
* The human-readable name.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $label;
/**
* A description of the plugin.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $description;
}
Each property is an item that can be defined in the plugin's annotation. The annotated definition will start with @GeoLocator for our plugins.
- Next, we will define the plugin interface that we defined in the plugin manager. The plugin manager will validate the GeoLocator plugins that implement this interface. Create a GeoLocatorInterface.php file in our module's src/Plugin/GeoLocator directory to hold the interface:
<?php
namespace Drupal\geoip\Plugin\GeoLocator;
/**
* Interface GeoLocatorInterface.
*/
interface GeoLocatorInterface {
/**
* Get the plugin's label.
*
* @return string
* The geolocator label
*/
public function label();
/**
* Get the plugin's description.
*
* @return string
* The geolocator description
*/
public function description();
/**
* Performs geolocation on an address.
*
* @param string $ip_address
* The IP address to geolocate.
*
* @return string|NULL
* The geolocated country code, or NULL if not found.
*/
public function geolocate($ip_address);
}
We provide an interface so that we can guarantee that we have these expected methods when working with a GeoLocator plugin, and that we have an output, regardless of the logic behind each method.
- Next, we will create a default plugin, which returns the country code from CDN headers, if available. In src/Plugin/GeoLocator, create a Cdn.php file for our Cdn plugin class:
<?php
namespace Drupal\geoip\Plugin\GeoLocator;
use Drupal\Core\Plugin\PluginBase;
/**
* CDN geolocation provider.
*
* @GeoLocator(
* id = "cdn",
* label = "CDN",
* description = "Checks for geolocation headers sent by CDN services",
* weight = -10
* )
*/
class Cdn extends PluginBase implements GeoLocatorInterface {
/**
* {@inheritdoc}
*/
public function label() {
return $this->pluginDefinition['label'];
}
/**
* {@inheritdoc}
*/
public function description() {
return $this->pluginDefinition['description'];
}
/**
* {@inheritdoc}
*/
public function geolocate($ip_address) {
// Check if CloudFlare headers present.
if (!empty($_SERVER['HTTP_CF_IPCOUNTRY'])) {
$country_code = $_SERVER['HTTP_CF_IPCOUNTRY'];
}
// Check if CloudFront headers present.
elseif (!empty($_SERVER['HTTP_CLOUDFRONT_VIEWER_COUNTRY'])) {
$country_code = $_SERVER['HTTP_CLOUDFRONT_VIEWER_COUNTRY'];
}
else {
$country_code = NULL;
}
return $country_code;
}
}
- The GeoLocator plugin type is now set, with a default CDN-based plugin.