Articles de l'utilisateur

Par vincent59
Vincent Liefooghe

Retrieve Taxonomy Term by a custom field

Starting with Drupal7, once can add fields to taxonomy terms.

This can be interesting to add images or other informations. But what if you want to search a taxonomy term on this custom field in a module ?

Imagine we have a list of Music Categories, and we add a custom field, "music_code", that is used to interface with one other system.

Then we have 2 options :

In this article, I explain how to retreive a term ID based on a custom field on a Drupal taxonomy.

This example can be run on the command line for testing, no need for a module.

 

<?php
$_SERVER['REMOTE_ADDR']='localhost';
// drupal bootstrap
$drupal_directory = "/var/www/drupal";
chdir($drupal_directory);
define('DRUPAL_ROOT', getcwd());
require_once './includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);

// Retrieve Vocabulary ID (VID)
$vocabulary_name='categories';
$vocab = taxonomy_vocabulary_machine_name_load($vocabulary_name);

// bundle = Machine Name of the vocabulary
$query = new EntityFieldQuery();
$query
  ->entityCondition('entity_type', 'taxonomy_term')
  ->entityCondition('bundle', $vocabulary_name)
  ->propertyCondition('vid', $vocab->vid)
  ->fieldCondition('field_music_code', 'value', 'C042', '=');

$results = $query->execute();
if (!empty($results['taxonomy_term'])) {
  foreach($results['taxonomy_term'] as $tid) {
    $term = taxonomy_term_load($tid->tid);
    $name=$term->name;
    echo "Term ID = ",$term->tid," Name = ",$name,PHP_EOL;
    echo "Field Code Client Value = ",$term->field_music_code[LANGUAGE_NONE][0]['value'],PHP_EOL;
  }
}
else
{
  echo "No result for this code ! ",PHP_EOL;
}
?>

The first lines are here only to bootstrap Drupal when you are not in a module.

Then we define the Vocabulary ID, with taxonomy_vocabulary_machine_name_load function. This gives us the vocabulary ID (we suppose it is created).

Then we build the Entity Query, with several parameters :

  • Entity type : in our case, it is 'taxonomy_term' because we are dealing with taxonomies
  • Bundle : the machine name of our vocabulary
  • propertyCondition : the vocabulary ID we use to identify the terms (belonging to this vocabulary)
  • fieldCondition : the condition placed on the custom field.

In this example, the code is C042, and we search all terms (well, there is only one) that is using this code.

The result in our case is :

Term ID = 4 Name = Funk
Field Code Client Value = C042

 

Catégorie: 


Tag: 

Par vincent59
Vincent Liefooghe

Paramétrage fail2ban pour Drupal

Par défaut, Drupal permet de "bannir" des adresses IP manuellement, via la console d'administration.

Ceci est une première étape, mais peut vite devenir ingérable sur un site fréquenté. De plus, les requêtes http vont quand même arriver jusqu'au CMS, puisque c'est lui qui gère ces exclusions.

La solution dans ce cas est de coupler les logs Drupal avec fail2ban, qui s'occupe de scruter différents fichiers logs et de repérer, via des expressions régulières, des motifs qui permettront de bannir des adresses IP au niveau systèmes, via des règles iptables.

Pré-requis

Pour que fail2ban fonctionne avec Drupal, il faut activer le module syslog, et le paramétrer pour qu'il envoie ses logs dans un fichier séparé (voir à ce sujet http://www.vincentliefooghe.net/content/drupal-activer-les-traces-en-syslog).

Installation et paramétrage de fail2ban

Sur une distribution debian-like, simplement :

sudo apt-get install fail2ban

Les fichiers de configuration se trouvent dans /etc/fail2ban :

  • fail2ban.conf : fichier de configuration assez général, avec le fichier de logs de fail2ban, le niveau de logs et le socket unix
  • jail.conf : définition des "jails", c'est à dire les paramètres et les actions par défaut. Il est fortement recommandé d'en faire une copie dans un fichier jail.local pour éviter l'écrasement lors des mises à jour
  • filter.d : répertoire dans lequel se trouvent les définitions des filtres (regex)
  • action.d : répertoire dans lequel se trouvent les définitions des actions.

Dans le fichier jail.local, je vous recommande de renseigner la ligne

ignoreip = 127.0.0.1/8

et d'ajouter les adresses IP de vos PC clients ou serveurs, ce qui peut vous éviter un "auto-bannissement" en cas de manipulations douteuses sur le ssh, des accès à des scripts php, etc.

Paramétrage propre à Drupal

Pour bannier des adresses IP qui tenteraient des connexions, on ajoute un fichier de définition de règle dans le répertoire filter.d. Par exemple, drupal-user.conf, qui va contenir :

# filter to ban IP for Login attempt
[Definition]
failregex = \|user\|<HOST>\|.*\|Login attempt failed (.+)\.$
#failregex = \|user\|<HOST>\|.*spambot.Failed (.+)\.

ignoreregex =

Le filtre recherche les occurences de "Login attempt failed". La ligne est formatée avec *|user|adresse IP|*. Par exemple (cas réel) :

Sep 24 10:03:12 vps35701 drupal: http://www.vincentliefooghe.net|1411545792|user|89.66.70.109|http://www.vincentliefooghe.net/?q=user/login|http://www.vincentliefooghe.net/?q=user/login|0||Login attempt failed for Marcferozi.

Puis dans le fichier jail.local, on ajoute une nouvelle section :

#
# Drupal
#
[drupal-user]
enabled = true
port = http,https
protocol = tcp
filter = drupal-user
logpath = /var/log/drupal.log
maxretry = 3
# 1 hour
findtime = 3600
# 20 hours
bantime = 72000

On déclare donc à fail2ban qu'il doit utiliser le filtre drupal-user, sur le fichier de logs /var/log/drupal.log. Dans celui-ci, il cherche pendant une heure maximum 3 tentatives de connexions.

Si fail2ban détecte une "attaque", il le signale dans ses logs :

2014-09-23 00:41:56,025 fail2ban.actions: WARNING [drupal-user] Ban 89.66.70.109

On peut également voir les adresses IP bannies via iptables -L :

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
fail2ban-drupal-user  tcp  --  anywhere             anywhere             multiport dports http,https
fail2ban-ssh-ddos  tcp  --  anywhere             anywhere             multiport dports ssh
fail2ban-ssh  tcp  --  anywhere             anywhere             multiport dports ssh
ACCEPT     all  --  was59-4-88-175-36-21.fbx.proxad.net  anywhere            

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

Chain fail2ban-drupal-user (1 references)
target     prot opt source               destination         
DROP       all  --  qqa.1113460302.com   anywhere            
DROP       all  --  seo7.heilink.com     anywhere            
RETURN     all  --  anywhere             anywhere  

On peut également ajouter d'autres règles, si l'on a mis en place des modules de type honeypot ou botcha, pour bannier les spammeurs.

 

Catégorie: 


Tag: 

Par vincent59
Vincent Liefooghe

Influence du paramétrage APC sur les performances Drupal

Avec une version PHP inférieure à 5.5 il est recommandé d'utiliser un cache d'OpCode. Ce cache permet d'améliorer les performances de PHP, en mettant en cache le code PHP une fois qu'il a été analysé.

L'un des caches les plus utilisés est APC. Son installation sur une distribution type Debian consiste simplement en une commande

apt-get install php-apc

Par défaut, la taille mémoire réservée est de 32 Mo.

Si cette taille n'est pas optimale, les performances peuvent être dégradées au lieu d'être améliorées. En effet, on a alors un phénomène de saturation du cache, de fragmentation, et le code doit passer par des phases de check / miss / insert.

Pour nos tests, nous réalisons une première séance de tirs avec l'outil siege, pendant 2 minutes, sur une liste de 200 urls (les contenus ont été générés par le module Devel generate).

Résultats sans APC

Transactions:		         770 hits
Availability:		      100.00 %
Elapsed time:		      119.15 secs
Data transferred:	        3.97 MB
Response time:		        3.81 secs
Transaction rate:	        6.46 trans/sec
Throughput:		        0.03 MB/sec
Concurrency:		       24.63
Successful transactions:         766
Failed transactions:	           0
Longest transaction:	        4.65
Shortest transaction:	        0.55

Typiquement, sans Cache d'OpCode et sans cache Drupal, les performances sont assez faibles.

Résultats avec 16 Mo

Le résultat de la commande siege est :

Transactions:		        2928 hits
Availability:		      100.00 %
Elapsed time:		      119.51 secs
Data transferred:	       14.98 MB
Response time:		        1.02 secs
Transaction rate:	       24.50 trans/sec
Throughput:		        0.13 MB/sec
Concurrency:		       24.89
Successful transactions:        2917
Failed transactions:	           0
Longest transaction:	        2.78
Shortest transaction:	        0.55

Sur cette VM de test, on obtient 24 transactions par secondes, sans cache drupal activé. On constate que la taille mémoire utilisée par le cache est de 15 Mo environ. Par rapport à la configuration sans le cache APC, on augmente les performances d'un rapport 4 environ.

La consommation mémoire APC est la suivante :

Résultats avec 12 Mo

Transactions:		         784 hits
Availability:		       99.87 %
Elapsed time:		      119.73 secs
Data transferred:	        3.99 MB
Response time:		        3.75 secs
Transaction rate:	        6.55 trans/sec
Throughput:		        0.03 MB/sec
Concurrency:		       24.58
Successful transactions:         778
Failed transactions:	           1
Longest transaction:	        4.42
Shortest transaction:	        0.58

Dans ce cas, le paramétrage du cache APC n'est pas correct. En effet, la taille du cache n'est pas suffisante pour stocker toutes les opérations.
Du coup, les performances chutent drastiquement (environ 1/4 des performances précédentes), et l'on a des performances identiques au fonctionnement sans le cache APC.

On constate que le cache APC est saturé, et que l'on voit des "cache full count", qui signifient que le cache a été rempli et qu'il a fallu vider des éléments, ce qui est très pénalisant.

Résultats avec 24 Mo

Transactions:		        3104 hits
Availability:		      100.00 %
Elapsed time:		      119.67 secs
Data transferred:	       15.90 MB
Response time:		        0.96 secs
Transaction rate:	       25.94 trans/sec
Throughput:		        0.13 MB/sec
Concurrency:		       24.89
Successful transactions:        3085
Failed transactions:	           0
Longest transaction:	        1.53
Shortest transaction:	        0.48

Avec 24 Mo, les choses rentrent dans l'ordre. Le cache est correctement utilisé, mais il n'y a pas d'amélioration des performances.

Résultats avec 32 Mo

Transactions:		        2595 hits
Availability:		      100.00 %
Elapsed time:		      119.78 secs
Data transferred:	       13.48 MB
Response time:		        1.15 secs
Transaction rate:	       21.66 trans/sec
Throughput:		        0.11 MB/sec
Concurrency:		       24.87
Successful transactions:        2583
Failed transactions:	           0
Longest transaction:	        5.92
Shortest transaction:	        0.89

Conclusion

Cette série de tests a montré que le paramétrage du cache APC doit être régulièrement monitoré et validé, sous peine d'avoir un effet inverse à celui attendu et de dégrader les performances.

On peut également constater qu'il ne sert à rien de surallouer la mémoire, car une fois la taille optimale allouée, il n'y a aucun gain.

Cela prouve également que le choix de l'hébergeur est important, et que la richesse du CMS Drupal a également des impacts sur les performances de la plate-forme. Il convient donc de faire le bon choix. Un hébergement mutualisé ne pourra généralement pas offrir la souplesse d'un hébergeur spécialisé, capable de mettre en oeuvre les bons composants et les bons paramétrages.

Addendrum : impact du cache Drupal

Les tests ont été réalisés avec le cache désactivé. De ce fait, de nombreuses requêtes MySQL sont générées, et tendent à ralentir le débit.
Si on active le cache Drupal pour les utilisateurs anonymes, les résultats sont spectaculaires.

Un premier essai sans cache APC, mais avec le cache Drupal :

Transactions:		        3610 hits
Availability:		      100.00 %
Elapsed time:		      119.95 secs
Data transferred:	       18.70 MB
Response time:		        0.83 secs
Transaction rate:	       30.10 trans/sec
Throughput:		        0.16 MB/sec
Concurrency:		       24.91
Successful transactions:        3598
Failed transactions:	           0
Longest transaction:	        5.18
Shortest transaction:	        0.59

On constate une nette amélioration par rapport à un paramétrage sans cache (30 transactions / seconde vs 6.5). Au final, on est assez proche d'une configuration APC sans cache Drupal.

Deuxième essai avec le cache Drupal et le cache APC :

Transactions:		       23611 hits
Availability:		      100.00 %
Elapsed time:		      119.03 secs
Data transferred:	      122.00 MB
Response time:		        0.13 secs
Transaction rate:	      198.36 trans/sec
Throughput:		        1.02 MB/sec
Concurrency:		       24.93
Successful transactions:       23483
Failed transactions:	           0
Longest transaction:	        2.49
Shortest transaction:	        0.06

Lorsqu'on active les caches APC et Drupal, les performances sont multipliées par 30 !

N'oubliez donc pas, pour avoir de bonnes performances, d'activer tous les niveaux de cache possibles : au niveau Drupal, mais également au niveau PHP. Le choix d'un hébergeur qui supporte ces différents niveaux est un gage de performances. Mon hébergeur, HebInWeb.com, met en oeuvre APC sur les instances Drupal.

 

Catégorie: 


Tag: 

Par vincent59
Vincent Liefooghe

Interdire l'accès anonyme sur un site Drupal

Dans certains cas on peut vouloir interdire l'accès en mode anonyme à un site Drupal : site complètement privé, ou site en test.

Plusieurs étapes permettent de faire cela simplement :

  • configurer les permissions pour supprimer l'accès au contenu aux visiteurs
  • rediriger les erreurs 403 (accès refusé) vers la page de login
  • modifier la page de login (optionnel)

Configuration des permissions

Via l'écran de gestion des permissions, (admin/people/permissions), supprimer l'accès au contenu pour les visiteurs anonymes :

Redirection des accès refusés

Dans l'écran de configuration système (admin/config/system/site-information), mettre comme URL user/login pour les accès refusés (403).

Ce paramétrage suffit maintenant à rediriger tous les accès non authentifiés vers la page de connexion.

Modification de la page de login

Si on veut aller plus loin et anonymiser la page de connexion (ou au contraire la rendre plus belle), on peut également la modifier.
Pour cela, il faut ajouter un fichier page--user--login.tpl.php, qui peut par exemple contenir :

<!-- Template pour la page de login -->
<div id="page" class="container login-page <?php print $classes; ?>">
      <div id="content-column" class="login-content">
           <h1 id="page-title"> Please Log In </h1>
            <!-- region: Main Content -->
            <?php if ($content = render($page['content'])): ?>
              <div id="content">
                <?php print $content; ?>
              </div>
            <?php endif; ?>
      </div>
</div><!-- //End #page, .container -->

Note : si on veut conserver une partie des informations des pages "normales", on peut partir sur la base du fichier page.tpl.php.

Attention : il ne faut pas oublier de vider le cache pour que le nouveau modèle de page soit pris en compte.

 

Tags: 


Catégorie: 

Par vincent59
Vincent Liefooghe

VirtualHost Apache pour Drupal

Dans une installation "de base" Drupal, celui-ci embarque un fichier .htaccess qui définit les règles de réécriture d'URL et de sécurité, et autres paramétrage propre au serveur http Apache.Le souci principal des fichiers .htaccess étant, par contre, que le serveur http va vérifier à chaque fois sur disque (via la lecture du fichier) le contenu de ce fichier.Pour améliorer les performances, il est possible de ne pas laisser le serveur Apache lire les fichiers .htaccess, via la directive
AllowOverride None

Si on fait cela, on a alors 2 options :

  • soit mettre les directives du fichier .htaccess directement dans la déclaration du VirtualHost
  • soit inclure le fichier .htaccess dans la déclaration ; ainsi, il ne sera chargé qu'une seule fois, au démarrage du serveur http.

Un exemple de fichier est donné ci-dessous, en prenant pour hypothèse que Drupal est installé dans le répertoire /var/www/mondomaine.

<VirtualHost *:80>
   ServerAdmin     webmaster@hebinweb.com
   ServerName   www.mondomaine.com
   DocumentRoot /var/www/mondomaine

  <Directory /var/www/mondomaine>
    AllowOverride None
    Order allow,deny
    Allow from all

    Include /var/www/mondomaine/.htaccess
    Include /var/www/mondomaine/sites/default/files/.htaccess

  </Directory>

  ServerSignature Off

  ErrorLog /var/log/apache2/error.log
  LogLevel info
  CustomLog /var/log/apache2/access.log combined
</VirtualHost>

L'avantage de cette méthode est que, en cas de modification du fichier .htaccess (par exemple pour des raisons de sécurité lors d'un upgrade Drupal) les nouveautés seront prises en compte juste en redémarrant le serveur http.On profite donc du meilleur des 2 approches : l'utilisation du fichier .htaccess de Drupal, mais avec un chargement une fois pour toute au démarrage, donc avec un impact moindre sur les performances.

Catégorie: 


Tag: 

Par vincent59
Vincent Liefooghe

File_entity : image_dimensions does not exists

Suite à une alerte de sécurité Drupal (PSA-2014-001), j'ai procédé à la mise à jour du module Media 7.1.x, et de File_entity, qui est compris dans ce module.

Problème en retournant sur les pages, une belle erreur :

PDOException: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'mysite.image_dimensions' doesn't exist: SELECT * FROM {image_dimensions} id WHERE id.fid IN (:fids_0); Array ( [:fids_0] => 75 ) in file_entity_file_load() (line 225 of /home/mysite/domains/mysite.be/public_html/dev/sites/all/modules/media/file_entity/file_entity.module).

Identification du problème

Lorsqu'on vérifie dans la base de données, la table n'existe pas dans le schéma. Elle est pourtant créée dans le fichier .install de la nouvelle version de file_entity, qui fait partie du module Media 7.1.x. Dans l'update du module, elle porte la version 7101. On peut alors vérifier dans la base quel est le numéro de schéma référencé par Drupal. Par exemple avec une connexion MySQL ou MariaDB :

select name, type, schema_version from system where name = 'file_entity';
+-------------+--------+----------------+
| name        | type   | schema_version |
+-------------+--------+----------------+
| file_entity | module |           7215 |
+-------------+--------+----------------+
1 row in set (0.00 sec)

Dans le fichier file_entity.install, on trouve :

function file_entity_update_7101() 
{
...
}

Ce qui correspond à une version 7101,  inférieure à celle en base. Dans ce cas, le lancement de update.php ne va rien déclencher.

Solution

Forcer la version dans la table à une numéro inférieur à celui de l'update :

update system set schema_version = '7100' where name = 'file_entity';

On relance ensuite update.php et la table est alors créée.

Retour sur la cause du problème

Dans mon cas, la cause primaire était une première installation de Media 7.1 avec le module File Entity téléchargé séparément sur le site Drupal, qui est en version 7.2. En effet, dans la version Media 7.2, le module File entity n'est plus inclus.

Ce souci n'apparaît donc que dans un cas bien particulier : installation de Media 7.1 avec File_entity 7.2,(ce qui de toute manière pose des problèmes), puis désinstallation de file_entity 7.2, suppression du module, pour pouvoir utiliser le module inclus dans Media 7.1.

 

Catégorie: 


Tag: 

Par vincent59
Vincent Liefooghe

Créer un type de contenu et y attacher des champs

Il est possible via l'IHM de créer un nouveau type de contenu et d'y ajouter des champs. Tout ceci peut également se faire de manière programmatique, par un simple module.

Le module va comporter au moins 2 fichiers : le .info qui décrit le module, et le .module qui comprend le code.

Pour notre démonstration, nous allons créer un module qui permettra de créer des contenus qui seront repris par Views Slideshow par la suite.

Le fichier monslideshow.info est simple :

name = Mon Slideshow
description = [slideshow] Custom Slideshow based on Views Slideshow
package = Custom modules
core = 7.x
version = 7.x-dev-0.7
dependencies[] = views
dependencies[] = link
stylesheets[all][] = vcmslideshow.css

Le type de contenu et les champs seront créés dans le fichier monslideshow.install :

function monslideshow_install () {

// Add Node Type mon_carrousel if it does not exist
 if (!in_array('mon_carrousel', node_type_get_names())) {
    $type = array(
      'type' => 'mon_carrousel',
      'name' => st('Custom Carrousel Accueil'),
      'base' => 'node_content',
      'description' => st('Utiliser Custom Carrousel pour les articles mis en évidence.'),
      'custom' => 1,
      'modified' => 1,
      'locked' => 0 
  	);

    $type = node_type_set_defaults($type);
    node_type_save($type);
    node_add_body_field($type);

// Turn off comments
  variable_set('comment_mon_carrousel', '1');
  variable_set('node_submitted_mon_carrousel', 0);  
  }

// Ajout d'un champ de type Link (apporté par le module link)

  $field = array(
    'field_name' => 'field_lien',
    'type' => 'link_field',
    'cardinality' => 1,
    'locked' => FALSE,
    'indexes' => array(),
    'settings' => array(
        'attributes' => array(
          'class' => '',
          'rel' => '',
          'target' => 'default',
        ),
        'display' => array(
          'url_cutoff' => 80,
        ),
        'enable_tokens' => 1,
        'title' => 'optional',
        'title_maxlength' => 128,
        'title_value' => '',
        'url' => 0,    ),
    'storage' => array(
      'type' => 'field_sql_storage',
      'settings' => array(),
    ),
  );
  field_create_field($field);
 
// On attache ensuite au type de contenu mon_carrousel

  $instance = array (
    'field_name' => 'field_lien',
    'entity_type' => 'node',
    'label' => 'Lien',
    'bundle' => 'mon_carrousel',
    'description' => st('Lien vers la page.'),
    'required' => FALSE,
    'settings' => array(
        'attributes' => array(
          'class' => '',
          'configurable_title' => 0,
          'rel' => '',
          'target' => 'default',
          'title' => '',
        ),
        'display' => array(
          'url_cutoff' => '80',
        ),
        'enable_tokens' => 1,
        'title' => 'optional',
        'title_maxlength' => '128',
        'title_value' => '',
        'url' => 0,
        'user_register_form' => FALSE,
        'validate_url' => 1,
      ),
      'widget' => array(
        'active' => 0,
        'module' => 'link',
        'settings' => array(),
        'type' => 'link_field',
        'weight' => '32',
      )
      
  );
 
  field_create_instance($instance);



}

Catégorie: 


Tag: 

Par vincent59
Vincent Liefooghe

Développement de modules

L'une des grandes force de Drupal est d'être un mélange entre le CMS pur souche et la plate-forme de construction (CMF = Content Management Framework).

Il a été conçu de base pour être étendu, via ses mécanismes de <em>hooks</em>, et ses nombreuses API documentées (cf . http://api.drupal.org/api/drupal).

De plus il dispose d'une base importante de modules "contribués", qui permettent d'étendre les fonctionnalités. Mais parfois, les besoins d'un projet sont tellement spécifiques qu'il est nécessaire de développer un module. Dans ce cas, il est important, pour permettre une intégration optimale, de respecter certaines règles : utilisation des API (encore elles), des fonctions Drupal (notamment pour les accès à la base de données), et des bonnes pratiques de codage.

On peut se reposer sur une documentation bien fournie, que ce soit en ligne (https://drupal.org/documentation/develop), ou via des livres, par exemple chez Apress http://www.drupalbook.com/ ou Packt publishing : http://www.packtpub.com/drupal-7-module-development/book.

Depuis la V7, les Entities permettent d'ajouter des champs à tout contenu spécifique ce qui étend encore un peu plus les possibilités.

Bref, il y a pas mal de choses à faire...

Catégorie: 


Tag: 

Par vincent59
Vincent Liefooghe

Performances Drupal

Drupal est généralement basé sur une plate-forme LAMP (Linux / Apache / MySQL / PHP), même si on peut au final changer la majorité des couches, sauf le PHP.

Plusieurs options sont donc possibles pour améliorer les performances, qui peuvent parfois être calamiteuses sur un serveur mal dimensionné :

  • O.S. (méme si c'est souvent en dernier recours qu'on va faire le tuning à ce niveau)
  • Serveur http : Apache n'est pas le plus rapide, mais il est le plus courant
  • Base de données : l'optimisation de la base MySQL est l'un des gros facteurs de gains
  • PHP : le mode de fonctionnement et l'ajout d'un OpCode permettent de gagner encore.

 

Catégorie: 


Tag: 

Pages