Différences entre versions de « Plugin SPIP »
imported>SylvainBeucler |
imported>WikiSysop (Annulation des modifications 5904 de 188.92.74.95) |
||
(26 versions intermédiaires par 2 utilisateurs non affichées) | |||
Ligne 32 : | Ligne 32 : | ||
* $res = mysql_query -> | * $res = mysql_query -> | ||
** sql_select($champs, $from, $where, ...) | ** sql_select($champs, $from, $where, ...) | ||
− | ** sql_insertq($table, $couples, ...) | + | ** sql_insertq($table, $couples, ...) => return mysql_insert_id() |
** sql_insertq_multi($table, $tab_couples, ...) | ** sql_insertq_multi($table, $tab_couples, ...) | ||
** sql_updateq($table, $couples, $where, ...) | ** sql_updateq($table, $couples, $where, ...) | ||
Ligne 48 : | Ligne 48 : | ||
** sql_count($req) | ** sql_count($req) | ||
** sql_countsel($from, $where) | ** sql_countsel($from, $where) | ||
+ | * mysql_error -> sql_error | ||
+ | |||
+ | Note: si le nom des tables commence par <code>spip</code>, il sera remplacé par le préfixe configuré à l'installation de SPIP (il n'y a pas besoin de spécifier le préfixe à la main). | ||
= Passer de 1.9 à 2.0 = | = Passer de 1.9 à 2.0 = | ||
Certaines fonctions ont changé. Pour convertir le code de votre plugin, une bonne source d'information est <code>ecrire/inc/vieilles_defs.php</code> qui définit des anciennes fonctions avec la nouvelle API 2.0. | Certaines fonctions ont changé. Pour convertir le code de votre plugin, une bonne source d'information est <code>ecrire/inc/vieilles_defs.php</code> qui définit des anciennes fonctions avec la nouvelle API 2.0. | ||
+ | |||
+ | = Fichiers de langue = | ||
+ | |||
+ | Traductions à la mode SPIP, en passant par une clef pour chaque chaîne. | ||
+ | |||
+ | Conventions de nommage: | ||
+ | * appel au niveau du code PHP: préfixe du plugin + ":" + l'identifiant de la chaîne: | ||
+ | _T('monprefixe:clef') | ||
+ | * nom du fichier de traduction: remplacer CODE par le code de la langue (fr, it, nl, etc.): | ||
+ | monplugin/lang/monprefixe_CODE | ||
+ | * contenu du fichier de traduction: notez l'absence de préfixe: | ||
+ | <pre> | ||
+ | <?php | ||
+ | if (!defined("_ECRIRE_INC_VERSION")) return; | ||
+ | |||
+ | $GLOBALS[$GLOBALS['idx_lang']] = array( | ||
+ | 'clef' => 'Traduction', | ||
+ | # ... | ||
+ | ); | ||
+ | </pre> | ||
+ | |||
+ | * [http://forum.spip.org/fr_201977.html Fichier de langue et plug in] | ||
+ | * [http://programmer.spip.org/Fichiers-de-langues Fichiers de langues] | ||
= Partie admin = | = Partie admin = | ||
Ligne 66 : | Ligne 92 : | ||
<pre> | <pre> | ||
<?php | <?php | ||
− | if (!defined( | + | if (!defined('_ECRIRE_INC_VERSION')) return; // sécurité |
function exec_monprefixe_index() | function exec_monprefixe_index() | ||
Ligne 108 : | Ligne 134 : | ||
Pour le contenu, on peut soit l'écrire avec des <code>echo</code>, soit faire appel à un squelette dans <code>plugins/mon_plugin-0.1/prive/mon_squelette.html</code>: | Pour le contenu, on peut soit l'écrire avec des <code>echo</code>, soit faire appel à un squelette dans <code>plugins/mon_plugin-0.1/prive/mon_squelette.html</code>: | ||
− | recuperer_fond('prive/mon_squelette', $_GET); | + | echo recuperer_fond('prive/mon_squelette', $_GET); |
Note: le préfixe n'est techniquement pas obligatoire pour le nom de la page, mais c'est une bonne habitude à prendre pour éviter les conflits avec d'autres plugins. | Note: le préfixe n'est techniquement pas obligatoire pour le nom de la page, mais c'est une bonne habitude à prendre pour éviter les conflits avec d'autres plugins. | ||
Ligne 157 : | Ligne 183 : | ||
Les barres existantes sont principalement: configuration, administration (elle-même dans configuration), stats_depuis, stats_referers, calendrier, config_lang. | Les barres existantes sont principalement: configuration, administration (elle-même dans configuration), stats_depuis, stats_referers, calendrier, config_lang. | ||
+ | |||
+ | == Ajouter une information objet == | ||
+ | |||
+ | ... c'est à dire un bloc comme "Article numéro X" pour les articles, avec un bel encadré et tout. | ||
+ | |||
+ | La manière propre pour un objet machin: http://programmer.spip.org/Boite-d-information | ||
+ | * passe par le pipeline <code>boite_infos</code> | ||
+ | * créer un squelette <code>prive/infos/machin.html</code> (ouvrir les bons <code>div</code> à la main) | ||
+ | * appeler <code>debut_boite_info</code>/<code>fin_boite_info</code> | ||
+ | |||
+ | Sinon à la main ça donne quelque chose comme: | ||
+ | <pre> | ||
+ | echo debut_boite_info(true); | ||
+ | echo "<div class='infos'>"; | ||
+ | echo "<div class='numero'>MACHIN NUMÉRO :<p>" . $id_machin . "</p></div>"; | ||
+ | echo "</div>"; | ||
+ | echo fin_boite_info(true); | ||
+ | </pre> | ||
+ | |||
+ | Ensuite il y a un tas d'autres informations qu'on peut y insérer; pour les changements de statut, voir les fonctions "instituer_*" dans le code source, il y a des styles CSS déjà prêts. | ||
= Traitement dans la partie publique = | = Traitement dans la partie publique = | ||
Ligne 251 : | Ligne 297 : | ||
* <code>_pipeline</code> fait passer le formulaire dans un pipeline | * <code>_pipeline</code> fait passer le formulaire dans un pipeline | ||
− | Quand le traitement est terminé, on peut (cf. <code>traiter_formulaires_dynamiques(...)</code>: | + | Quand le traitement est terminé, on peut (cf. <code>traiter_formulaires_dynamiques(...)</code>): |
* renvoyer un message: | * renvoyer un message: | ||
$retour['message_ok'] = 'Bien reçu'; | $retour['message_ok'] = 'Bien reçu'; | ||
* rediriger vers une autre page: | * rediriger vers une autre page: | ||
$retour['redirect'] = URL; | $retour['redirect'] = URL; | ||
+ | |||
+ | Mémento des fichiers nécessaires: | ||
+ | * ./formulaires/XXX.html | ||
+ | * ./formulaires/XXX.php | ||
+ | ** formulaires_xxx_charger_dist() | ||
+ | ** formulaires_xxx_verifier_dist() | ||
+ | ** formulaires_xxx_traiter_dist() | ||
TODO | TODO | ||
Ligne 268 : | Ligne 321 : | ||
== Boucles == | == Boucles == | ||
− | On peut | + | On peut créer ses propres boucles dans SPIP. |
+ | |||
+ | En l'absence de code spécifique, SPIP interprète le nom de la boucle comme le nom d'une table, et les balises comme les noms de ses champs. | ||
+ | Par exemple, si je crée une table <code>mammiferes</code> avec des champs <code>id_mammifere</code>, <code>nom</code> et <code>famille</code>: | ||
+ | <pre> | ||
+ | CREATE TABLE `spip_mammiferes` ( | ||
+ | `id_mammifere` int(11) NOT NULL auto_increment, | ||
+ | `nom` varchar(255) default NULL, | ||
+ | `famille` varchar(255) default NULL, | ||
+ | `statut` ENUM ('prepa', 'publie', 'poubelle'), | ||
+ | PRIMARY KEY (`id_mammifere`) | ||
+ | ); | ||
+ | INSERT INTO `spip_mammiferes` (nom, famille, statut) VALUES ('dauphin', 'cétacés', 'publie'); | ||
+ | INSERT INTO `spip_mammiferes` (nom, famille, statut) VALUES ('lapin', 'lagomorphes', 'prepa'); | ||
+ | INSERT INTO `spip_mammiferes` (nom, famille, statut) VALUES ('singe', 'primates', 'publie'); | ||
+ | </pre> | ||
+ | alors je peux écrire directement le squelette suivant: | ||
+ | <pre> | ||
+ | <BOUCLE_test(SPIP_MAMMIFERES){id_mammifere=3}> | ||
+ | Le #NOM appartient à la famille des #FAMILLE. | ||
+ | </BOUCLE_test> | ||
+ | </pre> | ||
+ | |||
+ | Pour pouvoir utiliser une table sans le préfixe <code>_spip</code>, on la déclare dans l'interface <code>table_des_tables</code>: | ||
+ | <pre> | ||
+ | <pipeline> | ||
+ | <nom>declarer_tables_interfaces</nom> | ||
+ | <inclure>monprefixe_pipelines.php</inclure> | ||
+ | </pipeline> | ||
+ | </pre> | ||
+ | <pre> | ||
+ | function &monprefixe_declarer_tables_interfaces(&$interfaces) | ||
+ | { | ||
+ | $interfaces['table_des_tables']['mammiferes'] = 'mammiferes'; | ||
+ | return $interfaces; | ||
+ | } | ||
+ | </pre> | ||
+ | On peut alors écrire: | ||
+ | <pre> | ||
+ | <BOUCLE_test(MAMMIFERES){id_mammifere=3}> | ||
+ | Le #NOM appartient à la famille des #FAMILLE. | ||
+ | </BOUCLE_test> | ||
+ | </pre> | ||
+ | |||
+ | Noter que les boucles sont prévues pour accéder à des tables SQL et à leurs champs; l'utiliser dans d'autres contextes est possible, mais relève de l'acrobatie et est donc forcément limité. Lorsque vous introduisez de nouvelles structures de données dans SPIP, il y a des chances que vous deviez les adapter à ce concept de boucles agissant sur une table, plutôt que d'essayer d'utiliser la boucle hors de son champ normal d'application. | ||
+ | |||
+ | On peut affiner le comportement de la boucle en déclarant une fonction <code>boucle_MABOUCLE_dist</code> ou <code>critere_MABOUCLE_moncritere_dist</code>. Le plus simple est d'inclure ces déclarations dans un fichier <code><fonction></code> de <code>plugin.xml</code> (chargé à chaque recalcul), par exemple: | ||
+ | |||
+ | <pre> | ||
+ | <fonctions>public/monprefixe_boucles.php</fonctions> | ||
+ | </pre> | ||
+ | |||
+ | <pre> | ||
+ | function boucle_MABOUCLE_dist($id_boucle, &$boucles) | ||
+ | { | ||
+ | $boucle = &$boucles[$id_boucle]; | ||
+ | // ... | ||
+ | return calculer_boucle($id_boucle, $boucles); | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | * <code>$id_boucle</code>: identifiant de la boucle, ex: _mesarticles pour une boucle <code><BOUCLE_mesarticles></code> | ||
+ | * <code>&$boucles</code>: tableau de toutes les boucles de la page, indexées par leur identifiant. Ce sont des objets SPIP complexes et internes. On utilisera typiquement <code>$boucle = &$boucles[$id_boucle];</code>. Noter que les champs vont être | ||
+ | ** <code>$boucle->id_table</code>: le nom de la table détecté par SPIP | ||
+ | ** <code>$boucle->primary</code>: la clef primaire, apparemment auto-détectée | ||
+ | ** <code>$boucle->select</code>: un tableau avec le nom de champs à récupérer; SPIP le remplit par défaut avec la liste des balises trouvées dans la boucle | ||
+ | ** <code>$boucle->from</code>: | ||
+ | ** <code>$boucle->from_type</code>: | ||
+ | ** <code>$boucle->where</code>: | ||
+ | ** <code>$boucle->join</code>: | ||
+ | ** <code>$boucle->having</code>: | ||
+ | ** <code>$boucle->limit</code>: | ||
+ | ** <code>$boucle->group</code>: | ||
+ | ** <code>$boucle->order</code>: | ||
+ | |||
+ | Par exemple, pour filtrer les mammifères non-publiés: | ||
+ | <pre> | ||
+ | function boucle_MAMMIFERES_dist($id_boucle, &$boucles) | ||
+ | { | ||
+ | $boucle = &$boucles[$id_boucle]; | ||
+ | |||
+ | $id_table = $boucle->id_table; | ||
+ | $mstatut = $id_table .'.statut'; | ||
+ | $boucle->where[] = array("'='", "'$mstatut'", "'\\'publie\\''"); | ||
+ | |||
+ | return calculer_boucle($id_boucle, $boucles); | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | Noter qu'un objet boucle est déjà présent au moment où l'on entre dans notre fonction. Il ne reste plus qu'à le compléter. | ||
+ | |||
+ | |||
+ | Étape suivante: on souhaite accéder à des champs d'autres tables dans la boucle. Imaginons que nous voulons dire qui a photographié un mammifère, et pouvoir filtrer uniquement les mammifères de cet auteur: | ||
+ | <pre> | ||
+ | CREATE TABLE `spip_auteurs_mammiferes` ( | ||
+ | `id_auteur` bigint(21) NOT NULL default '0', | ||
+ | `id_mammifere` bigint(21) NOT NULL default '0', | ||
+ | PRIMARY KEY (`id_auteur`,`id_mammifere`) | ||
+ | ); | ||
+ | INSERT INTO spip_auteurs_mammiferes (`id_auteur`, `id_mammifere`) VALUES (1,1); | ||
+ | </pre> | ||
+ | |||
+ | Si l'on spécifie <code>{id_auteur=1}</code> dans notre boucle, il y a une erreur, car SPIP ne sait pas où chercher <code>id_auteur</code>. Il faut le lui dire explicitement, à nouveau dans le pipeline <code>declarer_tables_interfaces</code>: | ||
+ | <pre> | ||
+ | function &monprefixe_declarer_tables_interfaces(&$interfaces) | ||
+ | { | ||
+ | $interfaces['table_des_tables']['mammiferes'] = 'mammiferes'; | ||
+ | |||
+ | $interfaces['table_des_tables']['auteurs_mammiferes'] = 'auteurs_mammiferes'; // ou dans declarer_tables_auxiliaires | ||
+ | $interfaces['tables_jointures']['spip_mammiferes']['id_auteur'] = 'auteurs_mammiferes'; | ||
+ | return $interfaces; | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | Ce qui nous permet par exemple d'écrire dans le squelette: | ||
+ | <pre> | ||
+ | Les miens:<br /> | ||
+ | <BOUCLE_lesmiens(MAMMIFERES){id_auteur=1}{doublons}> | ||
+ | Le #NOM appartient à la famille des #FAMILLE.<br /> | ||
+ | </BOUCLE_lesmiens> | ||
+ | Les autres:<br /> | ||
+ | <BOUCLE_lesautres(MAMMIFERES){doublons}> | ||
+ | Le #NOM appartient à la famille des #FAMILLE.<br /> | ||
+ | </BOUCLE_lesautres> | ||
+ | </pre> | ||
+ | |||
+ | Voir la section [Jointures automatiques->article64] pour plus de détails. | ||
Cf. <code>ecrire/public/compiler.php:public_compiler_dist(...)</code> et <code>ecrire/public/criteres.php:calculter_criteres(...)</code>. | Cf. <code>ecrire/public/compiler.php:public_compiler_dist(...)</code> et <code>ecrire/public/criteres.php:calculter_criteres(...)</code>. | ||
− | Exemples: <code>ecrire/public/boucles.php</code>, <code> | + | Exemples: <code>ecrire/public/boucles.php</code>, <code>forms_et_tables_1_9_1/public/forms_boucles.php</code>. |
+ | |||
+ | == Critères == | ||
+ | |||
+ | Par défaut un critère est le nom d'une colonne dans la table SQL parcourue par la boucle. | ||
+ | |||
+ | On peut également définir des critères dont le résultat est donné par une fonction PHP. | ||
+ | Déclaration: | ||
+ | <fonctions>public/monprefixe_criteres.php</fonctions> | ||
+ | |||
+ | critere_moncritere_dist($idb, &$boucles, $crit) | ||
+ | critere_MABOUCLE_moncritere_dist($idb, &$boucles, $crit) | ||
+ | |||
+ | Noter que la boucle (ici <code>MABOUCLE</code>) n'a pas besoin d'être explicitement définie par ailleurs (on peut, mais ce n'est pas toujours le cas). | ||
+ | |||
+ | * $idb: identifiant de la boucle, ex: <code>_mesarticles</code> pour une boucle <code><BOUCLE_mesarticles></code> | ||
+ | * &$boucles: tableau de toutes les boucles de la page, indexées par leur identifiant. Ce sont des objets SPIP complexes et internes. On utilisera typiquement <code>$boucle = &$boucles[$idb];</code> | ||
+ | * $crit: objet <code>Critere</code>; on utilisera notamment: | ||
+ | ** <code>$crit->not</code>: le critère est inversé (<code>!moncritere</code>) | ||
+ | ** <code>$crit->param</code>: une structure complexe décrivant les paramètres du critères; il doit falloir passer par des fonctions SPIP pour les exploiter (<code>calculer_liste</code>?) | ||
+ | ** TODO | ||
+ | * Résultat: aucun; la fonction doit modifier l'objet boucle, par exemple son champ <code>having[]</code>, <code>order[]</code> ou <code>where[]</code>. | ||
+ | |||
+ | Si, dans le squelette, on utilise la syntaxe <code>{moncritere == qqchose}</code>, SPIP force apparemment l'utilisation de moncritere comme champ de table (en plus de l'appel de notre fonction), ce qui cause une erreur si le critère n'en est pas un. | ||
+ | |||
+ | Exemples: <code>ecrire/public/criteres.php</code>, <code>spip-bonux/public/spip_bonux_criteres.php</code>. | ||
+ | |||
+ | Liens: | ||
+ | * [http://www.spip-contrib.net/NouveauCritere Nouveau Critère]: sur le wiki spip | ||
== Paramètre 'action' == | == Paramètre 'action' == | ||
Ligne 300 : | Ligne 507 : | ||
* <code>nom</code> est obligatoire est précise le nom de pipeline dans lequel on insère notre traitement | * <code>nom</code> est obligatoire est précise le nom de pipeline dans lequel on insère notre traitement | ||
− | * <code>inclure</code> est facultif, par défaut cherche les fonctions existantes ( | + | * <code>inclure</code> est facultif, par défaut cherche les fonctions existantes - cependant certains pipelines (ex: <code>declarer_tables_interfaces</code>) sont lancés avant que <code>monprefixe_options.php</code> ne soit chargé, donc c'est une bonne habitude de toujours le spécifier. |
* <code>action</code> est facultatif, par défaut lance la fonction <code>monprefixe_nom_pipeline(...)</code> | * <code>action</code> est facultatif, par défaut lance la fonction <code>monprefixe_nom_pipeline(...)</code> | ||
Ligne 308 : | Ligne 515 : | ||
Lancement d'un pipeline perso: [http://programmer.spip.org/Declarer-un-nouveau-pipeline Déclarer un nouveau pipeline] | Lancement d'un pipeline perso: [http://programmer.spip.org/Declarer-un-nouveau-pipeline Déclarer un nouveau pipeline] | ||
+ | |||
+ | = Partie utilisateur = | ||
+ | |||
+ | == Modèles == | ||
+ | |||
+ | Les modèles sont de nouveaux raccourcis typographiques que vos rédacteurs pourront inclure dans leurs articles. | ||
+ | |||
+ | * http://www.spip.net/fr_article3454.html | ||
+ | * http://monsitespip.com/spip.php?article9 | ||
+ | |||
+ | TODO |
Version actuelle datée du 11 juillet 2010 à 20:26
Introduction
- Le développement de SPIP et ses outils: introduction officielle mais limitée
- Programmer avec SPIP 2.0: la documentation la plus complète, on y retrouve les concepts de cette page; description de quelques pipelines et fonctions importants; pénible à lire dû à un découpage trop fréquent.
- Développer avec SPIP: une liste de liens sur SPIP-Contrib; noter que certaines fonctionnalités des plugins sont des reprises de l'existant < v1.9, par exemple la définition de balises personnalisées; d'autres liens sont dépassés
- Plugin.xml: référence de la syntaxe de plugin.xml
- Glossaire: un index de tous les boucles/balises/filtres et autres mots-clefs de SPIP
Le SPIP_PATH
Il s'agit du répertoire de recherche de SPIP, pour les squelettes, mais aussi pour les fichiers PHP personnalisés, (définitions de balises, de pages privées, etc.), les modèles, etc.
Dans l'ordre: la variable globale $mes_squelettes
(chemins séparés par ':'), puis squelettes, plugins/mon_plugin-1, plugins/mon_plugin-2, ..., (racine), squelettes-dist, prive, ecrire
Techniquement, les chemins des squelettes sont définis dans le fichier PHP généré tmp/charger_plugins_options.php
, qui définit également les constantes _DIR_PLUGIN_MONPREFIXE
.
Références:
ecrire/inc/utils.php:_chemin()
- Où placer les fichiers des squelettes
API
http://doc.spip.org/ a pour but de documentation l'API de SPIP, une page par fonction, modifiable par tous. En pratique, peu de fonctions sont documentées. Qui plus est, cette documentation étant la documentation officielle, le code source se contente d'y faire référence, souvent sans plus de détails.
La meilleure source de documentation reste la lecture du code source, et l'étude d'autres plugins.
SQL
Mémento pour conversion PHP/MySQL -> SPIP (cf. ecrire/base/abstract_sql.php
et ecrire/req/mysql.php
):
- $res = mysql_query ->
- sql_select($champs, $from, $where, ...)
- sql_insertq($table, $couples, ...) => return mysql_insert_id()
- sql_insertq_multi($table, $tab_couples, ...)
- sql_updateq($table, $couples, $where, ...)
- sql_delete($table, $where, ...)
- sql_replace($table, $couples, ...)
- ...
- qqchose IN (...) -> sql_in($val, $valeurs, $not)
- mysql_real_escape_string -> sql_quote($val) ou _q($val) (même si c'est fait de manière sale avec addslashes)
- mysql_fetch_assoc ->
- sql_fetch($res) - la prochaine ligne
- sql_fetsel($select, $from, $where, ...) - une seule ligne
- sql_allfetsel($select, $from, $where, ...) - toutes les lignes
- sql_getfetsel($select, $from, $where, ...) - une seule ligne et un seul champ
- mysql_num_rows ->
- sql_count($req)
- sql_countsel($from, $where)
- mysql_error -> sql_error
Note: si le nom des tables commence par spip
, il sera remplacé par le préfixe configuré à l'installation de SPIP (il n'y a pas besoin de spécifier le préfixe à la main).
Passer de 1.9 à 2.0
Certaines fonctions ont changé. Pour convertir le code de votre plugin, une bonne source d'information est ecrire/inc/vieilles_defs.php
qui définit des anciennes fonctions avec la nouvelle API 2.0.
Fichiers de langue
Traductions à la mode SPIP, en passant par une clef pour chaque chaîne.
Conventions de nommage:
- appel au niveau du code PHP: préfixe du plugin + ":" + l'identifiant de la chaîne:
_T('monprefixe:clef')
- nom du fichier de traduction: remplacer CODE par le code de la langue (fr, it, nl, etc.):
monplugin/lang/monprefixe_CODE
- contenu du fichier de traduction: notez l'absence de préfixe:
<?php if (!defined("_ECRIRE_INC_VERSION")) return; $GLOBALS[$GLOBALS['idx_lang']] = array( 'clef' => 'Traduction', # ... );
Partie admin
Insérer une nouvelle page admin
- Définir le préfixe de votre plugin (convention de nommage) dans
plugin.xml
:
<prefix>monprefixe</prefix>
- Créer
plugins/mon_plugin-0.1/exec/monprefixe_mapage.php
- Y définir
exec_monprefixe_mapage()
(ouexec_monprefixe_mapage_dist()
) - On y accède via http://www.monsite.tld/ecrire/?exec=prefixe_index
Contenu du fichier:
<?php if (!defined('_ECRIRE_INC_VERSION')) return; // sécurité function exec_monprefixe_index() { $commencer_page = charger_fonction('commencer_page', 'inc'); echo $commencer_page("Titre (barre de titre du navigateur)"); echo gros_titre("Titre (dans la page)", '<img src="logo.png" alt="" />', false); echo debut_grand_cadre(true); echo "Bandeau en haut"; echo fin_grand_cadre(true); echo debut_gauche("ignored", true); echo "À gauche<br />"; echo creer_colonne_droite("", true); echo "À droite si grand écran, à gauche sinon<br />"; echo debut_boite_info(true); echo "Encadré"; echo fin_boite_info(true); $res = icone_horizontale("Page 1", generer_url_ecrire("monprefixe_page1"), "../"._DIR_PLUGIN_MONPREFIXE."fond.gif", "../"._DIR_PLUGIN_MONPREFIXE."page1.gif", false); echo bloc_des_raccourcis($res); // crée creer_colonne_droite si besoin echo debut_droite("ignored", true); // ferme creer_colonne_droite, si utilisé echo "Contenu, au milieu"; echo fin_gauche(); echo fin_page(); }
Les true
et false
qui se baladent partout permettent de dire qu'on s'occupe d'afficher le contenu, sans quoi SPIP affiche un avertissement. Il faut utiliser true
ou false
au cas par cas, selon la fonction, cela manque de cohérence.
Pour le contenu, on peut soit l'écrire avec des echo
, soit faire appel à un squelette dans plugins/mon_plugin-0.1/prive/mon_squelette.html
:
echo recuperer_fond('prive/mon_squelette', $_GET);
Note: le préfixe n'est techniquement pas obligatoire pour le nom de la page, mais c'est une bonne habitude à prendre pour éviter les conflits avec d'autres plugins.
Exemples: ecrire/exec/sites_tous.php
, et acces_restreint_3_0/exec/acces_restreint
dans le plugin "Accès restreint".
Ajouter un bouton
Le menu du haut est un ensemble de "boutons", organisés en une structure arborescente (parent, enfants).
Pour ajouter un bouton:
- Définir des boutons: déclaration dans plugin.xml, et des fonctions
autoriser_monbouton_dist(...)
pour les autorisations. - Pipeline
monprefixe_ajouter_boutons($boutons_admin)
qui ajouter des élémentsnew Bouton($icone, $titre) dans le tableau
$boutons_admin["exec_parent"]->sousmenu["exec_enfant"]
ou directement$boutons_admin["exec_enfant"]
et gère les autorisations en même temps.
La syntaxe plugin.xml (url et args sont facultatif, auquel cas exec=identifiant):
<bouton id="identifiant" parent="identifiant_parent">
<icone>chemin/vers/icone.png</icone>
<titre>titre ou identifiant de traduction</titre>
<url>nom_exec</url>
<args>arguments pour exec</args>
</bouton>
Les parents existants: accueil, naviguer, forum, auteurs, configuration, aide_index, visite
Pour afficher l'arborescence courante, on peut utiliser le pipeline suivant:
<pipeline>
<nom>ajouter_boutons</nom>
<inclure>monprefixe_pipelines.php</inclure>
</pipeline>
function monprefixe_ajouter_boutons($boutons_admin)
{
print '<pre>';
print_r($boutons_admin);
print '</'.'pre>'; // mediawiki work-around
return $boutons_admin;
}
Ajouter un onglet
Un onglet, c'est un sous-menu de page, par exemple "Intéractivité" dans la partie "Configuration".
- Soit on déclare une nouvelle barre d'onglets dans son exec (
barre_onglet("menu", "sous_menu_courant")
)
- Soit on s'insère dans une barre existante:
- Définir des onglets: déclaration dans plugin.xml, et des fonctions
autoriser_mononglet_dist(...)
pour les autorisations.
- Pipeline
monprefixe_ajouter_onglets($flux)
: $flux['args']
contient le nom de la barre d'onglets, $flux['data']
contient une barre similaire à la barre de boutons dans ajouter_boutons
.
Les barres existantes sont principalement: configuration, administration (elle-même dans configuration), stats_depuis, stats_referers, calendrier, config_lang.
Ajouter une information objet
... c'est à dire un bloc comme "Article numéro X" pour les articles, avec un bel encadré et tout.
La manière propre pour un objet machin: http://programmer.spip.org/Boite-d-information
- passe par le pipeline
boite_infos
- créer un squelette
prive/infos/machin.html
(ouvrir les bons div
à la main)
- appeler
debut_boite_info
/fin_boite_info
Sinon à la main ça donne quelque chose comme:
echo debut_boite_info(true);
echo "<div class='infos'>";
echo "<div class='numero'>MACHIN NUMÉRO :<p>" . $id_machin . "</p></div>";
echo "</div>";
echo fin_boite_info(true);
Ensuite il y a un tas d'autres informations qu'on peut y insérer; pour les changements de statut, voir les fonctions "instituer_*" dans le code source, il y a des styles CSS déjà prêts.
Traitement dans la partie publique
Diverses solutions possibles:
Balise statique
La balise statique a pour but de présenter de l'information, si possible mise en cache.
Champ
Une balise #MONCHAMP à l'intérieur d'une boucle doit aller chercher le champ correspondant dans la base SQL.
TODO
Génération de code
SPIP va chercher une fonction balise_MABALISE($p)
ou balise_MABALISE_dist($p)
, et charge automatiquement le fichier balise/mabalise.php
s'il existe (vous pouvez donc définir la balise soit dans ce fichier, soit dans un fichier *_fonctions
).
La fonction qui fournit la balise modifie et renvoie un paramètre $p
qui a les attributs suivants:
$p->code
: l'expression PHP qui sera substituée à la balise
$p->interdire_scripts
: booléen, indique si le résultat de la balise doit être filtré avec la fonction interdire_scripts(...)
$p->param[][]
: les paramètres passés à la balise (#BALISE{param1, param2, ...}
); pour y accéder, on utilise interprete_argument_balise($n, $p)
où $n
est le numéro du paramètre (en comptant à partir de 1). Attention, la valeur obtenue est du code PHP à insérer dans $p->code, pas une valeur directement exploitable. Techniquement, $p->param[0][]
contient les paramètres entre {}
, et les éléments suivants contiennent les pseudo-filtres dépréciés ([(#BALISE|oldparam1|oldparam2|...)]
).
$p->etoile
: booléen, détermine si la balise est de type '*'
(MABALISE*
) ou '**'
(MABALISE**
). On peut le rédéfinir. Au final, '*'
signifie de ne pas appliquer divers post-traitements (cf. $table_des_traitements
), et '**'
signifie de, en plus, ne pas lancer interdire_scripts(...)
sur $p->code
(même si $p->interdire_scripts
est à true
). Cf. ecrire/public/references.php:applique_filtres(...)
.
$p->descr['session']
: si positionné à true
, un cache différent est créé par utilisateur authentifié, ainsi qu'un cache pour les visiteurs non authentifiés. Cela signifie que la balise fait référence à des informations personnelles du visiteur.
Exemple:
function balise_CITATION_dist($p)
{
$hasard = mt_rand(0, 1);
if ($hasard == 1)
$p->code = "'Gel en novembre, Noël en décembre! -- sagesse populaire'";
else
$p->code = "'Une de mes journées les plus productives a été de jeter 1000 lignes de code -- Ken Thompson'";
$p->code .= " . ' Cache squelette = ' . '" . strftime('%T') . "'";
$p->code .= " . ' Cache page = ' . strftime('%T')";
return $p;
}
Apparté: notez les deux types de code:
- D'une part, un traitement effectué au calcul de la balise: le choix de la citation. Le résultat est une expression PHP, ici une chaine de caractère - qui pour être spécifiée en PHP doit être insérée dans une autre chaine de caractères. La citation sélectionnée ne changera pas dans le cache.
- D'autre part, un traitement effectué au calcul de la page: la date. On enregistre non pas le résultat, mais le code, qui affichera l'heure courante, même si le cache squelette n'a pas changé.
En pratique, il n'est pas très utile de distinguer le cache de la page (tmp/cache/a
- calcul) et le cache de la balise (tmp/cache/skel
- recalcul). Le cache de la page est recalculé sans toucher celui de la balise dans le cas où l'on passe des paramètres supplémentaires dans l'URL, par exemple. Sinon préférez le cache complet (enregistrement du résultat plutôt que de l'appel de fonction).
Attention: le fichier balise/mabalise.php
n'est chargé qu'au calcul de la balise. Si vous avez besoin de fonctions auxiliaires, soit il faut les déclarer dans d'autres fichiers (xxx_fonctions.php
), soit il faut passer à une balise dynamique.
Notez qu'il existe des balises génériques, du type MABALISE_
(tout court), qui sont évaluées après pour toutes les balises MABALISE_XXX
, même si la balise en question n'existe pas. Cela permet de traiter notamment les balises spéciales FORMULAIRE_XXX
; lecode de SPIP définit également LOGO_XXX
et URL_XXX
. Cf. ecrire/public/references.php:calculer_balise(...)
et ecrire/balise/logo_.php
. Les balises ainsi crées peuvent être statiques (LOGO_XXX
) ou dynamiques (FORMULAIRE_XXX
).
Balise dynamique
Ces balises sont prévues pour traiter des données utilisateur, notamment des formulaires. Elle sont rechargées à chaque appel de page. La balise pourra afficher un <form>
avec pour cible la page courante, et on utilisera _request
dans le code PHP pour effectuer le traitement. Exemple: #FORMULAIRE_ABONNEMENT dans SPIP-Listes, cf. spip-listes_1_9_3/balise/formulaire_abonnement.php
.
La balise est définie par trois fonctions: TODO
balise_MABALISE($p)
: déclaration de la balise et des paramètres constants. Elle va en général appeler calculer_balise_dynamique($p, 'MABALISE', array('nom_arg1', 'nom_arg2', ...))
. Ces arguments sont des noms de champs que SPIP doit compléter avec de les passer à _stat
.
balise_MABALISE_stat($args, $filtres)
: $args
contient la listes des valeurs des arguments passés à calculer_balise_dynamique
, suivis des valeurs de ceux passés à la balise (MABALISE{val_arg3, val_arg4, ...}
). $filtres
contient des "pseudos-filtres", c'est à dire des paramètres non interprétés passés avec la syntaxe des filtres SPIP [(#MABALISE|filtre1|filtre2|...)]
; cette syntaxe est apparemment dépréciée en faveur de la précédente avec $args
. La fonction retourne un tableau d'arguments à passer à _dyn
, ou bien directement une valeur (qui peut être un message d'erreur ou non), auquel cas _dyn
ne sera pas appelée.
balise_MABALISE_dyn(...)
: en paramètre, les arguments construits dans _stat
. La valeur de retour est un tableau à 3 éléments: squelette, durée du cache (0 pour un traitement de formulaire par POST), environnement du squelette.
Exemple:
TODO
Trace:
#MABALISE{a,b,c,d}
=> balise_MABALISE_dist($p)
=> creer_balise_dynamique($p, 'MABALISE', array('id_auteur'))
=> balise_MABALISE_stat(array(18, 'a', 'b', 'c', 'd'), array())
=> balise_MABALISE_dyn(18, 4)
Pour le traitement des paramètres, voir par exemple ecrire/balise/login_public.php
et .ecrire/balise/formulaire_site.php
Techniquement, la balise dynamique est une balise statique normale, mais son code est triplement exécuté par PHP:
- d'abord la balise est calculée en PHP, avec l'appel à
calculer_balise_dynamique(...)
, et renvoie une expression PHP ($p->code
) qui est stockée dans le cache de squelette
$p->code
, qui contient un appel à executer_balise_dynamique(...)
, est exécuté pour générer le contenu, qui est stocké dans le cache de page; mais ici il ne s'agit pas de HTML comme dans la balise dynamique, mais à nouveau de code PHP
- le cache de la page est exécuté au moment de la visite du site, appelant la fonction
_dyn
, dont le résultat est passé à inclure_balise_dynamique(...)
.
- (et vu que
_dyn
peut faire appel à un squelette, on peut encore avoir du code interprété par la suite!)
Formulaire
Techniquement: traitement déclenché par le paramètre formulaire_action
On peut s'appuyer sur les outils "CVT" (charger/vérifier/traiter) de SPIP. Cette approche crée les formulaires prédéfinis, un par squelette - ce n'est pas prévu pour la génération de formulaires à la volée:
De plus, balise_FORMULAIRE_XXX_stat
(definit dans balise/formulaire_xxx.php
) devrait être appelé avant la génération du formulaire, ce qui permet de renvoyer un tableau de valeurs, qui sera passé via formulaire_action_args
sous forme comprimée et signée. Ces arguments seront par la suite passés en paramètre de _charger/_verifier/_traiter
. Cf. ecrire/balise/formulaire_inscription.php
et squelettes-dist/formulaires/inscription.php
.
Techniquement les formulaires CVT sont implémentés via la balise dynamique et générique FORM_
. Vous pouvez donc affiner votre formulaire en déclarant une balise dynamique de même nom.
Paramètres spéciaux pour _charger
:
_action
permet d'appeler securiser_action
_pipeline
fait passer le formulaire dans un pipeline
Quand le traitement est terminé, on peut (cf. traiter_formulaires_dynamiques(...)
):
- renvoyer un message:
$retour['message_ok'] = 'Bien reçu';
- rediriger vers une autre page:
$retour['redirect'] = URL;
Mémento des fichiers nécessaires:
- ./formulaires/XXX.html
- ./formulaires/XXX.php
- formulaires_xxx_charger_dist()
- formulaires_xxx_verifier_dist()
- formulaires_xxx_traiter_dist()
TODO
Modèles
L'utilisation d'une syntaxe <modeleN>
dans un article appelle le squelette modeles/modele.html
avec un contexte id_modele=N
. Ce squelette pourra inclure une balise correspondante, par exemple.
Cf. ecrire/inc/lien.php:traiter_modeles(...)
.
Exemples: prive/modeles/img.html
, plugins/forms_et_tables_1_9_1/modeles/form.html
.
Boucles
On peut créer ses propres boucles dans SPIP.
En l'absence de code spécifique, SPIP interprète le nom de la boucle comme le nom d'une table, et les balises comme les noms de ses champs.
Par exemple, si je crée une table mammiferes
avec des champs id_mammifere
, nom
et famille
:
CREATE TABLE `spip_mammiferes` (
`id_mammifere` int(11) NOT NULL auto_increment,
`nom` varchar(255) default NULL,
`famille` varchar(255) default NULL,
`statut` ENUM ('prepa', 'publie', 'poubelle'),
PRIMARY KEY (`id_mammifere`)
);
INSERT INTO `spip_mammiferes` (nom, famille, statut) VALUES ('dauphin', 'cétacés', 'publie');
INSERT INTO `spip_mammiferes` (nom, famille, statut) VALUES ('lapin', 'lagomorphes', 'prepa');
INSERT INTO `spip_mammiferes` (nom, famille, statut) VALUES ('singe', 'primates', 'publie');
alors je peux écrire directement le squelette suivant:
<BOUCLE_test(SPIP_MAMMIFERES){id_mammifere=3}>
Le #NOM appartient à la famille des #FAMILLE.
</BOUCLE_test>
Pour pouvoir utiliser une table sans le préfixe _spip
, on la déclare dans l'interface table_des_tables
:
<pipeline>
<nom>declarer_tables_interfaces</nom>
<inclure>monprefixe_pipelines.php</inclure>
</pipeline>
function &monprefixe_declarer_tables_interfaces(&$interfaces)
{
$interfaces['table_des_tables']['mammiferes'] = 'mammiferes';
return $interfaces;
}
On peut alors écrire:
<BOUCLE_test(MAMMIFERES){id_mammifere=3}>
Le #NOM appartient à la famille des #FAMILLE.
</BOUCLE_test>
Noter que les boucles sont prévues pour accéder à des tables SQL et à leurs champs; l'utiliser dans d'autres contextes est possible, mais relève de l'acrobatie et est donc forcément limité. Lorsque vous introduisez de nouvelles structures de données dans SPIP, il y a des chances que vous deviez les adapter à ce concept de boucles agissant sur une table, plutôt que d'essayer d'utiliser la boucle hors de son champ normal d'application.
On peut affiner le comportement de la boucle en déclarant une fonction boucle_MABOUCLE_dist
ou critere_MABOUCLE_moncritere_dist
. Le plus simple est d'inclure ces déclarations dans un fichier <fonction>
de plugin.xml
(chargé à chaque recalcul), par exemple:
<fonctions>public/monprefixe_boucles.php</fonctions>
function boucle_MABOUCLE_dist($id_boucle, &$boucles)
{
$boucle = &$boucles[$id_boucle];
// ...
return calculer_boucle($id_boucle, $boucles);
}
$id_boucle
: identifiant de la boucle, ex: _mesarticles pour une boucle <BOUCLE_mesarticles>
&$boucles
: tableau de toutes les boucles de la page, indexées par leur identifiant. Ce sont des objets SPIP complexes et internes. On utilisera typiquement $boucle = &$boucles[$id_boucle];
. Noter que les champs vont être
$boucle->id_table
: le nom de la table détecté par SPIP
$boucle->primary
: la clef primaire, apparemment auto-détectée
$boucle->select
: un tableau avec le nom de champs à récupérer; SPIP le remplit par défaut avec la liste des balises trouvées dans la boucle
$boucle->from
:
$boucle->from_type
:
$boucle->where
:
$boucle->join
:
$boucle->having
:
$boucle->limit
:
$boucle->group
:
$boucle->order
:
Par exemple, pour filtrer les mammifères non-publiés:
function boucle_MAMMIFERES_dist($id_boucle, &$boucles)
{
$boucle = &$boucles[$id_boucle];
$id_table = $boucle->id_table;
$mstatut = $id_table .'.statut';
$boucle->where[] = array("'='", "'$mstatut'", "'\\'publie\\''");
return calculer_boucle($id_boucle, $boucles);
}
Noter qu'un objet boucle est déjà présent au moment où l'on entre dans notre fonction. Il ne reste plus qu'à le compléter.
Étape suivante: on souhaite accéder à des champs d'autres tables dans la boucle. Imaginons que nous voulons dire qui a photographié un mammifère, et pouvoir filtrer uniquement les mammifères de cet auteur:
CREATE TABLE `spip_auteurs_mammiferes` (
`id_auteur` bigint(21) NOT NULL default '0',
`id_mammifere` bigint(21) NOT NULL default '0',
PRIMARY KEY (`id_auteur`,`id_mammifere`)
);
INSERT INTO spip_auteurs_mammiferes (`id_auteur`, `id_mammifere`) VALUES (1,1);
Si l'on spécifie {id_auteur=1}
dans notre boucle, il y a une erreur, car SPIP ne sait pas où chercher id_auteur
. Il faut le lui dire explicitement, à nouveau dans le pipeline declarer_tables_interfaces
:
function &monprefixe_declarer_tables_interfaces(&$interfaces)
{
$interfaces['table_des_tables']['mammiferes'] = 'mammiferes';
$interfaces['table_des_tables']['auteurs_mammiferes'] = 'auteurs_mammiferes'; // ou dans declarer_tables_auxiliaires
$interfaces['tables_jointures']['spip_mammiferes']['id_auteur'] = 'auteurs_mammiferes';
return $interfaces;
}
Ce qui nous permet par exemple d'écrire dans le squelette:
Les miens:<br />
<BOUCLE_lesmiens(MAMMIFERES){id_auteur=1}{doublons}>
Le #NOM appartient à la famille des #FAMILLE.<br />
</BOUCLE_lesmiens>
Les autres:<br />
<BOUCLE_lesautres(MAMMIFERES){doublons}>
Le #NOM appartient à la famille des #FAMILLE.<br />
</BOUCLE_lesautres>
Voir la section [Jointures automatiques->article64] pour plus de détails.
Cf. ecrire/public/compiler.php:public_compiler_dist(...)
et ecrire/public/criteres.php:calculter_criteres(...)
.
Exemples: ecrire/public/boucles.php
, forms_et_tables_1_9_1/public/forms_boucles.php
.
Critères
Par défaut un critère est le nom d'une colonne dans la table SQL parcourue par la boucle.
On peut également définir des critères dont le résultat est donné par une fonction PHP.
Déclaration:
<fonctions>public/monprefixe_criteres.php</fonctions>
critere_moncritere_dist($idb, &$boucles, $crit)
critere_MABOUCLE_moncritere_dist($idb, &$boucles, $crit)
Noter que la boucle (ici MABOUCLE
) n'a pas besoin d'être explicitement définie par ailleurs (on peut, mais ce n'est pas toujours le cas).
- $idb: identifiant de la boucle, ex:
_mesarticles
pour une boucle <BOUCLE_mesarticles>
- &$boucles: tableau de toutes les boucles de la page, indexées par leur identifiant. Ce sont des objets SPIP complexes et internes. On utilisera typiquement
$boucle = &$boucles[$idb];
- $crit: objet
Critere
; on utilisera notamment:
$crit->not
: le critère est inversé (!moncritere
)
$crit->param
: une structure complexe décrivant les paramètres du critères; il doit falloir passer par des fonctions SPIP pour les exploiter (calculer_liste
?)
- TODO
- Résultat: aucun; la fonction doit modifier l'objet boucle, par exemple son champ
having[]
, order[]
ou where[]
.
Si, dans le squelette, on utilise la syntaxe {moncritere == qqchose}
, SPIP force apparemment l'utilisation de moncritere comme champ de table (en plus de l'appel de notre fonction), ce qui cause une erreur si le critère n'en est pas un.
Exemples: ecrire/public/criteres.php
, spip-bonux/public/spip_bonux_criteres.php
.
Liens:
- Nouveau Critère: sur le wiki spip
Paramètre 'action'
Le fichier action/monprefixe_monaction.php
sera exécuté; cependant ce n'est pas prévu pour afficher du contenu, seulement pour du traitement.
Squelette dédié
Utiliser le paramètre page=
pour afficher un squelette de votre plugin, qui pourra contenir du PHP. L'inconvénient est le manque d'intégration dans le site public, puisque ce ne sera pas intégré dans les squelettes du webmestre.
Pipeline
- Les points d’entrée (pipelines): les hooks, quoi; une description partielle
Le terme pipeline vient du fait que le processus est amorcé avec des données initiales, qui sont passés à chaque hook qui les traite puis renvoie ces données modifiées pour les passer au hook suivant, formant ainsi une chaine de traitement.
La listes des hooks SPIP est dans ecrire/inc_version.php
. D'autres plugins peuvent en rajouter pour leurs besoins propres (ex: Forms&Tables).
Le plugin d'exemple, même s'il n'a pas été mis à jour pour la version 2, présente quelques points d'entrée importants, classés par catégorie (admin/cron/public/typo).
Déclaration dans plugin.xml:
<pipeline>
<nom>nom_pipeline</nom>
<inclure>monprefixe_pipelines.php</inclure>
<action>monprefixe_mafonction</action>
</pipeline>
nom
est obligatoire est précise le nom de pipeline dans lequel on insère notre traitement
inclure
est facultif, par défaut cherche les fonctions existantes - cependant certains pipelines (ex: declarer_tables_interfaces
) sont lancés avant que monprefixe_options.php
ne soit chargé, donc c'est une bonne habitude de toujours le spécifier.
action
est facultatif, par défaut lance la fonction monprefixe_nom_pipeline(...)
La valeur initiale passée au pipeline est:
- soit un tableau à deux élement:
args
qui contient des paramètres, et data
qui sera la valeur de retour du pipeline.
- soit une valeur quelconque, qui sera directement la valeur de retour du pipeline.
Lancement d'un pipeline perso: Déclarer un nouveau pipeline
Partie utilisateur
Modèles
Les modèles sont de nouveaux raccourcis typographiques que vos rédacteurs pourront inclure dans leurs articles.
TODO