Carnet Wiki

Compléments techniques pour l’ajout de tables dans SPIP

Version 5 — Octobre 2008 Antonio Damián

Note d’intention

J’ai déroulé le bel itinéraire décrit par Pierre Andrew (alias Mortimer, mortimerpa) sur spip-english [->english/1855" class="spip_url spip_out auto" rel="nofollow external">http://article.gmane.org/gmane.comp.web.spip.english/1855
] english/1855
]
et ai poursuivi sa documentation de manière durable notamment en recopiant dans le corps de l’article les morceaux pertinents de codes liés.

Il semblerai qu’un commit 11997 aie passablement modifié la bonne manière de faire. Cela fait l’objet d’un dernier chapitre d’après citation de Cedric Morin. Je ne crois pas que cela invalide cette doc mais cela en modifie les modalités d’usages. _ Ce serait bien de préciser cela :-)

Sébastien Denooz a ensuite proposé un ensemble de points à confirmer, dont Rastatopoulos a donné confirmation. Ces points figurent à la suite.

Il reste
-  à traduire tout en anglais ou tout en français.
-  à structurer le texte (et créer des intertitres) en dégageant par exemple les étapes successives de la vie des tables par exemple
-  améliorer la présentation en stylant par exemple les fichiers utilisés
-  préciser ce qu’il en est pour les critères

A bon rédacteur salut !

JLuc

Mémo technique

There is an article in french that is a bit more detailled here :
http://www.spip-contrib.net/Acces-S...

For creating tables of your own, it is quite easy with spip 1.9.2 and
2.0 as SPIP will gladly do Everything for you if you tell him the schema
of the table.

For an example, check out the plugin :
http://trac.spip.org/trac/spip-zone...

it is unfinished, but has the table declaration/creation already done.
First thing to do is create a description of the schema of the table ; In
the plugin this is done for three tables :

-  1re table : spip_basefiltrageip_whitelist.
En voici la déclaration complète :

$spip_basefiltrageip_whitelist 
  = array(
       "first_ip"     => "long NOT NULL",
       "last_ip"     => "long NOT NULL",
       "maj"         => "TIMESTAMP");


$spip_basefiltrageip_whitelist_key = array(
 	                         "PRIMARY KEY" => "first_ip",
 	                         "UNIQ" => "last_ip");
 	
global $tables_principales;
$tables_principales['spip_basefiltreageip_whitelist'] 
  = array(
       'field' => &$spip_basefiltrageip_whitelist,
       'key' => &$spip_basefiltrageip_whitelist_key);


$tables_principales['spip_basefiltreageip_whitelist'] 
  = array(
       'field' => &$spip_basefiltrageip_whitelist,
       'key' => &$spip_basefiltrageip_whitelist_key);

-  la 2e table spip_basefiltrageip_log est déclarée de la même manière, sauf qu’au lieu d’avoir une clé primaire et une clé unique, ce sont 2 clés simples qui sont déclarées.
Voici simplement la déclaration des clés

$spip_basefiltrageip_log_key
   = array(
        "KEY" => "ip",
        "KEY" => "reason_id");
 

-  la 3e table spip_basefiltrageip_reasons est déclarée pareil. La particularité dans la déclaration des clés est qu’elle a juste une clé primaire :

$spip_basefiltrageip_reasons_key 
  = array(
     "PRIMARY KEY" => "reason_id");

Then you declare the relations between them (joints) :

global $tables_jointures;
$tables_jointures['spip_basefiltreageip_reasons'][]
    = 'spip_basefiltreageip_log';

The joints is what you want to use to tell SPIP that your new
keywords/documents tables relate to your new object.

For example, in spip by default, you have :

$tables_jointures['spip_articles'][]= 'mots_articles';
$tables_jointures['spip_articles'][]= 'auteurs_articles';
$tables_jointures['spip_articles'][]= 'documents_articles';

You can also define more « complex » joint criterion with :

global  $exceptions_des_jointures;
$exceptions_des_jointures['titre_mot'] = array('spip_mots', 'titre');
$exceptions_des_jointures['type_mot'] = array('spip_mots', 'type');

and also the name of the loop for the table :

global $table_des_tables;
$table_des_tables['basefiltrageip_whitelist']='basefiltrageip_whitelist';
$table_des_tables['basefiltrageip_log']='basefiltrageip_log';

If you do not do this table_des_tables thing, then SPIP doesn’t know
that BOUCLE_(XXX) is about the table spip_xxx

Once you do that, you should tell the plugin how to install itself when
it’s activated :
dans http://trac.spip.org/trac/spip-zone...
c’est la ligne

<install>base/basefiltrageip_upgrade.php</install>

This tells spip to call the functions in /basefiltrageip_upgrade.php

This file should contain a function :
function prefix_install($action)

The function can be called with 3 actions :

-  $action=='test' every time the plugin admin page is loaded (hence, it should be light) and should return true if the plugin is installed
properly and up to date and false otherwise

-  $action=='install' will be called if the test returned false. In this
case, the plugin should install or upgrade itself (i.e. create tables,
etc. etc.)

-  $action=='uninstall' is called when the user click the uninstall
button (and not when he deactivates the plugin). This should remove
everything the plugin installed (i.e. drop tables)

Here, the interesting part is the install part :

function basefiltrageip_doupgrade() {
  include_spip('base/basefiltrageip_db');
  $installe = unserialize($GLOBALS['meta']['basefiltrageip:installe']);
  $uptodate = $installe && ($installe == $GLOBALS['basefiltrageip_version']);
  if(!$uptodate) {
      include_spip('base/create');
      include_spip('base/abstract_sql');
      creer_base();
      ecrire_meta('basefiltrageip:installe',$GLOBALS['basefiltrageip_version']);
   }
   ecrire_metas();
}

This is where tables should be created/updated, this can be done by spip
automatically (as the code exists to do it for spip tables), just do :

include_spip('base/basefiltrageip_db'); 
include_spip('base/create');
include_spip('base/abstract_sql');
creer_base();

creer_base will create the tables... or upgrade them according to the description in $tables_principales.
(beware : this will also upgrade the other tables described in
$tables_principales at the time of call...)

Once you have done all of this, then you can loop on your new tables
without declaring any criterion or any tags functions as SPIP knows how
to map them to the SQL schema.

If you want special processing (propre, etc.) applied to a field before returning it as a tag, you can do :
$table_des_traitements['TEXTE'][]= 'propre(%s)';

The second [] is for specifying a particular processing for a field in
one specific table. (see ecrire/public/interfaces.php)

You can also define more complex criterion and tags by defining
functions if you do not want more than a processed SQL field (like
#INTRODUCTION etc.).

This has a bit more documentation and example around (in french :( ) :
-  http://www.spip-contrib.net/CreerSa...
-  http://www.spip-contrib.net/Nouvell...

See also
-  http://trac.spip.org/trac/spip-zone...
for a bunch of basic examples (this plugin has also examples of new loops).

A new tag is created by making a file
balise/new_tag.php
in your plugin directory and defining the function. Here is an example
from the FpipR plugin :

<?php
  	function balise_ISFAMILY_dist($p) {
  	  $isfamily = champ_sql('isfamily',$p);
  	  $id_photo = champ_sql('id_photo',$p);
  	  $p->code = 
"(($isfamily)?$isfamily:FpipR_photos_getPerms($id_photo,'isfamily'))";
  	  return $p;
  	}
?>

The tricky part to understand here is that this function is executed
when the template cache is computed... hence, you will NEVER have real
values from a specific instance in the function. This code is executed
BEFORE any call to the sql database is done and before the loop is executed.

The balise_XXX is responsible for computing php code that will be
executed when the loop is executed.

The code can be static code when it
doesn’t depend on the loop context (i.e. db fields), or it can be php
code that will be executed when the html cache is computed.

Also note that the code you return will be used to compute the html
cache, so it cannot depend on specific form entries from the user etc..
in that case, you need to create a form tag which is a whole different
story :D

Anyway, back to the example...

-  champ_sql returns code to get the value of a sql field (but does not
yet contain that value) in the table of the loop where the tag is used.
In the example, we get the fields isfamily and id_photo.

-  $p->code contains the code you want to execute later when the loop is
executed. A good thing to do here is avoid putting complex code (you will get mixup in quotting, etc. etc.) but make a function in your
<function> file of the plugin that will do the hard job. For example,

here we call :
FpipR_photos_getPerms($id_photo,'isfamily')

There are other more complex things you can do with $p, I let you see
the examples around spip-zone and in
http://trac.rezo.net/trac/spip/brow...

You can see the resulting code by adding var_mode=debug and choosing the
code tab. It is very useful to experiment and try things to see what
happens.

I will let you experiment with all these and send a later mail about the
criterion.

Nouvelle gestion de tout cela dans SPIP 2

Le commit 11997 introduit dans SPIP 2.0 une nouvelle interface.
Les plugin agenda et acces restreint utilisent cette nouvelle interface.

En 2.0 mots :
-  avant
Chaque plugin définissait ses tables dans la globale en prenant soin d’inclure avant le base/serial pour avoir la definition de base et passer après. Résultat tout le monde incluait base/serial, et les tables étaient définies bien souvent inutilement.
-  maintenant
le core appelle base/serial, base/aux, et public/interfaces quand il en a besoin. A ce moment, les pipelines respectifs declarer_tables_principales, declarer_tables_auxiliaires et declarer_tables_interfaces sont appelés, ce qui permet a chaque plugin de faire ses definitions.
On peut ainsi optimiser la perfo de l’ensemble en ne chargeant que ce qui est necessaire (souvent les interfaces suffisent)

L’ancienne méthode fonctionne encore, mais est maintenant déconseillée.

Reformulation / Questions / réponses

  1. Déclaration des tables

-  La déclaration des tables passent elles UNIQUEMENT via ces trois fonctions :

prefixplugin_declarer_tables_interfaces($interface)
prefixplugin_declarer_tables_principales($tables_principales)
prefixplugin_declarer_tables_auxiliaires($tables_auxiliaires)

 ?

<blockquote class="spip">

Non.
-  Les pipelines n’ont pas forcément ces noms de fonctions : on donne le nom qu’on veut.
-  Ces pipelines ne sont pas la seule manière de déclarer : on peut toujours faire comme avant, c’est juste que c’est plus facile et plus standard.

Il vaudrait mieux dire « s’insérer dans le pipeline machin_truc » plutôt que donner des noms de fonctions comme tu l’as fait, qui donne à penser qu’on doit forcément nommer comme ça et que ça suffit.
En effet, si tu crées ces fonctions avec le nom que tu as donné, mais que tu ne te déclares pas dans plugin.xml, ça sert à rien non plus. Donc il vaut mieux être plus général.

Sinon le reste, c’est cool, ça va en éclaircir plus d’un !

</blockquote>

En conséquence on peut considérer que toutes les assertions suivantes sont « correctes, oui ! »...

-  prefixplugin_declarer_tables_interfaces($interface) sert à donner
le nom des boucles aux tables et à déclarer les jointures et les
traitements par défaut sur les champs des tables.

-  Dans cette fonction, la ligne
$interface['table_des_tables']['truc_machin']='trumachin'; donnera
une boucle <...(TRUCMACHIN)...>. Correct ?

-  Toujours dans cette fonction, la ligne
$interface['tables_jointures']['spip_bazar'][] = 'bazar_cathedral';
lie la table de jointure ’bazar_cathedral’ à la table ’spip_bazar’.
Correct ?

-  Toujours dans cette fonction, la ligne
$interface['exceptions_des_tables']['blizzar']['nuage']=array('mouton','poil');
déclare que dans la table ’blizzar’, le champ ’nuage’ fait référence
au champ ’poil’ de la table ’mouton’. Correct ?

-  Dans le cas ci dessus, la table ’mouton’ DOIT être déclarer en table
de jointure avant / après cette ligne, dans cette fonction. Correct ?

-  Toujours dans cette fonction, la ligne
$interface['table_des_traitements']['PLUIE'][]= 'mouille(%s)';
Appliquera la fonction php « mouille » à toutes les balises « #PLUIE ».
Correct ?

-  Est-ce que cette notation fonctionnera :

$interface['table_des_traitements']['PLUIE'][]=
'rafraichir(tombe(mouille(%s)))';

 ?

-  Dans le cas ci-dessus, sur n’importe quel table ?

-  Toujours dans cette fonction, que ferais
$interface['table_date']['concert'] = 'date_ouverture';"  ?

-  prefixplugin_declarer_tables_principales($tables_principales) sert
pour déclarer les tables principales, celles qui contiendront
l’information à afficher, et notamment celles sur lesquelles on fera
les boucles. Correcte ?

-  prefixplugin_declarer_tables_auxiliaires($tables_auxiliaires) sert
à déclarer les tables de jointure. Correct ?

2) Installation des bases

-  Est-ce que prefixplugin_install() est encore nécessaire /
d’actualité dans SPIP 2. ?

-  Sinon, est-ce que
prefixplugin_vider_tables()
et
prefixplugin_upgrade()
du fichier php renseigné dans le <install> suffisent à installer/désinstaller les tables ?

-  La fonction prefixplugin_vider_tables() contient le code php de
suppression des tables du plugin. Correct ?

-  La fonction prefixplugin _upgrade() contient le code de mise à
jour ET d’installation des tables du plugin. Correct ?