Interfaces extras !

, par Matthieu Marcillaud

Après les petites péripéties de tout à l’heure (Outils de debug), nous pouvons poursuivre la modification du plugin champs extras. Nous souhaitons 2 choses : pouvoir ordonner les champs extras, et pouvoir prendre en compte les champs déjà présents dans la base de données !

Ce qui parait évident au premier abord ne l’est pas forcément dans la réalité. On va être méthodique et commencer par corriger les principaux écueils actuels du plugin :

  • ne pas déclarer les autorisations dans le fichier d’option mais bien en utilisant un fichier spécifique appelé par le pipeline autoriser,
  • ne pas jouer au fou avec des id_extra basé sur les clés de tableau qui pourraient être problématiques si deux administrateurs modifient un même extra en même temps (bon, j’admets c’est peu probable),
  • utiliser la classe ChampExtra aussi dans l’interface plutôt que d’utiliser un autre tableau, ce qui sera plus pérenne,
  • rationaliser les noms des fonctions entre les deux plugins,
  • ajouter des points de log.

Rationaliser les nommages

Afin d’éviter de trop se disperser dans les noms des fonctions entre le plugin ’core’ et le plugin ’interface’ pour les champs extras, nous allons les renommer, ainsi que leurs fonctions cextras et iextras. Et en plus, je l’ai déjà fait, ce qui donne : http://zone.spip.org/trac/spip-zone/changeset/25554 et le retardataire http://zone.spip.org/trac/spip-zone/changeset/25555 !

Autorisations au bon endroit !

Je l’avais oublié un peu trop vite, mais il y a un pipeline pour les autorisations, qui permet de charger un fichier contenant de nouvelles autorisations.

Nous allons donc le déclarer dans l’interface à la place du fichier d’options.

  1. <pipeline>
  2. <nom>autoriser</nom>
  3. <inclure>inc/iextras_autoriser.php</inclure>
  4. </pipeline>

Télécharger

Le fichier d’options est déplacé, renommé dans inc/iextras_autoriser.php et modifié comme suit :

  1. <?php
  2. if (!defined("_ECRIRE_INC_VERSION")) return;
  3.  
  4. // fonction pour le pipeline, n'a rien a effectuer
  5. function iextras_autoriser(){}
  6.  
  7. // declarations d'autorisations
  8. function autoriser_iextras_onglet_dist($faire, $type, $id, $qui, $opt) {
  9. return autoriser('configurer', 'iextra', $id, $qui, $opt);
  10. }
  11.  
  12. function autoriser_iextras_configurer_dist($faire, $type, $id, $qui, $opt) {
  13. return autoriser('webmestre', $type, $id, $qui, $opt);
  14. }
  15. ?>

Télécharger

La fonction iextras_autoriser doit être présente, mais elle ne sert à rien. Elle est juste prétexte à la lecture du fichier d’autorisation au moment de l’appel à la librairie autoriser de SPIP.

Ajouter des points de logs

Certaines actions effectuées méritent d’être logguées, comme l’ajout et la suppression de champ extra.

Créons une fonction pour cela dans le core du plugin :

  1. function extras_log($contenu, $important=false) {
  2. if ($important
  3. OR (defined('EXTRAS_DEBUG') and EXTRAS_DEBUG)) {
  4. spip_log($contenu,'extras');
  5. }
  6. }

Télécharger

On ajoutera au fur et à mesure des appels aux points essentiels du plugin.

Utiliser la classe ChampExtra dans l’interface graphique

Les objets PHP peuvent se linéariser et se dé-linéariser de la même manière que les tableaux PHP. La condition est que la classe soit définie au moment de la dé-linéarisation. Ce qu’on appelle linéarisation, ou sérialisation, c’est l’action de transformer une variable PHP en une chaine de caractère particulière pouvant alors être stockée, par exemple dans un fichier. Ces chaines peuvent être ensuite dé-sérialisées pour obtenir de nouveau la variable d’origine.

Donc, à la place d’un tableau PHP, nous allons stocker une classe ChampExtra.

Rien d’extraordinaire à montrer ici : http://zone.spip.org/trac/spip-zone/changeset/25559

Utiliser un identifiant qui identifie réellement !

L’identifiant que nous avons attribué aux champs extras correspond en ce moment à la clé du tableau PHP contenant la liste des champs extras. Cette clé, générée automatiquement n’identifie pas les contenus, mais juste une position dans un tableau. Si par mégarde la position change entre le moment où l’on affiche la liste des extras et le moment où l’on appuie sur le bouton supprimer (parce que quelqu’un a déplacé l’extra en question presque au même moment), alors ce sera un mauvais champ qui sera supprimé ! Pas good !

Nous allons donc créer un identifiant qui représente réellement le contenu du champ extra, de sorte que si le champ est déplacé dans le tableau, ce soit bien lui néanmoins qui soit supprimé et non son voisin.

On ajoute donc des petites fonctions à la classe ChampExtra pour gérer cet identifiant :

  1. // creer l'id du champ extra :
  2. function make_id(){
  3. // creer un hash
  4. $hash = array();
  5. foreach ($this as $cle=>$val) {
  6. if ($cle[0]!=='_') {
  7. $hash[] = $val;
  8. }
  9. }
  10. $this->_id = substr(md5(serialize($hash)),0,6);
  11. }
  12.  
  13. // determiner un identifiant
  14. function get_id(){
  15. if (!$this->_id) $this->make_id();
  16. return $this->_id;
  17. }
  18.  
  19. // transformer en tableau PHP les variable publiques de la classe.
  20. function toArray(){
  21. $extra = array();
  22. foreach ($this as $cle=>$val) {
  23. if ($cle[0]!=='_') {
  24. $extra[$cle] = $val;
  25. }
  26. }
  27. $extra['id_extra'] = $this->get_id();
  28. return $extra;
  29. }

Télécharger

La première fonction make_id() crée un hash (une chaine unique) en utilisant les variables de la classe ChampExtra qui ne commencent pas par un souligné.

La seconde get_id() renvoie l’identifiant (ou le crée puis le renvoie), enfin, la dernière déjà présente mais modifiée retourne un tableau PHP avec le contenu de la classe ChampExtra et en profite pour ajouter dedans une clé ’id_extra’ avec l’identifiant en question.

De la sorte, le formulaire peut maintenant récupérer des données en fonction d’un identifiant fiable. Voyons par exemple comment devient la fonction charger du formulaire :

  1. function formulaires_editer_champ_extra_charger_dist($id_extra='new', $redirect=''){
  2. // nouveau ?
  3. $new = ($id_extra == 'new') ? ' ': '';
  4.  
  5. // valeur par defaut
  6. $valeurs = array(
  7. 'champ' => '',
  8. 'table' => '',
  9. 'type' => '',
  10. 'label' => '',
  11. 'sql' => "text NOT NULL DEFAULT ''",
  12. 'id_extra' => $id_extra,
  13. 'new' => $new,
  14. 'redirect' => $redirect,
  15. );
  16.  
  17. // si un extra est demande (pour edition)
  18. // remplir les valeurs avec infos de celui-ci
  19. if (!$new) {
  20. $extras = iextras_get_extras();
  21. foreach($extras as $extra) {
  22. if ($extra->get_id() == $id_extra) {
  23. $valeurs = array_merge($valeurs, $extra->toArray());
  24. break;
  25. }
  26. }
  27. }
  28. return $valeurs;
  29. }

Télécharger

Lorsque nous sommes face à un champ en modification (pas nouveau), on parcoure la liste des champs extras tant qu’on ne trouve pas celui ayant le bon identifiant. Lorsque c’est le bon, on ajoute ses valeurs dans le chargement. C’est un peu plus long en traitement, mais ce n’est pas un lieu d’un site fréquenté (ça ne sert qu’à la création du site généralement). Les changements complets sont là : http://zone.spip.org/trac/spip-zone/changeset/25560

Nous avons maintenant corrigé les principaux écueils et pouvons attaquer la prise en compte par le plugin des champs déjà ajoutés dans la base de donnée. Ca ne va pas être si simple que ça !

Bien poser la problématique...

Quand on ne sait pas trop comment commencer, il y a plusieurs possibilités :

  • dormir (le plus efficace certainement),
  • faire des schémas et des plans sur papier qui permettent d’avoir une vue d’ensemble meilleure ou encore écrire en français la problématique,
  • commencer par ce dont on est certain ; le reste viendra en codant.

Cernons la problématique : certains webmestres ont déjà ajouté dans des tables des champs via phpMyAdmin ou en écrivant dans un squelette SPIP :

  1. <?php
  2. sql_alter("TABLE spip_articles ADD COLUMN fruits text NOT NULL DEFAULT ''");
  3. ?>

Télécharger

Ces champs déjà présents ne sont pour le moment pas pris en compte par le plugin. La question, c’est comment les prendre en compte, et que prendre en compte aussi. Car il peut y avoir une lecture à plusieurs niveaux : le champ peut exister et être déclaré, mais sans être accessible aux rédacteurs d’articles, c’est à dire ne s’affichant pas dans les formulaires. Le champ peut aussi être déclaré et devoir s’afficher.

Le plus simple, il me semble, c’est de pouvoir dire qu’un champ peut non seulement être supprimé, mais peut aussi être lié et délié au plugin champs extras. Ainsi, on pourrait lier un champ au plugin, il s’affiche alors dans le formulaire, puis le délier (il ne s’y affiche plus, et n’est plus géré par le plugin d’interface) mais est toujours présent dans la table. Ceci résoudrait l’épineux problème de la suppression car sinon, on ne pourrait retirer un champ extra ajouté dans le plugin qu’en le supprimant... Ce qui n’est pas forcément idéal.

On a donc un premier aperçu de ce qu’il faut réaliser (ajouter un champ existant / délier un champ extra du plugin).

Commençons par pouvoir ajouter les champs existants. Il faut déjà pouvoir les lister. Enfin, lister tous les champs de la base de donnée qui ne sont pas déclarés à SPIP, c’est bien de ceux là qu’il s’agit, car ceux déjà déclarés ont toutes les chances d’être déjà gérés par d’autres plugins. Ca tombe assez bien car Fil a déjà réalisé cette partie dans le futur défunt plugin « extras2 » que ce plugin est en train de manger !

On va donc intégrer tout ou partie de son code. (¡ Que viva GPL !)

Récupérer les descriptions des tables SQL

On va donc comparer deux descriptions : celle des tables réelles et celles des déclarations faites à SPIP. La différence des deux donnera les champs possiblement utilisables par le plugin. Ajoutons ces fonctions au core, adaptées de Fil :

  1. // Liste les champs anormaux par rapport aux definitions de SPIP
  2. // (aucune garantie que $connect autre que la connexion principale fasse quelque chose)
  3. function extras_champs_anormaux($connect='') {
  4. // recuperer les tables et champs accessibles
  5. $tout = extras_base($connect);
  6.  
  7. // recuperer les champs SPIP connus
  8. include_spip('base/auxiliaires');
  9. include_spip('base/serial');
  10. $tables_spip = array_merge($GLOBALS['tables_principales'], $GLOBALS['tables_auxiliaires']);
  11.  
  12. // chercher ce qui est different
  13. $ntables = array();
  14. $nchamps = array();
  15. foreach ($tout as $table => $champs) {
  16. if (!isset($tables_spip[$table]['field'])) {
  17. $nchamps[$table] = $champs;
  18. } else {
  19. foreach($champs as $champ => $desc) {
  20. if (!isset($tables_spip[$table]['field'][$champ])) {
  21. $nchamps[$table][$champ] = $desc;
  22. }
  23. }
  24. }
  25. }
  26.  
  27. unset($tout);
  28. if($nchamps) {
  29. $tout = $nchamps;
  30. }
  31.  
  32. return $tout;
  33. }
  34.  
  35. // etablit la liste de tous les champs de toutes les tables du connect donne
  36. // ignore la table 'spip_test'
  37. function extras_base($connect='') {
  38. $champs = array();
  39. foreach (extras_tables($connect) as $table) {
  40. if ($table != 'spip_test') {
  41. $champs[$table] = extras_champs($table, $connect);
  42. }
  43. }
  44. return $champs;
  45. }
  46.  
  47. // liste les tables dispos ans la connexion $connect
  48. function extras_tables($connect='') {
  49. $a = array();
  50. if ($s = sql_showbase(null, $connect)) {
  51. while ($t = sql_fetch($s, $connect)) {
  52. $a[] = array_pop($t);
  53. }
  54. }
  55. return $a;
  56. }
  57.  
  58.  
  59. // liste les champs dispos ans la table $table de la connexion $connect
  60. function extras_champs($table, $connect) {
  61. $desc = sql_showtable($table, null, $connect);
  62. if (is_array($desc['field'])) {
  63. return $desc['field'];
  64. } else {
  65. return array();
  66. }
  67. }

Télécharger

Commençons par observer la fonction extra_tables(). Elle demande quelle sont les tables réellement présentes dans la base, gràce à la fonction sql sql_showbase(). Le paramètre $connect correspond au nom du fichier de connexion qu’utilise SPIP pour se connecter (dans le répertoire connect/). Lorsqu’il n’est pas renseigné, SPIP utilise sa connexion principale (la connexion ayant installée le SPIP).

La fonction extra_champs() prend un nom de table en entrée et récupère la liste des champs réels et leurs descriptions SQL, ceci gràce à la fonction sql_showtable().

La fonction extra_base() crée un tableau de toutes les descriptions des champs pour chaque table rencontrée.

Reste maintenant la fonction extras_champs_anormaux(). Elle récupère tous les champs réels en appelant extra_base(), tous les champs SPIP déclarés en concaténant les tableaux tables_principales et tables_auxiliaires, et retourne la liste des champs présents en base, mais non déclarés à SPIP.

Afficher les champs utilisables dans l’interface

Nous allons maintenant afficher ces champs dans l’interface graphique du plugin, avec des actions pour les associer ou les supprimer définitivement.

Ajoutons déjà l’appel à un squelette les affichant dans exec/iextras.php :

  1. include_spip('inc/cextras_gerer');
  2. echo recuperer_fond('prive/contenu/champs_extras_possibles', array(
  3. 'extras'=>extras_champs_anormaux(),
  4. ));

Télécharger

Puis créons le squelette champs_extras_possibles.html, sur le même principe que son collègue prive/contenu/champs_extra.html :

  1. <BOUCLE_si_extras(CONDITION){si #ENV{extras}}>
  2. [(#CHEMIN{images/iextras-24.png}|debut_cadre_trait_couleur{1, "", <:iextras:liste_des_extras_possibles:>})]
  3. <BOUCLE_tables(POUR){tableau #ENV**{extras}}>
  4. <h2>[(#VAL{iextras:table_}|concat{#CLE|replace{^spip_|s$,''}}|_T)]</h2>
  5. <B_extras>
  6. <ul class="liste_extras_possibles">
  7. <BOUCLE_extras(POUR){tableau #VALEUR}>
  8. <li>
  9. #CLE
  10. | <a href="[(#URL_ACTION_AUTEUR{iextras, [associer_champ/#_tables:CLE/(#CLE)], #SELF})]"><:iextras:action_associer:></a>
  11. | <a href="[(#URL_ACTION_AUTEUR{iextras, [supprimer_champ/#_tables:CLE/(#CLE)], #SELF})]" class="supprimer"><:iextras:action_supprimer:></a>
  12.  
  13. </li>
  14. </BOUCLE_extras>
  15. </ul>
  16. </B_extras>
  17. </BOUCLE_tables>
  18. <script type="text/javascript">
  19. (function($) {
  20. $('ul.liste_extras_possibles .supprimer').click(function(){
  21. return confirm("<:iextras:supprimer_reelement|attribut_html:>");
  22. });
  23. })(jQuery);
  24. </script>
  25. [(#VAL{1}|fin_cadre_trait_couleur)]
  26. </BOUCLE_si_extras>

Télécharger

Nous bouclons sur les différentes tables, puis sur les champs et nous affichons un lien pour associer et un lien pour supprimer le champ.

Nous en profitons pour ajouter en même temps, sur l’autre squelette prive/contenu/champs_extras.html un lien pour désassocier un champ extra déclaré :

Il faut maintenant s’occuper de traiter les différentes actions.

Créer les nouvelles actions

Modifions le fichier actions/iextras.php pour lui ajouter ces traitements. Cela peut donner le résultat ci-dessous. Chaque action appelle une fonction dédiée. Notons juste le cas de l’association d’un champ SQL déjà présent : après l’appel de la fonction d’association, on redirige la page vers le formulaire de modification. C’est un peu déroutant, mais lorsqu’on ajoute un tel champ, une des premières choses faite sera de renseigner correctement le label, donc envoyer sur le formulaire parait assez cohérent. (NdA : finalement j’ai réellement trouvé ça trop déroutant en pratique et j’ai mis en commentaire ce code)

Pour cela, on appelle la fonction redirige_par_entete() présent dans la librairie inc/header. Elle prend comme paramètre l’URL de redirection et doit être appelée avant tout affichage sur la page.

  1. <?php
  2.  
  3. if (!defined("_ECRIRE_INC_VERSION")) return;
  4.  
  5. function action_iextras_dist() {
  6. $securiser_action = charger_fonction('securiser_action', 'inc');
  7. $arg = $securiser_action();
  8.  
  9. // droits
  10. include_spip('inc/autoriser');
  11. if (!autoriser('configurer', 'iextra')) {
  12. include_spip('inc/minipres');
  13. echo minipres();
  14. exit;
  15. }
  16.  
  17. @list($arg, $id_extra_ou_table, $champ) = explode ('/', $arg);
  18.  
  19. // actions possibles
  20. if (!in_array($arg, array(
  21. 'supprimer_extra',
  22. 'desassocier_extra',
  23. 'associer_champ',
  24. 'supprimer_champ'))){
  25. include_spip('inc/minipres');
  26. echo minipres(_T('iextras:erreur_action',array("action"=>$arg)));
  27. exit;
  28. }
  29.  
  30. // cas de suppression
  31. if (($arg == 'supprimer_extra') and $id_extra = $id_extra_ou_table){
  32. action_supprimer_champ_extra($id_extra);
  33.  
  34. }
  35.  
  36. // cas de desassociation
  37. if (($arg == 'desassocier_extra') and $id_extra = $id_extra_ou_table){
  38. action_desassocier_champ_extra($id_extra);
  39.  
  40.  
  41.  
  42. }
  43.  
  44. // cas de l'association d'un champ existant
  45. if (($arg == 'associer_champ') and ($table = $id_extra_ou_table) and $champ){
  46. $id_extra = action_associer_champ_sql_comme_champ_extra($table, $champ);
  47. // si l'association fonctionne, rediriger vers le formulaire de modification du champ extra
  48. if ($id_extra) {
  49. // redirection vers le formulaire d'edition du champ
  50. $redirect = generer_url_ecrire('iextras_edit');
  51. $redirect = parametre_url($redirect,'id_extra', $id_extra, '&');
  52. include_spip('inc/header');
  53. redirige_par_entete($redirect);
  54. }
  55. }
  56.  
  57. // cas de la suppression d'un champ existant
  58. if (($arg == 'supprimer_champ') and $table = $id_extra_ou_table){
  59. action_supprimer_champ_sql($table, $champ);
  60. }
  61. }
  62.  
  63. // suppression d'un champ extra donne
  64. function action_supprimer_champ_extra($id_extra) {
  65. include_spip('inc/iextras');
  66. $extras = iextras_get_extras();
  67. foreach($extras as $i=>$extra) {
  68. if ($extra->get_id() == $id_extra) {
  69. extras_log("Suppression d'un champ par auteur ".$GLOBALS['auteur_session']['id_auteur'],true);
  70. extras_log($extra, true);
  71.  
  72. $table = table_objet_sql($extra->table);
  73. sql_alter("TABLE $table DROP ".$extra->champ);
  74.  
  75. unset($extras[$i]);
  76. iextras_set_extras($extras);
  77. break;
  78. }
  79. }
  80. }
  81.  
  82. // desassocier un champ extra
  83. // (ne plus le gerer avec le plugin champ extra
  84. // mais ne pas le supprimer de la base de donnee)
  85. function action_desassocier_champ_extra($id_extra) {
  86. include_spip('inc/iextras');
  87. $extras = iextras_get_extras();
  88. foreach($extras as $i=>$extra) {
  89. if ($extra->get_id() == $id_extra) {
  90. extras_log("Desassociation du champ $extra->table/$extra->champ par auteur ".$GLOBALS['auteur_session']['id_auteur'],true);
  91.  
  92. unset($extras[$i]);
  93. iextras_set_extras($extras);
  94. break;
  95. }
  96. }
  97. }
  98.  
  99. // definir un champ SQL existant comme un champ extra a prendre
  100. // en compte par ce plugin
  101. function action_associer_champ_sql_comme_champ_extra($table, $champ){
  102. // recuperer la description du champ
  103. include_spip('inc/cextras_gerer');
  104. $champs = extras_champs_anormaux();
  105. if (isset($champs[$table][$champ])) {
  106. $sql = $champs[$table][$champ];
  107. // creer un champ extra avec ce champ
  108. $extra = new ChampExtra(array(
  109. 'table' => objet_type($table),
  110. 'champ' => $champ,
  111. 'label' => 'label_'.$champ,
  112. 'type' => 'input',
  113. 'sql' => $sql,
  114. ));
  115. // penser a creer une fonction pour ajouter et supprimer un champ...
  116. // ajout du champ
  117. extras_log("Ajout d'un champ deja existant par auteur ".$GLOBALS['auteur_session']['id_auteur'],true);
  118. extras_log($extra, true);
  119.  
  120. $extras = iextras_get_extras();
  121. $extras[] = $extra;
  122. iextras_set_extras($extras);
  123.  
  124. // retourner id_extra
  125. return $extra->get_id();
  126. }
  127. }
  128.  
  129. // suppression de la base d'un champ d'une table donnee.
  130. function action_supprimer_champ_sql($table, $champ) {
  131. // recuperer les descriptions
  132. // pour verifier que le champ n'est pas declare par quelqu'un
  133. include_spip('inc/cextras_gerer');
  134. $champs = extras_champs_anormaux();
  135. if (isset($champs[$table][$champ])) {
  136. // suppression
  137. extras_log("Suppression du champ $table/$champ par auteur ".$GLOBALS['auteur_session']['id_auteur'],true);
  138.  
  139. $table = table_objet_sql($table);
  140. sql_alter("TABLE $table DROP ".$champ);
  141. }
  142. }
  143.  
  144. ?>

Télécharger

Ordonner les champs extras

Lorsqu’il y a au moins deux champs extras déclarés sur une table, on peut avoir envie de les afficher dans un certain ordre. Nous allons donc ajouter deux actions : monter et descendre. Par ailleurs, on ne peut pas monter le premier élément ni descendre le dernier !

  1. [(#TOTAL_BOUCLE|>{1}|oui)
  2. [(#COMPTEUR_BOUCLE|!={1}|oui)
  3. | <a href="[(#URL_ACTION_AUTEUR{iextras, [monter_extra/(#VALEUR|table_valeur{id_extra})], #SELF})]"><:iextras:action_monter:></a>
  4. ]
  5. [(#COMPTEUR_BOUCLE|!=={#TOTAL_BOUCLE}|oui)
  6. | <a href="[(#URL_ACTION_AUTEUR{iextras, [descendre_extra/(#VALEUR|table_valeur{id_extra})], #SELF})]"><:iextras:action_descendre:></a>
  7. ]
  8. ]

Télécharger

En ajoutant ces deux nouvelles actions, on remarque qu’il commence à y en avoir beaucoup (d’actions) et que leur affichage est un peu le fouillis. Avant de traiter les actions, on va effectuer un peu de présentation CSS.

D’une part, nous allons mettre les actions dans une liste ul/li :

  1. <ul class="actions">
  2. [(#TOTAL_BOUCLE|>{1}|oui)
  3. [(#COMPTEUR_BOUCLE|!={1}|oui)
  4. <li><a href="[(#URL_ACTION_AUTEUR{iextras, [monter_extra/(#VALEUR|table_valeur{id_extra})], #SELF})]" title="<:iextras:action_monter_title|attribut_html:>"><:iextras:action_monter:></a></li>
  5. ]
  6. [(#COMPTEUR_BOUCLE|!=={#TOTAL_BOUCLE}|oui)
  7. <li><a href="[(#URL_ACTION_AUTEUR{iextras, [descendre_extra/(#VALEUR|table_valeur{id_extra})], #SELF})]" title="<:iextras:action_descendre_title|attribut_html:>"><:iextras:action_descendre:></a></li>
  8. ]
  9. ]
  10. <li><a href="[(#URL_ECRIRE{iextras_edit}|parametre_url{id_extra,#VALEUR|table_valeur{id_extra}})]" title="<:iextras:action_modifier_title|attribut_html:>"><:iextras:action_modifier:></a></li>
  11. <li><a href="[(#URL_ACTION_AUTEUR{iextras, [desassocier_extra/(#VALEUR|table_valeur{id_extra})], #SELF})]" title="<:iextras:action_desassocier_title|attribut_html:>"><:iextras:action_desassocier:></a></li>
  12. <li><a href="[(#URL_ACTION_AUTEUR{iextras, [supprimer_extra/(#VALEUR|table_valeur{id_extra})], #SELF})]" class="supprimer" title="<:iextras:action_supprimer_title|attribut_html:>"><:iextras:action_supprimer:></a></li>
  13. </ul>

Télécharger

Ajouter du CSS dans l’interface privé

Ensuite, nous allons ajouter du CSS pour présenter cette liste. Comment ? Très simple, chaque plugin peut déclarer un fichier prive/style_prive_plugin_prefix.html qui sera interprété comme un fichier css. Créons le fichier prive/style_prive_plugin_iextras.html et ajoutons dedans :

  1. ul.actions {text-align:right;}
  2. ul.actions li{display:inline; padding-right:3px; border-right:1px solid ##ENV{couleur_claire};}

Télécharger

On remarquera que ce squelette reçoit quelques paramètres (lang, couleur_foncee, couleur_claire et ltr (direction de la langue)). Nous utilisons ici la couleur claire pour mettre une petite bordure.

Par contre, voir les modifications CSS dans le privé n’est pas simple du tout car d’une part les CSS sont compactées automatiquement dans l’espace privé, mais on peut désactiver cela en ajoutant dans son fichier d’option :

  1. define('_INTERDIRE_COMPACTE_HEAD_ECRIRE',true);

Et d’autre part, ce sont des CSS calculées à partir de squelettes SPIP, et les faire recalculer n’est pas simple. Le plus rapide étant de vider à la fois le cache de SPIP et celui du navigateur.

Traiter les actions monter et descendre

Nous allons utiliser une fonction assez géniale de PHP pour cela : array_splice(). Cette fonction permet d’insérer ou de remplacer des éléments dans un tableau. Nous allons l’utiliser pour insérer notre champ déplacé au bon endroit.

Pour cela, on ordonne notre tableau pour avoir tous les champs d’une même table à la suite, puis il suffit de remonter ou descendre un élément pour modifier l’ordre.

Commençons par trier les champs par table :

  1. // tableau des extras, tries par table SQL
  2. function iextras_get_extras_tries_par_table(){
  3. $extras = iextras_get_extras();
  4. $tables = $extras_tries = array();
  5. foreach($extras as $e) {
  6. if (!isset($tables[$e->table])) {
  7. $tables[$e->table] = array();
  8. }
  9. $tables[$e->table][] = $e;
  10. }
  11. sort($tables);
  12. foreach ($tables as $table) {
  13. foreach ($table as $extra) {
  14. $extras_tries[] = $extra;
  15. }
  16. }
  17. return $extras_tries;
  18. }

Télécharger

Ensuite, nous pouvons ajouter les actions monter et descendre :

  1. // remonter d'un cran un champ extra
  2. function action_monter_champ_extra($id_extra) {
  3. include_spip('inc/iextras');
  4. $extras = iextras_get_extras_tries_par_table();
  5. foreach($extras as $i=>$extra) {
  6. if ($extra->get_id() == $id_extra) {
  7. extras_log("Remonter le champ $extra->table/$extra->champ par auteur ".$GLOBALS['auteur_session']['id_auteur']);
  8.  
  9. if ($i !== 0) {
  10. unset($extras[$i]);
  11. array_splice($extras, $i-1, 0, array($extra));
  12. iextras_set_extras($extras);
  13. }
  14. break;
  15. }
  16. }
  17. }
  18.  
  19. // descendre d'un cran un champ extra
  20. function action_descendre_champ_extra($id_extra) {
  21. include_spip('inc/iextras');
  22. $extras = iextras_get_extras_tries_par_table();
  23. $total = count($extras);
  24. foreach($extras as $i=>$extra) {
  25. if ($extra->get_id() == $id_extra) {
  26. extras_log("Descendre le champ $extra->table/$extra->champ par auteur ".$GLOBALS['auteur_session']['id_auteur']);
  27.  
  28. if ($i+1 !== $total) {
  29. unset($extras[$i]);
  30. array_splice($extras, $i+1, 0, array($extra));
  31. iextras_set_extras($extras);
  32. }
  33. break;
  34. }
  35. }
  36. }

Télécharger

Résultat des courses : http://zone.spip.org/trac/spip-zone/changeset/25572 (et petit « oups »).

Afficher une boite d’information

Pour terminer ce plugin, nous allons ajouter des descriptions sur les pages de l’interface. Rien de transcendant, mais il existe des fonctions et pipelines déjà prévus, alors utilisons les !

Commençons par créer les chaines de langues :

  1. 'info_description_champ_extra' => "Cette page permet de g&eacute;rer des champs extras,
  2. c'est &agrave; dire des champs suppl&eacute;mentaires dans les tables de SPIP,
  3. pris en compte dans les formulaires d'&eacute;dition.",
  4. 'info_description_champ_extra_creer' => "Vous pouvez cr&eacute;er de nouveaux champs qui s'afficheront alors
  5. sur cette page, dans le cadre «Liste des champs extras», ainsi que dans les formulaires.",
  6. 'info_description_champ_extra_presents' => "Enfin, si des champs existent d&eacute;j&agrave; dans votre base de donn&eacute;e,
  7. mais ne sont pas d&eacute;clar&eacute;s (par un plugin ou un jeu de squelettes), vous
  8. pouvez demander &agrave; ce plugin de les g&eacute;rer. Ces champs, s'il y en a,
  9. apparaissent dans un cadre «Liste des champs pr&eacute;sents non g&eacute;r&eacute;s».",

Télécharger

Puis créons une nouvelle fonction cadre_champs_extras_infos() dans le fichier exec/iextras.php. Elle contient simplement l’appel d’un pipeline boite_infos avec un paramètre type indiquant quelle information afficher :

  1. // afficher les informations de la page
  2. function cadre_champs_extras_infos() {
  3. $boite = pipeline ('boite_infos', array('data' => '',
  4. 'args' => array(
  5. 'type'=>'champs_extras',
  6. )
  7. ));
  8.  
  9. if ($boite)
  10. return debut_boite_info(true) . $boite . fin_boite_info(true);
  11. }

Télécharger

Nous appelons la fonction dans la partie gauche de la page comme ceci :

  1. // colonne gauche
  2. echo debut_gauche('', true);
  3. echo cadre_champs_extras_infos();
  4. echo pipeline('affiche_gauche', array('args'=>array('exec'=>'iextras'),'data'=>''));

Télécharger

Le pipeline « boite_infos » appelle automatiquement un squelette prive/infos/$type avec les autres paramètres qui lui sont transmis (ici il n’y a pas d’autre chose que « type »), mais pour les infos de la page auteur sont transmis l’id_auteur ainsi que tout le contenu de l’entrée SQL de l’auteur en question (row) :

  1. $boite = pipeline ('boite_infos', array('data' => '',
  2. 'args' => array(
  3. 'type'=>'auteur',
  4. 'id' => $id_auteur,
  5. 'row' => $auteur
  6. )
  7. ));

Télécharger

Bref, il nous suffit donc, pour avoir quelque chose de fonctionnel, de créer le squelette prive/infos/champs_extras.html avec :

  1. <p><:iextras:info_description_champ_extra:></p>
  2. <p><:iextras:info_description_champ_extra_creer:></p>
  3. <p><:iextras:info_description_champ_extra_presents:></p>

Télécharger

Et c’est tout ! Résultat : http://zone.spip.org/trac/spip-zone/changeset/25575 .

Conclusions

Promis, j’arrête de parler de ce plugin « champs extras », de toute façon il n’y a plus grand chose à dire, l’essentiel est maintenant codé. Il restera à prendre en compte des champs extras autre que le type text ou textarea, comme des sélections, des liste de choix, etc...

En tout cas, cet exemple de plugin vous aura fait découvrir l’essentiel des pipelines de SPIP pour ajouter des éléments dans des formulaires, pour afficher des pages dans le privé. J’espère que cela vous aura été profitable et que ça vous donnera des idées pour vos réalisations.

Plus vous utiliserez les outils prévus par SPIP, meilleure sera sa portabilité au fil des évolutions. Sur ce, bons champs extras !

Vue du plugin Champs Extras
Modification des champs extras

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à.