MAJ Magento 1.4.1.1 vers 1.6.2.0

Mettre à jour Magento n’est pas toujours une chose facile, dans cette article, je vais vous montrer la manière dont je m’y suis pris pour passer d’une version 1.4.0.1 vers la dernière version à l’heure ou j’écris, à savoir la 1.6.2.0.

Préparer le terrain…

Il faut tout d’abord récupérer la version de votre site en production (la 1.4) pour la dupliquer sur un serveur ou en local.

Dans un terminal vous pouvez faire :

Sur le serveur de production:

PHP
1
2
mysqldump -h localhost -p bdd > mage.sql
tar -cvf mage.tar ./magento mage.sql

Sur le serveur local:

PHP
1
2
3
4
5
6
mkdir currentmage
cd currentmage
wget http://domaine.tld/mage.tar
tar -xvf mage.tar
rm -rf ./var/cache ./var/session
mkdir newmage

A ce stade, nous avons la copie des fichiers de la version de production dans le dossier “currentmage“.

Il faut maintenant mettre en place la base de données. Si vous avez suivi les commandes plus haut, nous avons fait un dump de la base. Allons donc dans phpMyAdmin pour créer une base de données que nous allons nommer “newmage“.

Retour dans notre terminal pour insérer notre dump..

Toujours sur le serveur local:

PHP
1
mysql -h localhost -u user -p currentmage < mage.sql

Nous retournons ensuite dans phpMyAdmin afin de modifier le base_url de magento présent dans la table "core_config_data". Ce que je fais un général, c'est une recherche sur le champs "path" avec un like% sur "secure". Deux entrées doivent ressortir, il faut donc les éditer pour mettre l'url local de notre magento. Par exemple "newmage.mondomaine.tld". Si vous n'avez pas de sous-domaine sous la main, il est tout à fait possible de faire de modifier votre fichier hosts pour que la résolution DNS soit faites. Attention, si vous travaillez sur un serveur distant, Magento vérifiera la résolution du domaine, il faut donc modifier également le hosts du serveur.

Maintenant que le terrain est prêt, nous allons pouvoir mettre à jour Magento !

Récupérons tout d'abord la dernière version de Magento

Serveur local:

PHP
1
2
3
4
5
6
7
8
cd newmage
wget http://www.magentocommerce.com/downloads/assets/1.6.2.0/magento-1.6.2.0.tar.gz
tar -zxvf magento-1.6.2.0.tar.gz
mv magento/* magento/.htaccess .
chmod -R o+w media var
chmod o+w app/etc
rm -rf magento/ magento-1.6.2.0.tar.gz
cp ../currentmage/app/etc/local.xml ./app/etc/

N'oubliez pas que le fichier local.xml provient normalement de votre site de production, il faut donc l'éditer pour modifier les informations de connexion et mettre ceux de la base de données "newmage“.

Une fois que c’est fait, il n’y à plus qu’à se rendre sur l’url qui pointe vers le dossier “currentmage“.

Si tout se passe bien, Magento devrais mettre à niveau la base de données et vous n’aurez plus qu’à copier votre thème sur la nouvelle installation, vérifier que tout se passe bien et remettre en production…

Il se peut que la MAJ prenne beaucoup de temps ou que MySQL bloque sur un process “copy tmp table”. ce que vous pouvez faire dans ce cas c’est:

- Stopper Mysql
- Monter un fichier en TMFS ->
mkdir /var/tmpfs
mount -t tmpfs -o size=2G tmpfs /var/tmpfs/
- Copier le datadir de MySQL dans /var/tmpfs/ -> cp -Rvf /var/lib/mysql /var/tmpfs
- Modifier la directive data_dir dans le my.cnf pour y mettre le chemin /var/tmpfs/mysql
- Redémarrer ensuite MySQL

Attention, ne faites pas cette modification sur un serveur MySQL contenant des données critiques ou des sites en production. si le serveur redémarre, le fichier /var/tmpfs sera vidé et la différence entre les données de /var/lib/mysql et /var/tmpfs sera perdue !

Après cette modification, retenter la MAJ comme en pointant sur l’url de “newmage”.
Pour vérifier le temps d’exécution des requêtes et s’assurer que la MAJ se passe bien, vous pouvez vous connectez à mysql et faire un SHOW PROCESSLIST;

Si tout se passe bien, les requêtes devraient défiler (pas très très vite…) :) .

Il se peut également que vous obteniez cette erreur: Duplicate entry ’0-2981′ for key ‘UNQ_BY_CUSTOMER’

si c’est le cas, éditez le fichier app/etc/config.xml pour remplacer le noeud XML suivant:

SET NAMES utf8;

par:

SET NAMES utf8; SET FOREIGN_KEY_CHECKS=0; SET UNIQUE_CHECKS=0;

Et enfin, relancer encore la MAJ… Cette fois, elle devrait passer !

Déplacer la pagination de la liste de produit Magento

Si vous souhaitez déplacer la pagination de Magento qui par défaut se trouve dans la toolbar, c’est très simple avec un peu de code…

Dans mon cas, je voulais une pagination de produit juste en bas de la liste de produit et hors toolbar. Malheureusement si l’on déplace la méthode $this->getPagerHtml() appelée dans le template toolbar.phtml dans le template list.phtml, le type du block sera différent et la méthode n’existera pas.

La deuxième solution qui m’est venu à l’esprit était de modifier le layout catalog.xml afin de mettre le bloc “product_list_toolbar_pager” dans le block “product_list” pour ensuite faire un echo $this->getChildHtml(‘product_list_toolbar_pager’) mais sans succès car la collection n’est pas chargée et donc la pagination ne peut pas se faire…

LA solution à ce problème est de ne toucher qu’au template list.phtml et rajouter à l’endroit voulu:

PHP
1
2
3
4
5
6
7
<?php
            $toolbar = $this->getToolbarBlock();
            $toolbar->setCollection($_productCollection);
            if($toolbar->getCollection()->getSize() > 0) {
                echo $toolbar->getPagerHtml();
            }
            ?>

Après cette modification, la pagination devrait s’afficher !

Can’t write to file (Interakting Slider)

J’ai eu plusieurs fois une erreur sur le downloader de Magento pour installer l’extension Interakting Slider. Cette erreur doit surement se produire pour d’autre extension, mais je n’ai pour le moment jamais rencontré de cas similaire.

Pour résoudre cette erreur, il suffit simplement d’éditer le fichier suivant:

/downloader/lib/Mage/Archive/Abstract.php

Il faut ensuite éditer la méthode _writeFile pour rajouter cette ligne au dessus de la condition:

PHP
1
$destination = preg_replace('/[^(\x20-\x7F)]*/','', $destination);

Pour ceux qui veulent appliquer un directement un patch, le voici:

PHP
1
2
3
4
5
6
7
8
--- Abstract.php
+++ downloader/lib/Mage/Archive/Abstract.php
@@ -43,4 +43,5 @@
protected function _writeFile($destination, $data)
{
+ $destination = preg_replace('/[^(\x20-\x7F)]*/','', $destination);
if(false === file_put_contents($destination, $data)) {
throw new Mage_Exception("Can't write to file: " . $destination);

Tout devrait rentrer dans l’ordre après cette modification.

le pattern observateur

Dans cet article, nous allons aborder le pattern observateur.

Ce pattern permet de définir une relation ente objet de type un-à-plusieurs. Il permet d’informer une liste d’observateurs d’un changement d’état de l’objet dont ils dépendent.

Nous allons nous servir de deux interfaces fournit dans la SPL.

Le résultat que nous voulons obtenir est très simple, nous avons un objet “Personne” représentant un utilisateur de notre application et nous voulons mettre en place un système permettant d’enregistrer chaque connexion de l’utilisateur dans notre base de données.

Développement sans le pattern observateur

Voici la classe utilisateur:

PHP
1
2
3
4
5
6
7
8
9
10
11
12
<?php
class Personne
{
    public function connexion($login, $password)
    {
        //Vérification des identifiants
        //Traitement de la connexion...
    }
}
?>

La solution la plus simple et la plus rapide pour enregistrer la connexion de l’utilisateur dans notre base de données est de rajouter le traitement pour l’enregistrement directement dans la méthode “connexion” comme ceci:

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class Personne
{
    public function connexion($login, $password)
    {
        //Vérification des identifiants
        //Traitement de la connexion...
        
        $pdo = new PDO('mysql:host=localhost;dbname=database', 'root', '');
        $pdo->prepare('INSERT INTO log_connexion VALUES(:login, :date)');
        $pdo->execute(array(
            ':login' => $login,
            ':date' => date('Y-m-d H:i:s')
        ));
    }
}
?>

Cette façon de faire fonctionne, cependant elle n’est pas optimisée et surtout très couplée. En effet, le traitement de la connexion n’a rien à voir avec l’enregistrement de la connexion dans la base de données. Nous pourrions rajouter une méthode dans cette classe chargé d’enregistrer la connexion de l’utilisateur et ainsi sortir le traitement d’enregistrement de la méthode “connexion”.

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
class Personne
{
    public function connexion($login, $password)
    {
        //Vérification des identifiants
        //Traitement de la connexion...
        
        $this->enregistrement($login);
    }
    
    public function enregistrement($login)
    {
        $pdo = new PDO('mysql:host=localhost;dbname=database', 'root', '');
        $pdo->prepare('INSERT INTO log_connexion VALUES(:login, :date)');
        $pdo->execute(array(
            ':login' => $login,
            ':date' => date('Y-m-d H:i:s')
        ));
    }
}
?>

Dans cet exemple, le code est un peu mieux, mais pas encore au point. La méthode “connexion” fait un appel direct à une méthode contenant un traitement spécifique. Ceci dit, ce code fonctionne et ne modifie que très peu la méthode de connexion. Et si nous voulions par la suite rajouter une méthode permettant d’informer l’utilisateur de sa connexion en cas de vol de ses identifiants, comment ferions-nous ?

Une nouvelle fois, nous pourrions rajouter une méthode “avertir” dans la classe “Personne” contenant le traitement (une notification par mail) et pour finir, rajouter un appel à cette méthode dans la méthode “connexion”.

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
class Personne
{
    public function connexion($login, $password)
    {
        //Vérification des identifiants
        //Traitement de la connexion...
        
        $this->enregistrement($login);
        $this->avertir();
    }
    
    public function enregistrement($login)
    {
        $pdo = new PDO('mysql:host=localhost;dbname=database', 'root', '');
        $pdo->prepare('INSERT INTO log_connexion VALUES(:login, :date)');
        $pdo->execute(array(
            ':login' => $login,
            ':date' => date('Y-m-d H:i:s')
        ));
    }
    
    public function avertir()
    {
        mail('email@domain.tld', 'Alerte de connexion', 'Message');
    }
}
?>

En agissant ainsi, il est évident qu’à chaque action voulue au changement d’état de la méthode “connexion”, nous devrions rajouter autant d’appels et de méthodes nécessaire au nombre d’actions voulues. Voyons un peu ce que nous pourrions faire avec l’aide du pattern observateur…

Avec le pattern !

Pour commencer, nous allons sortir les méthodes “enregistrement” et “avertir” de la classe “Personne”. Le but est simple, nous ne voulons pas que la classe “Personne” ait connaissance du traitement de ces deux méthodes, car cela ne la concerne tout simplement pas !

Nous nous retrouvons donc avec trois classes, la classe “Personne”, la classes “Mail” ainsi que la classe “Bdd”. Dans la classe “Personne”, nous allons implémenter l’interface “SplSubject” qui nous oblige à écrire trois méthodes:

- public function attach(SplObserver $observer);
- public function detach(SplObserver $observer);
- public function notify();

La méthode “attach” va nous permettre d’enregistrer un observateur, la méthode “detach” de supprimer un des observateurs et enfin, la méthode “notify” va permettre de notifier l’ensemble des observateurs d’un changement d’état sans se soucier de ce qui se passe ensuite. Nous sauvegarderons l’ensemble des observateurs dans un tableau.

Voici le nouveau code:

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
class Personne implements SplSubject
{
    private $_observers = array();
    
    private $_login;
    
    public function connexion($login, $password)
    {
        //Vérification des identifiants
        //Traitement de la connexion...
        $this->_login = $login;
        $this->notify();
    }
    
    public function getLogin()
    {
        return $this->_login;
    }
    public function attach(SplObserver $observer)
    {
        $this->_observers[spl_object_hash($observer)] = $observers;
    }
    public function detach(SplObserver $observer)
    {
        unset($this->_observers[spl_object_hash($observer)]);
    }
    public function notify()
    {
        foreach ($this->_observers as $object)
        {
            $object->update($this);
        }
    }
}
?>

Nous implémentons donc les 3 méthodes décrites par l’interface “SplSubject” et pour finir, nous rajoutons un appel direct à “notify” qui va se charger d’informer tout les observateurs de cette classes. Seulement pour que les observateurs soit capable de réagir au changement d’état de la classe “Personne”, il devront obligatoirement implémenter la méthode “update”. Pour cela, nous allons implémenter la seconde interface de la SPL, “SplObserver”.

Classe Bdd:

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class Bdd implements SplObserver
{
    public function update(SplSubject $subject)
    {
        $pdo = new PDO('mysql:host=localhost;dbname=database', 'root', '');
        $pdo->prepare('INSERT INTO log_connexion VALUES(:login, :date)');
        $pdo->execute(array(
            ':login' => $login,
            ':date' => date('Y-m-d H:i:s')
        ));
    }
}
?>

La classe Mail:

PHP
1
2
3
4
5
6
7
8
9
10
11
<?php
class Mail implements SplObserver
{
    public function udpate(SplSubject $subject)
    {
        mail($subject->getLogin(), 'Alerte de connexion', 'Message');
    }
}
?>

Et enfin pour finir, l’utilisation de tout ça:

PHP
1
2
3
4
5
6
7
8
9
10
<?php
$personne = new Personne();
$personne->attach(new Bdd());
$personne->attach(new Mail());
$personne->connexion('mail@domaine.tld', 'password');
?>

Voici le résultat final:

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?php
class Personne implements SplSubject
{
    private $_observers = array();
    
    private $_login;
    
    public function connexion($login, $password)
    {
        //Vérification des identifiants
        //Traitement de la connexion...
        $this->_login = $login;
        $this->notify();
    }
    
    public function getLogin()
    {
        return $this->_login;
    }
    public function attach(SplObserver $observer)
    {
        $this->_observers[spl_object_hash($observer)] = $observers;
    }
    public function detach(SplObserver $observer)
    {
        unset($this->_observers[spl_object_hash($observer)]);
    }
    public function notify()
    {
        foreach ($this->_observers as $object)
        {
            $object->update($this);
        }
    }
}
class Bdd implements SplObserver
{
    public function update(SplSubject $subject)
    {
        $pdo = new PDO('mysql:host=localhost;dbname=database', 'root', '');
        $pdo->prepare('INSERT INTO log_connexion VALUES(:login, :date)');
        $pdo->execute(array(
            ':login' => $login,
            ':date' => date('Y-m-d H:i:s')
        ));
    }
}
class Mail implements SplObserver
{
    public function udpate(SplSubject $subject)
    {
        mail($subject->getLogin(), 'Alerte de connexion', 'Message');
    }
}
$personne = new Personne();
//Attachement des deux observateurs
$personne->attach(new Bdd());
$personne->attach(new Mail());
//Connexion de la personne
$personne->connexion('mail@domaine.tld', 'password');
?>

Et voila, notre code est découplé, nous n’avons plus besoin de retourner dans la classe “Personne” pour rajouter du traitement lié à son changement d”état et elle n’a plus aucune connaissances des traitements des différentes classes qui viennent se liées à elle !

Le pattern Singleton

Le pattern singleton, un joli nom pour un puissant pattern très simple à mettre en place. Le principe du singleton est de restreindre l’instanciation à un seul et unique objet. Ainsi nous aurons toujours le même identifiant d’objet. Dans le cas d’une connexion à une base de données par exemple, dans la majeure partie des cas, nous ne voulons qu’un seul thread.

Voici donc un exemple de connexion avec PDO suivant le pattern Singleton

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* Class db, renvoie une instance de connexion à la base de données
*
* @abstract
*/
abstract class Gmsiweb_Db_Pdo
{
    /**
  * Objet PDO
  *
  * @var Gmsiweb_Db_Pdo
  */
    private static $_instance;
    private function __construct() { }
    private function __clone() { }
    /**
  * Renvoie l'instance de connexion à la base de données
  *
  * @static
  * @param string $db Nom de la base de données
  * @return PDO
  */
    public static function getInstance($db)
    {
        if(!isset(self::$_instance)) {
            $config = Gmsiweb_Config_Ini::getDbConfig($db);
            try{
                self::$_instance = new PDO(
                    'mysql:host=' . $config['pdo.host'] .';dbname=' .
                    $config['pdo.dbname'],
                    $config['pdo.username'],
                    $config['pdo.password'],
                    array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'')
                );
                self::$_instance->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            } catch (PDOException $e) {
                echo $e->getMessage();
            }
            
            if(!is_object(self::$_instance)) {
                throw new Gmsiweb_Exception('Erreur de connexion à la base de données');
            }
        }
        return self::$_instance;
    }
}
?>

Avec cet exemple, pour récupérer une instance de connexion à la base, il suffira de faire

PHP
1
2
3
<?php
$instance = Gmsiweb_Db_Pdo::getInstance('db');
?>

Simple non ?

Autocomplétion api google maps

Dans cet article, nous allons voir comment mettre en place fenêtre modale  avec Jquery-ui avec une autocomplétion grâce à l’api de google maps. Le rendu voulu est une case à cocher “visible sur la map”, le fait de cocher la case ouvre une fenêtre modale avec la map ainsi que le champs d’autocomplétion.

Prérequis

- Jquery

- Jquery-ui

- Api Google Maps

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<html>
    <head>
        <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/jquery-ui.min.js"></script>
        
        <link rel="stylesheet" media="screen" type="text/css" href="../jquery-ui/css/ui-lightness/jquery-ui-1.8.14.custom.css" />
        <script type="text/javascript">
        $(document).ready(function(){
            
            $('#map').click(function() {
                
                var geocoder = new google.maps.Geocoder();
                var latlng = new google.maps.LatLng(48.86607,2.353706);
                var options = { zoom: 5, center: latlng, mapTypeId: google.maps.MapTypeId.ROADMAP };
                var marker;
                var map;
                var item;
                
                if($('#map').attr('checked')) {
                    $('#dialogMap').dialog({
                        width: 850,
                        height: 580,
                        modal: true,
                        open: function(event, ui) {
                            map = new google.maps.Map(document.getElementById("map_canvas"), options);
                            marker = new google.maps.Marker({ map: map, draggable: false });
                        },
                        buttons: {
                            'Valider': function() {
                                $('#dialogMap').dialog('destroy');
                            },
                            'Annuler': function() {
                                if($('#map').is(':checked')) {
                                    $('#map').attr('checked', false);
                                    $.uniform.update('#map');
                                }
                                $('#dialogMap').dialog('destroy');
                            }
                        },
                        close: function() {
                            if($('#map').is(':checked')) {
                                $('#map').attr('checked', false);
                                $.uniform.update('#map');
                            }
                            $('#dialogMap').dialog('destroy');
                        }
                    });
                }
                $(function() {
                    $('#geolocation').autocomplete({
                        source: function(request, response) {
                            geocoder.geocode({'address': request.term}, function(results, status) {
                                response($.map(results, function(item) {
                                    return {
                                        label: item.formatted_address,
                                        value: item.formatted_address,
                                        latitude: item.geometry.location.lat(),
                                        longitude: item.geometry.location.lng()
                                    }
                                }));
                            });
                        },
                        select: function(event, ui) {
                            item = ui.item;
                            var location = new google.maps.LatLng(ui.item.latitude, ui.item.longitude);
                            marker.setPosition(location);
                            map.setCenter(location);
                        }
                    });
                });
            });
        });
        </script>
    </head>
    
    <body>
        <form>
        <label for="map">Visible sur la carte</label>
        <input type="checkbox" name="map" id="map" />
        </form>
        <div id="dialogMap" title="Localisation" style="display:none;">
            <form>
                <label for="geolocation">Adresse: </label>
                <input type="text" name="geolocation" id="geolocation" size="33" />
            </form>
            <div id="map_canvas" style="width:850px;height:580px;"></div>
        </div>    
    </body>
</html>

Et voila, relativement simple et efficace :) .

Voir le resultat