Carnet Wiki

Astuces longues pour SPIP !

Version 16 — Juillet 2012 denisb

----

  • Partager des données entre plusieurs squelettes inclus (Zpip)
  • ZPIP : faire un inclure de squelette suivant une condition
  • Une boucle qui ne doit être exécutée que dans un certain contexte
  • Afficher la date du jour
  • Jouons avec les dates
  • Exporter au format CSV
  • Extraire les adresses email contenues dans un(des) texte(s)
  • Une fonction pour joliment afficher les variables de #ENV, #GET ou #SESSION
  • Connaître le niveau de profondeur d’une rubrique
  • Les modèles et leur contexte en SPIP3

<p style=« text-align:right ; »>(voir aussi Les Astuces longues de SPIP sont des astuces qui nécessitent plus de 20 lignes pour être expliquées  : voir les Astuces courtes pour SPIP )</small > </p > courtes .

----

Partager des données entre plusieurs squelettes inclus (Zpip)

Question :
Dans ZPIP, la structure de base est définie à coup d’inclusion de grands blocs logiques du style

... <div id="contenu">
      &lt;INCLURE{fond=contenu/#ENV{type },  env &lt;  INCLURE{fond=contenu/#ENV{type},env }>
    </div>
    <div id="navigation">
      &lt;INCLURE{fond=navigation/#ENV{type },  env &lt;  INCLURE{fond=navigation/#ENV{type},env }>
      &lt;INCLURE{fond=extra/#ENV{type },  env &lt;  INCLURE{fond=extra/#ENV{type},env }>
    </div>....

Supposons par exemple que nous soyons dans un contexte « rubrique » http///monsite.fr/spip.php?rubriqueXX
et que le squelette contenu/rubrique.html se comporte de manière à afficher l’article N°yy

Supposons par exemple que nous soyons dans un contexte « rubrique » http///monsite.fr/spip.php?rubriqueXX et que le squelette

contenu/rubrique.html&lt;/code >  se  comporte  de  manière  à  afficher  l'article  n°yy
 Comment peut-on transmettre cet <code>id_article

dans l’inclusion extra ou dans l’inclusion navigation pour, par exemple, lister les autres articles de la rubrique ? . Il faut bien que le squelette appelé connaisse l’id de l’article à exclure que seul contenu/rubrique.html connait !
Pour que extra/rubrique.html ou navigation/rubrique.html puisse tenir compte de la situation, il faut disposer d’un moyen pour leur transmettre cette valeur. Or, seul le squelette appelant peut transmettre une variable...

Pour que extra/rubrique.html ou navigation/rubrique.html puisse tenir compte de la situation, il faut disposer d’un moyen pour leur transmettre cette valeur. Or, seul le squelette appelant peut transmettre une variable...

Réponse :
L’astuce (alambiquée) pour remonter des variables d’un squelette appelé, est d’affecter le résultat sérialisé de ce squelette à une variable tableau dans le squelette appelant :
Dans < code>structure . html :

[(#SET{myvars, [(#INCLURE{fond=vars/#ENV{type}, env})]})]

Dans vars/rubrique.html :

#CACHE{3600}
&#91;(#SET{vars, #ARRAY{
    objet, rubrique,
    id_objet, #ENV{id_rubrique},
    titreRubrique, Rubriques,
    titrePage, #ENV{id_rubrique}?
}})]


Dans structure.html :
<code class="spip sans_telechargement">
[(#SET{myvars,[(#INCLURE{fond=vars/#ENV{type},env})]})]

Dans vars/rubrique.html :

#CACHE{3600}
[(#SET{vars,#ARRAY{
    objet,rubrique,
    id_objet,#ENV{id_rubrique},
    titreRubrique,Rubriques,
    titrePage,#ENV{id_rubrique}?
}})]


&lt;BOUCLE_rub(RUBRIQUES )  {id_rubrique &lt;BOUCLE_rub(RUBRIQUES){id_rubrique }>
    &#91;(#SET{vars, #GET{vars}|array_merge{#ARRAY{titreRubrique ,  #TITRE ,  idRubrique ,  #ID_RUBRIQUE #GET{vars}|array_merge{#ARRAY{titreRubrique,#TITRE,idRubrique,#ID_RUBRIQUE }}})]
</BOUCLE_rub>


[(#GET{vars}|serialize)]
#FILTRE{trim}

Puis dans structure.html :
<code class=« spip sans_telechargement »><INCLUREfond=extra/#ENVtype , env > sans_telechargement ">
<INCLUREfond=extra/#ENVtype,env >


ZPIP : faire un inclure de squelette suivant une condition

dans le body de ZPIP, charger ou non un INCLURE qui contient #ENV{type}

dans le body de ZPIP , charger ou non un INCLURE qui contient < code>#ENVtype</code >

[(#ID_RUBRIQUE|=={10}|?{
  ''
,
  <INCLURE{fond=extra/#ENV{type}, env}>
})]

ne fonctionne pas à cause de accolades de #ENV, donc contourner avec

[(#ID_RUBRIQUE|=={10}|non) <INCLURE{fond=extra/#ENV{type}, env}> ]

Une

une boucle qui ne doit être exécutée que dans un certain contexte

Tout tout le monde le sait (beaucoup le déplorent) :

&#91;(#ENV{var}|=={val}|oui) &lt;BOUCLE_test(TABLES )  {critere &lt;BOUCLE_test(TABLES){critere }>...</BOUCLE_test> ]

ne fonctionne pas.

En en SPIP3, le critère {si ...} permet de définir la condition d’exécution depuis la boucle elle-même.
En SPIP2, il faut utiliser le plugin « bonux » et sa boucle « CONDITION ».

en SPIP2, il faut utiliser le plugin « bonux » et sa boucle « CONDITION ».

il existe néanmoins un autre moyen pour arriver à nos fins...

#SET{test, #ENV{truc}|=={coucou}|?{0, 999999}}
<BOUCLE_de_test(TABLES){id_table > #GET{test}}>
coucou !
</BOUCLE_de_test>

explications :

Il existe néanmoins un autre moyen pour arriver à nos fins...

#SET{test, #ENV{truc}|=={coucou}|?{0, 999999}}
<BOUCLE_de_test(TABLES) {id_table > #GET{test}}>
coucou !
&lt;/BOUCLE_de_test >
 &lt;/code >
 Explications  : 
-*  en premier lieu nous définissons notre test (présence de la variable «{truc}» dans l'environnement) et nous affectons à une variable «{test}» la valeur« {0}» ou «{999999}».
_ à ce stade, <code>#GET{test}

vaut donc soit « zéro », soit « neuf cent quatre-vingt-dix-neuf mille neuf cent quatre-vingt-dix-neuf ».

  • nous lançons alors la boucle dans laquelle nous demandons que l’« id_table » soit supérieur à notre valeur de test définie juste au-dessus.
    et le tour est joué...

Afficher la date du jour

DATE DU JOUR

Dans dans un squelette, afficher la date du jour (la date courante) n’est pas toujours aisé.
deux variables peuvent entrer en jeu :

  • la date retournée par la boucle dans laquelle on veut afficher notre date du jour
  • une variable nommée date passée en url ou par le biais d’un formulaire

Pour pour chaque cas, néanmoins, il existe une possibilité d’afficher la date du jour et ce sans que cette date soit tributaire du cache du squelette (ce sera toujours la date courante qui sera affichée)

dans une boucle dont la table possède

_
un champ date ou date_redac
hors boucle
pas de date ni de date_redac en url

_
ni passées par formulaire
#ENV{date_redac}
_ ou
_

#ENV{date}
#DATE_REDAC
_ ou
_

#DATE
variable date passée en url ou par formulaire #ENV{date_redac} #DATE_REDAC
variable date_redac passée en url ou par formulaire #ENV{date} #DATE
variables date et date_redac passées

_
en url ou par formulaire
définir en début de squelette la date courante avec par exemple :
_

#SET{cur_date, #VAL{Y-m-d h:i:s}|date}
_ puis l’utiliser dans le squelette avec :
_

#GET{cur_date}
<

Jouons avec les dates

JOUONS AVEC LES DATES

<blockquote class="spip">

voir aussi saisie des dates de publication et de rédaction antérieure (spip 2.0.x et 2.1.x)

</blockquote>

-  La la date du prochain jeudi qui suit #DATE : <code class=« spip sans_telechargement  »>
>
[(#VALY-m-d|date#VALnext Thursday|strtotime#DATE|affdateU

)]

-  Le le nom du jour de l’avant-veille de #DATE :<code class=« spip sans_telechargement  »>
>
[(#VALY-m-d|date#VAL-2 days|strtotime#DATE|affdateU

|nom_jour)]

-  La la date de lundi prochain :<code class=« spip sans_telechargement  »>
>
[(#SETlundi_prochain, #VALY-m-d 00:00:00|date#VALnext Monday|strtotime#DATE|affdateU

)]
#GETlundi_prochain

-  La la date du dimanche d’après lundi prochain :<code class=« spip sans_telechargement  »>
>
[(#SETdimanche_apres, #VALY-m-d 23:59:59|date#VAL+6days|strtotime#GETlundi_prochain|affdateU

)]
#GETdimanche_apres

-  Afficher afficher le label nouveau ! pour un article publié ou modifié il y a moins de 10 jours :<code class=« spip sans_telechargement  »>
>
#SETperiode, -10days
#TITRE[ (#DATE_MODIF|>#VALY-m-d H:i:s|date#GETperiode|strtotime|oui) nouveau !]
et _fil_ (sur irc) propose encore plus simple :<code class=« spip sans_telechargement  »>
>
#TITRE[ (#DATE|strtotime|>#VAL-10days|strtotime| ? nouveau nouveau !, ’’)]


Exporter au format CSV

EXPORTER AU FORMAT CSV

Triton Triton nous confie un petit exemple qui marche pour MS Excel et openOffice (il y a des particularités d’encodage de caractères) :

#CACHE{0} #HTTP_HEADER{Content-Type: text/csv; charset=windows-1252/Winlatin1} #HTTP_HEADER{Content-Disposition: attachment; filename=inscriptions.csv}
"date"; "nom_participant";"prenom";"paiement effectue"; "tarif_valide"; "promo";"telephone"; "email";"societe"; "nbr_part";"conjoint"; "mobile_dom";"type_paiement"; "souscripteur"
<BOUCLE_les_inscriptions_ouvertes(EVENEMENTS_LESINSCRIPTIONS) {id_evenement} {nbr_part > 0}>"[(#DATE|affdate{'d-m-Y'})]";"[(#NOM_PARTICIPANT|utf8_decode)]";"[(#PRENOM|utf8_decode)]";"[(#VERIFIER_PAIEMENT_INSCRIPTION{#ID_INSCRIPTION}|utf8_decode)]";"[(#TARIF_VALIDE|utf8_decode)]";"[(#PROMO|utf8_decode)]";"[(#TELEPHONE|utf8_decode)]";"[(#EMAIL|utf8_decode)]";"[(#SOCIETE|utf8_decode)]";"[(#NBR_PART|utf8_decode)]";"[(#CONJOINT|utf8_decode)]";"[(#MOBILE_DOM|utf8_decode)]";"[(#TYPE_PAIEMENT|utf8_decode)]";"[(#SOUSCRIPTEUR|utf8_decode)]"
</BOUCLE_les_inscriptions_ouvertes>
  • le http_header indique qu’il s’agit d’un document csv encodé pour MS excel
  • la premiere ligne c’est les têtières (en tete de colonne) du fichier csv
  • La boucle écrit ensuite le contenu ligne par ligne

Il faut, souvent, coller toutes les lignes comme ci-dessus, sinon, on peut, ou pas, se retrouver avec des lignes vides dans le csv....

Si tu colles ça ca dans un squelette (nommé squelette_cvs), il suffit ensuite d’appeler la page spip.php ?page=squelette_cvs et le navigateur te proposera de sauvegarder le fichier (ici inscriptions.csv - voir le filename au dessus) ou de l’ouvrir l ouvrir avec le logiciel de ton choix....

Après Apres , faut voir à a quoi doit servir ton cvs, à être a etre lu directement par une appli type MS/ Open Office (cet exemple) ou à a autre chose, import dans une base sql par exemple, auquel cas, il faudra peut-être modifier l’encodage de caractères...

Par ailleurs, paulbe paube indique qu’il faut d’abord passer les champs par le filtre textebrut avant de les passer à la moulinette de utf8_decode

on peut aussi « jolifier » et simplifier le code (surtout dans la boucle) :

#CACHE{0}
#HTTP_HEADER{Content-Type: text/csv; charset=windows-1252/Winlatin1}
#HTTP_HEADER{Content-Disposition: attachment; filename=inscriptions.csv}


"ordre"; "date"; "nom_participant"; "prenom"; "paiement effectue"; "tarif_valide"; "promo"; "telephone"; "email"; "societe"; "nbr_part"; "conjoint"; "mobile_dom"; "type_paiement"; "souscripteur"


<BOUCLE_inscriptions_ouvertes(EVENEMENTS_LESINSCRIPTIONS) {id_evenement} {nbr_part > 0}>#VAL{#COMPTEUR_BOUCLE;"[(#DATE|affdate{'d-m-Y'})]"; "#NOM_PARTICIPANT"; "#PRENOM"; "#VERIFIER_PAIEMENT_INSCRIPTION{#ID_INSCRIPTION}"; "#TARIF_VALIDE"; "#PROMO"; "#TELEPHONE"; "#EMAIL"; "#SOCIETE"; "#NBR_PART"; "#CONJOINT"; "#MOBILE_DOM"; "#TYPE_PAIEMENT"; "#SOUSCRIPTEUR"}|textebrut|utf8_decode)]
</BOUCLE_inscriptions_ouvertes>

l’astuce consiste à ajouter #COMPTEUR_BOUCLE; comme premier champ de #VAL (donc aussi "ordre"; en première ligne de la feuille)


Extraire

extraire les adresses email contenues dans un(des) texte(s)

/**
 * Le filtre extraire_email permet de récuperer toutes les adresses email
 * présentes dans le texte retourné par la balise sur laquelle il s'applique.
 * 
 * @param string $texte
 *     l'affichage retourné par la balise
 * 		
 * @return array
 *     un tableau dédoublonné des adresses email
**/
function extraire_email($texte) {
    //  /toto@truc.net étant une adresse valide ne pas confondre
    //  avec toto@truc.net dans href="mailto://toto@truc.net"
    $texte = preg_replace(',mailto://,', '', $texte);


//  contrôle : voir http://tools.ietf.org/html/rfc3696#page-6
    if (preg_match_all(";[a-z0-9!#$%&'*+/=?^_<span class="base64" title="PGNvZGUgY2xhc3M9InNwaXBfY29kZSBzcGlwX2NvZGVfaW5saW5lIiBkaXI9Imx0ciI+e3x9fi1dKyg/OlwuW2EtejAtOSEjJCUmYW1wOycqKy89P15fPC9jb2RlPg=="></span>{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?;ims", $texte, $matches))
        return array_unique($matches[0]);
    return array();
}

exemple d’utilisation dans un squelette :

#SET{res, #ARRAY}
<BOUCLE_art(ARTICLES) {tout}>
[(#SET{res, #GET{res}|array_merge{#TEXTE|extraire_email}})]
</BOUCLE_art>


[(#GET{res}|foreach)]


Une fonction pour joliment afficher les variables de #ENV, #GET ou #SESSION

Copiée copiée sur la fonction du débugueur debusquer_contexte() (de ecrire/public/debusquer.php), cette fonction est à déposer dans le fichier mes_options.php

/**
 * Une fonction récursive pour joliment afficher #ENV, #GET, #SESSION...
 *    en squelette : [(#ENV|bel_env)], [(#GET|bel_env)], [(#SESSION|bel_env)]
 *    ou encore [(#ARRAY{0,1, a,#SESSION, 1,#ARRAY{x,y}}|bel_env)]
 *
 * @param string|array $env
 *    si une string est passée elle doit être le serialize d'un array 
 *
 * @return string
 *    une chaîne html affichant une <table>
**/
function bel_env($env) {
    $env = str_replace(array('"', '&#039;'), array('"', '\''), $env);
    if (is_array($env_tab = @unserialize($env))) {
        $env = $env_tab;
    }
    if (!is_array($env)) {
        return '';
    }
    $style = " style='border:1px solid #ddd;'";
    $res = "<table style='border-collapse:collapse;'>\n";
    foreach ($env as $nom => $val) {
        if (is_array($val) || is_array(@unserialize($val))) {
            $val = bel_env($val);
        }
        else {
            $val = entites_html($val);
        }
        $res .= "<tr>\n<td$style><strong>". entites_html($nom).
                   " : </strong></td><td$style>" .$val. "</td>\n</tr>\n";
    }
    $res .= "</table>";
    return $res;
}

ne reste plus qu’à l’appeler (que l’on soit loggé ou pas) dans un squelette avec :

[(#ENV|bel_env)]

ou encore :

[(#GET|bel_env)]

ou encore :

[(#SESSION|bel_env)]

Connaître le niveau de profondeur d’une rubrique

-  Avec SPIP3 c’est même pas drôle :

  • #PROFONDEUR dans une boucle (RUBRIQUES)
  • #INFO_x{objet, id_objet} en dehors d’une boucle

-  Avec SPIP2.1 deux solutions :

  • <BOUCLE_niveau(HIERARCHIE) {id_rubrique} {tout}>
    </BOUCLE_niveau>
          #SET{niveau,#TOTAL_BOUCLE}
    <//B_niveau>

    et on récupère l’info avec un #GET{niveau}

  • une fonction à poser dans squelettes/mes_fonctions.php :
    function filtre_profondeur_dist($id_rubrique) {
        $id_rubrique = intval($id_rubrique);
        // sauver les calculs déjà faits
        static $profs = array();
        if (isset($profs[$id_rubrique])) {
          return $profs[$id_rubrique];
        }
        // récupérer le parent.
        $id_parent = sql_getfetsel('id_parent', 'spip_rubriques', 'id_rubrique='.$id_rubrique);
        // pas de parent : id_rubrique n'existe pas
        if (is_null($id_parent)) {
          return '';
        }
        // parent zéro : on est tout en haut (racine)
        if ($id_parent == '') {
          return $profs[$id_rubrique] = 1;
        }
        // sinon, on trouve la profondeur du parent
        $parent = filtre_profondeur_dist($id_parent);
        $profs[$id_rubrique] = ($parent + 1);
        return $profs[$id_rubrique];
    }

    à appeler dans un squelette avec : [(#ID_RUBRIQUE|profondeur)]


Les modèles et leur contexte en SPIP3

En spip2.En spip2 x , le contexte d’appel d’un modèle n’était pas connu lors de la compilation de ce modèle. Ainsi un modèle appelé depuis le corps du texte de l’article 18 ne pouvait savoir qu’il était appelé depuis cet article précis.

Cela a changé avec spip3 ; désormais le modèle véhicule le contexte de son objet appelant. Cela veut dire que appelé depuis l’article 18, le modèle intègre la donnée id_article = 18 ; donnée que l’on peut donc utiliser dans le modèle avec :

  • soit #ID_ARTICLE,
  • soit #ENV{id_article},
  • soit encore #ENV{args/id_article}.

Mais plusieurs cas peuvent se présenter :

  • modèle appelé depuis un article sans argument explicite :
    <mon_modele1>
    • #ID_ARTICLE vaudra la valeur id_article de l’article appelant (celui où se trouve l’appel du modèle)
    • #ENV{id_article} vaudra la valeur id_article de l’article appelant (celui où se trouve l’appel du modèle)
    • #ENV{args/id_article} vaudra ’’ (vide)
  • modèle appelé depuis un article avec argument explicite vide :
    &lt;mon_modele1|id_article &lt;mon_modele1|id_article=25 >
    • #ID_ARTICLE vaudra la valeur id_article de l’article appelant ( celui se trouve l’appel du modèle )
      passée en argument  : 25 -**
      #ENV{id_article}&lt;/code  &lt; code>#ENV{id_article}&lt;/code > vaudra la valeur id_article  de  l'article  appelant  ( celui   se  trouve  l'appel  du  modèle )
       passée  en  argument  :   25 -** <code>#ENV{args/id_article}&lt;/code  &lt; code>#ENV{args/id_article}&lt;/code > vaudra ''  ( vide )
       la  valeur  passée  en  argument  :   25 -*   modèle appelé depuis un article avec argument explicite sans  valeur vide  :
      _ {{<code> <mon_modele1|id_article=> 
    • #ID_ARTICLE vaudra ’’ (vide)
    • #ENV{id_article} vaudra ’’ (vide)
    • #ENV{args/id_article} vaudra ’’ (vide)
      -* modèle appelé depuis un article avec argument explicite renseigné  :
      _
      < code > <mon_modele1|id_article=25 > </code >
      -**
      #ID_ARTICLE&lt;/code >  vaudra  la  valeur  passée  en  argument  :   25 
      -**  &lt; code>#ENV{id_article}&lt;/code >  vaudra  la  valeur  passée  en  argument  :   25 
      -**  &lt; code>#ENV{args/id_article}&lt;/code >  vaudra  la  valeur  passée  en  argument  :   25 
      
      
      {{Et alors ?}}
      
      
      {{Et alors ?}} 
       Connaissant l'id de {l'objet} (article, rubrique, mot, site...) depuis lequel notre modèle est appelé, il est désormais possible d'utiliser des boucles utilisant cette information, de telle sorte que le modèle devient ainsi {{contextuel}} en affichant des informations dépendantes de son lieu d'appel. Un {{même}} modèle appelé depuis plusieurs articles retournera ainsi des informations spécifiques différentes pour chaque article.
      
      
      {{Attention toutefois  !}}
      Comme désormais l'id de l'objet appelant (id_article, id_rubrique, id_mot...) est toujours transmis au modèle, il devient impossible d'utiliser dans le modèle une boucle avec le [critère conditionnel->http://www.spip.net/fr_article3996.html] <code>{id_article &lt;code >  {id_article ?}&lt;/code }  &lt;/ code > (ou &lt;code>{id_rubrique code >  {id_rubrique ?}&lt;/code }  &lt;/ code >, ou &lt;code>{id_mot code >  {id_mot ?}&lt;/code }  &lt;/ code > ...)
      L'astuce consistera à utiliser 2 critères dépendants de &lt;code>#ENV{args/id_article}&lt;/code code >  #ENV{args/id_article}  &lt;/ code > car seul &lt;code >  #ENV{args/id_article}  &lt;/ code # ENV{args/id_article}&lt;/code > nous renseigne sur l'argument «id_article» passé {{explicitement}} au modèle.
      <code class="spip sans_telechargement">
      <BOUCLE_a_cond(ARTICLES) {id_article >= #ENV{args/id_article, 0}} {id_article <= #ENV{args/id_article, 99999999}} {"<br />"}>
      #ID_ARTICLE - #TITRE
      </BOUCLE_a_cond>

Explications :

  • si #ENV{args/id_article} existe c’est que id_article a été explicitement renseigné dans l’appel du modèle (exemple : <mon_modele1|id_article=25>).
    Dans ce cas, la boucle cherchera id_article >= 25 et id_article <= 25 ; soit (tout le monde aura trouvé) : id_article = 25 ;
  • si #ENV{args/id_article} n’existe pas (id_article n’a pas été explicitement renseigné dans l’appel du modèle : <mon_modele1>), alors la boucle cherchera id_article >= 0 et id_article <= 99999999 ; soit (tout le monde aura trouvé) : tous les articles.

on reproduit bien là le comportement de {id_article?}.


Retour à la version courante

Toutes les versions