Le workflow représente le cycle de vie d'un contenu : c'est à dire les différents étapes ou opérations pouvant être effectuées durant la durée de vie du contenu.

Voici ci-dessous une représentation graphique du workflow utilisé par défaut dans Ametys CMS :

Les étapes et actions du workflow sont décrites au travers d'un fichier XML. Il est possible de facilement modifier ce workflow en modifiant le fichier correspondant.

Ametys s'appuie sur le moteur de workflow OSWorkflow

Les workflows de l'application

Workflows déclarés automatiquement (à partir d'Ametys 4.2)

En version 4.2, la façon de faire 4.1 décrite ci-dessous fonctionne toujours mais il est recommandé de procéder comme suit :

En l'absence de fichier WEB-INF/param/workflows.xml celui-ci est généré automatiquement avec le contenu du dossier WEB-INF/param/workflows. Pour ajouter un workflow il suffit donc de poser le fichier dans ce dossier.

Le nom du fichier (sans extension) deviendra le "name" du workflow.
Pour le workflow "content" il faut donc avoir un fichier WEB-INF/param/workflows/content.xml

Workflows déclarés manuellement

Dans l'application CMS, le fichier WEB-INF/param/workflows.xml liste les différents worfklows de l'application avec leur nom et leur localisation :

<workflows>  
    <workflow name="content" type="file" location="workflow.xml"/>  
    <workflow name="newsletter" type="file" location="workflow-newsletter.xml"/>  
    <workflow name="blog" type="file" location="workflow-blog.xml"/>  
</workflows>  

L'attribut "name" permet de nommer le workflow, tandis que l'attribut "location" indique la localisation du fichier de description du workflow.

Le workflow nommé "content" est utilisé par défaut pour la plupart des contenus du CMS (articles, actualités, FAQ, ...).

Certains contenus apportés par d'autres plugins, peuvent avoir des cycles de vie différents et définissent donc leur propre workflow. C'est le cas par exemple du plugin Lettre d'information pour le contenu de type "Newsletter" et le plugin Blog qui apporte un workflow simplifié pour ses contenus de type "Billet".

Nommage des cycles de vie 

Pour chaque définition de workflow, vous devez définir une clé i18n WORKFLOW_nom_du_workfow dans le catalogue application (WEB-INF/i18n/application_*.xml)

<message key="WORKFLOW_bpm-default">Processus avec double validation expert/élus</message> 

Fichier de description d'un workflow

Un workflow est constitué d'états (step) et d'actions menant à ces états (action).

Il existe 4 types d'actions:

  • les actions initiales : il s'agit des actions permettant d'initialiser le workflow (l'action de création par exemple). Elles sont décrites dans la balise <initial-actions>
  • les actions globales: il s'agit des actions pouvant être effectuées depuis n'importe quel état. Elles sont décrites dans la balise <global-actions>
  • les actions communes : il s'agit des actions pouvant être effectuées depuis plusieurs états différents. Elles sont décrites dans la balise <common-actions>
  • les actions propre à un état : il s'agit des actions pouvant être effectuées uniquement depuis un état particulier. Elles sont décrite dans l'état lui-même

Tous les états sont décrits dans la balise <steps>.

Chaque action et chaque état doit posséder un identifiant numériques unique et un nom (clés i18n).

Exemple de fichier de workflow

<workflow>  
 <initial-actions>  
 <!-- Création-->  
 <action id="0" name="plugin.web:WORKFLOW_ACTION_CREATE">  
             ...  
        </action>       
    </initial-actions>  

 <!-- Actions globales -->  
    <global-actions>  
 <!-- Suppression -->  
 <action id="5" name="plugin.web:WORKFLOW_ACTION_DELETE" finish="TRUE">  
             ...  
        </action>  
    </global-actions>  

 <!-- Actions communes -->  
    <common-actions>  
 <!-- Edition -->  
 <action id="2" name="plugin.web:WORKFLOW_ACTION_EDIT">  
             ...  
        </action>  
 <!-- Validation-->  
 <action id="4" name="plugin.web:WORKFLOW_ACTION_VALIDATE">  
             ...  
        </action>  
 </common-actions>  

 <steps>  
 <!-- Etat Brouillon -->  
 <step id="1" name="plugin.web:WORKFLOW_STATE_DRAFT">  
             <common-action id="2"/>  
  <common-action id="4"/>  
  <!-- Proposition -->  
  <action id="3" name="plugin.web:WORKFLOW_ACTION_PROPOSE">  
              ...  
           </action>  
 </step>  
 <!-- Etat Proposé-->  
 <step id="2" name="plugin.web:WORKFLOW_STATE_PROPOSED">  
            <actions>  
 <common-action id="2"/>  
 <common-action id="4"/>  
 </action>  
        </step>  
 <!-- Etat Validé-->  
 <step id="3" name="plugin.web:WORKFLOW_STATE_VALIDATED">  
            <actions>  
 <common-action id="2"/>  
 </actions>  
 </step>  
 </steps>  
</workflow>  

Le fichier ci-dessus est décrit pour information mais ne doit pas être modifié en dehors des recommandations d'intégration d'un nouveau plugin. En effet, il n'est pas possible d'utiliser un nouveau workflow sans développement Java.

Les états (step)

Un état de workflow est défini par :

  • un identifiant numérique unique (attribut id)
  • un nom (attribut name)
  • une liste d'actions disponibles depuis cet état (balise <actions>)
  • des pré-fonctions (optionnelles) : fonction exécutées juste avant l'arrivée du contenu dans cet état (balise <pre-functions>)
  • des post-fonctions (optionnelles) :fonction exécutées juste après l'arrivée du contenu dans cet état (balise <post-functions>)

Dans Ametys, le nom de l'état est toujours une clé i18n (voir Internationalisation).
Si aucun catalogue n'est défini, la traduction de la clé i18n doit se trouver dans le catalogue de l'application : WEB-INF/i18n/application.xml, WEB-INF/i18n/application_fr.xml, ...

En plus de la clé i18n pour le nom de l'état, il est nécessaire de créer 2 autres clés:

  • une pour la description de l'état, suffixée par _DESCRIPTION
  • une suffixée par _FOOTER

Ces clés i18n sont utilisées dans Ametys pour les info-bulles d'aide sur les états.

Exemple avec l'état "Brouillon"
<message key="WORKFLOW_STATE_DRAFT">Brouillon</message>  
<message key="WORKFLOW_STATE_DRAFT_DESCRIPTION">L'état 'Brouillon' est activé lorsque la version actuelle d'un contenu est dans un tel état.</message>  
<message key="WORKFLOW_STATE_DRAFT_FOOTER"></message>  

Voici pour exemple la définition d'un l'état "Brouillon"

<step id="1" name="plugin.web:WORKFLOW_STATE_DRAFT">  
 <actions>  
     <!-- Edition -->  
        <common-action id="2" />  
        <!-- Validation -->  
        <common-action id="4" />  
        <!-- Proposition -->  
        <action id="3" name="plugin.web:WORKFLOW_ACTION_PROPOSE">  
         <!-- Description de l'action de proposition -->         
        </action>  
 </actions>  
 <post-functions>  
     <function type="avalon">  
         <arg name="role">org.ametys.cms.workflow.CommentStepFunction</arg>  
        </function>  
    </post-functions>  
</step>  

Dans l'exemple ci-dessus,

  • 3 actions sont disponibles depuis l'état "Brouillon" (état n°1) : 2 actions communes (édition et validation) et 1 action accessible uniquement depuis cet état (proposition)
  • 1 post-fonction est exécutée lors de l'arrivée dans cet état.

 

Les fonctions sont exécutées dans l'ordre de définition, l'ordre peut donc être important !

Les icônes

Dans Ametys, un état est associé à 3 icônes de taille différente représentatives de l'état. Le nom des icônes est composé de la clé i18n du nom de l'état, suffixé par -small, -medium ou -large suivant leur taille.

  • [STEPNAME]-small.png de taille 16x16 pixels
  • [STEPNAME]-medium.png de taille 32x32 pixels
  • [STEPNAME]-large.png de taille 48x48 pixels

L'emplacement de ces icônes dépend du catalogue i18n utilisé pour définir le nom de l'état :

  • si aucun catalogue n'est précisé les icônes doivent être placées dans le répertoire WEB-INF/param/workflow_resources
  • si un catalogue de plugin est précisé alors les icones doivent être placées dans le répertoire plugins/[nom_plugin]/resources/img/workflow.

 

Les actions ou transitions

Un action est une transition d'un état à un autre.

Une action de workflow est défini par :

  • un identifiant numérique unique (attribut id)
  • un nom (attribut name)
  • une liste de restrictions : liste de conditions devant être vérifiées avant l'exécution de l'action. Si une des conditions n'est pas remplie, l'action n'est pas disponible.
  • des pré-fonctions (optionnelles) : fonctions exécutées avant le changement d'état (balise <pre-functions>). Ne pas confondre avec les pré-fonctions d'état évoquées plus haut.
  • le résultat de l'action (<result>) : état dans lequel on aboutit lors de cette transition.
  • des post-fonctions (optionnelles) : fonctions exécutées après le changement d'état (balise <post-functions>)

Comme pour les états, dans Ametys, le nom de l'action doit correspondre à une clé i18n.
Si aucun catalogue n'est défini, la traduction de la clé i18n doit se trouver dans le catalogue de l'application : WEB-INF/i18n/application.xml, WEB-INF/i18n/application_fr.xml, ...

En plus de la clé i18n pour le nom de l'action, il est nécessaire de créer une autre clé pour la description suffixée par _DESCRIPTION.

<message key="WORKFLOW_ACTION_VALIDATE">Validation</message>  
<message key="WORKFLOW_ACTION_VALIDATE_DESCRIPTION">Cliquez ici pour valider le(s) contenu(s) sélectionné(s).</message>  

Ces clés i18n sont utilisées dans Ametys pour les info-bulles d'aide sur les états.

Voici pour exemple la définition de l'action de validation :

<action id="4" name="plugin.web:WORKFLOW_ACTION_VALIDATE">  
 <restrict-to>  
     <conditions type="AND">  
         <condition type="avalon">  
             <arg name="role">org.ametys.cms.workflow.ContentCheckRightsCondition</arg>  
                <arg name="right">Workflow_Rights_Validate</arg>  
            </condition>  
            <condition type="avalon">  
             <arg name="role">org.ametys.cms.workflow.LockCondition</arg>  
            </condition>  
            <condition type="avalon">  
             <arg name="role">org.ametys.cms.workflow.ValidateMetadataCondition</arg>  
            </condition>  
        </conditions>  
    </restrict-to>  
    <results>  
     <unconditional-result old-status=" " status=" " step="3" />  
    </results>  
    <post-functions>  
        <function type="avalon">  
         <arg name="role">org.ametys.web.workflow.ValidateContentFunction</arg>  
        </function>  
 <function type="avalon">  
         <arg name="role">org.ametys.cms.workflow.ValidationStepFunction</arg>  
        </function>  
 </post-functions>  
</action>  

Dans cet exemple :

  • l'action de validation n'est réalisable qu'à 3 conditions:
    • le contributeur possède le droit de validation : org.ametys.cms.workflow.ContentCheckRightsCondition
    • le contenu n'est pas verrouillé par un autre utilisateur : org.ametys.cms.workflow.LockCondition
    • l'ensemble des métadonnées du contenus sont valides (métadonnées obligatoires renseignées par exemple) : org.ametys.cms.workflow.ValidationStepFunction
  • le résultat de cette action est le passage du contenu dans l'état n°3 (état validé)
  • 2 fonctions sont exécutées suite à cette action et au passage à l'état 3:
    • la version courante du contenu est marqué comme la dernière version validé (mis à jour du contenu en ligne) : org.ametys.web.workflow.ValidateContentFunction
    • l'état courant (n°3) est marqué comme l'état de validation : org.ametys.cms.workflow.ValidationStepFunction

Les pré ou post fonctions dans Ametys

Voici ci-dessous les fonctions pouvant être appelées dans une action, avant ou après le changement d'état :

Fonctionpre-fonctionpost-fonctionRole
org.ametys.web.workflow.CreateContentFunctionx Crée d'un contenu.
Ne pas utiliser ailleurs que dans l'action de création (initial-action)
org.ametys.cms.workflow.SetCurrentStepFunction xPositionne la propriété "ametys-internal:currentStepId" représentant l'état courant.
A utiliser en post-fonction dans la plupart des actions, sauf pour les actions de validation et de dépublication
org.ametys.cms.workflow.CreateVersionFunction xCrée une nouvelle version du contenu.
A utiliser dans tous les actions nécessitant la création d'une nouvelle version (création, édition, restauration, ..)
org.ametys.cms.workflow.EditContentFunctionx Enregistre les modifications apportées au contenu.
A utiliser dans l'action d'édition.
org.ametys.plugins.forms.workflow.FormEditionFunction xCherche les formulaires Ametys présent dans le contenu et les enregistre en base de données.
A utiliser dans l'action d'édition.
org.ametys.web.workflow.ValidateContentFunction xValide la version courante du contenu pour permettre sa mise en ligne.
A utiliser dans l'action de validation.
org.ametys.cms.workflow.ValidationStepFunction xMarque l'état courant comme l'état de validation.
A utiliser dans l'action de validation.
org.ametys.cms.workflow.RestoreRevisionFunctionx Restaure une ancienne version du contenu.
A utiliser dans l'action de restauration.
org.ametys.web.workflow.UnpublishContentFunctionx Retire l'information spécifiant que le contenu est validé.
Attention ! Il faut impérativement que l'action associée sortent le contenu de l'état validé sous peine de créer une confusion chez le contributeur.
A utiliser dans les actions d'archivage et de dépublication.
org.ametys.cms.workflow.MarkContentArchivedFunctionx 

Place ou retire le contenu dans l'espace des archives. L'argument "unarchive" placé à "true" indique que l'on veut sortir le contenu de l'espace d'archivage.
A utiliser dans l'action d'archivage sans argument et dans l''action de désarchivage avec l'argument unarchive= true

<function type="avalon">  
 <arg name="role">org.ametys.cms.workflow.MarkContentArchivedFunction</arg>  
    <arg name="unarchive">true</arg>  
</function>  
org.ametys.cms.workflow.SetProposalDateContentFunctionx 

Positionne ou retire la date de proposition du contenu.
A utiliser dans l'action de proposition sans argument et dans l''action de refus de validation avec l'argument remove= true

<function type="avalon">  
      <arg name="role">org.ametys.cms.workflow.SetProposalDateContentFunction</arg>  
      <arg name="remove">true</arg>  
</function>  
org.ametys.web.workflow.SendMailToUserFunction  x

Envoie un e-mail à la personne ayant exécuté l'action précédente.
Cette fonction s'utilise avec 2 arguments :

  • "subjectkey" faisant référence à une clé i18n afin de déterminer le sujet du mail. Dans la traduction de la clé i18n message vous pouvez utiliser le paramètre {1} pour le titre du contenu
  • "bodyKey" faisant référence à une clé i18n afin de déterminer le corps du mail

La clé  i18n de ""bodyKey" doit en réalité être déclinée en 3 clés dans le catalogue : 

  • [I18N_KEY]: pour la cas normal d'un contenu inséré dans une page
  • [I18N_KEY]_ORPHAN : pour le cas où le contenu concerné par l'action est orphelin, c'est à dire rattaché à aucune page
  • [I18N_KEY]_NOSITE: pour le cas où le contenu concerné par l'action n'est pas rattaché à un site (contenu de l'offre de formation par exemple)

Pour chacune des traductions vous pouvez utiliser les paramètres suivants :

  •  {0} nom de l'utilisateur
  • {1} titre du contenu
  • {2} titre du site pour les clés [I18N_KEY] et [I18N_KEY]_ORPHAN, ou url du CMS pour la clé [I18N_KEY]_NOSITE

  • {3} titre de la page pour la clé [I18N_KEY] ou lien vers le contenu pour la clé [I18N_KEY]_ORPHAN

  • {4} lien vers la page du contenu pour la clé [I18N_KEY]

Exemple

<!-- // Fichier workflow.xml -->  
<function type="avalon">  
 <arg name="role">org.ametys.web.workflow.SendMailToUserFunction</arg>  
    <arg name="subjectKey">plugin.web:WORKFLOW_MAIL_SUBJECT_ACTION_REFUSE_PROPOSITION</arg>  
    <arg name="bodyKey">plugin.web:WORKFLOW_MAIL_BODY_ACTION_REFUSE_PROPOSITION</arg>  
</function>  

<!-- // Fichier messages_fr.xml -->  
<message key="WORKFLOW_MAIL_SUBJECT_ACTION_REFUSE_PROPOSITION">Refus de validation du contenu "{0}"</message>  
<message key="WORKFLOW_MAIL_BODY_ACTION_REFUSE_PROPOSITION">L'utilisateur {0} a refusé la demande de validation du contenu "{1}" sur la page "{3}" du site "{2}". Le contenu est de nouveau en cours d'édition.  

Pour vous rendre sur la page cliquez sur le lien ci-après {4}.</message>  
<message key="WORKFLOW_MAIL_BODY_ACTION_REFUSE_PROPOSITION_ORPHAN">L'utilisateur {0} a refusé la demande de validation du contenu "{1}" du site "{2}". Le contenu est de nouveau en cours d'édition.  

Pour vous rendre sur le contenu cliquez sur le lien ci-après {3}.</message>  
<message key="WORKFLOW_MAIL_BODY_ACTION_REFUSE_PROPOSITION_NOSITE">L'utilisateur {0} a refusé la demande de validation du contenu "{1}". Le contenu est de nouveau en cours d'édition.  

Pour vous rendre sur le CMS cliquez sur le lien ci-après {2}.</message>  
   
org.ametys.web.workflow.SendMailFunction x

Envoie un e-mail aux personnes ayant le ou les droits définis dans l'argument "rights" de la fonction, pour le contenu en question.

Les arguments "subjectkey" et "bodykey" respectent les mêmes règles que pour SendMailToUserFunction (cf ligne précédente)

Exemple

<!-- // Fichier workflow.xml -->  
<function type="avalon">  
 <arg name="role">org.ametys.web.workflow.SendMailFunction</arg>  
    <arg name="rights">Workflow_Rights_Validate, Workflow_Rights_Notification</arg>  
    <arg name="subjectKey">plugin.web:WORKFLOW_MAIL_SUBJECT_ACTION_PROPOSE</arg>  
    <arg name="bodyKey">plugin.web:WORKFLOW_MAIL_BODY_ACTION_PROPOSE</arg>  
</function>  

<!-- // Fichier messages_fr.xml -->  
<message key="WORKFLOW_MAIL_SUBJECT_ACTION_PROPOSE">Contenu "{0}" proposé</message>  
<message key="WORKFLOW_MAIL_BODY_ACTION_PROPOSE">L'utilisateur {0} a proposé pour validation le contenu "{1}" sur la page "{3}" du site "{2}". Le contenu est en attente de validation.  

Pour vous rendre sur la page et valider le contenu cliquez sur le lien ci-après {4}.</message>  
<message key="WORKFLOW_MAIL_BODY_ACTION_PROPOSE_ORPHAN">L'utilisateur {0} a proposé pour validation le contenu "{1}" du site "{2}". Le contenu est en attente de validation.  

Pour vous rendre sur le contenu et le valider cliquez sur le lien ci-après {3}.</message>  
<message key="WORKFLOW_MAIL_BODY_ACTION_PROPOSE_NOSITE">L'utilisateur {0} a proposé pour validation le contenu "{1}". Le contenu est en attente de validation.  

Pour vous rendre sur le CMS cliquez sur le lien ci-après {2}.</message>  
    

Pour aller plus loin
Une action peut contenir un attribut finish="TRUE" : elle définit la fin du workflow et renvoie vers un step vide qui est généralement mis en commentaire (ce qui permet de ne pas réutiliser l'identifiant du step de fin, ce qui pourrait poser des problèmes !).

Pour aller plus loin
Concernant la validation d'un contenu, il y a plusieurs notions techniques qu'il est important de lier au sein d'un fichier de workflow cohérent.
1) Le cycle de vie du workflow. Le libellé des états (Brouillon ou Validé par exemple) sont porteurs d'information pour le contributeur mais ne permettent pas de techniquement avoir un sens : ce sens sémantique leur ait donné au travers des fonctions (d'état ou d'action) associés à ces états.
2) L'indicateur "Live" posé sur une version du contenu. A un instant donné, au maximum une version de l'historique d'un contenu peut porter cet indicateur, c'est la version qui est visible sur le site par les visiteurs. Par cohérence, il faut que cet indicateur soit placé lorsque l'état de workflow "Validé" est atteint. C'est la ValidateContentFunction qui est chargée de cela. Cet indicateur peut être retiré par la UnpublishContentFunction.
3) L'indicateur "validation" posé dans l'historique du workflow. Cet indicateur est positionné sur chaque version validée : cela permet par exemple à l'historique de mettre visuellement en avant les versions validées, ou à la purge de conserver uniquement des versions validées de contenus. C'est la ValidationStepFunction qui est chargée de cela.
Un workflow cohérent est donc, par exemple, un workflow dans lequel un état validé utilise toujours la ValidateContentFunction et la ValidationStepFunction.
En ce qui concerne l'archivage, il faut bien noter qu'un contenu archivé passé dans un autre espace de contenus (cf le Manuel utilisateurs, Archivage des contenus). Ainsi, la fonction MarkContentArchivedFunction est nécessaire car c'est elle qui effectue le déplacement.

Attention si vous modifiez le workflow d'un projet existant, car des contenus ont déjà été saisis, avec leur historique. Dans ce cas il ne faut pas supprimer les steps et les actions déjà définis, ni modifier leurs identifiants, car ils sont potentiellement référencés (et cela provoquerait des incohérences et des erreurs d'affichage de l'historique par exemple). Il faut également vérifier qu'aucun contenu ne soit actuellement dans l'état à supprimer : sinon aucune action ne sera disponible pour en sortir.
Il est donc plutôt recommandé de cacher visuellement les actions associées à ces états ou à ces actions.

Les conditions de workflow dans Ametys

Voici ci-dessous les conditions de workflow (<restrict-to>) disponible dans Ametys :

ConditionRole
org.ametys.cms.workflow.ContentCheckRightsCondition

Vérifie que l'utilisateur possède le droit passé en argument sur le contenu.

<condition type="avalon">  
 <arg name="role">org.ametys.cms.workflow.ContentCheckRightsCondition</arg>  
    <arg name="right">Workflow_Rights_Edition_Online</arg>  
</condition>  
org.ametys.plugins.workflow.component.CheckRightsConditionVérifie que l'utilisateur possède le droit passé en argument sur le contexte applicatif
org.ametys.cms.workflow.LockConditionVérifie que le contenu n'est pas verrouillé par un autre utilisateur.
org.ametys.cms.workflow.ValidateMetadataConditionVérifie que toutes les métadonnées du contenus sont valides

 

Les boutons du workflow dans le ruban

Dans l'application de démonstration Ametys, le plugin "default-workflow" définit les boutons associés aux états et actions du workflow, qui sont affichés dans le ribbon.

Il existe 2 extensions possibles pour les actions et état du workflow :

  • org.ametys.cms.clientsideelement.SmartContentClientSideElement
    Cette extension est utilisé pour les boutons permettant de faire une action de workflow. Par exemple : Modifier le contenu, Dépublier, ...
    Le bouton sera grisé si l'action de workflow n'est pas disponible.

  • org.ametys.cms.clientsideelement.WorkflowMenu
    Cette extension est utilisé pour representer l'état courant du workflow. Le menu apparait enfoncé si le contenu est dans l'état correspondant (Brouillon, Proposé, Validé, ..). Le menu fait apparaitre les actions de workflow disponibles à partir de cet état.


 

Pour aller plus loin
Dans la page Surcharger les boutons de workflow, vous trouverez comment surcharger ces boutons.

Exercice d'exemple

A titre d'exemple, nous allons prendre le workflow de la version de démonstration et retirer l'état Proposé.

Nous avons donc au départ les boutons suivants :

Dans le cas d'un projet vierge (sans contenu existant) il suffit de :

  1. supprimer le bouton dans le ribbon : fichier WEB-INF/param/cms-ribbon.xml, supprimer les lignes qui affichent le menu "Proposé" (identifiant org.ametys.web.workflow.Proposed) 
  2. supprimer la définition du bouton dans le plugin default-workflow
  3. supprimer l'état "Proposé" et l'action "Proposer" du workflow lui-même dans le fichier WEB-INF/param/workflow.xml, supprimer (ou mettre en commentaires...) l'action 3 définie dans le step 1, et supprimer le step 2.

Nous avons maintenant les boutons suivants :

 

Dans le cas d'un projet existant (avec des contenus existants) il faut faire comme ci-dessus mais au niveau du fichier de workflow, il faut simplement retirer l'action "Proposer" de l'action d'état "Brouillon" et la transférer dans les actions communes (<common-action>); ainsi l'action "Proposer" n'est plus disponible depuis l'état de "Brouillon", mais elle existe quand même : l'historique restera cohérent alors même que l'action semble de plus exister pour les contributeurs.

Retour en haut