SPIP 2 se prépare (suite)

, par Matthieu Marcillaud

Parce que SPIP 2.0 s’étoffe, il a été difficile de tout dire dans l’article SPIP 2 se prépare. Continuons la découverte de la version à venir. Cette partie parlera particulièrement de programmation avancée avec SPIP.

Gestion de conflit d’édition

Une grande amélioration qu’amène SPIP 2.0 est la gestion de conflits lors de la rédaction d’un même article (ou autre objet d’édition de SPIP) par deux personnes différentes. Si A et B modifient un article X, et que A valide en premier, ses modifications sont effectuées. Si B valide ensuite, seules les champs que n’a pas modifié A sont enregistrés, et un message apparaît pour les autres champs qui affiche les deux versions et propose à B de corriger le conflit.

Gestion de conflit d'édition

Tris des pétitions

Dans le domaine des pétitions, qu’il est possible d’activer depuis l’espace privé d’un article, des petites nouveautés apparaissent : un critère {petition} appliqué à une boucle ARTICLES permet de selectionner les articles ayant une pétition. On peut aussi proposer uniquement les pétitions qui contiennent un texte de description particulier. Ici, toutes les pétitions qui contiennent soutien dans le descriptif : {petition==soutien}

Dans les statistiques du site, un nouveau graphique apparait sur un article ayant une pétition : le nombre de signatures par mois.

Graphique de pétition

Des API SQL...

Une grosse partie du travail effectué sur SPIP 2.0 est invisible au premier abord, et concerne le code de SPIP lui même. Les requêtes SQL d’une part ont été sensiblement optimisées, et d’autre part, celles produites par le compilateur de squelettes génèrent aussi un code assez optimal. Le fait de pouvoir interroger PostgreSQL ou SQLite en plus de MySQL a permis de définir des fonctions spécifiques d’interactions avec la base de données. Ces fonctions sql_* s’inspirent de la logique de la syntaxe SQL tout en permettant d’être assez génériques pour s’adapter à d’autres types de serveurs de données.

  1. // mettre a jour une table 'nom_table'
  2. // le q de sql_updateq indique que les éléments $titre et $texte
  3. // sont automatiquement protégés lors de l'insertion.
  4. sql_updateq('nom_table', array('titre'=>$titre,'texte'=>$texte), 'id_nom_table='.sql_quote($id_nom_table));

Télécharger

et points d’entrées stabilisés

L’aventure des pipelines (points d’entrées) continue. La plupart des actions de SPIP peuvent être complémentées ou modifiées sans toucher le code source du logiciel, simplement en indiquant dans son fichier mes_options.php ou dans les fichiers de plugin plugin.xml qu’un pipeline est utilisé.

  1. // exemple dans mes_options.php
  2. $GLOBALS['spip_pipeline']['pre_edition'] .= '|ma_fonction';

Télécharger

  1. // exemple dans plugins.xml
  2. <pipeline>
  3. <nom>formulaire_charger</nom>
  4. <inclure>explos_pipelines.php</inclure>
  5. </pipeline>

Télécharger

Toutes les fonctions de modification de contenu des objets SPIP passent maintenant par les pipelines pre_edition et post_edition permettant de modifier l’enregistrement. Tous les formulaires CVT ont aussi 3 pipelines formulaire_charger, formulaire_verifier, formulaire_traiter pour créer des actions supplémentaires. Un autre nouveau pipeline peut être très pratique : recuperer_fond appelé juste après la compilation d’un squelette et permettant de lui ajouter du contenu.

Voici 3 exemples de plus en plus complexes :

  • Au chargement d’un formulaire "Ecrire à un auteur", ajouter l’email de l’expediteur si celui ci est connu dans le champ prévu.
  1. // au chargement d'un formulaire
  2. function explos_formulaire_charger($flux){
  3. if ($flux['args']['form']=='ecrire_auteur') {
  4. if ($mail = $GLOBALS['visiteur_session']['email'])
  5. $flux['data']['email_message_auteur'] = $mail;
  6. }
  7. return $flux;
  8. }

Télécharger

  • Ajouter une liste de documents après le formulaire "ajouter un document" (plugin ajaxforms) qui se rechargera en ajax en même temps que le formulaire.
  1. // apres le calcul d'un fond
  2. function explos_recuperer_fond($flux){
  3. if ($flux['args']['fond']=='formulaires/ajouter_un_document') {
  4. $contexte=array();
  5. // si objet (article, rubrique...),
  6. // on calcule le contexte (id_article=XX)
  7. if ($objet = $flux['args']['contexte']['objet']) {
  8. $_id_objet = id_table_objet($objet);
  9. $contexte[$_id_objet] = $flux['args']['contexte']['id_objet'];
  10. }
  11. // compilation du squelette avec le contexte
  12. // et ajout du resultat a la fin du formulaire
  13. $liste = recuperer_fond('noisettes/inc-mini-documents',$contexte);
  14. $flux['data']['texte'] .= "\n".$liste;
  15. }
  16. return $flux;
  17. }

Télécharger

  • Enfin, un exemple plus complexe : au moment de publier un article, d’un secteur particulier (8 ici pour simplifier), fixer la date de publication à j+1 du dernier article publié dans le secteur si sa date est supérieure à aujourd’hui, sinon fixer la date à aujourd’hui.
  1. // a la pre_edition d'elements spip.
  2. function explos_pre_edition($flux){
  3. if ($flux['args']['action'] == 'instituer'
  4. AND $flux['args']['table'] == 'spip_articles'
  5. AND $flux['data']['statut'] == 'publie'
  6. ) {
  7. $id_secteur = sql_getfetsel('id_secteur','spip_articles','id_article='.sql_quote($flux['args']['id_objet']));
  8. if ($id_secteur AND $id_secteur == 8) {
  9. // on cherche la date de derniere publication
  10. $dernier = sql_getfetsel('date','spip_articles',array('id_secteur=' . sql_quote($id_secteur), 'statut=' . sql_quote('publie')),'','date DESC','1');
  11. $dernier = strtotime($dernier);
  12. $aujourdhui = mktime(0, 0, 0, date("m") , date("d"), date("Y"));
  13. // le dernier est vieux, on prend aujourd'hui
  14. if ($dernier < $aujourdhui) {
  15. $flux['data']['date'] = date('Y-m-d H:i:s');
  16. }
  17. // sinon on prend le jour suivant
  18. else {
  19. $flux['data']['date'] = date('Y-m-d H:i:s',
  20. mktime(date("H"), date("i"), date("s"), date("m",$dernier), date("d",$dernier)+1, date("Y",$dernier)));
  21. }
  22. }
  23. }
  24. return $flux;
  25. }

Télécharger

Créer ses propres pipelines

Les plugins de SPIP pouvaient créer leurs pipelines en le déclarant dans le fichier d’options du plugin. Les pipelines étaient uniquement pour PHP. Un squelette peut maintenant définir ses propres pipelines avec la balise #PIPELINE{nom} de sorte que des plugins peuvent ajouter du contenu à certains endroits prévus par un squelette.

Pour un exemple, voici un menu qui s’agrandit lorsque des plugins se déclarent dedans (voir aussi le plugin Boussole qui utilise #PIPELINE{boussole} )

  • Un squelette ecrit [(#PIPELINE{explos_menu,#ARRAY}|en_liste_svp)]
  • Des plugins ajoutent des elements au tableau comme ceci (en déclarant le pipeline "explos_menu" dans le fichier plugin.xml) :
  1. function monplugin_explos_menu($flux){
  2. $flux[10] = array(
  3. 'page'=>'lien_a_generer',
  4. 'titre'=>_T("monplugin:chaine_langue"),
  5. );
  6. return $flux;
  7. }

Télécharger

  • La fonction en_liste_svp() peut être de la sorte :
  1. function en_ligne_svp($flux){
  2. ksort($flux);
  3. $html = '';
  4. foreach($flux as $i) {
  5. $url = generer_url_public($i['page']);
  6. $titre = attribut_html($i['titre']);
  7. $html .= "<a href='$url'>$titre</a>";
  8. }
  9. return $html;
  10. }

Télécharger

  • on pourrait tout aussi bien éviter l’utilisation de cette fonction en utilisant le plugin SPIP-Bonux qui permet de boucler des tableaux. On obtiendrait ainsi :
  1. <BOUCLE_menu(POUR){tableau #PIPELINE{explos_menu,#ARRAY}}{par cle}>
  2. <a href="[(#URL_PAGE{[(#VALEUR|table_valeur{page})]})]">
  3. [(#VALEUR|table_valeur{titre})]
  4. </a>
  5. </BOUCLE_menu>

Télécharger

Joindre les deux boucles

Si vous souhaitez créer un objet (appelons le Wistiti) qui pourrait être lié aux articles, aux rubriques ou aux mots clés (pourquoi pas hein ?!), SPIP propose maintenant un moyen simple de s’en sortir.

Il suffit de créer une table spip_wistitis ayant un id_wistiti comme clé primaire et d’autres champs. Une table de liaison avec les autres objets de SPIP sera nommée spip_wistitis_liens et se composera d’au minimum 3 champs : id_wistiti, objet, id_objet.

Pour déclarer et installer les tables, il faudra définir où se situe le fichier d’installation, ainsi que signaler l’utilisation de certains pipelines dans le fichier plugin.xml :

  1. <install>base/wistitis_install.php</install>
  2. <pipeline>
  3. <nom>declarer_tables_interfaces</nom>
  4. <inclure>base/wistitis.php</inclure>
  5. </pipeline>
  6. <pipeline>
  7. <nom>declarer_tables_principales</nom>
  8. <inclure>base/wistitis.php</inclure>
  9. </pipeline>
  10. <pipeline>
  11. <nom>declarer_tables_auxiliaires</nom>
  12. <inclure>base/wistitis.php</inclure>
  13. </pipeline>

Télécharger

Commençons par regarder le fichier base/wistitis.php qui permet de définir la table à créer et les jointures à appliquer :

  1. <?php
  2. if (!defined("_ECRIRE_INC_VERSION")) return;
  3.  
  4. function wistitis_declarer_tables_interfaces($interface){
  5. // definir les jointures possibles
  6. $interface['tables_jointures']['spip_wistitis'][] = 'wistitis_liens';
  7. $interface['tables_jointures']['spip_wistitis_liens'][] = 'wistitis';
  8. $interface['tables_jointures']['spip_articles'][] = 'wistitis_liens';
  9. $interface['tables_jointures']['spip_mots'][] = 'wistitis_liens';
  10. $interface['tables_jointures']['spip_rubriques'][] = 'wistitis_liens';
  11.  
  12. // definir les noms raccourcis pour les <BOUCLE_(WISTITIS) ...
  13. $interface['table_des_tables']['wistitis']='wistitis';
  14. $interface['table_des_tables']['wistitis_liens']='wistitis_liens';
  15.  
  16. // Titre pour url
  17. $interface['table_titre']['wistitis'] = "titre, '' AS lang";
  18. return $interface;
  19. }
  20.  
  21. function wistitis_declarer_tables_principales($tables_principales){
  22. // definition de la table wistitis
  23. $spip_wistitis = array(
  24. "id_wistiti" => "bigint(21) NOT NULL",
  25. "titre" => "varchar(255) NOT NULL DEFAULT ''",
  26. "maj" => "TIMESTAMP");
  27.  
  28. // definir les cle primaire et secondaires
  29. $spip_wistitis_key = array(
  30. "PRIMARY KEY" => "id_wistiti");
  31.  
  32. // inserer dans le tableau
  33. $tables_principales['spip_wistitis'] = array(
  34. 'field' => &$spip_wistitis,
  35. 'key' => &$spip_wistitis_key);
  36.  
  37. return $tables_principales;
  38. }
  39.  
  40. function wistitis_declarer_tables_auxiliaires($tables_auxiliaires){
  41. $spip_wistitis_liens = array(
  42. "id_wistiti" => "bigint(21) NOT NULL",
  43. "objet" => "VARCHAR (25) DEFAULT '' NOT NULL",
  44. "id_objet" => "bigint(21) NOT NULL");
  45.  
  46. $spip_wistitis_liens_key = array(
  47. "PRIMARY KEY" => "id_wistiti,id_objet,objet");
  48.  
  49. $tables_auxiliaires['spip_grappes_liens'] = array(
  50. 'field' => &$spip_wistitis_liens,
  51. 'key' => &$spip_wistitis_liens_key);
  52.  
  53. return $tables_auxiliaires;
  54. }
  55. ?>

Télécharger

Les tables étant alors déclarées, il est possible d’ajouter les fonctions d’installation et de suppression des tables du plugin. Pour cela, il faut créer les 2 fonctions correspondantes dans le fichier d’installation :

  1. <?php
  2. if (!defined("_ECRIRE_INC_VERSION")) return;
  3. include_spip('inc/meta');
  4.  
  5. // fonction d'installation, mise a jour de la base
  6. function wistitis_upgrade($nom_meta_base_version,$version_cible){
  7. $current_version = 0.0;
  8. if ( (!isset($GLOBALS['meta'][$nom_meta_base_version]) )
  9. || (($current_version = $GLOBALS['meta'][$nom_meta_base_version])!=$version_cible)){
  10. include_spip('base/wistitis');
  11. if (version_compare($current_version,'0.0','<=')){
  12. include_spip('base/create');
  13. include_spip('base/abstract_sql');
  14. // cette fonction cree les tables declarees manquantes
  15. // ou ajoute des champs declares, manquants
  16. creer_base();
  17. echo "Installation des Wistitis r&eacute;alis&eacute;e<br/>";
  18. ecrire_meta($nom_meta_base_version,$current_version=$version_cible,'non');
  19. }
  20. }
  21. }
  22.  
  23. // fonction de desinstallation
  24. function wistitis_vider_tables($nom_meta_base_version) {
  25. sql_drop_table("spip_wistitis");
  26. sql_drop_table("spip_wistitis_liens");
  27. effacer_meta($nom_meta_base_version);
  28. }
  29. ?>

Télécharger

Vous aurez besoin de créer 2 formulaires CVT : un pour editer un Wistiti, l’autre pour lier un wistiti à un objet. Je ne détaille pas cela, seulement je vais présenter une fonction de SPIP bien pratique pour la modification de contenu.

Lorsque vous créez (via le formulaire) un nouveau Wistiti, vous allez effectuer une insertion dans la base de donnée en récupérant le nouvel identifiant créé, par exemple, dans la partie traiter() du formulaire CVT vous pouvez tester cela :

  1. function formulaire_editer_wistiti_traiter($id_wistiti=""){
  2. // si pas d'identifiant transmis on cree un nouvel enregistrement
  3. if (!$id_wistiti = intval($id_wistiti)) {
  4. $id_wistiti = sql_insertq("spip_wistitis");
  5. }
  6. ...

Télécharger

Une fois que l’enregistrement est présent, il suffit de récupérer les champs et d’effectuer la modification du contenu en fonction de ce qui a été posté dans le formulaire, grâce à la fonction modifier_contenu() :

  1. // modifier le contenu via l'API
  2. include_spip('inc/modifier');
  3. // recuperer les champs (y en a qu'un, c'est facile !)
  4. $c = array();
  5. foreach(array('titre') as $x)
  6. $c[$x] = _request($x);
  7. // modifier
  8. modifier_contenu(
  9. 'wistitis', // nom de la table
  10. $id_wistiti, // identifiant
  11. array('nonvide' => array('titre' => _T('info_sans_titre'))), // parametres
  12. $c); // champs a modifier

Télécharger

Cette fonction a l’avantage d’appeler les pipelines pre_edition et post_edition pour l’appel realisé.

Ce qui est intéressant de noter, c’est qu’une fois les liaisons établies, SPIP sait se débrouiller à joindre les boucles et à traduire automatiquement les critères {id_article=XX} en requete SQL objet='article' AND id_objet=XX lorsque nécessaire.

Ainsi, vous pouvez ecrire :

  1. Tous les wistitis liés à l'article 3
  2. <BOUCLE_(WISTITIS){id_article=3}>
  3.  
  4. Toutes les rubriques liées au wistiti 8
  5. <BOUCLE_(RUBRIQUES){id_wistiti=8}>
  6.  
  7. Tous les articles liés à un wistiti dont le titre est "wapiti" :
  8. <BOUCLE_(ARTICLES){wistitis.titre=wapiti}>

Télécharger

Mutualiser les ressources

Ce qui peut intéresser les hébergeurs, ou les développeurs qui souhaitent se faciliter la maintenance est la réalisation de mutualisations. Un jeu de fichiers de SPIP peut maintenant servir à faire fonctionner plusieurs sites en SPIP. C’est ce qu’on appelle la mutualisation du noyau de SPIP. Il est par ailleurs possible de mutualiser d’autres éléments de SPIP, comme des plugins ou des squelettes.

Pour cela, une fonction spécifique (spip_initialisation) permet de démarrer SPIP avec un certain nombre de paramètres définissant le lieu des fichiers indispensables au site à charger. Ces paramètres souvent calculés à partir de l’URL de la page appelée permettent de charger un site ou un autre en fonction de l’URL appelée. Le serveur php peut alors bénéficier d’un meilleur cache des fichiers (puisque un même fichier est appelé pour plusieurs sites) et le webmaster bénéficie d’une maintenance plus facile : mettre à jour tous les sites revient à mettre à jour uniquement le noyau de SPIP.

Un plugin mutualisation facile permet de faciliter la création de tels espaces.

Compléments alimentaires...

Il y a forcément des oublis... Et un manque de temps pour tout écrire !
L’article sera complété au fur et à mesure...

Sur ce, bon soleil à tous.

Coup de pouce

Pour me maintenir en éveil et en pleine forme physique et mentale, vous pouvez me faire le cadeau d'un jus de fruit pressé.

En plus de m'hydrater, de m'offrir une alimentation saine crudivore et frugivore, cela peut aussi me motiver à produire d'autres documentations ou peut-être à prendre des vacances ! :)

Vous pouvez également me « Flattrer » si vous utilisez le service en ligne très malin Flattr de microdonations qui permet d'allouer un budget mensuel à des créateurs de contenu. Votre budget est partagé chaque mois entre toutes les personnes que vous avez flattées ce mois là.