KrISS feed 8.7 - A simple and smart (or stupid) feed reader. By Tontof
  • Friday 01 August 2014 - 13:04

    En cette période de vacances, nous sommes nombreux à nous connecter depuis nos terminaux ( smartphone, tablette .. ) pour surfer, parfois travailler , dépanner .
    Il est vrai que ces appareils sont formidables sur ce point ( le coté nomade ). Mais le revers de la médaille, c'est la sécurité !
    Lorsque vous vous connectez à une borne wifi ( gratuite ou non ), qui vous assure que votre navigation n'est pas épiée ?
    Voilà pour le coté sécurité, mais il peut être aussi utile de se connecter à son réseau local depuis l'extérieur.
    Nous allons pour cela nous connecter via un serveur proxy de type socks.
    Maintenant peu importe ce qui motive l'utilisation d'une telle connexion, voyons comment faire pour nous y connecter depuis un client Android non chrooté

    Les apps à installer

    1. juiceSSH
    2. Firefox

    JuiceSSH est un client SSH qui fonctionne vraiment bien, gratuit pour les fonctionnalités de base, mais payant pour faire une redirection de port. Vous allez donc devoir l'acheter, mais l'investissement en vaut la peine.
    D'autres ont utilisé ConnectBOT qui lui est à 100% gratuit, mais de mon coté il plantait régulièrement je n'ai donc pas insisté.

    Configuration de juiceSSH

    Nous allons commencer par créer une connexion SSH de base, le tout en image :

    2014_08_01_12.06.59.png

    2014_08_01_12.08.03.png

    • Nickname : nom de la connexion
    • Type : SSH
    • Adresse : Adresse du serveur SSH
    • Identity : Login pour se connecter au serveur SSH
    • Port : port à utiliser pour se connecter au serveur SSH

    Configuration de la redirection de port

    Allez dans l'onglet PORT FORWARD, et nous allons utiliser la connexion SSH précédemment crée, comme ceci.

    2014_08_01_12.08.40.png

    Il est ensuite possible de mettre une icône sur votre page principale, pour se connecter plus rapidement au serveur proxy SOCKS

    2014_08_01_12.08.57.png

    Configuration de Firefox

    Pour utiliser le serveur proxy ( après s'y être connecté ), dans firefox, ouvrez un nouvel onglet et entrez dans la barre d'adresse :

    about:config


    Voici les 5 paramètres à configurer comme ceci

    2014_08_01_12.05.51.png 2014_08_01_12.06.11.png

    Tests

    Voilà maintenant vous pouvez tester le bon fonctionnement en entrant l'url suivante : http://www.whatismyip.com/, qui devrait vous afficher l'ip du serveur SSH à partir duquel vos requêtes sont envoyés .

    Bonnes vacances .

  • Monday 15 September 2014 - 11:25

    Après une belle maj de votre système, vous avez, au moment de vous remettre au travail, un sympathique message du genre

    ImportError: No module named datetime
    

    detatime !! Cette lib fait pourtant partie du standard de python ! Le fait est que lorsque l'on met à jour python, les liens utilisés à la création du venv sont hs !

    Ici c'est la lib datetime qui a été remonté, mais ça peu être n'importe laquelle ..

    Solution

    Il ne m'a pas fallu longtemps pour résoudre ce problème tout bête, ( beaucoup de cas similaires sur le net ).

    Réinitialiser le virtualenv

    Par exemple avec mon venv nommé foo

    virtualenv /home/cdsl/.virtualenvs/foo
    New python executable in /home/foo/.virtualenvs/creasoft3/bin/python
    Installing setuptools............done.
    Installing pip...............done.
    

    Et voilà ..

  • Sunday 22 February 2015 - 16:29

    Cela fait maintenant quelques mois que je n'ai pas édité de nouveau billet sur ce site, je vais donc couper cette période creuse en vous proposant un petit tutoriel sur la mise en place de php5-fpm avec apache2 et tout ça configurable au besoin dans des virtualhost .

    Nous avons déjà vu comment faire avec Nginx ( qui est franchement plus clair qu'apache ) mais parfois on a pas le choix du serveur web sur lequel on travail .. alors voyons comment faire ça proprement avec apache2.

    Etat des lieux

    Le tutoriel se base sur la version stable de Débian du moment, Debian 7 "Wheezy" .
    Nous allons également activer les dépôts contrib et non-free pour installer ce qui va suivre .

    Pour cela commencez par éditer le fichier /etc/apt/sources.list

    sudo vim /etc/apt/sources.list
    

    Et ajoutez à la fin de chaque ligne contrib non-free
    Ce qui donne quelque chose comme çà

    deb http://cloudfront.debian.net/debian wheezy main contrib non-free
    deb-src http://cloudfront.debian.net/debian wheezy main contrib non-free
    deb http://security.debian.org/ wheezy/updates main contrib non-free
    deb-src http://security.debian.org/ wheezy/updates main contrib non-free
    deb http://cloudfront.debian.net/debian wheezy-updates main contrib non-free
    deb-src http://cloudfront.debian.net/debian wheezy-updates main contrib non-free
    

    PS: ne faite pas attention à l'url de mes dépôts ci-dessus "cloudfront.debian.net" , j'utilise une instance (VM) Amazon pour réaliser mes tests .

    Installation des paquets nécessaires

    Nous allons installer les paquets suivants :

    sudo apt-get install php5-fpm libapache2-mod-fastcgi php5-cgi
    

    Puis il nous faut activer le module fastcgi et actions via la commande a2enmod

    sudo a2enmod actions fastcgi
    

    Configuration du module fastcgi

    Il nous faut lier le module fastcgi aux processus php5-fpm, pour celà nous allons éditer le fichier de configuration du module fastcgi

    vim /etc/apache2/mods-available/fastcgi.conf
    

    Pour arriver à ce résultat

    <IfModule mod_fastcgi.c>
     AddType application/x-httpd-fastphp5 .php
     Action application/x-httpd-fastphp5 /php5-fcgi
     Alias /php5-fcgi /usr/bin/php5-fcgi
     FastCgiExternalServer /usr/bin/php5-fcgi -socket /var/run/php5-fpm.sock -pass-header Authorization
    </IfModule>
    

    NB : il vous faut vérifier le chemin du socket php5-fpm dans votre cas, normalement, /var/run/php5-fpm.sock et la valeur par défaut pour l’environnement Débian 7, pour vérifier exécuter cette commande

    cat /etc/php5/fpm/pool.d/www.conf | grep "listen = "
    

    Le test

    Il ne vous reste plus qu'à tester cette nouvelle configuration, commençons par tester l'ensemble des paramètres d'apache via la commande très pratique

    sudo apache2ctl configtest
    

    Puis il ne reste plus qu'à relancer Apache si tout est ok

    sudo service apache2 restart
    

    Enfin, nous allons mettre un simple fichier php dans le DocumentRoot par défaut d'apache2

    sudo echo "<?php echo phpinfo();" > /var/www/lindev.php && chown www-data:www-data /var/www/lindev.php
    

    Il ne vous reste plus qu'à entrer l'url http://localhost/lindev.php pour voir le résultat .

    Conf spécifique par virtualhost

    Jusque là, les scripts php sont exécutés par l'utilisateur système www-data dans la plupart des cas, cela va être adéquat, mais si pour une raison quelconque vous devez utiliser un autre utilisateur pour exécuter un script/site en particulier, dans le virtualhost correspondant, il vous faudra surcharger la configuration du module fastcgi, ce qui donnera par exemple

    <VirtualHost *:80>
        ServerAdmin webmaster@lindev.fr
        ServerName lindev.fr
        ServerAlias www.lindev.fr
        DocumentRoot /var/www/lindev/
        ErrorLog /var/www/lindev/error.log
        CustomLog /var/www/lindev/access.log combined
    
        <IfModule mod_fastcgi.c>
            AddType application/x-httpd-fastphp5 .php
            Action application/x-httpd-fastphp5 /php5-fcgi
            Alias /php5-fcgi /usr/lib/cgi-bin/php5-fcgi_lindev
            FastCgiExternalServer /usr/lib/cgi-bin/php5-fcgi_lindev -socket /var/run/php5-fpm_lindev.sock -pass-header Authorization
        </IfModule>
    
    </VirtualHost>
    

    Ici , j'ai donc donné un autre socket à utiliser, que j'ai nommé php5-fpm_lindev.sock. Ce socket devra bien évidement être configuré coté php-fpm, en créant un nouveau pool ( dans un prochain article certainement ), en attendant, pour tester copiez juste la conf du pool par défaut, et changer le paramètre listen pour spécifier le socket à créer pour ce pool.
    C'est également dans ce fichier de pool que vous pourrez spécifier l'utilisateur système à utiliser pour interpréter les fichiers php.

    cp /etc/php5/fpm/pool.d/www.conf /etc/php5/fpm/pool.d/lindev.conf
    

    Changer les paramètres listen, user et group , puis relancer php-fpm

    sudo service php5-fpm reload
    

    L'article touche maintenant à sa fin, vous savez maintenant installer apache, php5-fpm et configurer un pool spécifique de php pour un virtualhost.

    Ch.

  • Sunday 28 June 2015 - 11:32

    Ce billet va présenter deux outils Amazon autour d'un exemple concret, la gestion des Bounces en utilisant Amazon en relais pour vos mails.

    Bounces

    Lorsque vous envoyez une "newsletter" à de nombreuses adresses mails, il y a ce que l'on appel les "BOUNCES" , qui traduit donne "rebonds". Il s'agit en fait du retour des mails qui partent vers une adresse qui ne fonctionne pas. Il y a plusieurs raisons qui peuvent engendrer un tel rebond, certaines raisons sont temporaires, d'autres définitives. Par exemple :

    • Le destinataire n’existe pas ( définitif )
    • Boite pleine ( temporaire )
    • Erreur Serveur ( temporaire )
    • Plainte ( définitif )
    • Pas de réponse ( temporaire )
    • Pas de résolution DNS ( définitif )

    Pourquoi traiter ces rebonds ?

    Tout est une histoire de réputation, en effet ne pas traiter ces rebonds et les laisser s'augmenter à chaque newsletter, peut tout doucement vous faire passer pour un "spammeur", d'autant que nous utilisons le relais mails Amazon ( service SES ) qui va réduire votre capacité à envoyer des mails si le nombre de bounces augmente sans action de votre part.

    Deuxième raison, financière, pourquoi continuer à envoyer et payer des mails à des adresses qui n’existent pas ! ( ok le cout du mail est ridicule avec Amazon, mais c'est une question de principe )

    Comment les gérer ?

    Souvent, vous définissez une adresse de retour via ue entête spécifique, dédiée au retour "système" ( Entête : return-path ), ainsi les bounces atterrissement dans cette boite mail dédiée et c'est à vous de les traiter. ( les outils des mailing intègrent souvent une fonction de récupération des bounces par une lecture de boite mail en pop ).

    Cependant, Amazon permet d'aller un peu plus loin grâce à son service de notification SNS.
    Le principe est simple, lorsque Amazon reçoit un "Bounce" il va transmettre un message de notification dans le topic associé . A ce "topic" nous allons lui associer une "inscription" ( qui est en fait une action à lancer lorsque qu'un "message" arrive sur ce topic ). Dans notre cas, ce sera une simple requête HTTP avec dans son corps les informations du Bounce en format JSON.

    Création du Topic

    Depuis l'interface Amazon SNS, vous créez un nouveau "topic"

    Capture_d_e_cran_2015-05-24_a__11.04.03.png

    On va le nommer Bounces

    Capture_d_e_cran_2015-05-24_a__11.04.33.png

    Puis nous allons créer une "subscription"

    Capture_d_e_cran_2015-05-24_a__11.04.57.png

    On va donc choisir le protocole HTTP et dans le champ "endpoint", nous mettons l'url qui pointe sur notre script décrit plus bas ( exemple : http://lindev.fr/sns.php )

    Capture_d_e_cran_2015-05-24_a__11.05.20.png

    Pour le moment, voici notre script sns.php :

    <?php
    ob_start();
    var_dump( $_POST );
    $content = ob_get_clean();
    $fp = fopen('sns_validation.txt', a+);
    fwrite($fp,$content);
    fclose($fp);
    

    Le but étant d'enregistrer dans un fichier texte, ce que nous envoi Amazon . NÉCESSAIRE AU DÉBUT POUR LA VALIDATION DE LE SOUSCRIPTION

    Vous pouvez maintenant valider votre formulaire d'ajout de "subscription", vous allez alors voir apparaitre dans le fichier sns_validation.txt une url pour valider définitivement l'enregistrement.
    Il vous suffit alors d'ouvrir cette url pour terminer l'enregistrement.

    SES et SNS, le lien

    Maintenant nous allons faire en sorte que tout les bounces liés à un domaine géré par le service SES d'amazon, utilisent le système de notification SNS et surtout le "topic" fraichement créé.

    Pour cela, nous allons dans la section SES d'amazon, ( le domaine doit auparavant être validé pour pouvoir relayer des mails, pour cela il suffit d'ajouter des champs TXT dans la zone DNS. La documentation d'amazon est très bien faite à ce sujet ).

    Nous allons donc dans la section Notifications, et l'on édite la configuration

    Capture_d_e_cran_2015-05-24_a__11.06.36.png

    Et là nous allons choisir les options adéquats

    Capture_d_e_cran_2015-05-24_a__11.06.52.png

    On sélectionne donc notre souscription dans les menus "Bounces" et "Complaints" uniquement, car ce sont ces deux cas que nous souhaitons traiter automatiquement.

    Et voilà, après validation, dés qu'un "bounce" montre le bout de son nez, un message est transmit au service SNS et une requête HTTP avec toutes les informations nécessaires dans un format JSON est lancée en POST.

    Tests

    Pour tester le service, Amazon nous met à disposition des adresses spécifiques, qui simulent chaque cas.

    • bounce@simulator.amazonses.com
    • complaint@simulator.amazonses.com

    Ne vous reste donc plus qu'à envoyer un mail à ces deux adresses, et regarder le contenu des données POST qui vont s'ajouter dans votre fichier sns_validation.txt

    Cas simple

    Imaginons, que nous souhaitons, simplement lister les mails des Bounces et Complaints dans un fichier TXT avec trois champs

    1. Date de réception du Bounce
    2. Adresse mail
    3. Type de Bounce ( temporaire ou définitif ou complaint ).

    Nous allons donc extraire ces données du JSON, notre script sns.php devient :

    <?php
    
    // Fetch the raw POST body containing the message
    $postBody = file_get_contents('php://input');
    
    // JSON decode the body to an array of message data
    $msg = json_decode($postBody, true);
    
    if ($msg) {
    
        //Ouverture du fichier de log
        $fp = fopen('sns_bounces.txt', 'a+');
    
        $data = json_decode($msg['Message'],true);
        $typeMsg = $data['notificationType'];//A utiliser pour séparer plaintes et Bounces
    
        switch( $typeMsg ){
            case 'Bounce':
                $bounceType = $data['bounce']['bounceType'];
                foreach( $data['mail']['destination'] AS &$mail ){
                    //Ecriture dans la liste txt
                    fwrite($fp, date('Y-m-d H:i:s').";".$mail.";". $bounceType . "\n");
                }
            break;
            case 'Complaint':
                foreach( $data['mail']['destination'] AS &$mail ){
                    //Ecriture dans la liste txt
                    fwrite($fp, date('Y-m-d H:i:s').";".$mail.";complaint" . "\n");
                }
            break;
        }
        fclose($fp);
    
    }
    

    Vous allez alors vous retrouver avec une liste exploitable comme un CSV .

    L'idée, c'est d'améliorer ce script de démo pour une désinscription automatique dans votre outil de mailing ( très simple avec phplist )

    Restez informé

    inscrivez vous à la newsletter pour recevoir les nouveaux billets par mail. ( formulaire en haut à droite )

  • Sunday 28 June 2015 - 11:33

    J'ai eu quelques soucis récemment avec une instance Amazon qui héberge une base de données Mysql très sollicitée par période.
    En effet les données de la base étaient sur la même partition que le système ( déjà c'est mauvais ), celle-ci étant fortement sollicitée en IO, la charge système se met à grimper, les temps d'attente d’accès au disque étant de plus en plus grand, jusqu'au crash complet .

    La solution simple et rapide est de déplacer les données vers une partition dédiée, avec des performances en IO plus importantes (tant qu'à faire). C'est dans ce contexte que je vais présenter, dans ce billet, la mise en place d'une partition en Raid0 pour augmenter significativement les IO/s et surtout éviter de surcharger le système.

    Ce que l'on va voir

    • Création de système de fichiers XFS
    • Création d'un Raid logiciel
    • Monitoring avec atop
    • Ajout d'un disque au Raid
    • Extension d'une partition XFS

    Etat des lieux

    Je vais utiliser une instance micro ( gratuite ) sous Debian Jessie en HVM ( virtualisation totale ). je vais y attacher 4 disques.

    Capture_d_e_cran_2015-05-17_a__10.28.54.png

    • /dev/xvda pour le système
    • /dev/sdb pour tester les perfs. sur un seul disque
    • /dev/sdc premier disque du Raid
    • /dev/sdb deuxième disque du Raid

    Installation des outils

    Le système étant vierge nous allons installer les paquets nécessaires pour les manipulations ci-dessous.

    sudo apt-get install xfsprogs atop mdadm
    

    Premier test : Disque seul

    Pour pouvoir comparer, nous allons commencer avec un test sur un disque "seul" possédant une partition XFS

    Voici comment sont perçus les disques à ce moment

    Capture_d_e_cran_2015-05-17_a__10.35.40.png

    Commençons par créer la partition :

    sudo cfdisk /dev/xvdb
    

    Une fois la partition créée, voici comment sont perçus les disques

    Capture_d_e_cran_2015-05-17_a__10.37.39.png

    Nous allons maintenant créer le système de fichiers sur cette nouvelle partition

    mkfs.xfs /dev/xvdb1
    

    Enfin il ne nous reste plus qu'à monter cette partition sur un répertoire que nous allons également créer maintenant

    mkdir /home/admin/diskLocal
    mount -t xfs /dev/xvdb1 /home/admin/diskLocal/
    

    Dirigeons nous dans ce répertoire et effectuons le premier test basique, grâce à l'outil dd

    cd /home/admin/diskLocal
    dd if=/dev/zero of=bench.dat bs=1M count=1024 oflag=direct
    

    Pendant le test on voit bien grâce à la commande atop, l'utilisation du disque qui est à son maximum

    Capture_d_e_cran_2015-05-17_a__10.45.21.png

    Et voici le résultat

    Capture_d_e_cran_2015-05-17_a__10.45.39.png

    Deuxième test : 2 Disques en Raid0

    Commençons par construire notre raid0 "software" ( via la commande mdadm )

    mdadm --create --verbose /dev/md0 --level=stripe --raid-devices=2 /dev/xvdc /dev/xvdd
    

    Ce qui nous donne :

    Capture_d_e_cran_2015-05-17_a__10.48.43.png

    Un petit check :

    mdadm -D /dev/md0
    

    Maintenant, créons notre système de fichiers sur md0

    mkfs.xfs /dev/md0
    

    Enfin il ne nous reste plus qu'à monter cette partition sur un répertoire que nous allons créer: diskRaid

    mkdir /home/admin/diskRaid
    mount -t xfs /dev/md0 /home/admin/diskRaid/
    

    Dirigeons nous dans ce répertoire et effectuons le second test sur ce disque Raid0

    cd /home/admin/diskRaid
    dd if=/dev/zero of=bench.dat bs=1M count=1024 oflag=direct
    

    Regardons ce qui se passe pendant le test

    Capture_d_e_cran_2015-05-17_a__10.52.27.png

    Les deux disques sont en pleine charge, et quelques secondes plus tard ...

    Capture_d_e_cran_2015-05-17_a__10.52.37.png

    On passe donc de 30 secondes à 18 sec. pour effectuer ce test et un débit d’écriture de 35.5MB/s à 51MB/s

    Conclusion

    Il est plus intéressant de mettre en place 2 disques en Raid0 qu'avoir un gros disque pour améliorer les IO/s.

    Ajoutons un disque au Raid0

    Si nous ajoutons un disque à notre Raid0, nous passerons ainsi à 3 disques

    Voici le disque à ajouter

    • /dev/xvde

    Capture_d_e_cran_2015-05-17_a__14.21.34.png

    Ajoutons le à notre raid md0

    sudo mdadm --grow /dev/md0 --raid-devices=3 --level=0 --add /dev/xvde
    sudo xfs_growfs /dev/md0
    

    Regardons si tout est ok

    lsblk
    

    Capture_d_e_cran_2015-05-17_a__14.59.08.png

    Vérifions que la partition fasse bien 15G

    df -h
    

    Capture_d_e_cran_2015-05-17_a__15.00.30.png

    Bien ça me semble parfait, lançons le troisième test

    cd /home/admin/diskRaid
    dd if=/dev/zero of=bench.dat bs=1M count=1024 oflag=direct
    

    Capture_d_e_cran_2015-05-17_a__15.02.34.png

    Résultat :

    Capture_d_e_cran_2015-05-17_a__15.03.51.png

    Les performances restent les mêmes, certainement limitées par la virtualisation ou simplement physiquement, cependant la charge étant répartie sur 4 disques, l'occupation ne dépasse pas les 65%, ce qui permet d'avoir d'autres processus en parallèles.

    L'ajout de disques reste donc très intéressante pour pouvoir encaisser une forte charge d'IO/s .

    Bon tests ,

    Ch.

    Restez informé

    inscrivez vous à la newsletter pour recevoir les nouveaux billets par mail. ( formulaire en haut à droite )

  • Sunday 28 June 2015 - 11:33

    logo.png Un petit truc tout bête bien pratique, à tel point que je ne comprends pas pourquoi ce n'est pas en place dans les fonctionnalités de base de prestashop.

    Le besoin

    Prestashop 1.6

    Lorsqu'un employé crée un compte utilisateur depuis le backoffice de prestashop, aucun mail n'est envoyé au client final ( le mail de bienvenue contenant ses identifiants pour se connecter ), de plus l'employé doit entrer lui-même un mot de passe, ce qui n'est pas des plus pratique, surtout lorsque l'employé est en manque d'imagination, on peut se retrouver alors dans une problématique de sécurité non négligeable.

    La solution

    Nous allons simplement permettre à l'employé de laisser le champs "passwd" vide, il sera alors automatiquement généré de façon aléatoire.
    Enfin, nous allons ajouter un bouton "On/Off" dans le formulaire de création de compte qui va permettre à l'employé de décider si oui ou non les identifiants seront envoyés au client.

    Le code

    Bien commençons par créer la classe de surcharge

    override/controllers/admin/AdminCustomersController.php
    

    Nous allons donc surcharger les méthodes suivantes :

    1. processAdd()
    2. renderForm()

    Et créer une fonction

    1. sendConfirmationMail()

    Voici donc le code complet de notre fichier AdminCustomersController.php

    <?php
    /*
    *  05-2015
    *
    *  @author Christophe De Saint Leger 
    *  @Description Surcharge Formulaire création compte depuis le BackEnd
    */
    class AdminCustomersController extends AdminCustomersControllerCore
    {
    
    
            public function processAdd()
            {
                if (Tools::getValue('submitFormAjax'))
                    $this->redirect_after = false;
                // Check that the new email is not already in use
                $customer_email = strval(Tools::getValue('email'));
                $customer = new Customer();
                if (Validate::isEmail($customer_email))
                    $customer->getByEmail($customer_email);
                if ($customer->id)
                {
                    $this->errors[] = Tools::displayError('An account already exists for this email address:').' '.$customer_email;
                    $this->display = 'edit';
                    return $customer;
                }
                elseif (trim(Tools::getValue('passwd')) == '')
                {
                    $_POST['passwd'] = Tools::passwdGen();
                }
                if ($customer = parent::processAdd())
                {
                    $this->context->smarty->assign('new_customer', $customer);
                    if( Tools::getValue('sendWelcomeEmail') ){
                        $this->sendConfirmationMail($customer);
                    }
                    return $customer;
                }
                return false;
            }
    
    
    
    
            public function renderForm()
            {
    
                if (!($obj = $this->loadObject(true)))
                    return;
                
                $genders = Gender::getGenders();
                $list_genders = array();
                foreach ($genders as $key => $gender)
                {
                    $list_genders[$key]['id'] = 'gender_'.$gender->id;
                    $list_genders[$key]['value'] = $gender->id;
                    $list_genders[$key]['label'] = $gender->name;
                }
    
                $years = Tools::dateYears();
                $months = Tools::dateMonths();
                $days = Tools::dateDays();
    
                $groups = Group::getGroups($this->default_form_language, true);
                $this->fields_form = array(
                    'legend' => array(
                        'title' => $this->l('Customer'),
                        'icon' => 'icon-user'
                    ),
                    'input' => array(
                        array(
                            'type' => 'radio',
                            'label' => $this->l('Social title'),
                            'name' => 'id_gender',
                            'required' => false,
                            'class' => 't',
                            'values' => $list_genders
                        ),
                        array(
                            'type' => 'text',
                            'label' => $this->l('First name'),
                            'name' => 'firstname',
                            'required' => true,
                            'col' => '4',
                            'hint' => $this->l('Invalid characters:').' 0-9!&lt;&gt;,;?=+()@#"°{}_$%:'
                        ),
                        array(
                            'type' => 'text',
                            'label' => $this->l('Last name'),
                            'name' => 'lastname',
                            'required' => true,
                            'col' => '4',
                            'hint' => $this->l('Invalid characters:').' 0-9!&lt;&gt;,;?=+()@#"°{}_$%:'
                        ),
                        array(
                            'type' => 'text',
                            'prefix' => '<i class="icon-envelope-o"></i>',
                            'label' => $this->l('Email address'),
                            'name' => 'email',
                            'col' => '4',
                            'required' => true,
                            'autocomplete' => false
                        ),
                        array(
                            'type' => 'password',
                            'label' => $this->l('Password'),
                            'name' => 'passwd',
                            'required' => ($obj->id ? false : true),
                            'col' => '4',
                            'hint' => ($obj->id ? $this->l('Leave this field blank if there\'s no change.') :
                                sprintf($this->l('Password should be at least %s characters long. or void for automatic generation'), Validate::PASSWORD_LENGTH))
                        ), 
                        array(
                            'type' => 'switch',
                            'label' => $this->l('Send Welcome Email'),
                            'name' => 'sendWelcomeEmail',
                            'required' => false,
                            'class' => 't',
                            'is_bool' => true,
                            'values' => array(
                                array(
                                    'id' => 'sendWelcomeEmail_on',
                                    'value' => 1,
                                    'label' => $this->l('Enabled')
                                ),
                                array(
                                    'id' => 'sendWelcomeEmail_off',
                                    'value' => 0,
                                    'label' => $this->l('Disabled')
                                )
                            ),
                            'hint' => $this->l('Send the credentials to the client')
                        ),
                        array(
                            'type' => 'birthday',
                            'label' => $this->l('Birthday'),
                            'name' => 'birthday',
                            'options' => array(
                                'days' => $days,
                                'months' => $months,
                                'years' => $years
                            )
                        ),
                        array(
                            'type' => 'switch',
                            'label' => $this->l('Enabled'),
                            'name' => 'active',
                            'required' => false,
                            'class' => 't',
                            'is_bool' => true,
                            'values' => array(
                                array(
                                    'id' => 'active_on',
                                    'value' => 1,
                                    'label' => $this->l('Enabled')
                                ),
                                array(
                                    'id' => 'active_off',
                                    'value' => 0,
                                    'label' => $this->l('Disabled')
                                )
                            ),
                            'hint' => $this->l('Enable or disable customer login.')
                        ),
                        array(
                            'type' => 'switch',
                            'label' => $this->l('Newsletter'),
                            'name' => 'newsletter',
                            'required' => false,
                            'class' => 't',
                            'is_bool' => true,
                            'values' => array(
                                array(
                                    'id' => 'newsletter_on',
                                    'value' => 1,
                                    'label' => $this->l('Enabled')
                                ),
                                array(
                                    'id' => 'newsletter_off',
                                    'value' => 0,
                                    'label' => $this->l('Disabled')
                                )
                            ),
                            'disabled' =>  (bool)!Configuration::get('PS_CUSTOMER_NWSL'),
                            'hint' => $this->l('This customer will receive your newsletter via email.')
                        ),
                        array(
                            'type' => 'switch',
                            'label' => $this->l('Opt-in'),
                            'name' => 'optin',
                            'required' => false,
                            'class' => 't',
                            'is_bool' => true,
                            'values' => array(
                                array(
                                    'id' => 'optin_on',
                                    'value' => 1,
                                    'label' => $this->l('Enabled')
                                ),
                                array(
                                    'id' => 'optin_off',
                                    'value' => 0,
                                    'label' => $this->l('Disabled')
                                )
                            ),
                            'disabled' =>  (bool)!Configuration::get('PS_CUSTOMER_OPTIN'),
                            'hint' => $this->l('This customer will receive your ads via email.')
                        ),
                    )
                );
                
                // if we add a customer via fancybox (ajax), it's a customer and he doesn't need to be added to the visitor and guest groups
                if (Tools::isSubmit('addcustomer') && Tools::isSubmit('submitFormAjax'))
                {
                    $visitor_group = Configuration::get('PS_UNIDENTIFIED_GROUP');
                    $guest_group = Configuration::get('PS_GUEST_GROUP');
                    foreach ($groups as $key => $g)
                        if (in_array($g['id_group'], array($visitor_group, $guest_group)))
                            unset($groups[$key]);
                }
    
                $this->fields_form['input'] = array_merge(
                    $this->fields_form['input'],
                    array(
                        array(
                            'type' => 'group',
                            'label' => $this->l('Group access'),
                            'name' => 'groupBox',
                            'values' => $groups,
                            'required' => true,
                            'col' => '6',
                            'hint' => $this->l('Select all the groups that you would like to apply to this customer.')
                        ),
                        array(
                            'type' => 'select',
                            'label' => $this->l('Default customer group'),
                            'name' => 'id_default_group',
                            'options' => array(
                                'query' => $groups,
                                'id' => 'id_group',
                                'name' => 'name'
                            ),
                            'col' => '4',
                            'hint' => array(
                                $this->l('This group will be the user\'s default group.'),
                                $this->l('Only the discount for the selected group will be applied to this customer.')
                            )
                        )
                    )
                );
    
                // if customer is a guest customer, password hasn't to be there
                if ($obj->id && ($obj->is_guest && $obj->id_default_group == Configuration::get('PS_GUEST_GROUP')))
                {
                    foreach ($this->fields_form['input'] as $k => $field)
                        if ($field['type'] == 'password')
                            array_splice($this->fields_form['input'], $k, 1);
                }
    
                if (Configuration::get('PS_B2B_ENABLE'))
                {
                    $risks = Risk::getRisks();
    
                    $list_risks = array();
                    foreach ($risks as $key => $risk)
                    {
                        $list_risks[$key]['id_risk'] = (int)$risk->id;
                        $list_risks[$key]['name'] = $risk->name;
                    }
    
                    $this->fields_form['input'][] = array(
                        'type' => 'text',
                        'label' => $this->l('Company'),
                        'name' => 'company'
                    );
                    $this->fields_form['input'][] = array(
                        'type' => 'text',
                        'label' => $this->l('SIRET'),
                        'name' => 'siret'
                    );
                    $this->fields_form['input'][] = array(
                        'type' => 'text',
                        'label' => $this->l('APE'),
                        'name' => 'ape'
                    );
                    $this->fields_form['input'][] = array(
                        'type' => 'text',
                        'label' => $this->l('Website'),
                        'name' => 'website'
                    );
                    $this->fields_form['input'][] = array(
                        'type' => 'text',
                        'label' => $this->l('Allowed outstanding amount'),
                        'name' => 'outstanding_allow_amount',
                        'hint' => $this->l('Valid characters:').' 0-9',
                        'suffix' => $this->context->currency->sign
                    );
                    $this->fields_form['input'][] = array(
                        'type' => 'text',
                        'label' => $this->l('Maximum number of payment days'),
                        'name' => 'max_payment_days',
                        'hint' => $this->l('Valid characters:').' 0-9'
                    );
                    $this->fields_form['input'][] = array(
                        'type' => 'select',
                        'label' => $this->l('Risk rating'),
                        'name' => 'id_risk',
                        'required' => false,
                        'class' => 't',
                        'options' => array(
                            'query' => $list_risks,
                            'id' => 'id_risk',
                            'name' => 'name'
                        ),
                    );
                }
    
                $this->fields_form['submit'] = array(
                    'title' => $this->l('Save'),
                );
    
                $birthday = explode('-', $this->getFieldValue($obj, 'birthday'));
    
                $this->fields_value = array(
                    'years' => $this->getFieldValue($obj, 'birthday') ? $birthday[0] : 0,
                    'months' => $this->getFieldValue($obj, 'birthday') ? $birthday[1] : 0,
                    'days' => $this->getFieldValue($obj, 'birthday') ? $birthday[2] : 0,
                );
    
                // Added values of object Group
                if (!Validate::isUnsignedId($obj->id))
                    $customer_groups = array();
                else
                    $customer_groups = $obj->getGroups();
                $customer_groups_ids = array();
                if (is_array($customer_groups))
                    foreach ($customer_groups as $customer_group)
                        $customer_groups_ids[] = $customer_group;
    
                // if empty $carrier_groups_ids : object creation : we set the default groups
                if (empty($customer_groups_ids))
                {
                    $preselected = array(Configuration::get('PS_UNIDENTIFIED_GROUP'), Configuration::get('PS_GUEST_GROUP'), Configuration::get('PS_CUSTOMER_GROUP'));
                    $customer_groups_ids = array_merge($customer_groups_ids, $preselected);
                }
    
                foreach ($groups as $group)
                    $this->fields_value['groupBox_'.$group['id_group']] =
                        Tools::getValue('groupBox_'.$group['id_group'], in_array($group['id_group'], $customer_groups_ids));
    
                return AdminController::renderForm();
            }
    
    
    
            /**
             * sendConfirmationMail
             * @param Customer $customer
             * @return bool
             */
            protected function sendConfirmationMail(Customer $customer)
            {
                if (!Configuration::get('PS_CUSTOMER_CREATION_EMAIL'))
                    return true;
    
                return Mail::Send(
                    $this->context->language->id,
                    'account',
                    Mail::l('Welcome!'),
                    array(
                        '{firstname}' => $customer->firstname,
                        '{lastname}' => $customer->lastname,
                        '{email}' => $customer->email,
                        '{passwd}' => Tools::getValue('passwd')),
                    $customer->email,
                    $customer->firstname.' '.$customer->lastname
                );
            }
    
    
    
    }
    

    Attention à ne pas oublier

    Pour toute nouvelle surcharge, il vous faut supprimer le fichier de cache suivant

    cache/class_index.php
    

    Résultat

    Et voilà le résultat au niveau du formulaire

    Capture_d_e_cran_2015-05-16_a__14.27.06.png

    Bonne journée,

    Ch.

    Restez informé

    inscrivez vous à la newsletter pour recevoir les nouveaux billets par mail. ( formulaire en haut à droite )

  • Sunday 28 June 2015 - 11:33

    Un nouveau billet pense bête ...

    A l'installation de Mysql selon la version ou la façon de l'installer, les Time Zone nommées ne sont pas toujours ajoutées dans la base de données mysql. Voilà la petite ligne de commande à exécuter (sous linux) pour y remédier ( les ajouter )

    mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u <utilisateur admin> mysql
    

    Et voilà .. vous pouvez utiliser les requête

    SET time_zone = timezonename;
    

    Restez informé

    inscrivez vous à la newsletter pour recevoir les nouveaux billets par mail. ( formulaire en haut à droite )

  • Sunday 28 June 2015 - 23:21

    Prestashop permet dans sa version actuelle (1.6) de surcharger à peu prés .. tout !
    Ce qui est vraiment pratique pour modeler votre site e-commerce comme vous l'entendez sans toucher au cœur du code. Ce qui vous permettra ( dans une certaine mesure ) de pouvoir mettre à jour prestashop sans devoir réécrire vos fonctionnalités spécifiques ou votre thème.

    Mais il y a les modules

    Une autre des force de cet outil e-commerce, ce son ses modules, très nombreux qui permettent d'ajouter des fonctionnalités de façon .. "plug and play", activer désactiver etc .. super !!

    Seulement dans la documentation, impossible de trouver le moyen de surcharger un module existant.( je parle de la surcharge de la fonctionnalité apportée par ce module, les templates associés sont surchargeable de façon classique )
    jusqu'à maintenant, si vous souhaitiez changer le fonctionnement d'un module, vous aviez le choix entre :

    1. Toucher directement au code du module ( prochaine mise à jour .. et hop ... vous devez recommencer )
    2. Copier le module pour le dupliquer à votre sauce ( Assez lourd )

    La signature à utiliser

    Et bien figurez vous que c'est possible .. si si ..

    Note : uniquement à partir de la v1.6.0.11 ( merci à ChDUP pour cette précision )
    Pour surcharger une classe, vous utilisez l'écriture suivante :

    class [className sans Core] extends [className]Core
    

    dans le répertoire override/ ou modules/monModule/override/

    Et bien pour surcharger le code d'un module, il vous faut utiliser l'écriture suivante

    class [classNameModule]Override extends ClassNameModule
    

    Il suffit donc d'utiliser la chaine "Override" dans le nom de votre classe qui va surcharger le module souhaité .
    Par contre, il faut placer votre code dans le répertoire

    override/modules/moduleName/

    L'exemple qui va bien

    Prenons le cas du module BlockPaymentLogo disponible par défaut, qui permet d'afficher les logis de paiement sur une colonne de votre home.
    Moi je souhaite les afficher .. dans le pied de page "Footer", ce module n'a pas prévu ce cas, est n'est donc pas enregistré au bon Hook ( displayFooter ) . Bref impossible de le faire en natif .

    Surchargeons

    Nous allons commencer par créer le répertoire du même nom que celui d'origine

    override/modules/blockpaymentlogo/
    

    Puis créer le code de surcharge que voici.

    if (!defined('_PS_VERSION_'))
        exit;
    
    class BlockPaymentLogoOverride extends BlockPaymentLogo
    {
    
    
        public function install(){
    
            if( parent::install() ){
                return $this->registerHook('displayFooter');
            }else{
                return false;
            }
    
        }
    
        public function hookDisplayFooter($params)
        {
            return $this->hookLeftColumn($params);
        }
    
    }
    

    Petites explications,

    • on commence par déclarer notre classe comme il se doit ( en ajoutant Override, et en héritant de la classe du module d'origine )
    • Puis j'ajoute dans le constructeur l'association du module dans le hook displayFooter
    • Et enfin, je définis le code spécifique à ce hook ( ici le code sera le même que sur le hook leftcolumn, donc je ne réécris pas le code j'appelle celui du hook leftcolumn )

    Ne reste plus qu'à réinitialiser le module .. et voilà les logos qui apparaissent dans mon footer .

    Restez informé

    inscrivez vous à la newsletter pour recevoir les nouveaux billets par mail. ( formulaire en haut à droite )

  • Tuesday 21 July 2015 - 19:20

    Je vois souvent passer des questions sur le forums ou autres fils de discussion concernant la mise en production d'un projet Django.
    Ici je vais présenter une façon de faire, utilisant Nginx Gunicorn et Supervisord .

    Prérequis

    Sur une Débian toute fraiche,

    Nginx

    sudo apt-get install nginx
    

    Virtualenv

    Car nous travaillons toujours avec un environnement virtuel, dans lequel nous allons y installer les dépendances de notre projet.
    Ce qui permet de ne pas avoir de conflit de version avec d'autres éventuels projets déjà en prod.

    pip install virtualenv virtualenvwrapper
    

    Note : Si vous n'avez pas l'outil pip de disponible, installez-le comme ceci

    sudo apt-get install python-setuptools
    sudo easy_install pip
    

    Puis dans votre fichier ~/.bashrc , ajoutez les lignes suivantes, pour l'autocompletion

    export VIRTUALENVWRAPPER_VIRTUALENV_ARGS='--no-site-package'
    export WORKON_HOME=$HOME/.virtualenvs
    source /usr/local/bin/virtualenvwrapper.sh
    

    Gunicorn

    Pourquoi pas utiliser uWSGI ? humm ... car je préfère Gunicorn qui me donne toute satisfaction sur mes projets en prod. donc ... pourquoi pas !

    sudo apt-get install libevent-dev
    pip install gunicorn
    

    Supervisor

    Installation simple :

    sudo pip install supervisor
    

    Récupération du fichier de conf par défaut

    echo_supervisord_conf > /etc/supervisord.conf
    

    Dans le fichier de conf, afin de pouvoir manager votre projet via l'interface web, il faut dé-commenter et paramétrer les lignes suivantes

    [inet_http_server]         ; inet (TCP) server disabled by default
    port=*:9001        ; (ip_address:port specifier, *:port for all iface)
    username=user              ; (default is no username (open server))
    password=123               ; (default is no password (open server))
    

    Note: Changer le username et password évidemment

    L'interface web sera disponible à l'adresse : http://ipduserveur:9001

    Pour le lancement automatique au démarrage du système, vous pouvez utiliser ces scripts d'init:

    1. debian init supervisor
    2. debian init supervisor

    Installer votre projet

    Notre environnement est prêt, nous allons commencer par installer le projet Django dans l’environnement virtuel.

    Création du virtualenv

    mkvirtualenv monprojet
    

    Si vous utilisez git, cloner votre projet, sinon, copier le à l'endroit que vous souhaitez, ( pour l'exemple ce sera /var/www/monprojet )

    Installation des dépendances du projet

    si vous utilisez également les environnements virtuels pour développer ( ce que je conseille ) vous pouvez alors enregistrer la liste des libs python installées dans cette environnement, afin de pouvoir également les installer ( avec la même version ) sur un autre serveur.

    Pour avoir la liste des libs :

    pip freeze > requirements.txt
    

    puis sur votre serveur, pour installer les libs depuis un export freeze :

    pip install -r requirements.txt
    

    Configuration de Django

    Il vous faudra peut-être toucher un peu à votre fichier settings.py pour le passer du mode debug au mode production.
    Personnellement j'utilise une autre subtilité qui me permet de ne pas avoir à toucher au fichier settings.py ( j'expliquerai celà dans un autre billet ) .

    N'oubliez pas de vérifier le paramètre STATIC_ROOT

    STATIC_ROOT = '/var/www/static_monprojet/'
    

    C'est le répertoire ou seront copiés les fichiers statiques du projet, pour ensuite être servis par Nginx

    Ce répertoire DOIT EXISTER

    Une fois le répertoire créé, nous allons y "placer/lier" les fichiers statiques du projet

    python manage.py collectstatic --link
    

    Perso je préfère y mettre des liens symboliques .. ( les gouts et les couleurs ... )

    Liaison du projet avec Gunicorn

    Nous allons créer un fichier dans notre projet qui sera utilisé pour le lancement du/des process Gunicorn ( vous devrez adapter les valeurs dans ce script )

    vim /var/www/monprojet/monprojet/gunicorn.sh
    

    Et voici le contenu

    #!/bin/bash
      set -e
      NUM_WORKERS=2
      USER=www-data
      GROUP=www-data
      ADDRESS=127.0.0.1:5002
      cd /var/www/monprojet
      source /home/monuser/.virtualenvs/monprojet/bin/activate
      exec gunicorn monprojet.wsgi:application -w $NUM_WORKERS --bind=$ADDRESS \
        --user=$USER --group=$GROUP --log-level=debug
    

    Puis on le rend exécutable

    chmod 777 /var/www/monprojet/monprojet/gunicorn.sh
    

    Configuration Supervisord

    Notre projet est prêt, afin de lancer Gunicorn automatiquement, nous allons utiliser supervisord, qui en plus de s'occuper de démarrer le projet automatiquement au démarrage du système, va aussi le relancer en cas de crash, gérer les logs et vous donner la main pour arrêter ré-demarrer les process, via l'interface web ou en mode console.

    supervisord.png

    Ajoutez à la fin du fichier de configuration /etc/supervisord.conf les lignes suivantes ( paramètres à adapter selon votre cas )

    [program:guni_monprojet]
    directory=/var/www/monprojet/monprojet
    user = www-data
    autostart=true
    autorestart=true
    stdout_logfile=/var/log/monprojet.log
    redirect_stderr=true
    stopsignal=QUIT
    command = /var/www/monprojet/monprojet/monprojet.sh
    

    Ne reste plus qu'à redémarrer supervisor pour prendre en compte la nouvelle config.

    Pour le lancer manuellement

    sudo supervisord -c /etc/supervisord.conf
    

    Vhost Nginx

    Dernier point, la création du vhost de Nginx, pour diriger les requêtes vers Gunicorn qui écoute sur le port 5002 ( il est aussi possible d'utiliser un fichier socket à la place )

    vim /etc/nginx/sites-enabled/monprojet
    

    Et voilà le contenu ( fonctionnel, mais vous pouvez l'adapter )

    upstream us_monprojet {
            server 127.0.0.1:5002;
    }
    
    
    server {
            listen 80;
    
            root /var/www/monprojet;
    
            gzip             on;
            gzip_min_length  1000;
            gzip_proxied     expired no-cache no-store private auth;
            gzip_types       text/plain application/xml text/css text/javascript application/x-javascript application/x-shockwave-flash video/x-flv;
            gzip_disable     "MSIE [1-6]\.";
    
    
            server_name monprojet.com;
            charset utf-8;
    
            client_max_body_size 75M;
    
    
            location ~ /\.ht {
                deny  all;
            }
    
            location /favicon.ico {
                    alias /var/www/monprojet/monprojet/static/favicon.ico;
    
                    if (-f $request_filename) {
                            access_log off;
                            expires max;
                    }
    
            }
    
    
            location /media {
                    alias /var/www/monprojet/monprojet/media;
    
                    if (-f $request_filename) {
                            access_log off;
                            expires max;
                    }
    
            }
    
            location /static {
                    alias /var/www/static_monprojet/;
    
                    if (-f $request_filename) {
                            access_log off;
                            expires max;
                    }
    
            }
    location / {
    
                    if (-f $request_filename) {
                            access_log off;
                            expires max;
                    }       
    
                    #gunicornParams
                    if (!-f $request_filename) {
                            proxy_pass         http://us_monprojet;
                            break;
                    }       
                    proxy_redirect     off;
                    proxy_set_header   Host             $host;
                    proxy_set_header   X-Real-IP        $remote_addr;
                    proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
    
            }       
    }       
    
    

    Ne reste plus qu'à activer le vhost, et relancer nginx

    ln -s /etc/nginx/sites-available/monprojet /etc/nginx/sites-enabled/
    service nginx reload
    
  • Tuesday 21 July 2015 - 19:20