Développer un plugin de géolocalisation via l'IP pour CaMykS
Cela fait un bon moment que je cherche à intéger à CaMykS, le CMS utilisé pour skymac.org, une solution de géolocalisation. Le but n'étant pas de connaître l'origine des visiteurs, mais plutôt de déterminer la source de tous les mauvais robots qui se baladent sur le site, ainsi que sur d'autres utilisant le même CMS.
Depuis donc un certain temps, je réfléchis à la meilleure solution pour gérer ce problème. A la base, j'étais parti pour gérer une base de données reliant ip et géolocalisation, il en existe un certain nombre sur Internet. Le soucis, c'est qu'il aurait fallut que je mette à jour cette base de données fréquemment afin que les données ne soient pas obsolètes trop rapidement. Malheureusement, le temps que cela me prendrait m'a longtemps freiné, et c'est d'ailleurs la seule raison pour laquelle le plugin n'était toujours pas créé.
Aujourd'hui, dans le cadre d'un développement spécifique, j'ai eu besoin d'intégrer une solution de géolocalisation dans l'un de mes projets. Et plutôt que de créer une solution locale, disponible dans ce seul site, je me suis lancé dans le développement d'un plugin générique, directement intégré dans le CMS, et donc disponible et réutilisable pour les modules qui le souhaiterait.
Finalement, plutôt que de gérer ma propre base de données, j'ai fais le choix de laisser le travail à d'autres, et d'intégrer leurs services. Afin de ne pas me bloquer sur un service ou de développer autant de plugins que de services, j'ai rassemblé un maximum de ces services au sein d'un seul plugin.
Voici la liste des services intégrés
- IP Geolocation API
- ip-api
- ipgeolocation
- ipstack
- ipapi
- ipdata
- ipify
- ipwhois.io
- ipapi.co
- ipregistry
- IPInfoDB
- db-ip
- IP2Location
En soit, le plugin n'est pas complexe à développer. Je ne vais pas trop rentrer dans le détail du code, qui, de plus, est spécifique à CaMykS. Mais en voici les principes.
Le plugin est un simple objet, disposant d'une configuration, étant capable de répondre à une demande, lors de laquelle il va appeler le service sélectionné, et revoyer une réponse uniformisée.
La configuration du plugin est, elle aussi, assez simple, puisqu'elle est composée de l'information du service que l'on souhaite utiliser par défaut, puis de la configuration de chacun des services que l'on souhaite utiliser.
En effet, tout ces services propose une utilisation gratuite limité. Pour une utilisation à grande échelle, il est nécessaire de payer selon son quota de requêtes. Pour s'identifier, ces services demandent de fournir une clé API qui est donnée lors de la création du compte.
La configuration nous permet donc de stocker ces clés API, ainsi que divers petites subtilités nécessaires au bon fonctionnement des services.
Sur CaMykS, la configuration est modifiable via l'interface et donne quelque chose comme cela :
Maintenant que la configuration est disponible, il nous faut se lancer sur le developpement de l'objet principal. En dehors des déclarations et méthodes spécifiques au CMS, le plugin ne dispose finalement que d'une seule méthode "execute" qui permet de demander une géolocalisation.
public function execute($params=array())
La première étape de cette fonction est gérer les paramètres qui lui sont donnés. On charge ainsi la configuration, que l'on fusionne avec les paramètres donnés en en-tête
$this->load_configuration();
$defaultParams = array('ipAddress'=>null, 'service'=>null, 'output'=>'CountryAlpha2');
$params = array_merge($this->config->vars, $defaultParams, $params);
On vérifie ensuite si une adresse IP et un service ont été indiqué. Si ce n'est pas le cas, on leur donne leur valeur par défaut, à savoir, l'adresse IP du visiteur (via une fonction de CaMykS), et le service indiqué comme outil par défaut dans la configuration.
if ($params['ipAddress'] == null)
$params['ipAddress'] = client_getIp();
if ($params['service'] == null)
$params['service'] = $this->get_configValue('service');
Ensuite, on charge la librairie du service sélectionné.
$library = 'IPGeolocator_'.$params['service'];
$this->load_library('Services/'.$library);
if (!class_exists($library))
return false;
$service = new $library;
Puis on demande au service d'effectuer la demande
$result = $service->execute($params);
On vérifie le résultat
if ($result == false)
return false;
Le but est que quelque soit le service, il faut retourner une réponse uniformisée. On fusionne donc la réponse avec la réponse par défaut, vierge de toute information.
$result = array_merge(array(
'Address' => '',
'AddressZipcode' => '',
'AddressCity' => '',
'RegionCode' => '',
'RegionName' => '',
'CountryAlpha2' => '',
'CountryAlpha3' => '',
'CountryName' => '',
'ContinentCode' => '',
'ContinentName' => '',
'Latitude' => '',
'Longitude' => '',
), $result);
Enfin, selon ce qui est demandé en sortie, nous retournons le résultat. Il peut s'agir soir de la réponse complète, soit d'une seule des valeurs présentes dans la réponse.
if ($params['output'] == 'full')
return $result;
if (isset($result[$params['output']]))
return $result[$params['output']];
return false;
Afin de conserver le code lisible et extensible, chaque service a été créé en tant que librairie distincte. La création d'une librairie n'est, une nouvelle fois, pas très compliquée à réaliser.
Il s'agit d'un objet (afin que ce soit plus simple à charger par le CMS), qui dispose d'une seule méthode. Voici un exemple avec le service IPInfoDB
final class IPGeolocator_IPInfoDB {
public function execute($params) {
Je vérifie d'abord que nous disposons bien d'une clé API pour le service.
if ($params['APIKey_IPInfoDB'] == '')
return false;
Puis, je construis l'URL de la requête vers le service, incluant l'adresse IP à tester ainsi que la fameuse clé API.
$url = 'http://api.ipinfodb.com/v3/ip-city/?format=json&key='.$params['APIKey_IPInfoDB'].'&ip='.$params['ipAddress'];
J'effectue enfin la requête vers le service.
$result = http_getContents(array('url'=>$url));
Il est maintenant nécessaire de tester le résultat, envoyé au format JSON, afin de s'assurer que le service fonctionne, et qu'il nous a renvoyé quelque chose de correct.
if (strlen($result) == 0)
return false;
$data = json_decode($result, true);
if (json_last_error() !== JSON_ERROR_NONE or !is_array($data))
return false;
IPInfoDB inclut dans sa réponse un code d'état, nous pouvons nous en service pour faire une vérification de plus, autant en profiter
if ($data['statusCode'] != 'OK')
return false;
Il n'y a plus qu'à retourner le résultat en récupérant les informations du service pour les insérer dans notre réponse uniformisée.
return array(
'Address' => '',
'AddressZipcode' => $data['zipCode'],
'AddressCity' => $data['cityName'],
'RegionCode' => '',
'RegionName' => $data['regionName'],
'CountryAlpha2' => strToLower($data['countryCode']),
'CountryAlpha3' => '',
'CountryName' => $data['countryName'],
'ContinentCode' => '',
'ContinentName' => '',
'Latitude' => $data['latitude'],
'Longitude' => $data['longitude'],
);
Comme vous pouvez le voir, j'ai laissé quelques champs vides, ce qui n'est pas nécessaire puisque nous fusionnons une réponse vide dans l'objet principal.
Il ne reste plus qu'à décliner la librairie pour l'ensemble des services. Tous les services fonctionnent pratiquement de la même manière, il ne s'agit donc qu'une déclinaison à faire pour chaque, en adaptant selon les subtilités de chacun.
Et voilà, notre plugin est prêt.
Si un jour, je dois intégrer un nouveau service, cela sera évidemment possible. Pour cela il suffira d'ajouter le service et ses réglages dans l'objet de configuration, puis de créer la librairie pour le service.
Puisque c'est un plugin générique et intégré au CMS, si vous le souhaitez, vous pouvez rentrer plus précisément dans le code source, il suffit de regarder le code source, libre, sur le GitHub du projet CaMykS.
Petite anecdote, créer le plugin n'a pas été le plus long, trouver les services non plus. L'étape la plus pénible de ce développement fut de créer un compte sur l'ensemble des outils. Heureusement, l'indication du pays semble toujours correct, et c'est bien le principal.
Petit bonus de fin, ayant tester l'ensemble de ces services, je peux vous confirmer qu'aucun n'a été capable de me donner mon emplacement précisément. J'ai été placé à pas mal d'endroit, à Paris, en région Parisienne, dans le sud-ouest, en Bretagne, alors que je suis situé dans le sud-est. J'ai, parfois et bizarrement, obtenu des réponses concordantes. Fausses, mais concordantes...