Planète

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 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
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['books']['bid'] / $data['books']['name'] / $data['books']['type'] 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: 
Par pounard

Le site qui vous fera dire Bonjour!

Mise à jour du 12 mars 2011 : Le fameux module est loin d'être efficace à 100%, je reçois régulièrement des SPAM qui arrivent à se faire publier sous la forme de commentaires. Cependant, beaucoup moins que lorsque que j'avais aucun filtre, il y'a encore quelques temps (de plus même avec le module CAPTCHA, il y'a toujours des SPAM manuels qui passent).

Blazé devant la lenteur du module Captcha pour Drupal à être porté sur Drupal 7, j'ai fini par écrire un micro module pour le remplacer. Ce module injecte des champs de manières arbitraire dans tous les formulaires et procède à une série d'altérations — plus ou moins arbitraires—  pour aller dessus. Certains de ces champs doivent être vidés, d'autres non, certains autres ne doivent pas changer. Bref, suffisament de cas d'utilisation, j'espère, pour éviter la majeure partie des SPAM.

Par Haza
Nicolas Meyer

Moaaarr entity infos

Un petit mot rapide pour signaler que Fago a posté les slides de sa session "Drupal 7 development done right: Leverage entities!" presénté aux DrupalDevDays de Bruxelles. On peut donc récupérer le pdf directement chez lui.

Fago - Drupal 7 development done right: Leverage entities!

Plein de bonnes informations a propos des entitées dedans. Et peut-être plein d'autre infos a venir ici même dans les prochains jours (le temps que je clean le tout ... teaser teaser)

Tags: 
Par Haza
Nicolas Meyer

Entités, Bundles et Fields sont dans un bateau ...

Esperons que personne ne tombe à l'eau ...

Histoire de clarifier un peu toutes ces histoires, essayons, en quelques mots, de définir ces concepts nouveaux de Drupal 7.

Entités

Une entité (entity) est, dans Drupal, l'expression d'un type de donnée dans sa plus simple expression, la plus abstraite.
L'entité offre une gestion normalisé des données ainsi qu'une interface de gestion standardisée. L'entité peut être vu comme étant une généralisation des nodes, commentaires, utilisateurs, ...
Les entité peuvent avoir plusieurs bundles.

Bundles

Un bundle est un groupement de field. On peut voir le bundle comme la spécialisation d'une instance d'entité. Si on prend comme exemple l'entité "node", alors "article" ou "page" serait un chacun un bundle.
Un bundle permet d'exprimer un même "concept" mais qui aurait besoin besoin d'exposer des champs en grande partie différents l'un de l'autre.
Si on prend pour exemple une entité "Livre". Celle-ci pourrait s'exprimer au travers de différents bundles comme "Roman", "BD", "magazine", ... La BD aura des champs spécifique à cette dernière et qu'on n'ont aucun intérêt d'exister pour un roman. (par exemple le nom de l'illustrateur, si on a une BD en couleur ou en noir&blanc, ...)
Néanmoins, ces bundles partagerons des champs commun : nombre de page, auteur, date de parution, ...

A noter qu'il est toujours possible de rajouter des fields a un bundle.

Fields

Les fields sont la suite logique des champs CCK de Drupal 6. Pour l'essentiel, un field est le type de donnée de base de Drupal. Les fields ont des formatteurs, des widgets et des éléments de configurations. Un field peut être partagé par plusieurs Bundles.

Par Haza
Nicolas Meyer

Et si on perdait un mot de passe ?

(hein, juste pour le fun ;D)
Avec Drupal 7, les mots de passe des utilisateurs ne sont plus simplement encodé via MD5 dans la base de données. Si l'on a besoin de changer un mot de passe, il faut maintenant en générer un en utilisant le script fourni.

Sous unix :


haza@dev:/projects/d7/www$ ./scripts/password-hash.sh MonBeauPassword

password: MonBeauPassword hash: $S$CfB5sd.BOdFRe63nKnJmQoyxVvw/uRn3t/R8ZylsLV5IlL5jVLsf

Sous Windows :


php .\scripts\password-hash.sh MonBeauPassword

Si l'on souhaite changer le mot de password du user 1, on obtiens donc :


mysql> UPDATE users set pass='$S$CfB5sd.BOdFRe63nKnJmQoyxVvw/uRn3t/R8ZylsLV5IlL5jVLsf' where uid = 1;

Pour les utilisateurs de Drush, on peut changer le password via ce dernier beaucoup plus facilement (love drush) :


haza@dev:/projects/d7/www$ drush upwd admin --password="MonBeauPassword"
Tags: 
Par pounard

Retour du DrupalCamp Nantes 2011

De retour du DC Nantes 2011, après avoir beaucoup dormi et absorbé une quantité certaine de café, j'ai enfin 5 minutes pour partager mes slides. Pour information, ils ont été créé avec Beamer.

Merci à la patience de tous ceux ayant assisté à ma courte session, c'est dans un état de fatigue assez improbable mais avec beaucoup de plaisir que j'ai échangé avec vous sur le sujet des performances. J'espère que le peu d'information que j'ai pu apporter vous sera utile.

Comme promis, voici quelques liens aggrégés ces 10 dernières minutes (j'avoue ne pas tous les avoir lus en entier).

Par pounard

Des caches Drupal! (et surtout des caches)

Comme promis dans l'article précédent, je vais vous introduire la gestion du cache dans Drupal 7. Dans cet article, je ne vais pas m'étendre sur les politiques de cache d'un point de vue du développeur, mais plutôt du choix du backend approprié en fonction de l'environnement. Cet article a donc vocation de se placer plutôt côté architecte ou administrateur système.

Je suis moi même développeur, architecte n'est pas mon métier, cependant avoir un point de vue éclairé sur les problématiques que rencontrent ces derniers – sans pour autant être un expert – peut amener à se poser des questions sur le code que l'on produit. L'architecture et le design d'une application web (ou d'un sous-ensemble de cette dernière) poussent à se poser la question d'une politique de gestion de cache efficace. Pour mener à bien la conception d'une politique de cache, il faut comprendre les problèmatiques qui se posent derrière.

Cet article va être tourné plus particulièrement à la gestion du cache dans Drupal 7, qui à été grandement améliorée depuis Drupal 6. L'accent sera mis sur les différents backends.

Note de fin de soirée : Terminant cet article, je me suis rendu compte que ma simple introduction à la problématique elle même s'avère être suffisament consistente pour exister en tant qu'article complet. C'est pourquoi je m'arrête ici ce soir. Je vous souhaite une bonne lecture.

Par pounard

Des caches Drupal! (et un peu APC aussi)

Bonjour à tous, comme convenu dans mon premier article, qui était un étalage de benchmark peu revelant significatifs (je finis toujours par retrouver le mot français, déformation professionnelle vous me direz), voici un article concernant les optimisations basiques obligatoires à effectuer pour des sites Drupal. Ces techniques sont issues de presque 3 ans d'expérience avec le framework, mais surtout du travail conjoint de plusieurs personnes, notamment Régis (notre admin système, PHP warrior qui plus est).

Cet article traitera brièvement des options APC, puis s'étendra sur la mise en place de backend de cache multiples sur Drupal 7, comment faire, lesquels choisir, et comment en mesurer l'impact. Je ne m'attarderais pas sur l'optimization du serveur SQL qui est un métier à part entière. De plus le débat ne m'intéresse pas puisque la communauté Drupal préfère toujours utiliser MySQL alors que PostgreSQL est bien supérieur, à tous les niveaux.

Mise à jour du 11/01/2011 : Merci à gagarine de m'avoir signalé que l'option apc.optimization à été supprimée depuis la version 3.0.13 d'APC.

Par badgones

Cacher la taxonomy sur un noeud Drupal

Pour ne pas afficher (ou cacher) la taxonomie sur un noeud Drupal, 3 moyens sont à notre disposition :

 - le premier, méthode crade, c'est d'utiliser les CSS. Mais au moins, le moteur de recherche les références

 - le deuxième, c'est d'éditer son template (node.tpl.php par exemple) et de supprimer la ligne suivante :
&nbsp;&nbsp;&nbsp; <span style="color: rgb(0, 0, 0);"><span style="color: rgb(0, 0, 187);">&lt;?php </span><span style="color: rgb(0, 119, 0);">if (</span><span style="color: rgb(0, 0, 187);">$taxonomy</span><span style="color: rgb(0, 119, 0);">): </span><span style="color: rgb(0, 0, 187);">?&gt;</span></span>

Par badgones

Thème Drupal et photos libres de droit

Vous cherchez des photos libres de droit pour faire votre thème Drupal, voici quelques liens de site qui en proposent.
Attention de bien lire les obligations avant (publier l'adresse du site, ...)

N'hésitez pas à en donner d'autres!

Par badgones

How to : Ajouter des pub AdSense sur son site Drupal

Vous voulez rentabiliser votre site et vous souhaitez ajouter des pub AdSense sur votre site Drupal, rien de plus simple, voilà un petit tutoriel (pour la version 3 de AdSense).

Tout d'abord, ouvrir un compte chez AdSense : www.google.com/adsense (il faut aussi un compte google, mais si vous en avez pas, vous pourrez le créer lors du processus d'inscription à AdSense).
Attendre le mail de confirmation et le valider.
Ensuite aller sur son compte AdSense (https://www.google.com/adsense/v3/app?hl=fr), onglet "Mes annonces", puis cliquer sur "Nouveau bloc d'annonces".

Par badgones

Partager ses contenus Drupal sur sites communautaires Twitter, Facebook, Digg, Delicious, ...

Pour partager ses contenus Drupal sur les sites communautaires tels que Twitter, Facebook, Digg, Delicious, MySpace, Google et autre, il suffit d'intaller le module AddThis disponible à l'URL suivante :
http://drupal.org/project/addthis

Une fois installé et activé, il faut sélectionner sur quelles pages on souhaite le voir (résulé et/ou noeud complet), puis choisir le boutton qu'on souhaite afficher, et enfin les supports pour lesquels on souhaite activer le module, par exemple :
favorites, email, twitter, digg, facebook, delicious, myspace, google, live, more

D'autres options sont disponibles, telles que le choix des couleurs, arrière-plan, ...

Par badgones

Thème Drupal gratuit ou payant, où les trouver ?

Vous avez envie de rafraichir votre site fait sous Drupal, et vous cherchez des thèmes tout fait pouvant correspondre à vos attentes?
Voici une liste non exhaustive de site proposant des thèmes, aussi bien gratuit que payant :

 - Thème Gratuit

- Thème Payant

Par GoZ
Fabien CLEMENT

Premiers clics sous OpenAtrium 1-BETA4

openatrium

1. Introduction

Development Seed vient de passer sa plateforme collaborative basée sur Drupal 6 à la version 1.0-BETA4. Des chiffres, des lettres, une beta de plus... Oui, mais pas n’importe quelle beta !

Outre la modification du thème avec ré-agencement des boutons, fil d’ariane, logo, diminution de la taille du header et bien d’autres permettant une visibilité accrue, la principale attente de cette nouvelle béta-mouture se trouve dans l’utilisation des versions 3.x des modules spaces et context, 2.x pour le module Admin et la suppression de FeedsAPI par Feeds.

Nous allons faire un premier état des lieux en explorant le potentiel des nouvelles fonctionnalités ainsi que l’ergonomie de l'interface disponibles juste après une installation basique (utilisation des modules activés par défaut uniquement).

en lire plus

Par GoZ
Fabien CLEMENT

Installer OpenAtrium sous MAMP

1. Introduction

Avec la sortie de OpenAtrium 1.0-beta4, j'ai bien entendu voulu le tester et ainsi pouvoir voir les améliorations apportées par rapport à la version précédente.

Travaillant depuis peu sous Mac, j'ai installé MAMP pour pouvoir faire mes tests rapidement en local (peut-être n'est-ce pas la meilleure solution pour développer sous mac, je reste ouvert à toute proposition).

Premier tour de roue, installation de drush et drush_make pour pouvoir suivre les étapes de l'installation fournies par Development Seed : http://openatrium.com/node/35.

en lire plus

Par badgones

Vue Calendrier avec Drupal - Gestion d'évènements

Gérer des contenus événements et un agenda en vue calendrier avec Drupal

Très bon article publié sur le site http://www.davidpetit.com/blog/drupal/gerer-contenus-evenements-agenda-vue-calendrier-drupal.
Je me permet de le recopier pour mémo.

 

Bonjour ! Il existe plusieurs méthodes pour gérer des événements et un agenda sous Drupal. Ceci dit, je souhaite partager avec vous la méthode que j'utilise pour y arriver. J'expliquerai aussi comment obtenir un affichage avec calendrier. On utilisera donc pour ceci les modules CCK, Views, Date et Calendar. Je suppose ainsi pour ce tutorial que vous savez installer des modules dans Drupal.

Installation des modules

La première chose à faire, est d'installer les modules requis:
Le rôle de ces modules : 
CCK est un module qui sert à créer des champs personnalisés dans ses propres types de contenu.
Views est un module qui permet de créer des affichages précis de tous les types de contenus ou d'éléments dans Drupal.
Date est ajoute le champ de type date aux champs disponibles dans CCK.
Calendar permet de faire des affichages de type calendrier dans Views.

Création du type de contenu "Evénement"

Une fois les modules installés, nous allons commencer par créer le contenu personnalisé qui représentera les événements. Pour cela, il faut aller dans "Gestion du contenu -> Types de contenu -> Ajouter". Vous remplissez vos champs et vous devriez obtenir quelque chose qui ressemble à ceci:
Par GoZ
Fabien CLEMENT

DrupalForFirebug - Debugger Drupal avec Firebug

1. Présentation de DrupalForFirebug

Lors du développement de sites sous Drupal, il est souvent nécessaires à un moment donné d'obtenir des informations sur des variables maisons ou gérées par Drupal. A titre indicatif, les plus courantes sont $node ou $form.

DrupalForFirebug permet donc d'afficher dans Firebug le contenu de variables par l'intermédiaire d'une méthode firep() et donne en tout temps le contenu des variables courantes drupal : $form, $node, $view, requêtes sql.

Nous allons voir dans un premier temps comment installer le module puis les fonctionnalités disponibles.

en lire plus

Par Artusamak
Julien Dubois

Créer des templates (fichier.tpl.php) pour themer vos modules

Lors de la création de vos modules vous pouvez être amenés à mettre en forme votre contenu pour garder une approche cohérence avec la dissociation fond / forme. Il est donc nécessaire de donner la possibilité au themer de pouvoir modifier la mise en forme du contenu que vous aller afficher.
Afin de rendre une telle action possible, vous allez devoir déclarer au sein de votre module un hook_theme, qui va déclarer les éléments qui pourront être themés via vos templates.
Le hook_theme est simplement constitué d’un tableau d’éléments skinnables avec pour chaque entrée une clé « template » qui correspond au nom du fichier .tpl.php utilisé pour le theme et une clé « arguments » correspondant à un tableau de paramètres à passer à la fonction de theme().

function hook_theme() {
  return array(
    'my_themeable_call' => array(
      'template' => 'gabarit.tpl.php',
      'arguments' => array("param1" => NULL),
    ),
  );
}

A noter : Le « .tpl.php » est facultatif car toujours suggéré par le moteur de template et il est recommandé d’utiliser le même nom comme clé et comme nom de fichier pour le template. (Les noms sont ici différents pour vous permettre de décortiquer la mécanique). [Merci Cyril pour la remarque]

Voilà votre fonction de thème déclarée, il ne reste plus qu’à l’utiliser.
Prenons l’exemple d’un bloc, vous souhaitez utiliser votre fonction de thème dans son contenu, il vous suffit d’utiliser dans le contenu du bloc (cf hook_block) votre fonction de theme : $bloc['content'] = theme(‘my_themeable_call’, $param1);
De cette façon vous enverrez à votre fichier de template la variable $param1 (pouvant contenir tout type de données). Si vous omettez de passer un argument, la valeur par défaut déclarée dans le hook_theme sera utilisée (dans notre exemple si j’omets $param1, sa valeur sera NULL).

Plaçons-nous maintenant dans notre fichier de template gabarit.tpl.php, c’est le fichier que votre themer pourra surcharger en le copiant collant dans le dossier de son thème.
Les variables à disposition sont celles que vous avez passé en paramètres lors de l’appel de la fonction de thème (ici $param1) et les éventuelles variables ajoutées par les fonctions de preprocess. (La signature de la fonction est template_preprocess_my_themeable_call, surchargeable par THEME_preprocess_my_themeable_call).

De cette façon vous pouvez donc créer vos propres fichiers de theming lors de la publication de vos modules afin de vous faire adorer par les themers !

tag

Par GoZ
Fabien CLEMENT

Installer gedit-drupal

1. A propos Gedit-drupal

Gedit-drupal est composé d'un plugin et de snippets qui permettent de faciliter le développement sous Drupal.

Nous allons voir ici comment installer le plugin sous ubuntu (qui doit pouvoir s'appliquer à n'importe quelle distribution GNU Linux munie de Gnome).

Cet article s'appuie sur les indications fournies sur http://github.com/mavimo/gedit-drupal et sur le site de son auteur mavimo : http://mavimo.org/drupal/

2. Préparer son poste

en lire plus

Par GoZ
Fabien CLEMENT

Plugin Symbol Browser pour Gedit et Drupal

Plugin-symbol-browser permet d'afficher les méthodes, variables etc d'un code spécifique. Dans notre cas, nous voulons pouvoir afficher les informations de notre développement drupal.

Avant tout, il est nécessaire d'installer le paquet ctags. Sous ubuntu, ce paquet est disponible via les dépôts.

apt-get install ctags

La procédure d'installation du plugin se trouve sur le site de Micah Carrick

en lire plus

Par badgones

Drupal, Drush et Cygwin

Activer la commande drush sous Cygwin

 

Afin de pouvoir lancer drush en ligne de commande dans la console de cygwin, il suffit de rajouter la ligne suivante dans le fichier path/to/drush/drush :

[[ $(uname -a) == CYGWIN* ]] && SCRIPT_PATH=$(cygpath -w -a -- "$SCRIPT_PATH")

 

Cette ligne est à mettre directement sous la ligne SELF_PATH=xxxxx (au début du fichier).

 

Pages