Le blog de Seb

Proof of Concept: Agrégateur Twitter pour Drupal avec Drupal

Bonne année ! Ah zut on est en Février déjà...

Bon comme c'est un peu les vacances pour moi (semestre à Dublin, 10h de cours par semaine, toussa...) j'ai le temps de tester des petits trucs.

Concept

J'ai donc monté un petit agrégateur de tweets concernant Drupal, en ne récupérant que ceux qui contiennent des liens (recherche Twitter). Il enregistre donc tous ces tweets, mais comme les nœuds sont pas très manipulables pour les statistiques, j'ai décidé de faire mon propre module et de ne pas utiliser l'Aggregator du Core. Quatre tables et c'est parti, on enregistre les tweets, les liens et leurs auteurs dans un hook_cron() :

  • agreg_tweets(id, author, text, date)
  • agreg_authors(id, name)
  • agreg_links(id, link)
  • agreg_links_tweets(link, tweet)

Statistiques

Ça, c'est bien, mais il faut en faire quelque chose maintenant... Donc c'est parti pour un hook_menu pour faire quelques pages de statistiques :

  • Une pour le temps pour voir quand les drupaliens twittent le plus, et donc quand faire de la veille
  • Une pour les trendings tweets et donc voir ce qui se passe et qu'est qui est intéressant ces temps-ci

C'est là que des nœuds m'auraient bien embêté, faire des requêtes GROUP BY et ROLLUP sur des jointures à foison, non merci !
La librairie highcharts.js pour faire joli, des petits tableaux et une requête ajax pour voir quels tweets parlent d'un lien (ajouter un peu de contexte) et voilou : un bel agrégateur Twitter

Twitter bootstrap

Ce thème tout frais (donc en dev) a quelques bugs dus à l'intégration Drupal et aux problèmes de différence de licences open source mais en quelques modifications c'est résolu. On a donc accès à des classes pour faire de beaux tableaux ainsi que quelques outils JS pour faire des fenêtres modales (j'ai rajouté bootbox.js pour plus de simplicité, merci @FGRibreau). On obtient donc en résultat une interface épurée idéale pour ce genre de proof of concept ou de projet court.

Drupal, un vrai framework

La recette se prépare donc en quelques 350 lignes de codes grâce aux fonctions et aux hooks de Drupal. Parmi ceux et celles utilisés :

  • hook_menu() et hook_cron()
  • db_merge()
  • variable_get() et variable_set(), juste indispensables
  • l(), truncate_utf8(), theme_table() pour le rendu
  • curl pour les requêtes HTTP

Je vais maintenant pouvoir vous twitter des liens plus pertinents concernant Drupal, pour cela n'hésitez pas à me suivre !

Live templates Drupal pour PHPStorm

L'ami Djebbz a attiré mon attention il y a peu sur IRC. Il a récemment mis en ligne un repository GitHub contenant un moyen de faire des Live templates en masse à partir du code de Drupal.

Explications

Il s'agit d'un script perl qui va parser les fichiers de l'api des hooks (contenus majoritairement dans le module examples et dans les fichiers *.api.php des modules contrib).

Utilisation

Avant toute chose, fermez PHPStorm car celui-ci écrase les fichiers lors de sa fermeture.
Les hooks basiques sont déjà disponibles dans le user.xml du repository, sous Mac il suffit de faire :
cd ~/Library/Preferences/WebIDE10/templates
curl -O -# https://raw.github.com/DjebbZ/Drupal-PHPStorm-Live-Templates/master/user.xml

Puis démarrez PHPStorm et dans votre code tapez h_menu<TAB> pour insérer un hook_menu.
Notez que taper h_menu<TAB> insérera la totalité de la documentation du hook.

PHPStorm Live template example

Mais encore

Le script est disponible dans le repository si vous souhaitez ajouter des hooks provenant d'autres modules contrib. Pour l'exécuter :
cd /path/to/drupal
find . -name \*.php | xargs grep -l '^function hook_' | xargs /path/to/parse_drupal_api.pl > ~/Library/Preferences/config/templates/user.xml

Enjoy !

Trier une View par longueur de chaîne

On continue dans les bouts de code pratiques. Voici, en 4 lignes, comment trier les résultats d'une view selon la longueur de chaîne du titre.

1
2
3
4
5
6
7
8
9
10
11
<?php
 
/**
 * Implements hook_query_TAG_alter().
 *
 * @param SelectQuery $query
 */
function glossaire_query_views_export_alter(SelectQuery $query) {
  $query->addExpression('LENGTH(node.title)', 'title_length');
  $query->orderBy('title_length', 'DESC');
}

Ce code provient du module personnalisé du glossaire de traduction française et a été construit depuis la documentation disponible sur drupal.org.

Forcer le téléchargement d'un fichier sous Drupal 7

Nouveau billet rapide pour vous partager une fonctionnalité.

Pour forcer le téléchargement d'un fichier, comme d'habitude avec Drupal, il y a un module pour ça. Dans ce cas précis il y en a même deux : Download file et File force. Mais bon comme d'habitude, la flemme d'installer un module pour une si petite fonctionnalité ! Alors voici la solution en quelques lignes de code :

Voici un bout de code se basant sur la File API. Il est plus sécurisé par rapport au deuxième mais ne fonctionnera que pour les fichiers gérés par Drupal (quelque soit la méthode de téléchargement) : je le recommande donc.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
/**
 * Implementation of hook_menu()
 */
function mymodule_menu() {
  $items['download/%file'] = array(
    'page callback' => 'mymodule_download_file',
    'access arguments' => array('administer site configuration'),
    'type' => MENU_CALLBACK,
  );
  return $items;
}
 
/**
 * Page callback for forcing a file to download
 */
function mymodule_download_file($file) {
  if($file) {
    file_transfer($file->uri, array('Content-disposition' => 'attachment; filename='.$file->filename));
  }
  else {
    return drupal_access_denied();
  }
}

Il suffira d'appeler le fichier sous la forme http://mysite.com/download/123 où 123 est l'identifiant fid du fichier à télécharger.

Ce deuxième bout de code présente un danger pour la sécurité ! Il est recommandé de l'utiliser en dernier recours lorsque les fichiers ne sont pas gérés par Drupal. Pour accroître la sécurité on proposera cette fonctionnalité seulement aux administrateurs, et on filtrera les extensions autorisées au téléchargement.

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
<?php
/**
 * Implementation of hook_menu()
 */
function mymodule_menu() {
  $items['download'] = array(
    'page callback' => 'mymodule_download_file',
    'access arguments' => array('administer site configuration'),
    'type' => MENU_CALLBACK,
  );
  return $items;
}
 
/**
 * Page callback for forcing a file to download
 */
function mymodule_download_file() {
  if (!isset($_GET['file']) || !file_exists($_GET['file'])) {
    return drupal_not_found();
  }
  $filepath = $_GET['file'];
  $realpath = realpath($path);
  $filename = basename($filepath);
  $extension = pathinfo($filepath, PATHINFO_EXTENSION);
  // Vérification de l'extension et restriction aux fichiers dans DRUPAL_ROOT
  if(in_array($extension, array('jpg', 'png', 'gif', 'mp4')) && substr($path, 0, strlen(DRUPAL_ROOT)) === DRUPAL_ROOT) {
    drupal_add_http_header('Content-disposition', 'attachment; filename=' . $filename);
    readfile($filepath);
  }
  else {
    return drupal_access_denied();
  }
}

Il vous suffira ensuite de créer votre lien sous la forme http://mysite.com/download?file=sites/default/files/monimage.png

Ceci ne fonctionne que pour la méthode de téléchargement publique mais c'est la plus répandue.

Twitter Pull, comment créer un bloc Drupal des derniers tweets

Rapide article pour vous montrer comment créer, via l'API de Drupal, un bloc qui récupère les derniers tweets.

Twitter Pull

Ce module va vous permettre de récupérer via l'API twitter les derniers tweets d'un compte ou d'une recherche, il est téléchargeable ici : Twitter Pull

Création du bloc

Dans votre module Drupal 7 (si vous n'en avez pas encore créé, veuillez d'abord lire ce tutorial de Yoran Brault), ajoutez un hook_block_info() :

1
2
3
4
5
6
7
8
9
10
11
<?php
/**
 * Implements hook_block_info().
 */
function monmodule_block_info() {
  $blocks['last_tweets'] = array(
    'info' => "Derniers tweets",
    'cache' => DRUPAL_NO_CACHE,
  );
  return $blocks;
}

Ici on désactive le cache pour pouvoir le gérer directement dans notre bloc. Puis on s'occupe du rendu de notre bloc via le hook_block_view() :

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
<?php
/**
 * Implements hook_block_view().
 */
function monmodule_block_view($delta) {
  $block = array();
  switch ($delta) {
    case 'last_tweets':
      $block['subject'] = "Derniers tweets";
      // Vérification des données en cache de 1 heure maximum
      if ($cache = cache_get('monmodule_last_tweets') && REQUEST_TIME - $cache->created < 3600) {
        // Le cache est déjà présent, on l'affiche
        $block['content'] = $cache->data;
      }
      else {
        // Récupération des tweets
        $tweets = twitter_pull_retrieve('@momuser', 10);
        $items = array();
        foreach ($tweets as $tweet) {
          // Affichage des tweets avec les URL parsées en liens
          $class = strtolower($tweet->username);
          $items[] = '<span class="' . $class . '">' . _filter_url($tweet->text, NULL) . '</span>';
        }
        $block['content'] = theme('item_list', array('items' => $items));
        cache_set('monmodule_last_tweets', $block['content'], 'cache', REQUEST_TIME + 3600);
      }
  }
  return $block;
}

Il ne vous reste plus qu'à activer votre module et placer votre bloc dans une région pour que la magie opère !

Personnalisation

Il vous est possible de régler le nombre de tweets que vous souhaitez récupérer en changeant le deuxième argument de twitter_pull_retrieve() (maximum 200 tweets). Il n'est cependant possible de récupérer qu'une seule série de tweets à la fois, pour en récupérer depuis plusieurs utilisateurs il vous faudra faire plusieurs requêtes et les trier sur $tweet->timestamp via uasort.

Vous pouvez par exemple créer un fil déroulant tel que sur www.macifcourseaularge.com à l'aide d'un plugin jQuery liScroll.

Alternative

Vous pouvez également utiliser le widget Twitter (que vous pouvez voir dans la sidebar de mon blog) mais il est moins personnalisable, et ralentira le temps de chargement de votre page côté client.