Valider abc7193e rédigé par marcimat's avatar marcimat
Parcourir les fichiers

Intégration dans SPIP de la possibilité de gérer des liens entre objets

avec des rôles.

On intègre l'API présente actuellement dans le plugin Rôles, en modifiant
un peu les fonctions d'édition de liens.

Celles-ci permettent maintenant d'éditer des liens ayant donc des rôles.
Ces différents rôles et le nom de la colonne SQL qui les reçoit,
s'ils sont utilisés, doivent être déclarés avec la déclaration
de l'objet éditorial correspondant.

Un exemple est donné avec le plugin «Roles auteurs» qui définit
quelques rôles. Les champs décrivant les rôles : `roles_colonne`, `roles_titres` et `roles_objets`
doivent être déclarés (via le pipeline declarer_tables_objets_sql).

```
"roles_colonne" => "role",
"roles_titres" => array(
	'redacteur'  => 'info_statut_redacteur',
	'traducteur' => 'roles_auteurs:traducteur',
	'correcteur' => 'roles_auteurs:correcteur',
	'relecteur'  => 'roles_auteurs:relecteur',
),
"roles_objets" => array(
	'articles' => array(
		'choix' => array('redacteur', 'traducteur', 'correcteur', 'relecteur'),
		'defaut' => 'redacteur'
	)
	#'*' => array()
)
```

Une fois déclaré, on peut appeler les fonctions d'édition de lien
en transmettant des valeurs de rôles, tel que :

```
objet_associer(
	array('auteur' => 3),
	array('article' => 11),
	array('role' => 'correcteur')
);

// utilisera le rôle par défaut
objet_associer(
	array('auteur' => 3),
	array('article' => 11)
);
```

Si aucun rôle n'est indiqué, le rôle par défaut est appliqué.

Dans le cas d'une dissociation également, si aucun rôle n'est indiqué,
seuls les liaisons avec le rôle par défaut seront supprimés ; pour
supprimer tous les rôles, il faut à ce moment là indiquer '*' :

```
objet_dissocier(
	array('auteur' => 3),
	array('article' => 11),
	array('role' => 'correcteur')
);

// utilisera le rôle par défaut
objet_dissocier(
	array('auteur' => 3),
	array('article' => 11)
);

// enlèvera tous les rôles
objet_dissocier(
	array('auteur' => 3),
	array('article' => 11),
	array('role' => '*')
);
```


Le formulaire d'édition de liens n'utilisera pas les mêmes squelettes
de liaison lorsqu'une colonne de rôle est déclarée.

Ainsi dans cet exemple, au lieu de `prive/objets/liste/auteurs_lies.html`
et `auteurs_associer.html`, cela utiliserait `prive/objets/liste/auteurs_roles_lies.html`
et `auteurs_roles_associer.html`. Il faut donc créer ces squelettes.


Ces squelettes peuvent poster les valeurs au formulaire pour insérer
de nouveaux liens, de la forme `qualifier_lien[auteur-3-article-11][role]`
en postant `redacteur` par exemple.

Il est possible au passage de poster en plus d'autres valeurs, qui seront
intégrées dans l'enregistrement du lien.
Ainsi, poster en même temps `qualifier_lien[auteur-3-article-11][valeur]` = `50`
enregistrera la valeur 50 dans la colonne `valeur` de la table de lien (qui doit
exister !).

D'autres informations sont présentes dans http://contrib.spip.net/Des-roles-sur-des-liens,
http://zone.spip.org/trac/spip-zone/browser/_plugins_/roles_auteurs ou encore
http://zone.spip.org/trac/spip-zone/browser/_plugins_/roles
parent ea418c69
Chargement en cours
Chargement en cours
Chargement en cours
Chargement en cours
+1 −0
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
@@ -144,6 +144,7 @@ ecrire/inc/puce_statut.php -text
ecrire/inc/queue.php -text
ecrire/inc/recherche_to_array.php -text
ecrire/inc/rechercher.php -text
ecrire/inc/roles.php -text
ecrire/inc/securiser_action.php -text
ecrire/inc/selectionner.php -text
ecrire/inc/texte_mini.php -text
+107 −30
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
@@ -3,7 +3,7 @@
/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2014                                                *
 *  Copyright (c) 2001-2015                                                *
 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
 *                                                                         *
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
@@ -13,8 +13,6 @@
/**
 * API d'édition de liens
 *
 * @package SPIP\Core\Liens\API
 *
 * Cette API gère la création, modification et suppressions de liens
 * entre deux objets éditoriaux par l'intermédiaire de tables de liaison
 * tel que spip_xx_liens.
@@ -22,12 +20,18 @@
 * L'unicité est assurée dans les fonctions sur le trio (id_x, objet, id_objet)
 * par défaut, ce qui correspond à la déclaration de clé primaire.
 *
 * Des rôles peuvent être déclarés pour des liaisons. À ce moment là,
 * une colonne spécifique doit être présente dans la table de liens
 * et l'unicité est alors assurée sur le quatuor (id_x, objet, id_objet, role)
 * et la clé primaire adaptée en conséquence.
 *
 * @package SPIP\Core\Liens\API
 */
 
if (!defined('_ECRIRE_INC_VERSION')) return;


// charger la gestion les rôles sur les objets
include_spip('inc/roles');


/**
@@ -78,7 +82,7 @@ function objet_associable($objet){
 * @return bool|int
 */
function objet_associer($objets_source, $objets_lies, $qualif = null){
	$modifs = objet_traiter_liaisons('lien_insert', $objets_source, $objets_lies);
	$modifs = objet_traiter_liaisons('lien_insert', $objets_source, $objets_lies, $qualif);

	if ($qualif)
		objet_qualifier_liens($objets_source, $objets_lies, $qualif);
@@ -101,13 +105,23 @@ function objet_associer($objets_source, $objets_lies, $qualif = null){
 * un * pour $objet,$id_objet permet de traiter par lot
 * seul le type de l'objet source ne peut pas accepter de joker et doit etre explicite
 *
 * S'il y a des rôles possibles entre les 2 objets, et qu'aucune condition
 * sur la colonne du rôle n'est transmise, on ne supprime que les liens
 * avec le rôle par défaut. Si on veut supprimer tous les rôles,
 * il faut spécifier $cond => array('role' => '*')
 *
 * @api
 * @param array $objets_source
 * @param array|string $objets_lies
 * @param array|null $cond
 *     Condition du where supplémentaires
 *
 *     À l'exception de l'index 'role' qui permet de sélectionner un rôle
 *     ou tous les rôles (*), en s'affranchissant du vrai nom de la colonne.
 * @return bool|int
 */
function objet_dissocier($objets_source,$objets_lies){
	return objet_traiter_liaisons('lien_delete',$objets_source,$objets_lies);
function objet_dissocier($objets_source,$objets_lies, $cond=null){
	return objet_traiter_liaisons('lien_delete',$objets_source,$objets_lies, $cond);
}


@@ -163,11 +177,12 @@ function objet_qualifier_liens($objets_source,$objets_lies,$qualif){
 * @api
 * @param array $objets_source      Couples (objets_source => identifiants) (objet qui a la table de lien)
 * @param array|string $objets_lies Couples (objets_lies => identifiants)
 * @param array|null $cond          Condition du where supplémentaires
 * @return array
 *     Liste des trouvailles
 */
function objet_trouver_liens($objets_source,$objets_lies){
	return objet_traiter_liaisons('lien_find',$objets_source,$objets_lies);
function objet_trouver_liens($objets_source,$objets_lies,$cond=null){
	return objet_traiter_liaisons('lien_find',$objets_source,$objets_lies,$cond);
}


@@ -325,27 +340,49 @@ function objet_traiter_liaisons($operation,$objets_source,$objets_lies, $set = n
 * @internal
 * @param string $objet_source  Objet source de l'insertion (celui qui a la table de liaison)
 * @param string $primary       Nom de la clé primaire de cet objet
 * @param sgring $table_lien    Nom de la table de lien de cet objet
 * @param string $table_lien    Nom de la table de lien de cet objet
 * @param int $id               Identifiant de l'objet sur lesquels on va insérer des liaisons
 * @param array $objets         Liste des liaisons à faire, de la forme array($objet=>$id_objets)
 *
 * @param array $qualif
 *     Liste des qualifications à appliquer (qui seront faites par lien_set()),
 *     dont on cherche un rôle à insérer également.
 *     Si l'objet dispose d'un champ rôle, on extrait des qualifications
 *     le rôle s'il est présent, sinon on applique le rôle par défaut.
 * @return bool|int
 *     Nombre d'insertions faites, false si échec.
 */
function lien_insert($objet_source,$primary,$table_lien,$id,$objets) {
function lien_insert($objet_source,$primary,$table_lien,$id,$objets,$qualif) {
	$ins = 0;
	$echec = null;
	if (is_null($qualif)) $qualif = array();

	foreach($objets as $objet => $id_objets){
		if (!is_array($id_objets)) $id_objets = array($id_objets);

		// role, colonne, where par défaut
		list($role, $colonne_role, $cond) =
			roles_trouver_dans_qualif($objet_source, $objet, $qualif);

		foreach($id_objets as $id_objet) {
			$objet = ($objet=='*')?$objet:objet_type($objet); # securite

			$insertions = array(
				'id_objet' => $id_objet,
				'objet'    => $objet,
				$primary   => $id
			);
			// rôle en plus s'il est défini
			if ($role) $insertions += array(
				$colonne_role => $role
			);
			$args = array(
				'table_lien'      => $table_lien,
				'objet_source'    => $objet_source,
				'id_objet_source' => $id,
				'objet'           => $objet,
				'id_objet'        => $id_objet,
				'role'            => $role,
				'colonne_role'    => $colonne_role,
				'action'          => 'insert',
			);

@@ -358,14 +395,12 @@ function lien_insert($objet_source,$primary,$table_lien,$id,$objets) {
			);
			$args['id_objet'] = $id_objet;

			if ($id_objet=intval($id_objet)
				AND !sql_getfetsel(
								$primary,
								$table_lien,
								array('id_objet='.intval($id_objet), 'objet='.sql_quote($objet), $primary.'='.intval($id))))
			{
			$where = lien_where($primary, $id, $objet, $id_objet, $cond);

			if ($id_objet=intval($insertions['id_objet'])
				AND !sql_getfetsel($primary, $table_lien, $where)) {

					$e = sql_insertq($table_lien, array('id_objet' => $id_objet, 'objet'=>$objet, $primary=>$id));
					$e = sql_insertq($table_lien, $insertions);
					if ($e!==false) {
						$ins++;
						lien_propage_date_modif($objet,$id_objet);
@@ -394,9 +429,10 @@ function lien_insert($objet_source,$primary,$table_lien,$id,$objets) {
 * @param int|string|array $id_source   Identifiant de la clé primaire
 * @param string $objet                 Nom de l'objet lié
 * @param int|string|array $id_objet    Identifiant de l'objet lié
 * @param array $cond                   Conditions par défaut
 * @return array                        Liste des conditions
 */
function lien_where($primary, $id_source, $objet, $id_objet){
function lien_where($primary, $id_source, $objet, $id_objet, $cond=array()){
	if ((!is_array($id_source) AND !strlen($id_source))
	  OR !strlen($objet)
	  OR (!is_array($id_objet) AND !strlen($id_objet)))
@@ -407,7 +443,9 @@ function lien_where($primary, $id_source, $objet, $id_objet){
		$not = array_shift($id_source);
		$id_source = reset($id_source);
	}
	$where = array();

	$where = $cond;

	if ($id_source!=='*')
		$where[] = (is_array($id_source)?sql_in(addslashes($primary),array_map('intval',$id_source),$not):addslashes($primary) . ($not?"<>":"=") . intval($id_source));
	elseif ($not)
@@ -438,26 +476,41 @@ function lien_where($primary, $id_source, $objet, $id_objet){
 * array($objet=>$id_objets,...)
 * un * pour $id,$objet,$id_objets permet de traiter par lot
 *
 * On supprime tous les liens entre les objets indiqués par défaut,
 * sauf s'il y a des rôles déclarés entre ces 2 objets, auquel cas on ne
 * supprime que les liaisons avec le role déclaré par défaut si rien n'est
 * précisé dans $cond. Il faut alors passer $cond=array('role'=>'*') pour
 * supprimer tous les roles, ou array('role'=>'un_role') pour un role précis.
 *
 * @internal
 * @param string $objet_source
 * @param string $primary
 * @param string $table_lien
 * @param int $id
 * @param array $objets
 * @param array|null $cond
 *     Conditions where par défaut.
 *     Un cas particulier est géré lorsque l'index 'role' est présent (ou absent)
 * @return bool|int
 */
function lien_delete($objet_source,$primary,$table_lien,$id,$objets){
function lien_delete($objet_source,$primary,$table_lien,$id,$objets,$cond=null){

	$retire = array();
	$dels = 0;
	$echec = false;
	if (is_null($cond)) $cond = array();

	foreach($objets as $objet => $id_objets){
		$objet = ($objet=='*')?$objet:objet_type($objet); # securite
		if (!is_array($id_objets) OR reset($id_objets)=="NOT") $id_objets = array($id_objets);
		foreach($id_objets as $id_objet) {
			list($cond, $colonne_role, $role) = roles_creer_condition_role($objet_source, $objet, $cond);
			// id_objet peut valoir '*'
			$where = lien_where($primary, $id, $objet, $id_objet);
			$where = lien_where($primary, $id, $objet, $id_objet, $cond);

			// lire les liens existants pour propager la date de modif
			$select = "$primary,id_objet,objet";
			if ($colonne_role) $select .= ",$colonne_role";
			$liens = sql_allfetsel($select,$table_lien,$where);

			// iterer sur les liens pour permettre aux plugins de gerer
@@ -469,6 +522,8 @@ function lien_delete($objet_source,$primary,$table_lien,$id,$objets){
					'id_objet_source' => $l[$primary],
					'objet' => $l['objet'],
					'id_objet' => $l['id_objet'],
					'colonne_role' => $colonne_role,
					'role' => ($colonne_role ? $l[$colonne_role] : ''),
					'action'=>'delete',
				);

@@ -481,8 +536,8 @@ function lien_delete($objet_source,$primary,$table_lien,$id,$objets){
				);
				$args['id_objet'] = $id_o;

				if ($id_o=intval($id_o)){
					$where = lien_where($primary, $l[$primary], $l['objet'], $id_o);
				if ($id_o=intval($l['id_objet'])) {
					$where = lien_where($primary, $l[$primary], $l['objet'], $id_o, $cond);
					$e = sql_delete($table_lien, $where);
					if ($e!==false){
						$dels+=$e;
@@ -495,6 +550,7 @@ function lien_delete($objet_source,$primary,$table_lien,$id,$objets){
						'source' => array($objet_source=>$l[$primary]),
						'lien'   => array($l['objet']=>$id_o),
						'type'   => $l['objet'],
						'role'   => ($colonne_role ? $l[$colonne_role] : ''),
						'id'     => $id_o
					);
					// Envoyer aux plugins
@@ -528,7 +584,7 @@ function lien_delete($objet_source,$primary,$table_lien,$id,$objets){
 * @internal
 * @param string $objet_source
 * @param string $primary
 * @param sgring $table_lien
 * @param string $table_lien
 * @param int $id
 * @param array $objets
 * @return bool|int
@@ -602,6 +658,10 @@ function lien_optimise($objet_source,$primary,$table_lien,$id,$objets){
 * @param array $objets         Liste des liaisons à faire, de la forme array($objet=>$id_objets)
 * @param array $qualif
 *     Liste des qualifications à appliquer.
 *
 *     Si l'objet dispose d'un champ rôle, on extrait des qualifications
 *     le rôle s'il est présent, sinon on applique les qualifications
 *     sur le rôle par défaut.
 * @return bool|int
 *     Nombre de modifications faites, false si échec.
 */
@@ -619,6 +679,11 @@ function lien_set($objet_source,$primary,$table_lien,$id,$objets,$qualif){
	unset($qualif['objet']);
	unset($qualif['id_objet']);
	foreach($objets as $objet => $id_objets) {

		// role, colonne, where par défaut
		list($role, $colonne_role, $cond) =
			roles_trouver_dans_qualif($objet_source, $objet, $qualif);

		$objet = ($objet=='*')?$objet:objet_type($objet); # securite
		if (!is_array($id_objets) OR reset($id_objets)=="NOT") $id_objets = array($id_objets);
		foreach($id_objets as $id_objet) {
@@ -629,6 +694,8 @@ function lien_set($objet_source,$primary,$table_lien,$id,$objets,$qualif){
				'id_objet_source' => $id,
				'objet'           => $objet,
				'id_objet'        => $id_objet,
				'role'            => $role,
				'colonne_role'    => $colonne_role,
				'action'          => 'modifier',
			);

@@ -641,7 +708,7 @@ function lien_set($objet_source,$primary,$table_lien,$id,$objets,$qualif){
			);
			$args['id_objet'] = $id_objet;

			$where = lien_where($primary, $id, $objet, $id_objet);
			$where = lien_where($primary, $id, $objet, $id_objet, $cond);
			$e = sql_updateq($table_lien,$qualif,$where);

			if ($e===false) {
@@ -671,6 +738,9 @@ function lien_set($objet_source,$primary,$table_lien,$id,$objets,$qualif){
 * array($objet=>$id_objets,...)
 * un * pour $id,$objet,$id_objets permet de traiter par lot
 *
 * Le tableau de condition peut avoir un index 'role' indiquant de
 * chercher un rôle précis, ou * pour tous les roles (alors équivalent
 * à l'absence de l'index)
 *
 * @internal
 * @param string $objet_source
@@ -678,14 +748,21 @@ function lien_set($objet_source,$primary,$table_lien,$id,$objets,$qualif){
 * @param string $table_lien
 * @param int $id
 * @param array $objets
 * @param array|null $cond
 *     Condition du where par défaut
 *
 *     On peut passer un index 'role' pour sélectionner uniquement
 *     le role défini dedans (et '*' pour tous les rôles).
 * @return array
 */
function lien_find($objet_source,$primary,$table_lien,$id,$objets){
function lien_find($objet_source,$primary,$table_lien,$id,$objets,$cond=null){
	$trouve = array();
	foreach($objets as $objet => $id_objets){
		$objet = ($objet=='*')?$objet:objet_type($objet); # securite
		// gerer les roles s'il y en a dans $cond
		list($cond) = roles_creer_condition_role($objet_source, $objet, $cond, true);
		// lien_where prend en charge les $id_objets sous forme int ou array
		$where = lien_where($primary, $id, $objet, $id_objets);
		$where = lien_where($primary, $id, $objet, $id_objets, $cond);
		$liens = sql_allfetsel('*',$table_lien,$where);
		// ajouter les entrees objet_source et objet cible par convenance
		foreach($liens as $l) {

ecrire/inc/roles.php

0 → 100644
+368 −0

Fichier ajouté.

La taille limite d'aperçu a été dépassée, l'affichage des modifications a donc été réduit.

+239 −80
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
<?php

/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
@@ -8,7 +7,7 @@
 *                                                                         *
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
\***************************************************************************/
 * \***************************************************************************/

/**
 * Gestion du formulaire d'édition de liens
@@ -70,10 +69,22 @@ function determine_source_lien_objet($a,$b,$c){
 * @param string $a
 * @param string|int $b
 * @param int|string $c
 * @param bool $editable
 * @param array|bool $options
 *    - Si array, tableau d'options
 *    - Si bool : valeur de l'option 'editable' uniquement
 *
 * @return array
 */
function formulaires_editer_liens_charger_dist($a,$b,$c,$editable=true){
function formulaires_editer_liens_charger_dist($a, $b, $c, $options = array()){

	// compat avec ancienne signature ou le 4eme argument est $editable
	if (!is_array($options)){
		$options = array('editable' => $options);
	} elseif (!isset($options['editable'])) {
		$options['editable'] = true;
	}

	$editable = $options['editable'];

	list($table_source, $objet, $id_objet, $objet_lien) = determine_source_lien_objet($a, $b, $c);
	if (!$table_source OR !$objet OR !$objet_lien OR !$id_objet)
@@ -91,13 +102,26 @@ function formulaires_editer_liens_charger_dist($a,$b,$c,$editable=true){
	include_spip('inc/autoriser');
	$editable = ($editable and autoriser('associer' . $table_source, $objet, $id_objet) and autoriser('modifier', $objet, $id_objet));

	if (!$editable AND !count(objet_trouver_liens(array($objet_lien=>$objet_lien==$objet_source?'*':$id_objet),array(($objet_lien==$objet_source?$objet:$objet_source)=>$objet_lien==$objet_source?$id_objet:'*'))))
	if (!$editable AND !count(objet_trouver_liens(array($objet_lien => '*'), array(($objet_lien==$objet_source ? $objet : $objet_source) => '*'))))
		return false;

	// squelettes de vue et de d'association
	// ils sont différents si des rôles sont définis.
	$skel_vue = $table_source . "_lies";
	$skel_ajout = $table_source . "_associer";

	// description des roles
	include_spip('inc/roles');
	if ($roles = roles_presents($objet_source, $objet)){
		// on demande de nouveaux squelettes en conséquence
		$skel_vue = $table_source . "_roles_lies";
		$skel_ajout = $table_source . "_roles_associer";
	}

	$valeurs = array(
		'id' => "$table_source-$objet-$id_objet-$objet_lien", // identifiant unique pour les id du form
		'_vue_liee' => $table_source."_lies",
		'_vue_ajout' => $table_source."_associer",
		'_vue_liee' => $skel_vue,
		'_vue_ajout' => $skel_ajout,
		'_objet_lien' => $objet_lien,
		'id_lien_ajoute' => _request('id_lien_ajoute'),
		'objet' => $objet,
@@ -108,37 +132,61 @@ function formulaires_editer_liens_charger_dist($a,$b,$c,$editable=true){
		'visible' => 0,
		'ajouter_lien' => '',
		'supprimer_lien' => '',
		'qualifier_lien' => '',
		'_roles' => $roles, # description des roles
		'_oups' => _request('_oups'),
		'editable' => $editable,
	);

	// les options non definies dans $valeurs sont passees telles quelles au formulaire html
	$valeurs = array_merge($options,$valeurs);

	return $valeurs;
}

/**
 * Traiter le post des informations d'édition de liens
 *
 * Les formulaires postent dans trois variables ajouter_lien et supprimer_lien
 * et remplacer_lien
 * Les formulaires peuvent poster dans quatre variables
 * - ajouter_lien et supprimer_lien
 * - remplacer_lien
 * - qualifier_lien
 *
 * Les deux premieres peuvent etre de trois formes differentes :
 * Les deux premières peuvent être de trois formes différentes :
 * ajouter_lien[]="objet1-id1-objet2-id2"
 * ajouter_lien[objet1-id1-objet2-id2]="nimportequoi"
 * ajouter_lien['clenonnumerique']="objet1-id1-objet2-id2"
 * Dans ce dernier cas, la valeur ne sera prise en compte
 * que si _request('clenonnumerique') est vrai (submit associe a l'input)
 * que si _request('clenonnumerique') est vrai (submit associé a l'input)
 *
 * remplacer_lien doit etre de la forme
 * remplacer_lien doit être de la forme
 * remplacer_lien[objet1-id1-objet2-id2]="objet3-id3-objet2-id2"
 * ou objet1-id1 est celui qu'on enleve et objet3-id3 celui qu'on ajoute
 *
 * qualifier_lien doit être de la forme, et sert en complément de ajouter_lien
 * qualifier_lien[objet1-id1-objet2-id2][role] = array("role1", "autre_role")
 * qualifier_lien[objet1-id1-objet2-id2][valeur] = array("truc", "chose")
 * produira 2 liens chacun avec array("role"=>"role1","valeur"=>"truc") et array("role"=>"autre_role","valeur"=>"chose")
 *
 * @param string $a
 * @param string|int $b
 * @param int|string $c
 * @param bool $editable
 * @param array|bool $options
 *    - Si array, tableau d'options
 *    - Si bool : valeur de l'option 'editable' uniquement
 *
 * @return array
 */
function formulaires_editer_liens_traiter_dist($a,$b,$c,$editable=true){
function formulaires_editer_liens_traiter_dist($a, $b, $c, $options = array()){
	// compat avec ancienne signature ou le 4eme argument est $editable
	if (!is_array($options)){
		$options = array('editable' => $options);
	} elseif (!isset($options['editable'])) {
		$options['editable'] = true;
	}

	$editable = $options['editable'];

	$res = array('editable' => $editable ? true : false);
	list($table_source, $objet, $id_objet, $objet_lien) = determine_source_lien_objet($a, $b, $c);
	if (!$table_source OR !$objet OR !$objet_lien)
@@ -153,7 +201,8 @@ function formulaires_editer_liens_traiter_dist($a,$b,$c,$editable=true){
		// annuler les suppressions du coup d'avant !
		if (_request('annuler_oups')
			AND $oups = _request('_oups')
			AND $oups = unserialize($oups)){
			AND $oups = unserialize($oups)
		){
			if ($oups_objets = charger_fonction("editer_liens_oups_{$table_source}_{$objet}_{$objet_lien}", "action", true)){
				$oups_objets($oups);
			}
@@ -199,14 +248,15 @@ function formulaires_editer_liens_traiter_dist($a,$b,$c,$editable=true){
				foreach ($supprimer as $k => $v){
					if ($lien = lien_verifier_action($k, $v)){
						$lien = explode("-", $lien);
						list($objet_source,$ids,$objet_lie,$idl) = $lien;
						list($objet_source, $ids, $objet_lie, $idl, $role) = $lien;
						// appliquer une condition sur le rôle si défini ('*' pour tous les roles)
						$cond = (!is_null($role) ? array('role' => $role) : array());
						if ($objet_lien==$objet_source){
							$oups = array_merge($oups,  objet_trouver_liens(array($objet_source=>$ids), array($objet_lie=>$idl)));
							objet_dissocier(array($objet_source=>$ids), array($objet_lie=>$idl));
						}
						else{
							$oups = array_merge($oups,  objet_trouver_liens(array($objet_lie=>$idl), array($objet_source=>$ids)));
							objet_dissocier(array($objet_lie=>$idl), array($objet_source=>$ids));
							$oups = array_merge($oups, objet_trouver_liens(array($objet_source => $ids), array($objet_lie => $idl), $cond));
							objet_dissocier(array($objet_source => $ids), array($objet_lie => $idl), $cond);
						} else {
							$oups = array_merge($oups, objet_trouver_liens(array($objet_lie => $idl), array($objet_source => $ids), $cond));
							objet_dissocier(array($objet_lie => $idl), array($objet_source => $ids), $cond);
						}
					}
				}
@@ -225,10 +275,12 @@ function formulaires_editer_liens_traiter_dist($a,$b,$c,$editable=true){
					if ($lien = lien_verifier_action($k, $v)){
						$ajout_ok = true;
						list($objet1, $ids, $objet2, $idl) = explode("-", $lien);
						if ($objet_lien==$objet1)
							objet_associer(array($objet1=>$ids), array($objet2=>$idl));
						else
							objet_associer(array($objet2=>$idl), array($objet1=>$ids));
						$qualifs = lien_retrouver_qualif($objet_lien, $lien);
						if ($objet_lien==$objet1){
							lien_ajouter_liaisons($objet1, $ids, $objet2, $idl, $qualifs);
						} else {
							lien_ajouter_liaisons($objet2, $idl, $objet1, $ids, $qualifs);
						}
						set_request('id_lien_ajoute', $ids);
					}
				}
@@ -253,24 +305,131 @@ function formulaires_editer_liens_traiter_dist($a,$b,$c,$editable=true){
 * ou supprimer_lien
 *
 * L'action est de la forme : objet1-id1-objet2-id2
 * ou de la forme : objet1-id1-objet2-id2-role
 *
 * L'action peut-être indiquée dans la clé ou dans la valeur.
 * Si elle est indiquee dans la valeur et que la clé est non numérique,
 * on ne la prend en compte que si un submit avec la clé a été envoyé
 *
 * @internal
 * @param string $k Clé du tableau
 * @param string $v Valeur du tableau
 * @return string Action demandée si trouvée, sinon ''
 */
function lien_verifier_action($k, $v){
	if (preg_match(",^\w+-[\w*]+-[\w*]+-[\w*]+,",$k))
		return $k;
	if (preg_match(",^\w+-[\w*]+-[\w*]+-[\w*]+,",$v)){
	$action = '';
	if (preg_match(",^\w+-[\w*]+-[\w*]+-[\w*]+(-[\w*])?,", $k))
		$action = $k;
	if (preg_match(",^\w+-[\w*]+-[\w*]+-[\w*]+(-[\w*])?,", $v)){
		if (is_numeric($k))
			return $v;
			$action = $v;
		if (_request($k))
			return $v;
			$action = $v;
	}
	// ajout un role null fictif (plus pratique) si pas défini
	if ($action and count(explode("-", $action))==4){
		$action .= '-';
	}
	return $action;
}


/**
 * Retrouve le ou les qualificatifs postés avec une liaison demandée
 *
 * @internal
 * @param string $objet_lien
 *    objet qui porte le lien
 * @param string $lien
 *   Action du lien
 * @return array
 *   Liste des qualifs pour chaque lien. Tableau vide s'il n'y en a pas.
 **/
function lien_retrouver_qualif($objet_lien, $lien){
	// un role est défini dans la liaison
	$defs = explode('-', $lien);
	list($objet1, , $objet2, , $role) = $defs;
	if ($objet_lien==$objet1){
		$colonne_role = roles_colonne($objet1, $objet2);
	}
	return '';
	else {
		$colonne_role = roles_colonne($objet2, $objet1);
	}
?>

	// cas ou le role est defini en 5e argument de l'action sur le lien (suppression, ajout rapide sans autre attribut)
	if ($role) {
		return array(
			// un seul lien avec ce role
			array($colonne_role=>$role)
		);
	}

	// retrouver les rôles postés pour cette liaison, s'il y en a.
	$qualifier_lien = _request('qualifier_lien');
	if (!$qualifier_lien OR !is_array($qualifier_lien)){
		return array();
	}

	// pas avec l'action complete (incluant le role)
	$qualif = array();
	if ((!isset($qualifier_lien[$lien]) OR !$qualif = $qualifier_lien[$lien])
	  AND count($defs)==5){
		// on tente avec l'action sans le role
		array_pop($defs);
		$lien = implode('-', $defs);
		if (!isset($qualifier_lien[$lien]) OR !$qualif = $qualifier_lien[$lien]){
			$qualif = array();
		}
	}

	// $qualif de la forme array(role=>array(...),valeur=>array(...),....)
	// on le reforme en array(array(role=>..,valeur=>..,..),array(role=>..,valeur=>..,..),...)
	$qualifs = array();
	while (count($qualif)){
		$q = array();
		foreach($qualif as $att=>$values){
			if (is_array($values)){
				$q[$att] = array_shift($qualif[$att]);
				if (!count($qualif[$att])){
					unset($qualif[$att]);
				}
			}
			else {
				$q[$att] = $values;
				unset($qualif[$att]);
			}
		}
		// pas de rôle vide
		if (!$colonne_role OR !isset($q[$colonne_role]) OR $q[$colonne_role])
			$qualifs[] = $q;
	}

	return $qualifs;
}

/**
 * Ajoute les liens demandés en prenant éventuellement en compte le rôle
 *
 * Appelle la fonction objet_associer. L'appelle autant de fois qu'il y
 * a de rôles demandés pour cette liaison.
 *
 * @internal
 * @param string $objet_source Objet source de la liaison (qui a la table de liaison)
 * @param array|string $ids Identifiants pour l'objet source
 * @param string $objet_lien Objet à lier
 * @param array|string $idl Identifiants pour l'objet lié
 * @param array $qualifs
 * @return void
 **/
function lien_ajouter_liaisons($objet_source, $ids, $objet_lien, $idl, $qualifs){

	// retrouver la colonne de roles s'il y en a a lier
	if (is_array($qualifs) and count($qualifs)){
		foreach ($qualifs as $qualif){
			objet_associer(array($objet_source => $ids), array($objet_lien => $idl), $qualif);
		}
	} else {
		objet_associer(array($objet_source => $ids), array($objet_lien => $idl));
	}
}