Ô noble voyageur du web ! Votre longue route vous a mené ici, poussé par la curiosité et une appétence certaine pour le monde du e-commerce. Vos doigts courent sur le clavier, tel un matelot s’affairant sur le pont d’un navire. Votre âme est un trois-mâts en quête de connaissance, et voici le dénouement de votre épuisant périple. Cet article sera je l’espère, une escale des plus enrichissantes : voici le « Store Locator » illustré par sa mise en place technique sur Bananamoon.
Qu’est-ce qu’un store locator ?
Il est préférable tout de suite de refréner vos pulsions aventureuses, ce n’est pas une carte au trésor renfermant le secret d’un trésor légendaire. C’est un service qui permet de localiser un point de vente physique sur une carte. Ah ! Que le monde est petit et accessible à la clarté d’un tel instrument d’exploration. C’est aujourd’hui un outil incontournable pour de nombreuses boutiques, d’autant plus pour les réseaux de points de vente tel que Bananamoon.
Le store locator permet de favoriser le drive-to-store, c’est à dire de transformer un respectable voyageur du web comme vous en visiteur physique du magasin. Dans ce cadre, l’importance d’une adaptation mobile est prépondérante car c’est là la réelle utilité : pouvoir générer des itinéraires depuis sa propre localisation jusqu’au magasin le plus proche. Sans compter la précieuse collecte d’informations s’y afférant : numéro de téléphone de la boutique, horaires d’ouverture, etc.
On prépare sa valise !
Pour mener à bien cette expédition, la refonte du store locator de Bananamoon sur Magento 1 a nécessité l’utilisation de plusieurs outils. Un maillot de bain, une jambe de bois à la dernière mode et son astrolabe de poche ? Exactement. Mais également, l’extension Advanced Content Manager pour la structuration et la gestion des données et Google Maps API pour la manipulation et l’affichage d’une carte.
-
Etape 1 : Structuration et gestion des données
Quoi de mieux qu’un véritable couteau suisse de la gestion de contenu sur Magento, l’extension CMS Advanced Content Manager, pour aller au bout de notre aventure ? Rien, non même pas un petit Mojito. Il faudra donc créer dans un premier temps notre Content Type « Store Locator ».
C’est lui qui détermine la structure de donnée de vos futurs Content, correspondante aux boutiques à placer sur la carte. On y retrouve le nom de la boutique, un numéro de téléphone, les horaires, l’adresse, etc… mais surtout les coordonnées GPS longitude et latitude.
Pour optimiser au mieux le référencement naturel de chaque magasin dans les résultats de recherche, on veille à renseigner un default URL pattern pertinent. Chaque magasin aura ainsi un chemin d’accès optimisé pour le référencement, dans notre exemple sur Bananamoon : Ville + Pays + Nom du magasin.
L’enregistrement de la donnée pays de chaque magasin se fait sous la norme ISO 3166-1, soit un code alphabétique de deux caractères (France : FR, Allemagne : DE, Etat-Unis : US, etc). Pour faire apparaître le nom complet du pays dans notre default URL pattern et gérer sa traduction, il a fallu surcharger le helper du module : Blackbird_ContentManager_Helper_Data.
**
* Replace {{.*}} patterns in data
* @param string $data
* @param Content $content
* @return string
*/
public function applyPattern($data, $content)
{
$matches = array();
preg_match_all('/{{([a-zA-Z0-9_\|]*)}}/', (string) $data, $matches);
if(!empty($matches[1]))
{
foreach($matches[1] as $key => $replacement)
{
[...]
if(preg_match('/\|country/', $replacement))
{
$replacement = str_replace('|country', '', $replacement);
$attributeContent = $this->_getCountryValue($content->getData($replacement));
}
[...]
}
}
}
/**
* Get plain country value for a content
*/
private function _getCountryValue($str)
{
$countryTranslation = Mage::app()->getLocale()->getCountryTranslationList();
return strtolower($countryTranslation[$str]);
}
-
Etape 2 : Import des données
Maintenant que la structure de données est en place, il faut nourrir la bête ! Nous allons chasser un bon gros fichier .CSV regroupant toutes les données à l’aide d’un script d’import php en guise de fusil.
Dans le dossier shell de notre arborescence du projet, on crée notre script d’import import_store_locator.php. C’est lui qui ira chercher toutes les données du fichier .CSV pour créer les Content dans ACM. La première ligne du .CSV correspond au type de donnée. Pour lancer l’import, il suffit d’exécuter la commande : php -f import_store_locator.php -- -f import_store_locator.csv
<?php
require_once 'abstract.php';
/**
* Script for automatic store locator import
*/
class Mage_Shell_ImportStoreLocator extends Mage_Shell_Abstract
{
const STORE_LOCATOR_CONTENT_TYPE_ID = 1;
/**
* Displays critical message and exit.
*/
protected function _fault($msg)
{
exit(PHP_EOL . $msg . PHP_EOL);
}
/**
* Main script method that imports all store from specified CSV file.
*/
public function run()
{
$start = microtime(true);
if (!$file = $this->getArg('f')) {
echo $this->usageHelp();
} else {
$delimiter = $this->getArg('d') ? $this->getArg('d') : ',';
$enclosure = $this->getArg('e') ? $this->getArg('e') : '"';
$fh = fopen($file, 'r');
if (!$fh) {
$this->_fault("An error occured opening file $file.");
}
try {
$i = 0;
// each row
while ($data = fgetcsv($fh, null, $delimiter, $enclosure)) {
$i++;
if ($i > 1) {
if ($data[0] != '') {
$store = Mage::getModel('contentmanager/content');
$store->setData('ct_id', self::STORE_LOCATOR_CONTENT_TYPE_ID); // Store locator
$store->setData('storelocator_latitude', $data[1]); // Latitude
$store->setData('storelocator_longitude', $data[2]); // Longitude
[...]
$store->save();
}
}
}
} catch (Exception $e) {
Mage::logException($e);
$this->_fault($e->getMessage());
}
}
$end = microtime(true);
$time = $end - $start;
echo PHP_EOL;
echo 'Script Start: ' . date('H:i:s', $start) . PHP_EOL;
echo 'Script End: ' . date('H:i:s', $end) . PHP_EOL;
echo 'Duration: ' . number_format($time, 3) . ' sec' . PHP_EOL;
}
/**
* List of available script options.
*
* @return string
*/
public function usageHelp()
{
return <<< USAGE
Usage: php -f import_store_locator.php -- [options]
-f File to import
-d Delimiter (default is ,)
-e Enclosure (default is ")
-h Short alias for help
help This help
ex : php -f import_store_locator.php -- -f import_store_locator.csv
USAGE;
}
-
Etape 3 : Manipulation et affichage de la carte
Pour récupérer les données des magasins dans un template .phtml, il suffit de générer une collection de Content depuis une méthode de block :
$storeCollection = Mage::getModel('contentmanager/content')
->getCollection('store_locator')
->addAttributeToSelect(array(
'storelocator_name',
'storelocator_longitude',
'storelocator_latitude',
[...]
))
->addAttributeToFilter('status', 1)
->setOrder('storelocator_ville', 'ASC');
La gestion de la carte et l’affichage des données est entièrement gérée par javascript : Maps Javascript API. Il est tout de même à noter que Google Maps a modifié sa tarification ce mois-ci et généralise une utilisation payante de son service. Il faut donc veiller à limiter les appels de géolocalisation, ou tout simplement opter pour un service de géolocalisation gratuit comme Open Street Map.
-
Etape 4 : Calcul de distance
Nous récupérons les coordonnées d’un lieu recherché et possédons celle de tous les magasins à afficher sur la carte. De là, il est possible de filtrer l’affichage des points de vente dans un rayon de X kilomètres autour du point de recherche. Le calcul de la distance peut se faire de la façon suivante :
/**
* Calcul de distance entre deux points en fonction de la latitude et longitude
* @param $point1_lat
* @param $point1_long
* @param $point2_lat
* @param $point2_long
* @param string $unit
* @param int $decimals
* @return float
*/
public function distanceCalculation($point1_lat, $point1_long, $point2_lat, $point2_long, $unit = 'km', $decimals = 2) {
// Calcul de la distance en degrés
$degrees = rad2deg(acos((sin(deg2rad($point1_lat))*sin(deg2rad($point2_lat)))
+
(cos(deg2rad($point1_lat))*cos(deg2rad($point2_lat))*cos(deg2rad($point1_long-$point2_long)))));
// Conversion de la distance en degrés à l'unité choisie
switch($unit) {
case 'km':
$distance = $degrees * 111.13384; // kilomètres
break;
case 'mi':
$distance = $degrees * 69.05482; // milles
break;
case 'nmi':
$distance = $degrees * 59.97662; // milles nautiques
break;
}
return round($distance, $decimals);
}
Fin du voyage
C’est la fin de votre escale, la marée monte, il est temps pour vous de lever l’ancre vers d’autres horizons de savoir. Le ronronnement sourd de votre ordinateur se confond à nouveau avec le ressac de la mer et vous rappel à votre errance digitale. Loin sur l’horizon, un rayon de soleil mutin se dépose sur cette drôle de plage, colorant notre store locator des ors de l’été. Tel un souvenir de voyage intarissable, gardez à l’esprit que cet usage contribue à faire sauter efficacement la frontière entre monde du web et physique.