Message d'avertissement

The subscription service is currently unavailable. Please try again later.

Planète

Par badgones

Drupal module Book et profondeur du sommaire

Quand on écrit un "Livre" via le module book de Drupal, on a une génération automatique du menu (sommaire), ou plûtot des enfants d'une page.
Or, on ne peut pas modifier la profondeur de cet affichage, qui affiche donc seulement les enfants directs de la page en cours, mais pas ses "petits-fils" (ou alors je suis preneur de la solution).

Voilà donc la solution pour modifier la profondeur du sommaire :
1- télécharger le module booktree (dispo en attachement aussi)
2- l'installer et l'activer
3- dans le fichier template.php de votre thème, ajouter le hook template_preprocess_book_navigation(&$variables), avec comme contenu :

function THEME_preprocess_book_navigation(&$variables) {
  $book_link = $variables['book_link'];

en lire plus

Par badgones

Drupal et groupe de champs multiples CCK, explication et correction d'erreur Wysiwyg - TinyMCE

Pour créer un groupe de champs multiple sous Drupal 6, la fonction n'est pas disponible de base avec le module CCK 2.
En fait, celle-ci a seulement été ajoutée dans CCK 3, qui est en béta, et non stable à l'heure actuelle. Mais on peut simplement l'ajouter dans CCK 2, en téléchargeant le module CCK3, en le décompressant, et en ajoutant le contenu du répertoire cck/modules/content_multigroup à CCK2, au même endroit.
Il faut en plus copier le répertoire cck/js du module CCK3 dans le répertoire du module CCK2, et ça fonctionne parfaitement.

Maintenant, vous avez un nouveau type de champs CCK "Multigroup", à utiliser comme un "Group", avec le même principe d'arborescence.

Une erreur est cependant visible : si vous utilisez un champ de type Wysiwyg dans le multigroup, le contenu de ce champ est supprimé lors du click sur "Add more values", ce qui est vraiment pénible. 
Pour ça, 1 solution qui m'a prit du temps :

en lire plus

Par pounard

How to fetch back Drupal 7 master password (uid 1)

Sometimes you lose your master password. Won't describe here how you managed to do it, since distraction that lead to that kind of errors is a burden to carry for all of us.

Drupal 6 was quite easy to recover, because its passwords were encrypted using the well known MD5 digest algorithm, while Drupal 7 uses SHA512 and a custom seed depending on the site. This statement makes us unable to use well known functions for that.

In order to recover your password, there are two easy steps.

Par badgones

Comment modifier le formulaire de recherche Drupal (Theme Drupal Search form)

Voila comment modifier le formulaire de recherche Drupal pour ajouter les CSS et le texte que l'on souhaite :

/**
* Override or insert PHPTemplate variables into the search_theme_form template.
*
* @param $vars
*   A sequential array of variables to pass to the theme template.
* @param $hook
*   The name of the theme function being called (not used in this case.)
*/
 
function montheme_preprocess_search_theme_form(&$vars, $hook) {
 
  // Modify elements of the search form
  $vars['form']['search_theme_form']['#title'] = t('Recherche sur mon site');
 
  // Set a default value for the search box
  $vars['form']['search_theme_form']['#value'] = t('Entrez ici votre recherche');
 
  // Add a custom class to the search box
  $vars['form']['search_theme_form']['#attributes'] = array('class' => t('customsearch'));

en lire plus

Par badgones

Solution pour l'erreur Captcha - Drupal : The answer you entered for the CAPTCHA was not correct.

Vous avez un problème avec le module Captcha, qui vous refuse à chaque fois les soumissions de vos modules custom?
Vous avez un message du genre "La réponse saisie pour le CAPTCHA est incorrecte.", ou "The answer you entered for the CAPTCHA was not correct." ?
Voici peut-être une solution qui règle le problème (en tout cas, qui a marché pour moi après de nombreuses heures de recherche) :

Il a "suffit" que je rajoute dans mon formulaire la valeur "#tree"=>0, en code cela donne :

$form['captcha'] = array(
  '#type' => 'captcha',
     '#weight' => 40,
     '#tree' =>0,  

en lire plus

Par haza
Nicolas Meyer

La premiere "initiative" de Drupal 8

Apres la sortie de Drupal 7, tout se met en place pour preparer la version 8.

Un des gros changements dans le cycle de développement qui va être mis en place pour Drupal 8 concerne l'apparition des "initiatives". On ne connait pas encore toutes les initiatives qui vont être mises en place, mais celles-ci concerneront sans doute les gros axes de Drupal 8 que Dries a évoqué lors de sa keynote.

La première initiative a été officiellement dévoilée le 28 mars dernier (oui, je sais, j'ai du retard) et concerne la gestion de la configuration de Drupal ("Configuration management" en anglais).
Le terme de "configuration management" peut sembler un peu abstrait pour beaucoup. Dans les faits, il s'agirait, dans l'idéal, d'une sorte de "Settings API" qui permetterait de pouvoir stocker toute la configuration de Drupal de manière plus "propre" qu'actuellement. En deux mots, avoir une sorte de fichier de configuration global.

Depuis features, on se raproche peu-à-peu de ce St Graal Drupalien, néanmoins, features ne couvre pas l'ensemble de nos besoins. Bien sûr, il est désormais facile d'exporter des views, des panels ... et même des variables si on passe via Strongarm. Mais il ne faut pas oublier que ceci n'est possible uniquement si l'auteur du module a pensé à integrer son code avec features.

Le vrai but du "Configuration management" est de proposer un mécanisme permettant de maintenir l'intégrité et la tracabilitée des changements de configuration dans la vie du site. Il serait par exemple possible de :

  • "Locker" la configuration d'un site en prod, afin qu'aucun changement ne puisse être effectuté.
  • Pouvoir "logguer" les changements de configuration entre le temps t et t+1, rendant alors possible le retour arrière si un soucis est detecté, ou bien encore rendant possible le fait de pouvoir "rejouer" ces changemements sur une autre instance du site

Ces changements ouvriront également la voie un meilleur contrôle du "content staging". Pouvoir fusionner facilement des changements effectués par les utilisateurs du site (les commentaires, les votes, ...) et la configuration d'une instance de dev par exemple, pour y apporter les mises à jour souhaitées, puis ensuite pouvoir reverser tout ceci vers la prod sans soucis majeur.

Comme Dries l'a evoqué lors de sa Keynote, des "Initiative Owner" sont nommé afin de suivre les avancements des ces projets.

Greg Dunlap
Greg Dunlap

C'est Greg Dunlap (heyrocker sur drupal.org) qui sera responsable de cette initiative.
Greg est déjà connu pour avoir énormément contribuer au module Deployment et a déjà beaucoup réfléchi à toutes les problématiques de staging et de déploiement de code.

Espérons qu'il saura réaliser ce que nous attendons tous.

David Strauss
David Strauss

Il sera épaulé par David Strauss, qui sera en charge du suivi de l'architecture et des API à mettre en place.

En attendant, n'oubliez pas de suivre les avancements et reflexions autour de ce projet sur le groups, http://groups.drupal.org/build-systems-change-management

Par juliendubreuil
julien dubreuil
Drupal since 2009

Créer un champ texte avec auto-complétion en utilisant la form Api

Créer un champ texte avec auto-complétion en utilisant la form Api

Voici un petit bout de code et quelques explications afin de créer un champ texte avec auto-complétion pour vos formulaire en utilisant la form api de Drupal.

Dans l’exemple suivant nous allons créer un formulaire avec un champ texte qui permettra de sélectionner un noeud parmi tous les contenus publiés sur le site. Pour pimenter l’exercice on permettra à notre utilisateur de saisir plusieurs valeurs, comme il est possible de faire dans un champ tag de taxonomie.

Étape 1 : Création de la fonction de récupération de informations

La première chose à faire consiste à créer une fonction de callback afin de récupérer les titres des nodes en base de données que l’on renverra par la suite au format json.


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
<span class='line'><span class="cm">/*</span>
</span><span class='line'><span class="cm">* Callback de récupération des titres des noeuds.</span>
</span><span class='line'><span class="cm">*/</span>
</span><span class='line'><span class="k">function</span> <span class="nf">mymodule_autocomplete_node_title</span><span class="p">(</span><span class="nv">$key</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="nv">$ret</span> <span class="o">=</span> <span class="k">array</span><span class="p">();</span>
</span><span class='line'>
</span><span class='line'>  <span class="c1">// L&#39;utilisateur insère une liste de tag séparés par des virgules et</span>
</span><span class='line'>  <span class="c1">// on ne traite que le dernier tag.</span>
</span><span class='line'>  <span class="nv">$keys</span> <span class="o">=</span> <span class="nx">drupal_explode_tags</span><span class="p">(</span><span class="nv">$key</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>  <span class="c1">// Récuperation du dernier tag en cour de saisie par l&#39;utilisateur.</span>
</span><span class='line'>  <span class="nv">$last_string</span> <span class="o">=</span> <span class="nx">trim</span><span class="p">(</span><span class="nb">array_pop</span><span class="p">(</span><span class="nv">$keys</span><span class="p">));</span>
</span><span class='line'>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">empty</span><span class="p">(</span><span class="nv">$last_string</span><span class="p">))</span> <span class="p">{</span>
</span><span class='line'>    <span class="nv">$sql</span> <span class="o">=</span> <span class="s2">&quot;SELECT DISTINCT (n.nid), n.title</span>
</span><span class='line'><span class="s2">           FROM {node} n</span>
</span><span class='line'><span class="s2">           WHERE status = 1</span>
</span><span class='line'><span class="s2">           AND UPPER(n.title) LIKE UPPER(&#39;%%%s%%&#39;)</span>
</span><span class='line'><span class="s2">           ORDER BY n.title ASC&quot;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>    <span class="nv">$query</span> <span class="o">=</span> <span class="nx">db_query_range</span><span class="p">(</span><span class="nv">$sql</span><span class="p">,</span> <span class="nv">$last_string</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>    <span class="c1">// Récupération des valeurs saisies précédemment.</span>
</span><span class='line'>    <span class="nv">$prefix</span> <span class="o">=</span> <span class="o">!</span><span class="k">empty</span><span class="p">(</span><span class="nv">$keys</span><span class="p">)</span> <span class="o">?</span> <span class="nb">implode</span><span class="p">(</span><span class="s1">&#39;, &#39;</span><span class="p">,</span> <span class="nv">$keys</span><span class="p">)</span> <span class="o">.</span> <span class="s1">&#39;, &#39;</span> <span class="o">:</span> <span class="s1">&#39;&#39;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>    <span class="c1">// Génération de la liste de résultats à renvoyer.</span>
</span><span class='line'>    <span class="k">while</span> <span class="p">(</span><span class="nv">$row</span> <span class="o">=</span> <span class="nx">db_fetch_array</span><span class="p">(</span><span class="nv">$query</span><span class="p">))</span> <span class="p">{</span>
</span><span class='line'>     <span class="nv">$ret</span><span class="p">[</span><span class="nv">$prefix</span> <span class="o">.</span> <span class="nv">$row</span><span class="p">[</span><span class="s1">&#39;nid&#39;</span><span class="p">]]</span> <span class="o">=</span> <span class="nx">check_plain</span><span class="p">(</span><span class="nv">$row</span><span class="p">[</span><span class="s1">&#39;title&#39;</span><span class="p">]);</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>    <span class="c1">// Transformation des résultats au format Json, suivi d&#39;un exit pour</span>
</span><span class='line'>    <span class="c1">// être sûr que l&#39;on ne renverra rien du thème.</span>
</span><span class='line'>    <span class="nx">drupal_json</span><span class="p">(</span><span class="nv">$ret</span><span class="p">);</span>
</span><span class='line'>    <span class="k">exit</span><span class="p">();</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span>

Lorsque vous faites des comparaisons entres des chaînes de caractères pensez à tout mettre en minuscule ou majuscule si vous ne voulez pas prendre en compte la casse, ça vous permettra d’être plus tolérant sur l’orthographe.
Il est préférable de limiter le jeu de résultats obtenus, d’une part parce que ça ne nous servirait à rien d’afficher une liste de 400 titres et d’autre part, cela consommerait plus de ressources de base de données. Ainsi pour limiter ma requête j’ai utilisé la fonction db_query_range() au lieu de mettre directement une limite dans ma requête.

Étape 2 : Ajout d’une nouvelle entrée dans le menu

Maintenant que nous avons crée la récupération des titres des noeuds, il nous faut ajouter une entrée de menu pour que les données renvoyées par la fonction soit accessible depuis une url.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<span class='line'><span class="sd">/**</span>
</span><span class='line'><span class="sd">* Implementation of hook_menu().</span>
</span><span class='line'><span class="sd">*</span>
</span><span class='line'><span class="sd">* @return An array of menu items.</span>
</span><span class='line'><span class="sd">*/</span>
</span><span class='line'><span class="k">function</span> <span class="nf">mymodule_menu</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>  <span class="nv">$items</span> <span class="o">=</span> <span class="k">array</span><span class="p">();</span>
</span><span class='line'>  <span class="c1">// Url de récupération des titres des nodes pour l&#39;auto-complétion.</span>
</span><span class='line'>  <span class="nv">$items</span><span class="p">[</span><span class="s1">&#39;mymodule/autocomplete/node_title&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span>
</span><span class='line'>    <span class="s1">&#39;page callback&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;mymodule_autocomplete_node_title&#39;</span><span class="p">,</span>
</span><span class='line'>    <span class="s1">&#39;access arguments&#39;</span> <span class="o">=&gt;</span> <span class="k">array</span><span class="p">(</span><span class="s1">&#39;access content&#39;</span><span class="p">),</span>
</span><span class='line'>    <span class="s1">&#39;type&#39;</span> <span class="o">=&gt;</span> <span class="nx">MENU_CALLBACK</span>
</span><span class='line'>  <span class="p">);</span>
</span><span class='line'>  <span class="k">return</span> <span class="nv">$items</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span>

N’oubliez pas qu’après chaque modification du menu il est nécessaire de flusher le cache :)
Pour tester si vous obtenez bien des résultats, rendez-vous sur l’url que vous avez créée dans le hook_menu suivi d’un / et d’un caractère pour simuler la frappe d’un utilisateur. Ce qui donne par exemple : mymodule/autocomplete/node_title/a

Étape 3 : Paramétrage du formulaire

La dernière étape, vous l’aurez deviné si vous avez tout suivi, c’est la création du formulaire.


1
2
3
4
5
6
7
8
9
10
11
12
13
<span class='line'><span class="cm">/*</span>
</span><span class='line'><span class="cm">* Formulaire custom de selection de nodes.</span>
</span><span class='line'><span class="cm">*/</span>
</span><span class='line'><span class="k">function</span> <span class="nf">mymodule_listing_nodes_form</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>  <span class="nv">$form</span> <span class="o">=</span> <span class="k">array</span><span class="p">();</span>
</span><span class='line'>  <span class="nv">$form</span><span class="p">[</span><span class="s1">&#39;nids&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span>
</span><span class='line'>    <span class="s1">&#39;#type&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;textfield&#39;</span><span class="p">,</span>
</span><span class='line'>    <span class="s1">&#39;#title&#39;</span> <span class="o">=&gt;</span> <span class="nx">t</span><span class="p">(</span><span class="s1">&#39;Sélectionnez vos contenus&#39;</span><span class="p">),</span>
</span><span class='line'>    <span class="c1">// Il est important de donner l&#39;url de récupération des titres sinon ça ne marche pas...</span>
</span><span class='line'>    <span class="s1">&#39;#autocomplete_path&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;mymodule/autocomplete/node_title&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="p">);</span>
</span><span class='line'>  <span class="k">return</span> <span class="nv">$form</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span>

Le secret réside dans le fait de configurer l’option #autocomplete_path du widget textfield en lui paramétrant une url et c’est tout!

Voilà, vous savez maintenant créer des champs textes avec auto-complétion et au final c’était pas bien compliqué! :)
Je suis sûr que vous avez déjà tout compris mais bon au cas où et histoire de rentabiliser l’espace disque de mon hébergement voici les sources de cet exemple (une grand première dans l’histoire de ce blog).

Bonus

Petite astuce pendant que l’on parle d’auto-complétion. Certains naviguateurs proposent d’utiliser les réponses que vous avez déjà saisies auparavant comme votre nom, prénom et autres, mais parfois vous ne voulez pas autoriser ça dans vos forms. Et bien pour désactiver ça il vous suffit de définir la complétion du champ à off et le tour est joué.


1
2
3
4
<span class='line'><span class="nv">$form</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span>
</span><span class='line'>  <span class="s1">&#39;#type&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;textfield&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="s1">&#39;#attributes&#39;</span> <span class="o">=&gt;</span> <span class="k">array</span><span class="p">(</span><span class="s1">&#39;autocomplete&#39;</span> <span class="o">=&gt;</span><span class="s1">&#39;off&#39;</span><span class="p">),</span>
</span><span class='line'><span class="p">);</span>
</span>

Par badgones

Site Drupal lent, comment l'optimiser ? Un début de réponse

Vous avez un site Drupal qui est lent? Vous voulez l'optimiser? Voici un début de réponses :

Modules à installer :

  • css_gzip (dernière version en document attaché)
  • javascript_aggregator (dernière version en document attaché)

Cache Drupal et config des 2 modules :

  • allez sur la page /admin/settings/performance, et activez le cache Drupal en mode "normal"
  • minimum cache lifetime => ce que vous voulez, si vous faites pas souvent de mise à jour de page, mettez 1 jour
  • page compression => "activé"
  • block cache => activé
  • optimize css => activé, et cochez gzip css
  • optimize et minified javascript => activé, cochez gzip javascript
  • sauvegardez

en lire plus

Par Artusamak
Julien Dubois

Rules : Ajouter programmatiquement des événements, actions ou conditions

Dans les épisodes précédents vous avez pu lire une première présentation de Rules, module très utile et avec qui vous allez devoir devenir meilleurs amis lorsque vous utiliserez Drupal Commerce.

Nous allons donc voir comment exposer vos propres événements, conditions et actions à Rules. Rules est très flexible et offre la possibilité par défaut de rajouter les briques dont vous pourriez avoir besoin pour vos développements.

Commençons par les événements qui déclenchent les rules. Nous allons déclencher un événement lorsqu’un type de contenu précis sera visualisé, nous pourrions le faire avec les composants de base de Rules mais cela permet d’avoir un exemple assez simple. N’hésitez pas à consulter la documentation rédigée par fago pour rules, elle est assez complète et à jour.

Pour déclarer un événement il suffit d’implementer le hook hook_rules_event_info() et de décrire tous vos événements avec leurs paramètres. Exemple :

Source code    
<?php
/**
* Implements hook_rules_event_info().
* Déclaration d'un événement basé sur le type du node vu.
*/
function monmodule_rules_event_info() {
  return array(
    'nodearticle_is_viewed' => array( // Nom de l'événement à invoquer via Rules.
      'label' => t("Un node de type article est entrain d'être vu"), // Description de la rule.
      'module' => 'Mon Module', // Groupe dans lequel faire apparaitre l'événement (node, utilisateur ou mon module).
      'arguments' => array( // Tableau des paramètres à passer à la fonction.
        'article' => array('type' => 'node', 'label' => t('Article visualisé.')), // Ici seulement le node.
      ),
    ),
  );
}
?>

Pour que l’implémentation de l’événement soit complète il ne reste plus qu’à signaler à rules que l’événement se produit. Pour cela il suffit d’appeler la fonction rules_invoke_event() au bon moment. Dans notre exemple il suffit de faire cela au sein du hook_nodeapi() si le node est affiché et qu’il a le type article.

/**
* Implements hook_nodeapi().
*/
function monmodule_nodeapi(&$node, $op) {
  if ($op == 'view' && $node->type == 'article') {
    rules_invoke_event('nodearticle_is_viewed', $node); // Invocation de l'événement.
  }
}

Regardons maintenant comment déclarer de nouvelles conditions, l’exemple ici consiste à déterminer si le node que nous sommes entrain de visualiser possède un champ CCK précis (de type nodereférence )avec une valeur.

Cette fois ci il faut implémenter le hook hook_rules_condition_info() pour arriver à nos fins. Comme d’habitude un exemple est plus parlant.

Source code    
/**
 * Implements hook_rules_condition_info().
 */
function monmodule_rules_condition_info() {
  return array(
    'monmodule_has_nodereference' => array(
      'label' => t('Mon node a un node référence.'),
      'arguments' => array(
        'node' => array('type' => 'node', 'label' => t('Node à vérifier')),
      ),
      'module' => 'User',
    ),
  );
}

Comme pour la déclaration d’un événement, les conditions font appel à une fonction de callback, ici monmodule_has_nodereference. Dans cette fonction nous allons implémenter concrètement effectuer le test de la condition à évaluer.

Source code    
function monmodule_has_reference($node, $settings) {
  if (isset($node->field_book)) {
    if (!is_null($node->field_book[0]['nid'])) {
      return TRUE;
    }
  }
  return FALSE;
}

Ici nous évaluons la valeur d’un éventuel node reference pour le champ field_book mais le nom de ce champ pourrait être exposé dans la configuration de la condition, cette valeur se retrouverait dans le paramètre $settings. Pour voir comment configurer les conditions et actions, reportez à la documentation.

Une fois que les événements et conditions sont là, il ne reste qu’une chose à faire, écrire le code qui va être exécuté une fois notre évènement déclenché et notre condition remplie. Vous l’avez compris cette fois il faut exposer cette action via le hook hook_rules_action_info().

Source code    
/**
 * Implements hook_rules_action_info().
 */
function monmodule_rules_action_info() {
  return array(
    'monmodule_domyaction' => array(
      'label' => t('Effectuer mon traitement'),
      'arguments' => array(
        'node' => array('type' => 'node', 'label' => t('Node exposé')),
      ),
      'module' => 'Mon Module',
    ),
  );
}

Et toujours sur le même principe, la fonction de callback que vous avez déclaré est appelée lorsque la rule est déclenchée et satisfait les conditions configurée.

Source code    
function monmodule_domyaction(&$node) {
  drupal_set_message(t("Ce noeud est de type article et possède au moins un node comme node reference dans le champ field_book"));
  // Effectuer tous les traitements imaginables.
}

Etendre Rules 1 est donc quelque chose de très simple, tout se passe principalement dans ces trois hooks et grâce à eux vous pouvez étendre les possibilités de Rules à l’infini. N’hésitez pas à consulter la documentation pour découvrir d’autres fonctionnalités offertes par Rules.

Rules 2 (Drupal 7)  permet d’utiliser Rules comme une API de façon beaucoup plus simple,  cette nouvelle version explore très facilement les types d’entités et si vous installé entity metadata, vous pourrez également exploiter la puissance des informations additionnelles mises à disposition (accès à toutes les données d’un type d’entity).

Par juliendubreuil
julien dubreuil
Drupal since 2009

Générer ses backups de base de données automatiquement avec Drush

Générer ses backups de base de données automatiquement avec Drush

Combien de fois vous vous êtes dit, “si seulement j’avais pris le temps de faire un backup”, “si seulement ma dernière sauvegarde ne remontait pas si loin” ? Oui comme moi il y a un jour ou vous l’avez vraiment regretté… Avec ce qui suit, vous n’aurez plus d’excuses et vous pourrez dormir sur vos deux oreilles (enfin si votre serveur est sous Linux, sinon je ne peux rien pour vous).

Aujourd’hui c’est un donc un énième billet sur Drush, ou du moins sur la commande sql-dump et ce afin de créer des sauvegardes automatiques de votre base de données. Je vous l’avais dit, une fois qu’on a goûté à Drush il devient difficile de s’en passer.

Au cas où vous ne découvriez Drush que maintenant voici quelques sujets qui pourraient vous intéresser, à savoir, comment installer drush et comment créer des alias afin de vous faciliter la vie dans la gestion de vos sites.

La commande Magique

Pas de Drush sans commande, du coup nous allons utiliser la commande drush sql-dump. Pour plus d’infos sur cette commande utilisez drush help sql-dump. Si vous utilisez la commande telle quelle, votre dump vous sera affiché dans votre terminal et comme nous, on aimerait bien garder ça dans un fichier, on va ajouter quelques options à notre commande.


1
<span class='line'><span class="nv">$ </span>drush @sandbox sql-dump --result-file --gzip
</span>

  • result-file permet de sauvegarder le dump dans un fichier. Par défaut la sauvegarde sera faite dans le répertoire ~/.drush-backups sauf si vous précisez votre propre chemin.
  • gzip sert à compresser le dump de votre base de donnée au format gzip.

Exclure les informations que l’on ne veut pas

La commande sql-dump va faire une sauvegarde complète de la base or nous n’avons pas besoin de récupérer les informations des tables de caches par exemple. Pour spécifier des tables à ne pas sauvegarder ou à exclure il suffit de modifier le fichier drushrc.php. Petit rappel sur ce fichier, vous le trouverez par défaut dans le répertoire examples de votre installation de drush, regardez les premières lignes du fichier pour savoir où vous pourrez le placer.

Dans ce fichier on va pouvoir définir les tables dont on ne veut pas les données (ex: tables de caches) et les tables dont on ne veut même pas la structure (ex: tables maisons, tables de migrations). Pour cela il vous suffira de remplir le tableau $options[‘structure-tables’] ou $options[‘skip-tables’].


1
2
3
4
5
6
7
8
9
<span class='line'><span class="cm">/*</span>
</span><span class='line'><span class="cm"> * Customize this associative array with your own tables. This is the list of</span>
</span><span class='line'><span class="cm"> * tables whose *data* is skipped by the &#39;sql-dump&#39; and &#39;sql-sync&#39; commands when</span>
</span><span class='line'><span class="cm"> * a structure-tables-key is provided. You may add new tables to the existing</span>
</span><span class='line'><span class="cm"> * array or add a new element.</span>
</span><span class='line'><span class="cm"> */</span>
</span><span class='line'><span class="nv">$options</span><span class="p">[</span><span class="s1">&#39;structure-tables&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span>
</span><span class='line'>  <span class="s1">&#39;common&#39;</span> <span class="o">=&gt;</span> <span class="k">array</span><span class="p">(</span><span class="s1">&#39;cache&#39;</span><span class="p">,</span> <span class="s1">&#39;cache_filter&#39;</span><span class="p">,</span> <span class="s1">&#39;cache_menu&#39;</span><span class="p">,</span> <span class="s1">&#39;cache_page&#39;</span><span class="p">,</span> <span class="s1">&#39;history&#39;</span><span class="p">,</span> <span class="s1">&#39;sessions&#39;</span><span class="p">,</span> <span class="s1">&#39;watchdog&#39;</span><span class="p">),</span>
</span><span class='line'><span class="p">);</span>
</span>

Maintenant qu’on a spécifié les choses inutiles à sauvegarder il ne nous reste plus qu’à le renseigner dans notre commande:


1
<span class='line'><span class="nv">$ </span>drush @sandbox sql-dump --result-file --gzip --structure-table-key<span class="o">=</span>common
</span>

Vous pouvez essayer cette commande pour vérifier que l’export fonctionne bien

Plannifier la sauvegarde

Il ne nous reste plus qu’à planifier l’exécution automatique de notre commande dans la crontab.

Pour visualiser ce qu’il y a dans la crontab il suffit d’éxecuter cette commande :


1
<span class='line'><span class="nv">$ </span>crontab -l
</span>

Pour modifier votre crontab exécutez celle-ci :


1
<span class='line'><span class="nv">$ </span>crontab -e
</span>

Voici un exemple qui va sauvegarder notre base tous les lundi à 4h15.


1
<span class='line'>04 15 * * 1 drush @sandbox sql-dump --result-file --gzip --structure-table-key<span class="o">=</span>common
</span>

C’est un exemple parmi tant d’autres à adapter en fonction de votre besoin. Pour plus d’infos sur la configuration de la crontab rendez-vous ici et si c’est encore trop compliqué à cette adresse.

Au final rien de bien compliqué, ça vous prendre plus de temps à lire cet article qu’à créer votre sauvegarde, mais maintenant vous n’avez plus d’excuse :)

Crédits Photo

Par haza
Nicolas Meyer

Martine et sa premiere entité

Martine écrit sa première entité

Disclaimer : Ce billet va être long... peut être trop long même. J'aurais bien pu le couper en 3 parties, histoire de poster un peu plus, mais techniquement il n'y aurait aucun intérêt a faire ca. Ce billet représente un "tout", qui n'a vraiment de sens qu'entier.

Deuxième point : il va y avoir du code. Du gros, du lourd qui tâche. Si vous n'aimez pas le code, vous pouvez arrêter de lire ici, je ne vous en voudrais pas ;).

Troisième point : Le code présenté ici n'est pas "parfait". Les commentaires ne sont pas a 100% "Drupal way of life" (sans compter qu'ils sont en français), et certaines fonctions pourraient être regroupés en une seule. La séparation est ici voulue dans le seul but de montrer l'action spécifique de chacune de celle-ci.

Ceci dis, que va-t-on trouver ici ? Je vais donc couvrir dans ce billet :

  • La création d’un type d’entité, pouvant recevoir des fields
  • La création, hardcodé, de deux bundles pour cette entité
  • La création/édition/suppression d’entités, avec affichage des éventuels fields pouvant y être attachés
  • Exposer les champs de la « base table » de l’entité dans Views

Par contre, on ne va pas couvrir ici :

  • La création à la volée de bundles (par soucis de simplicité)
  • L’utilisation du module contrib EntityAPI
  • Nous ne parlerons pas non plus de la démonstration du dernier théorème de Fermat

Aller, quand il faut se lancer, il faut ...

Notre première entité

Pour gérer sa bibliothèque, Martine souhaiterait utiliser Drupal pour réaliser une petite bibliothèque en ligne. Comme Martine est un peu geek sur les bords, elle va évidement partir sur un Drupal 7, et en plus, elle ne va pas utiliser un content type pour gérer ses livres mais elle va plutôt créer une nouvelle entité "books" spécifique à ses besoins.

Pour ce faire, elle va créer un nouveau module nommé "Books"

Pour commencer, Martine doit renseigner la structure de la base qu'elle va utiliser.

Rien de bien compliqué ici. C'est un hook_schema tout a fait classique, dans le .install du module.

function books_schema() {
  $schema['books'] = array(
    'fields' => array(
      'bid' => array(
        'type' => 'serial',
        'unsigned' => TRUE,
        'not null' => TRUE
      ),
      'type' => array(
        'description' => 'the bundle property',
        'type' => 'text',
        'size' => 'medium',
        'not null' => TRUE
      ),
      'name' => array(
        'type' => 'text',
        'size' => 'medium'
      )
    ),
    'primary key' => array('bid')
  );
  return $schema;
}

Passons aux choses sérieuses, dans le .module
Tout d’abord, Martine commence par utiliser le hook hook_entity_info(). Ce dernier va lui permettre de définir sa nouvelle entité.

Elle commence par y renseigner ce que nous allons appeler le « type » de l’entité.

 $entity['books'] = array (
    'label' => t('Book'),
    // la table definie dans books.install
    'base table' => 'books',
    'uri callback' => 'books_uri',
    // on peut y attacher des fields
    'fieldable' => TRUE,
    'entity keys' => array(
      // utilisé par entity_load pour requeter la base table
      'id' => 'bid' ,
      // utilisé par field_attach api pour charger les fields attachés
      'bundle' => 'type'
    ),
    'bundle keys' => array('bundle' => 'type'),
    'bundles' => array()
  );

Martine est quand même super sympa, elle a mis plein de commentaires pour essayer d'expliquer. Ca devrait suffire à la plupart des personnes.
Ce code va donc créer une entité dénommé "books", qui va se baser sur la table nommé "books" dans la BDD. Elle va pouvoir accepter de recevoir de nouveaux fields, et elle a pour nom de Bundle "type" (ca sera utile juste après).

Une entité, c'est beau. Avoir plusieurs bundle dedans, c'est encore mieux. Et comme Martine souhaite renseigner des informations aussi variables que roman ou des BD, il est bon de créer ces 2 bundles.
Pourquoi ne pas juste jouer sur les fields attaché ? On pourrait, mais si on réfléchie un peu, les livres vont partager des éléments commun. Par exemple, tout livre a un auteur, un titre, un nombre de page. Mais par contre, seuls les BD ont, potentiellement, un illustrateur. Une information permettant de savoir si la BD est en couleur ou non. Ces deux champs supplémentaires non rien a faire dans la base table books, car toutes les bundles (roman, BD, ...) ne vont pas les partager. D'où, stockage dans la base des informations de base, et utilisation de bundles qui porterons ces informations.
Pour des raisons de simplicité, Martine ne stockera ici que le Nom du livre. Et uniquement deux Bundles seront créé, directement dans le code, à savoir "Roman" et "BD".

Martine créer donc son Bundle "Roman", toujours dans le hook hook_entity_info().

 $entity['books']['bundles']['roman'] = array(
    'label' => t('Roman'),
    'admin' => array(
      // le chemin defini dans books_menu
      // Le Field api utilise ceci pour ajouter ses éléments MENU_LOCAL_TASK
      // qui servent a administer et afficher les fields
      'path' => 'admin/books/%book_type',
      // le "real path" une fois l'argument chargé
      'real path' => 'admin/books/roman',
      // %books_type, c'est arg(2), donc on le passe ...
      'bundle argument' => 2,
      // on reste en "administer nodes" juste pour ne pas s'embêter avec les permissions :)
      'access arguments' => array('administer nodes')
    )
  );

Les deux éléments important ici, le path et le real path.
Le "path" donne le chemin interne a Drupal a laquelle va répondre l'administration de notre entité, en y passant le "type" (comprendre le Bundle) de notre entité. Le "real path" donne l'url "user friendly" de cette même page.

Martine fait pareil pour sa deuxième entité.

 // BD bundle
  $entity['books']['bundles']['bd'] = array(
    'label' => t('Bande dessinée'),
    'admin' => array(
      'path' => 'admin/person/%book_type',
      'real path' => 'admin/books/bd',
      'bundle argument' => 2,
      'access arguments' => array('administer nodes')
    )
  );

Au final, le hook_entity_info() de Martine ressemble a ceci.

function books_entity_info() {
  $entity = array();
  $entity['books'] = array (
    'label' => t('Book'),
    // la table definie dans books.install
    'base table' => 'books',
    'uri callback' => 'books_uri',
    // on peut y attacher des fields
    'fieldable' => TRUE,
    'entity keys' => array(
      // utilisé par entity_load pour requeter la base table
      'id' => 'bid' ,
      // utilisé par field_attach api pour charger les fields attachés
      'bundle' => 'type'
    ),
    'bundle keys' => array('bundle' => 'type'),
    'bundles' => array()
  );
  // Roman bundle
  $entity['books']['bundles']['roman'] = array(
    'label' => t('Roman'),
    'admin' => array(
      // le chemin defini dans books_menu
      // Le Field api utilise ceci pour ajouter ses éléments MENU_LOCAL_TASK
      // qui servent a administer et afficher les fields
      'path' => 'admin/books/%book_type',
      // le "real path" une fois l'argument chargé
      'real path' => 'admin/books/roman',
      // %books_type, c'est arg(2), donc on le passe ...
      'bundle argument' => 2,
      // on reste en "administer nodes" juste pour ne pas s'embeter avec les permissions :)
      'access arguments' => array('administer nodes')
    )
  );
  // BD bundle
  $entity['books']['bundles']['bd'] = array(
    'label' => t('Bande dessinée'),
    'admin' => array(
      'path' => 'admin/person/%book_type',
      'real path' => 'admin/books/bd',
      'bundle argument' => 2,
      'access arguments' => array('administer nodes')
    )
  );
  return $entity;
}

Il ne faut pas oublier de renseigner le books_uri défini plus haut permettant de définir le chemin canonique de l'entité.

function books_uri($books) {
  return array('path' => 'books/' . $books->bid );
}

L'interface d'administration

Martine est contente, elle a son entité a elle toute seul. Néanmoins, il faut encore réaliser les pages d'administration, pour cette entité. Ca se passe donc du côté du hook_menu() cette fois-ci.
Martine va donc créer les entrées de menu pour la page d'administration générale, la page pour chaque bundle (%book_type etant passé en argument) où l'on va pouvoir gérer toute la partie ajout de Fields par exemple.
Pas oublier aussi la page permettant de créer une nouvelle entité :) (Martine va être contente)

 $items['admin/books'] = array(
    'title' => 'admin books',
    'access arguments' => array('access content'),
    'page callback' => 'books_admin',
    'file' => 'books.pages.inc',
    'type' => MENU_NORMAL_ITEM
  );
  // Pas d'admin des BooksType, utilisé par l'API de Fields
  // pour attacher les fields aux bundles.
  $items['admin/books/%book_type'] = array(
    'title callback' => 'books_type_title',
    'title arguments' => array(2),
    'access arguments' => array('access content'),
    'page arguments' => array(2),
  );
  // book default tab : add a book
  $items['admin/books/%book_type/add'] = array(
    'title' => 'add',
    'access arguments' => array('access content'),
    'type' => MENU_DEFAULT_LOCAL_TASK
  );

Vous avez sans doute remarqué les "%book_type" qui trainent un peu partout dans les url. Il s'agit des "Wildcard Loader Arguments" (si vous avez une traduction pas trop nulle, je suis preneur). En gros, %nimportequoi fera automatiquement appelle au hook_load correspondant, c'est a dire ici %nimportequoi_load().
Martine doit donc ecrire son book_type_load().
Toujours pour des raisons de simplifier un peu l'ensemble, on reste sur nos deux Bundles hardcodé.

/**
 * Argument loader for %book_type
 * Verification de ce qu'on passe via %book_type afin d'eviter toute tentative d'injection.
 */
function book_type_load($books_type) {
  switch ($books_type) {
  case 'roman':
  case 'bd':
    return $books_type;
  default:
    return FALSE;
  }
}

Il reste maintenant a écrire les "page callback" de notre hook_menu()
Martine écrit donc son books_type_title()

/**
 * On retourne juste le type de l'object
 */
function books_type_title($books_type) {
  return $books_type;
}

Et le books_admin() (attention, comme on peut le voir dans le hook_menu(), lui il se trouve dans le fichier books.pages.inc)

/**
 * La page d'administration de nos Bundles Books
 * Par defaut, on liste les bundles, si on clic sur un bundle,
 * on obtiens la page d'administration du bundle en question.
 * Il s'agit de la page où l'on va retrouver les tabs permettant
 * de gerer les fields attaché et l'affichage de ceux-ci.
 */
function books_admin($type = NULL) {
  if ($type) {
    return drupal_get_form('books_addbook', $type);
  }
  else{
    // pour faire plus simple, on hardcode les bundles existants.
    $rows = array();
    $rows[] = array(l(t('Roman'), 'admin/books/roman'));
    $rows[] = array(l(t('Bd'), 'admin/books/bd'));
    $header =  array('Type');
    $content = array(
      '#theme' => 'table', // on veut un affichage tableau
      '#header' => $header, // Un peu d'informations pour les headers, c'est joli
      '#rows' => $rows, // et les links vers les 2 bundles existants
    );
    return $content;
  }
}

En gros, si un "type" est présent dans l'url, on redirige vers le formulaire d'ajout d'entité, sinon, on se contente de lister les Bundles présents sur le site. Toujours hardcodé, simplicité, tout ça, vous commencez à comprendre je pense.

Et donc, a quoi va ressembler le books_addbook() de Martine ?

/**
 * Ajout d'un nouveau book, du type selectionné.
 */
function books_addbook($form ,&$form_state, $type) {
  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => 'name'
  );

  $book = new stdClass();
  $book->type = $type;
  // l'object book lui même
  $form['books'] = array(
    '#type' => 'value',
    '#value' => $book
  );
  // le type du bundle, indispensable pour passer la validation
  $form['type'] = array(
    '#type' => 'value',
    '#value' => $type
  );
  // On attache les formulaires des fields attachés
  field_attach_form('books',$book, $form, $form_state);

  $form['actions'] = array('#type' => 'actions');
  $form['actions']['add'] = array(
    '#type' => 'submit',
    '#value' => 'add'
  );
  return $form;
}

Ne pas oublier la fonction de validation.
Quel est l'utilité de cette fonction de validation ? Simplement, elle se contente de lancer la validation des fields attachés. (un field image n'acceptera que les images des extensions autorisées, un field "Entier" n'acceptera pas de nombres décimaux, etc...)

function books_addbook_validate($form, &$form_state) {
  entity_form_field_validate('books', $form, $form_state);
}

Ainsi que le submit, parce que bon, il faut bien les enregistrer les valeurs que l'on vient de remplir.

function books_addbook_submit($form, &$form_state) {
  $book = $form_state['values']['books'];
  $book->name = $form_state['values']['name'];
  // Enregistrement dans la base "books"
  drupal_write_record('books', $book);
  // L'objet est "rempli" avec les propriétés issues de form_state
  entity_form_submit_build_entity('books', $book, $form, $form_state);
  // Laissons aussi une chance à d'autres modules d'intervenir sur les Fields attachés.
  field_attach_submit('books', $book, $form, $form_state);
  // On insere les données des fields dans la base de données.
  field_attach_insert('books', $book);
  // Et un petit message de confirmation.
  drupal_set_message(
    t('new @type got added' ,
    array('@type' => $book->type))
  );
}

On devrait donc maintenant avoir une interface d'administration à cette url : http://exemple.com/admin/books

L'interface utilisateur

Les pages d'administration, c'est sympa, mais bon, Martine, elle veut quand même pouvoir afficher ses entités sur son site.

Pour commencer, on va donc rajouter les pages correspondantes au hook_menu()

 $items['books/%books'] = array(
    'title callback' => 'books_title',
    'title arguments' => array(1),
    'page callback' => 'books_display_one',
    'page arguments' => array(1),
    'access arguments' => array('access content'),
    'file' => 'books.pages.inc',
    'type' => MENU_CALLBACK
  );
  // Le tab "View"
  $items['books/%books/view'] = array(
    'title' => 'view',
    'access arguments' => array('access content'),
    'weight' => -3,
    'type' => MENU_DEFAULT_LOCAL_TASK
  );
  // Le tab "Edit"
  $items['books/%books/edit'] = array(
    'title' => 'edit',
    'page callback' => 'drupal_get_form',
    'page arguments' => array( 'books_edit' , 1 ),
    'access arguments' => array('access content'),
    'file' => 'books.pages.inc',
    'type' => MENU_LOCAL_TASK
  );

Rien de bien compliqué ici. On définit les deux entrée de menu qui serviront pour afficher les tabs "View" et "Edit" sur la page de l'entité.

Sinon, je pense que le reste peut se passer de détails approfondis.

On voit un %book qui traine, alors hop, le hook_load() qui va avec.

/**
 * Menu callback, argument loader for %books
 */
function books_load($bid) {
  // entity_load() requete la table de base de l'entité et se
  // charge éganelement de charger les champs attachés.
  $books = entity_load('books', array($bid) );
  return $books[$bid];
}

Sans oublier aussi le "title callback"

function books_title($books) {
  return $books->name;
}

Martine va tout d'abord s'occuper de $items['books/%books'], donc du callback "books_display_one" qui au final est très simple :

function books_display_one($book) {
  // l'objet "book" est déjà chargé via entity_load
  $content[] = array(
    '#markup' => l($book->name, 'books/' . $book->bid )
  );
  // on oublie pas d'attacher les fields supplémentaires.
  $content[] = field_attach_view('books', $book ,'full');
  return $content;
}

C'est tout pour lui.

Reste a s'occuper de $items['books/%books/edit'], donc du callback "books_edit", qui au final va être très très proche de books_addbook()

function books_edit($form, &$form_state, $book) {
  // Affichage du nom du book
  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => 'name',
    '#default_value' => $book->name
  );
  $form['books'] = array(
    '#type' => 'value',
    '#value' => $book
  );
  $form['type'] = array(
    '#type' => 'value',
    '#value' => $book->type
  );
  // On affiche les formulaires des fields attachés.
  field_attach_form('books', $book, $form, $form_state );
  // Sauvegarde
  $form['actions'] = array('#type' => 'actions');
  // le bouton de sauvegarde
  $form['actions']['save'] = array(
    '#type' => 'submit',
    '#value' => 'save'
  );
  // On gere aussi la suppression
  $form['actions']['delete'] = array(
    '#type' => 'submit',
    '#value' => 'delete',
    '#submit' => array('books_edit_delete')
  );
  return $form;
}

Avec toujours la validation

function books_edit_validate($form, &$form_state) {
  entity_form_field_validate('books', $form, $form_state);
}

Et le submit

function books_edit_submit($form, &$form_state) {
  $book = $form_state['values']['books'];
  $book->name = check_plain($form_state['values']['name']);
  drupal_write_record('books', $book, array('bid'));
  entity_form_submit_build_entity('books', $book, $form, $form_state);
  field_attach_submit('books', $book, $form, $form_state);
  field_attach_update('books', $book);

  drupal_set_message(
    t( 'the @type got saved' ,
    array('@type' => $book->type) )
  );
  $form_state['redirect'] = 'books/' . $book->bid;
}

Légère différence avec ce que l'on trouve sur les pages d'administration, la fonction de suppression d'une entité.
Pas besoin de s'attarder dessus tellement c'est simple (surtout que Martine a mis plein de commentaires, elle n’est pas magnifique Martine hein ?)

/**
 * Gestion de la suppresion d'un book
 */
function books_edit_delete($form, &$form_state) {
  $book = $form_state['values']['books'];
  // On supprime les info des fields attaché
  field_attach_delete('books', $book);
  // et on supprime aussi le book en lui-même.
  db_delete('books')
    ->condition('bip', $book->bid)
    ->execute();
  $form_state['redirect'] = 'books';
}

Intégration avec views

Martine est super contente, elle a son entité, mais ce serait le top du top si elle pouvoir utiliser les données de son entité directement dans Views. Et bien faisons ça !

La premiere étape est d'implémenter le hook_views_api(), qui reste toujours aussi court.

function books_views_api() {
  return array(
    'api' => 3,
    'path' => drupal_get_path('module', 'books') . '/views',
  );
}

L'implémentation de ce hook a pour conséquence que views qui essayer d'aller automagiquement chercher un fichier [module].views.inc dans le dossier [module]/views

Voici donc que Martine écrit le fichier books.views.inc et y implémente le hook_views_data(), qui permet de définir quels champs pour être exposé a Views.

function books_views_data() {
  $data['books']['table']['group']  = t('Books');

  $data['books']['table']['base'] = array(
    'field' => 'bid',
    'title' => t('Books'),
    'help' => t('Books definitions.'),
  );

  $data['books']['bid'] = array(
    'title' => t('Books ID'),
    'help' => t('The unique internal identifier of the book.'),
    'field' => array(
      'handler' => 'views_handler_field_numeric',
      'click sortable' => TRUE,
    ),
    'filter' => array(
      'handler' => 'views_handler_filter_numeric',
    ),
    'sort' => array(
      'handler' => 'views_handler_sort',
    ),
    'argument' => array(
      'handler' => 'views_handler_argument_numeric',
    ),
  );

  $data['books']['name'] = array(
    'title' => t('Book Name'),
    'help' => t('The name of the book.'),
    'field' => array(
      'handler' => 'views_handler_field',
      'click sortable' => TRUE,
    ),
    'filter' => array(
      'handler' => 'views_handler_filter_string',
    ),
    'sort' => array(
      'handler' => 'views_handler_sort',
    ),
    'argument' => array(
      'handler' => 'views_handler_argument_string',
    ),
  );

  $data['books']['type'] = array(
      'title' => t('Book Type'),
      'help' => t('The type of the book.'),
      'field' => array(
        'handler' => 'views_handler_field',
        'click sortable' => TRUE,
      ),
      'filter' => array(
        'handler' => 'views_handler_filter_string',
      ),
      'sort' => array(
        'handler' => 'views_handler_sort',
      ),
      'argument' => array(
        'handler' => 'views_handler_argument_string',
      ),
    );

  return $data;
}

Je n'irais pas trop dans les détails ici, ce n'est pas vraiment le but de cet exercice. Si vous ne comprenez pas tout ici, Martine vous invite a aller fouiller un peu la doc et l'API de Views. Néanmoins, quelques explications de bases :

$data[&#039;books&#039;][&#039;bid&#039;] / $data[&#039;books&#039;][&#039;name&#039;] / $data[&#039;books&#039;][&#039;type&#039;] sont nos 3 champs de la table 'books' de notre BDD.

Concernant la partie

   $data['books']['table']['base'] = array(
    'field' => 'bid',
    'title' => t('Books'),
    'help' => t('Books definitions.'),
  );

Elle permet de créer un nouveau "type" de views, qui va voir pour table de base "books"

Enfin, pour finir, ce petit bout de code

$data['books']['table']['group']  = t('Books');

Permet lui, juste, de définir un groupe de champs.

Voila, l'ensemble des champs de notre "base table" de l'entité sont maintenant disponible dans views, et automagiquement, tous les fields que l'on aurait attaché à un des bundles de l'entité se retrouvent eux aussi exposé dans views.

Martine va pouvoir classement sa bibliothèque de la manière la plus geek possible qu'il soit maintenant.

Pour les fainéants du fond qui n'ont pas envie de passer leur temps a copier/coller du code, vous pouvez télécharger ce micro module.

Tags: 
Par haza
Nicolas Meyer

Entité, cékouaça ?

Jusqu'à Drupal 6, pour stocker un peu tout et n'importe quoi comme information, on se tournait tout de suite vers les nodes. Ces éléments si présents partout dans nos sites, qui servent à faire tant de chose.

Mais au fait, un node, c'est quoi ?

Reprenons la définition de Wikipedia qui n'est pas si mauvaise que ça :

Drupal nomme tout contenu qu'il gère un "nœud". Une page d'article sera par exemple un nœud. Une page de livre aussi.

Ce nœud possèdera d'une part un type : forum, article de fond, information brève, tutoriel, blog, commentaire, formulaire de saisie, livre collaboratif, image ou galerie d'images, sondage interactif, page de wiki, etc. : la forme n'est plus assujettie à une architecture prédéterminée, ce qui rend le contenu aisément reconfigurable. Contrepartie de cette liberté : on doit se familiariser avec sa logique particulière.

Le nœud possèdera par ailleurs, conformément aux spécifications de son type, des champs : nom, type, date, auteur, image éventuelle, corps, votes de la communauté sur son contenu, etc.

Si on prend en compte la manière dont on se sert des nodes, on peut les considérer comme étant la première couche d'abstraction de Drupal.

Lors de l'ajout d'une fonctionnalité pour un node, sur Drupal 6, tous les nodes étaient alors immédiatement éligible pour obtenir cette nouvelle fonctionnalité. Fivestar rajoute la couche de vote, i18n la traduction, etc, etc ...

Puis est arrivé CCK, et chaque node se voit obtenir la possibilité de se voir octroyer des fields supplémentaires. Et là, ce fut un peu la fête du slip. On a commencé a rajouter des champs a un content type précis, qui se voit attaché a un user, et Content Profile est né. D'autre modules vont aller transformer les commentaires en node, et que sait-je encore...

Pour résumé, dans Drupal 6, on avait donc l'idée que "Tout est un node", ce qui se transforme en Drupal 7 en "Tout est une entity". Les utilisateurs, la taxonomy, les commentaires, ... sont des entity et par conséquent, toutes ces entity peuvent donc maintenant avoir des fields attachés directement.

Imaginons que l'on souhaite créer une nouvelle façon d'interagir avec un "type de contenu" (aberration de langage ici, il faut comprendre "Entity type"), sans que ce ne soit réellement un node. Par exemple, le module Commerce illustre ceci relativement bien. Les produits ne sont plus des nodes, mais bien une entity à part entière. Les commandes sont également des entities. Donc aussi bien les produits que les commandes peuvent recevoir des nouveaux champs (Un champ commentaire par exemple pour la commande, ou bien encore un champ Couleur pour un produit ...), mais ces derniers ne sont pas des nodes comme on pouvait les concevoir en Drupal 6.

Mais donc, une entity, c'est quoi a la fin ?

Comme précédemment, allons voir ce que Wikipedia en dit :

Une entité est une structure composée d'attributs, représentant un composant identifiable d'un domaine fonctionnel, et potentiellement en relation avec les autres entités du domaine."

Les entities dans le monde de Drupal sont les "choses", les "informations", les "trucs", avec lesquelle on va pouvoir interagir dans nos applications Drupal. Et au final, c'est bel et bien dans ce sens qu'il faut penser le mot "Entité" dans le monde Drupal. Tout comme les nodes, avec le sens qu'on lui connait dans Drupal, est typiquement du vocabulaire Drupal, le sens du terme "entité" lui sera intimment attaché au monde Drupal.

Ok, c'est cool. Mais au final, c'est la même chose que l'on avait dans Drupal 6 non ? Alors pourquoi tout ce rafut autour des entité de D7 ? Ils ont juste renommé le terme « Node » en « Entity » ?

Ce n'est pas si simple. Alors que pour Drupal 6, chaque "trucs" avait sa propre implémentation (les nodes, les commentaires, les utilisateurs, la taxonomy ... chacun obéit à ses règles propres), dans Drupal 7, toutes les entity ont une interface communes où leur comportement est définie. Ils partagent tous la même "base" programmaticalement parlant.

CCK pour Drupal 6, c'etait bien non ? On attachait un peu n'importe quoi a des nodes, afin de les transformer en "un peu n'importe quoi" (bis). Ceci est maintenant possible en Drupal 7. On peut attacher un peu n'importe quoi a n'importe quoi (et non plus juste des nodes) pour les transformer en "un peu plus n'importe quoi" (les deux du fond, vous suivez toujours ?)

Il n'est plus nécessaire de devoir se trainer la structure de base des tables de nodes si on en a absolument pas besoin.

Imaginons que l'on souhaite créer une entité qui va stocker un pointage de temps.
Si on pense Drupal 6, on crée un nouveau content type et on y attache un field "number" où l'on va renseigner un temps.
Un éventuel titre n’a ici aucun intérêt, de même que la date de création de ce pointage. Malgré cela, on aura toujours dans la base de donnée un titre pour le node, un ID de révision, une date de création, de modification, ... Pire encore, ces champs sont obligatoires pour certain !

En D7, on défini une entité pour stocker notre information. On a juste besoin d'avoir le field correspondant a la durée, avec un ID d'identification (et éventuellement un auteur ...). Tout ceci allégera d'autant plus la base de donnée que les infos que l'on avait en D6 sont inutiles dans ce cas précis...

Quand utiliser une entité ?

Les entités sont un concept nouveau et les bonnes pratiques qui y sont liées vont prendre un peu de temps avant d'être complètement clarifiées.
Il n'y a donc pas de réponse toute faite et c'est une question que l'on va devoir se poser très, très, souvent. Du moins dans les premiers temps de Drupal 7.
Ce qu'on peut dire, c'est que si vous comptiez utiliser une table custom pour stocker des informations supplémentaires pour vos traitements, alors oui, il est certainement utile de créer une nouvelle entité. Les requêtes SQL en seront d'autant allégées que seules les informations qui sont réellement utiles vont être stockées, et en plus dans la même table.

Et puis de toute façon, on peut toujours y rajouter des fields si besoin. Sympa non ? ;-)

Alors, ces entités, vous avez tout pigé maintenant ?

Tags: 

Pages