Des rôles sur des liens

Cette contribution surcharge des fichiers et/ou des fonctions de SPIP : il n’est donc pas garanti qu’elle fonctionne avec d’autres contributions surchargeant lesdits fichiers et/ou fonctions. Sa compatibilité avec les versions de SPIP est donc assez restreinte.

Pouvoir définir les rôles sur les liens, c’est à dire de typer ou qualifier la liaison entre deux objets est une demande qui revient fréquemment. Ce plugin a pour objectif à la fois de fournir des spécifications pour cela et de mettre en place ces spécifications.

Une partie des éléments pourront être intégrés dans une prochaine version de SPIP si l’on est d’accord sur l’objectif et sa réalisation. En attendant (voir Rôles de documents), ce plugin offre une base de travail fonctionnelle pour SPIP 3.0, au prix d’une surcharge de deux fichiers de SPIP afin d’étendre leurs possibilités.

Introduction

Définir des rôles sur des liaisons consiste à gérer une colonne supplémentaire sur certaines tables de liaisons spip_x_liens. La clé primaire de la table ne doit alors plus être sur le trio id_x, objet, id_objet, mais sur le quatuor id_x, objet, id_objet, colonne_role. Dit autrement, au lieu d’avoir une seule liaison possible entre X et Y, il peut y en avoir maintenant plusieurs : une par rôle.

Le jeu de ce plugin consiste donc à gérer la déclaration de ces rôles, gérer l’interface pour les saisir et enlever, gérer les traitements en base de données pour ces rôles. Techniquement parlant, cela consiste à prendre en compte des descriptions supplémentaires, concernant les rôles possibles, dans la déclaration de l’objet éditorial ; de modifier le formulaire d’édition de lien pour gérer les rôles lorsqu’il y en a et enfin de modifier l’API de liens pour prendre en compte le changement d’unicité s’il y a des rôles, et les rôles eux-mêmes.

Déclarer les rôles

Tel qu’imaginé actuellement la déclaration des rôles possibles sur la table de liaison d’un objet, se réalise dans la même déclaration que la description de l’objet, via le pipeline declarer_table_objet_sql donc.

Elle se décompose en 3 nouveaux index dans ce tableau :

  • roles_colonne indique la colonne utilisée pour placer les rôles
  • roles_titres indique des couples clé de rôle, clé de traduction, indiquant pour chaque type de rôle à quelle chaîne de langue il correspond
  • roles_objets est un tableau indiquant pour chaque objet vers lequel on peut se lier (l’index), la liste des rôles que l’on peut lier (choix), ainsi que le rôle utilisé par défaut lorsqu’il n’est pas précisé au moment de la création de la liaison (defaut). Chaque index est un nom d’objet tel que ’articles’, mais il accepte un index générique ’*’ indiquant quelque soit l’objet.

Voici un exemple complet de déclaration pour lier des auteurs avec des articles avec 4 rôles définis (et utilisés) :

function roles_auteurs_declarer_tables_objets_sql($tables){

	array_set_merge($tables, 'spip_auteurs', array(
		"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()
		)
	));

	return $tables;
}

Adapter la table de liens

Il y a 3 étapes : la déclaration, l’installation et la désinstallation

Il y a deux cas :

  1. La table de liaison n’existait pas : c’est le plus simple, par exemple pour gérer un nouvel objet éditorial et ses liaisons
  2. La table de liaison existe : vous voulez étendre un objet éditorial existant - du moins ses liaisons

1. Table de liaison toute neuve

La déclaration déclare tous les champs de la table, via le pipeline declarer_tables_auxiliaires, par exemple avec :

function roses_declarer_tables_auxiliaires($tables) {
	$tables['spip_roses_liens'] array(
		'field' => array(
			"id_rose"  => "bigint(21) DEFAULT '0' NOT NULL",
			"id_objet" => "bigint(21) DEFAULT '0' NOT NULL",
			"objet"    => "VARCHAR (25) DEFAULT '' NOT NULL",
			'role'     => "varchar(30) NOT NULL DEFAULT ''",
		),
		'key' => array(
			'PRIMARY KEY' => "id_rose,id_objet,objet,role",
		)
	);
}

L’installation et la désinstallation ne font que créer la table de liens (et peut être d’autres tables non mentionnées ici qui serait ’spip_roses’ dans notre exemple) :

function roses_upgrade($nom_meta_base_version, $version_cible) {
	$maj = array();
	$maj['create'] = array(array('maj_tables', array('spip_auteurs_liens')),);

	include_spip('base/upgrade');
	maj_plugin($nom_meta_base_version, $version_cible, $maj);
}

function roses_vider_tables($nom_meta_base_version) {
	sql_drop_table("spip_roses");
	effacer_meta($nom_meta_base_version);
}

2. Table de liaison modifiée

La déclaration consiste juste à ajouter la colonne et modifier la clé primaire pour ajouter la colonne dedans. Voici un exemple :

function roles_auteurs_declarer_tables_auxiliaires($tables) {
	$tables['spip_auteurs_liens']['field']['role'] = "varchar(30) NOT NULL DEFAULT ''";
	$tables['spip_auteurs_liens']['key']['PRIMARY KEY'] = "id_auteur,id_objet,objet,role";
	return $tables;
}

L’installation consiste à altérer la table pour modifier la clé primaire et d’ajouter les rôles par défaut aux enregistrements déjà présents.

Voici un exemple avec la liaison d’auteurs. Il faut enlever la clé primaire pour la remettre sur le quatuor de colonnes. Pour chaque liaison vers les articles, et lorsqu’aucun rôle n’est défini, on applique le rôle rédacteur.

function roles_auteurs_upgrade($nom_meta_base_version, $version_cible) {
	$maj = array();

	$maj['create'] = array(
		// supprimer la clé primaire actuelle pour pouvoir en changer en ajoutant la colonne rôle
		array('sql_alter', "TABLE spip_auteurs_liens DROP PRIMARY KEY"),
		// ajout de la colonne role
		array('maj_tables', array('spip_auteurs_liens')),
		// la nouvelle colonne est la, mettre sa nouvelle clé primaire
		array('sql_alter', "TABLE spip_auteurs_liens ADD PRIMARY KEY (id_auteur,id_objet,objet,role)"),
		// on passe par défaut tous les liens auteurs/articles en rôle : redacteur
		array('sql_updateq', 'spip_auteurs_liens', array('role' => 'redacteur'), array(
			'role=' . sql_quote(''),
			'objet=' . sql_quote('article')
		))
	);

	include_spip('base/upgrade');
	maj_plugin($nom_meta_base_version, $version_cible, $maj);
}

La désinstallation consiste à faire l’opération inverse : on enlève la clé primaire, les liaisons en doublons, puis la colonne de rôle. On remet ensuite la clé primaire sur le trio de colonne. La clé primaire ne peut pas être remise sur le trio s’il existe des doublons !

function roles_auteurs_vider_tables($nom_meta_base_version) {

	// tant qu'il existe des doublons, on supprime une ligne doublonnée
	// sinon on ne pourra pas modifier la cle primaire ensuite
	// cet algo est certainement a optimiser
	while ($doublons = sql_allfetsel(
				array('id_auteur', 'id_objet', 'objet', 'role'),
				array('spip_auteurs_liens'),
				'', 'id_auteur,id_objet,objet', '', '', 'COUNT(*) > 1'))
	{
		foreach ($doublons as $d) {
			$where = array();
			foreach ($d as $cle=>$valeur) {
				$where[] = "$cle=".sql_quote($valeur);
			}
			sql_delete('spip_auteurs_liens', $where);
		}
	}

	// supprimer la clé primaire, la colonne rôle, et remettre l'ancienne clé primaire
	sql_alter("TABLE spip_auteurs_liens DROP PRIMARY KEY");
	sql_alter("TABLE spip_auteurs_liens DROP COLUMN role");
	sql_alter("TABLE spip_auteurs_liens ADD PRIMARY KEY (id_auteur,id_objet,objet)");

	effacer_meta($nom_meta_base_version);
}

Fournir des squelettes pour les liaisons avec rôles

Pour faire fonctionner un formulaire de liaison habituel, SPIP utilise deux listes par défaut,

  • l’une prive/objets/liste/{objet}_lies.html gère l’affichage de ce qui est lié,
  • l’autre prive/objets/liste/{objet}_associer.html gère l’affichage de ce qu’on peut lier (il faut en avoir le droit évidemment).

Ces listes n’affichent pas les rôles attribués et ne permettent pas d’en saisir. Ces listes ont par contre un certain nombre d’actions possibles pour ceux qui ont des droits de modifications sur les objets en question, par exemple un bouton « ajouter cet auteur ».

Le choix pris ici a été de dire que si une liaison peut avoir des rôles (ils sont donc déclarés) alors ce n’est pas les mêmes squelettes que l’on appelle mais des nouveaux, qui listent et permettent d’attribuer des rôles. Ils sont respectivement :

  • prive/objets/liste/{objet}_roles_lies.html et
  • prive/objets/liste/{objet}_roles_associer.html

Leur code est semblable en grande partie aux squelettes d’origines, mais quelques points diffèrent :

  • ils disposent d’une colonne « Rôles » listant les rôles d’une liaison ou indiquant les rôles qui seront attribués pour une liaison,
  • les actions possibles sont légèrement différentes pour tenir compte des rôles.

Par ailleurs, pour obtenir une interface de saisie pratique, ce plugin ajoute 2 librairies JavaScript (et CSS) qui sont Chosen et les boutons dépliants de Bootstrap.

Enfin pour faciliter l’affichage, un filtre |role{objet} est créé permettant de traduire une clé de rôle pour une table de liaison donnée. De même 2 inclusions permettent de ne modifier que le minimum vis à vis des squelettes de liste d’origine, extrayant ainsi 2 parties complexes et réutilisables.

Les squelettes peuvent donc déclarer la colonne rôle dans l’en-tête du tableau :

<th class='role' scope='col'><:roles:role_titre:></th>

Ils peuvent lister les rôles pour la liste des liaisons existantes. Si le visiteur a le droit d’effectuer des actions, on inclut en plus un bouton « modifier les rôles » permettant d’ajouter ou de retirer des rôles à cette liaison, et cela déjà depuis la liste des liaisons existantes. Il faut transmettre à cet inclure :

  • l’environnement,
  • l’id en cours de parcours,
  • et la clé d’action (la même qui est présente sur l’action de suppression du lien du squelette de vue sans rôle).
<td class='role'>
	[(#ENV{editable}|oui)
		[(#INCLURE{fond=formulaires/inc-editer_liens_actions_roles,env,id=#ID_AUTEUR,cle=auteur-#ID_AUTEUR-#OBJET-#ID_OBJET})]
	]
	<BOUCLE_roles(auteurs_liens){id_auteur}{objet}{id_objet}{par role}{", "}>
		[(#ROLE|role{auteurs})]</BOUCLE_roles>
</td>

Pour le squelette d’association, de la même façon, la colonne rôle effectue simplement une inclusion (seul le nom de l’inclusion change) :

<td class='role'>[(#INCLURE{fond=formulaires/inc-editer_liens_roles,env,id=#ID_AUTEUR,cle=auteur-#ID_AUTEUR-#OBJET-#ID_OBJET})]</td>
Interface utilisateur des rôles
En haut, dans la liste des auteurs liés à l’article, un bouton « Modifier », dans la colonne rôle, permet d’agir rôle par rôle lorsqu’on le déplie, pour ajouter ou retirer un des rôles définis pour cette liaison.
En bas, lorsqu’on demande d’ajouter un auteur, la colonne rôle permet de saisir un ou plusieurs rôles qui seront ainsi définis pour cette liaison. Si ce n’est pas rempli, le rôle par défaut (défini dans la déclaration de l’objet) est utilisé. Pour notre exemple ce serait « Rédacteur ».

Tout ceci fait, votre plugin décrivant des rôles entre certaines liaisons est fonctionnel (lorsque ce plugin est installé).

Nous allons maintenant développer les modifications apportées à deux fichiers de SPIP pour réaliser tout cela.

Modifications du formulaires/editer_liens.php

Afin de gérer les rôles, le formulaire de liens est quelque peu modifié.

Dans le chargement du formulaire, on teste si des rôles existent pour ce lien grâce au code $roles = roles_presents($objet_source, $objet). Cela récupère le cas échéant un tableau de description des rôles pour ce type de liaison, qui sera envoyé dans l’environnement du formulaire. De plus, s’il y a des rôles, on change alors les squelettes de liste chargés, en ajoutant « _roles » dans leurs noms :

// on demande de nouveaux squelettes en conséquence
$skel_vue   = $table_source."_roles_lies";
$skel_ajout = $table_source."_roles_associer";

Dans les traitements du formulaire, on s’adapte aussi à la présence des rôles en acceptant que les actions qui ont normalement 4 valeurs, puissent en avoir 5, la 5è concernant le rôle. On peut alors recevoir du formulaire des éléments tel que :

  • ajouter_lien[objet1-id1-objet2-id2] ce qui est l’action de base qui existe sans cette surcharge. Cependant pour nous, lorsque des rôles sont possibles entre objet1 et objet2 et que rien n’est précisé (5e valeur ou clé definir_roles), cela ajoute la liaison avec le rôle par défaut déclaré.
  • ajouter_lien[objet1-id1-objet2-id2-role] Ajoute le rôle explicitement indiqué
  • supprimer_lien[objet1-id1-objet2-id2] Supprime la liaison s’il n’y a pas de rôles possibles entre les objets, sinon supprime la liaison qui a le rôle par défaut déclaré
  • supprimer_lien[objet1-id1-objet2-id2-role] Supprime la liaison qui a le rôle explicitement indiqué
  • supprimer_lien[objet1-id1-objet2-id2-*] Supprime toutes les liaisons (et tous les rôles donc)
  • definir_roles[objet1-id1-objet2-id2]=array('roleA', 'roleB') Permet d’indiquer, lorsque ajouter_lien est présent sans 5e argument, de prendre les rôles indiqués ici au moment de l’ajout

Enfin, les appels à l’API d’édition de lien sont modifiés également pour tenir compte des rôles qui sont ajoutés ou supprimés.

Modifications de l’API dans action/editer_liens.php

Actuellement, l’ajout, la modification ou la suppression se base sur le postulat qu’il n’existe qu’un seul couple (id_xx, objet, id_objet) et il faut donc pouvoir s’en passer dans certaines situations, particulièrement pour la gestion de nos rôles.

On peut séparer en 2 les adaptations faites dans ce plugin.

Contraindre les suppressions et modifications

Il s’agit ici de pouvoir dire : je veux trouver ou supprimer tous les liens qui ont tel champ à telle valeur, ou tel rôle à telle valeur.

Pour ce faire, on ajoute un paramètre $cond aux fonctions

  • objet_trouver_liens($objets_source, $objets_lies, $cond=null)
  • objet_dissocier($objets_source, $objets_lies, $cond=null)
  • et sur des fonctions internes.

Ce paramètre permet de passer un tableau de conditions tel que :

array("vu=" . sql_quote('oui'))

Dans ce cas on ne cherche ou supprime que les éléments répondant aux critères demandés. Mais ce paramètre peut également indiquer un index ’role’ indiquant quel rôle trouver ou supprimer (indépendamment du nom réel de la colonne de rôle), tel que :

array('role' => 'correcteur')
array('role' => '*')

L’API comprend alors qu’elle doit retrouver le rôle correcteur dans la colonne définie dans la déclaration des rôles pour les objets en question.

Note : dans le cas de objet_dissocier, si des rôles sont possibles mais qu’aucun n’est passé, seul le rôle par défaut déclaré est supprimé ! Pour supprimer tous les rôles les liaisons, il faut alors passer explicitement 'role'=>'*'

Gérer les insertions et qualifications

La signature les fonctions pour le faire ne change pas :

  • objet_associer($objets_source, $objets_lies, $qualif = null)
  • objet_qualifier_liens($objets_source, $objets_lies, $qualif)

Ce qui change est le traitement de $qualif lorsque la liaison entre les 2 objets peut avoir des rôles (rien ne change sinon). L’API extrait alors le rôle de $qualif s’il est présent, et en l’absence utilise alors le rôle par défaut.

Dans ce tableau $qualif, et contrairement à la variable $cond, c’est bien le nom véritable de la colonne qui est attendu, par exemple :

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

Le cas des pipelines pre_edition_lien et post_edition_lien

Les pipelines pre_edition_lien et post_edition_lien ne fonctionnent pas comme pre_edition et post_edition dans le sens où ils ne transmettent pas dans $flux['data'] la liste des champs impactés par l’insertion ou la suppression. De plus, il n’y a pas de pipeline sur la modification.

Bien que ce plugin pour SPIP 3 ne modifie pas leur fonctionnement, il sera bon pour les futures versions de SPIP de modifier ces pipelines pour être homogène avec leurs homologues en :

  • modifiant $flux['data'] des pipelines pour qu’ils deviennent un tableau des champs ajoutés ou modifiés (ou supprimés) (le code pour le faire est commenté dans ce plugin)
  • ajouter le passage de ces pipelines dans l’étape de modification d’un lien (c’est fait dans ce plugin)

Discussion

10 discussions

  • 1

    Bonjour,
    Une excellente idée qui va m’être bien utile.
    Juste une remarque : Dans la doc, au chapitre ’Adapter la table de liens’ cas 1, n’y a t-il pas une erreur sur la Primary Key : id_rose devrait remplacer id_auteur non ?
    Bravo pour cette contrib.

    Répondre à ce message

  • Bonjour,

    Merci pour ce plugin, qui répond à un besoin de longue date dans SPIP.

    Question très bête : comment fait-on pour le faire fonctionner, une fois qu’il est installé. Très concrètement : où faut-il placer quel code ? J’ai tenté de mettre le premier bloc de code dans mes_options.php, mais semble ne rien produire comme effet.

    Merci

    Répondre à ce message

  • Sur SPIP 3.0.17, la mise à jour de la version 1.5 du plugin provoque un bug chez moi.
    Lorsque je clique sur le bouton Modifier, je n’ai plus ma liste de role. Elle est vide.

    J’ai trouvé que le problème venait du fichier formulaires/inc-editer_liens_roles.html.

    <BOUCLE_roles_presents(DATA){source table,#ENV{_roles/roles/choix}}{valeur IN #GET{presents}}>

    En enlevant l’underscore sur la ligne

    <BOUCLE_roles_presents(DATA){source table,#ENV{roles/roles/choix}}{valeur IN #GET{presents}}>

    ..ça remarche. Ne comprenant pas à quoi sert l’underscore, je n’ai pas pu aller plus loin dans mes investigations

    Répondre à ce message

  • Bonjour,

    Il me semble que les rôles deviennent natifs en 3.1 (?)

    J’aimerais savoir si il sera possible de customiser/étendre l’usage des rôles pour « typer » des relations ?

    Un exemple avec deux nouveaux objets : VETEMENT et MATIERE
    VETEMENT 1 est lié à MATIERE 1 et à MATIERE 2
    VETEMENT 1 contient 50% de MATIERE 1 et 50% de MATIERE 2
    En clair : Une robe est composée à 50% de coton, et 50% d’élasthane

    La nouvelle API d’éditions de lien pourrait-elle permettre de réaliser cela ? À quel prix (surcharges) ?

    Merci pour votre aide !

    Répondre à ce message

  • Je pense avoir trouvé un petit bug.

    Lorsqu’un auteur a été créé par défaut sur un objet éditorial et que l’on souhaite lui attribuer un rôle, le plugin crée un deuxième lien avec le rôle dans la table de liaison.
    Dans la liste associée, une virgule apparaît d’ailleurs pour séparer les 2 liaisons.

    Cela se passe donc dans l’action « modifier la liaison »

    FDM

    Répondre à ce message

  • Merci pour ce plugin, associé à « Rôles d’auteurs ».

    Dans les sites que je fais, je suis souvent confronté à ce type de problème. Plus que correcteur ou relecteur, c’est surtout direction (« Sous la direction de ») qui est courant.

    Je n’ai pas bien saisi si l’on peut modifier le type de rôle. Est-ce possible ? Via une interface, ce serait bien pratique.

    Répondre à ce message

  • Bonjour,

    J’ai 2 questions sur le fonctionnement de ce plugin.

    1. Est-il possible de limiter à un rôle par liaison ?
    2. Est-il possible dans le FO de classer les objets associés {par role} ?

    Cas concret avec Rôles des auteurs :

    <BOUCLE_auteurs(AUTEURS){id_article}{par role}{' / '}>
    #NOM[ - (#ROLE|role{auteur})]
    </BOUCLE_auteurs>

    Répondre à ce message

  • mcommemarc

    ça a l’air génial comme plugin... Pour le moment, je survole seulement. En ajoutant de nouveaux statuts aux auteurs, il serait possible de proposer différentes inscriptions selon les statuts, non ?

    Répondre à ce message

  • Merci pour ce beau plugin que j’attendais depuis longtemps pour mon site de travail collaboratif.

    Suggestion : Ne peut-on pas définir les objets et les rôles depuis une page de configuration, plutôt qu’en éditant un fichier php qui sera écrasé lors de la prochaine mise à jour.

    Répondre à ce message

  • Vraiment extra.
    Un beau plugin qui ouvre de belles perspectives (jusqu’alors compliquées à envisager)
    J’ai créé un plugin avec la fabrique, j’ai mis des rôles sur les auteurs pour mon nouvel objet éditorial, ça marche parfaitement seulement je ne vois pas comment gérer les droits... ?
    Ex : faire en sorte qu’un auteur avec rôle de ’lecteur’ ne puisse pas modifier l’article.

    Bravo et merci pour ce plugin

    Répondre à ce message

Ajouter un commentaire

Avant de faire part d’un problème sur un plugin X, merci de lire ce qui suit :

  • Désactiver tous les plugins que vous ne voulez pas tester afin de vous assurer que le bug vient bien du plugin X. Cela vous évitera d’écrire sur le forum d’une contribution qui n’est finalement pas en cause.
  • Cherchez et notez les numéros de version de tout ce qui est en place au moment du test :
    • version de SPIP, en bas de la partie privée
    • version du plugin testé et des éventuels plugins nécessités
    • version de PHP (exec=info en partie privée)
    • version de MySQL / SQLite
  • Si votre problème concerne la partie publique de votre site, donnez une URL où le bug est visible, pour que les gens puissent voir par eux-mêmes.
  • En cas de page blanche, merci d’activer l’affichage des erreurs, et d’indiquer ensuite l’erreur qui apparaît.

Merci d’avance pour les personnes qui vous aideront !

Par ailleurs, n’oubliez pas que les contributeurs et contributrices ont une vie en dehors de SPIP.

Qui êtes-vous ?
[Se connecter]

Pour afficher votre trombine avec votre message, enregistrez-la d’abord sur gravatar.com (gratuit et indolore) et n’oubliez pas d’indiquer votre adresse e-mail ici.

Ajoutez votre commentaire ici

Ce champ accepte les raccourcis SPIP {{gras}} {italique} -*liste [texte->url] <quote> <code> et le code HTML <q> <del> <ins>. Pour créer des paragraphes, laissez simplement des lignes vides.

Ajouter un document

Suivre les commentaires : RSS 2.0 | Atom