src/pyams_content/shared/common/zmi/workflow.py
changeset 0 7c0001cacf8e
child 14 234db8f05928
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/shared/common/zmi/workflow.py	Thu Oct 08 13:37:29 2015 +0200
@@ -0,0 +1,813 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+from pyams_utils.text import text_to_html
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+from datetime import datetime
+
+# import interfaces
+from pyams_content.shared.common.interfaces import IWfSharedContent, ISharedTool, ISharedContent
+from pyams_form.interfaces.form import IInnerTabForm, IFormPrefixViewletsManager, IWidgetsPrefixViewletsManager, \
+    IFormSuffixViewletsManager
+from pyams_security.interfaces import ISecurityManager
+from pyams_skin.interfaces import IInnerPage
+from pyams_skin.layer import IPyAMSLayer
+from pyams_workflow.interfaces import IWorkflowInfo, IWorkflowTransitionInfo, IWorkflowPublicationInfo, \
+    IWorkflowCommentInfo, IWorkflowVersions, IWorkflowState, IWorkflowManagedContent, IWorkflow, IWorkflowStateLabel, \
+    IWorkflowRequestUrgencyInfo, SYSTEM, MANUAL
+from z3c.form.interfaces import IDataExtractedEvent
+
+# import packages
+from pyams_content.shared.common.zmi.summary import SharedContentSummaryForm
+from pyams_content.workflow import DRAFT, DELETED
+from pyams_form.form import AJAXAddForm, InnerDisplayForm
+from pyams_form.schema import CloseButton
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_template.template import template_config
+from pyams_utils.adapter import adapter_config
+from pyams_utils.date import format_datetime
+from pyams_utils.registry import get_utility
+from pyams_utils.timezone import tztime
+from pyams_utils.traversing import get_parent
+from pyams_utils.url import absolute_url
+from pyams_viewlet.viewlet import viewlet_config, Viewlet
+from pyams_workflow.zmi.transition import WorkflowContentTransitionForm, WorkflowContentTransitionAJAXForm
+from pyramid.events import subscriber
+from pyramid.view import view_config
+from z3c.form import field, button
+from zope.interface import implementer, Interface, Invalid
+
+from pyams_content import _
+
+
+#
+# Workflow summary view
+#
+
+@adapter_config(name='workflow-summary',
+                context=(IWfSharedContent, IPyAMSLayer, SharedContentSummaryForm),
+                provides=IInnerTabForm)
+class WfSharedContentWorkflowSummary(InnerDisplayForm):
+    """Shared content workflow summary"""
+
+    weight = 10
+    tab_label = _("Workflow")
+    tab_target = 'workflow-summary.html'
+
+    fields = field.Fields(Interface)
+
+
+@pagelet_config(name='workflow-summary.html', context=IWfSharedContent, layer=IPyAMSLayer)
+@implementer(IInnerPage, IInnerTabForm)
+class WorkflowSummaryDisplayForm(InnerDisplayForm):
+    """Workflow summary display form"""
+
+    @property
+    def fields(self):
+        fields = field.Fields(IWorkflowState).omit('history') + \
+                 field.Fields(IWorkflowPublicationInfo)
+        workflow = IWorkflow(self.context)
+        if IWorkflowState(self.context).state not in workflow.waiting_states:
+            fields = fields.omit('state_urgency')
+        return fields
+
+    def updateWidgets(self, prefix=None):
+        super(WorkflowSummaryDisplayForm, self).updateWidgets(prefix)
+        state = IWorkflowState(self.context)
+        content = get_parent(self.context, IWorkflowManagedContent)
+        workflow = get_utility(IWorkflow, name=content.workflow_name)
+        self.widgets['state'].value = self.request.localizer.translate(workflow.get_state_label(state.state))
+        self.widgets['state_date'].value = format_datetime(tztime(state.state_date))
+        info = IWorkflowPublicationInfo(self.context)
+        if info.publication_date:
+            self.widgets['publication_date'].value = format_datetime(tztime(info.publication_date))
+        if info.first_publication_date:
+            self.widgets['first_publication_date'].value = format_datetime(tztime(info.first_publication_date))
+        if info.publication_effective_date:
+            self.widgets['publication_effective_date'].value = format_datetime(tztime(info.publication_effective_date))
+        if info.publication_expiration_date:
+            self.widgets['publication_expiration_date'].value = format_datetime((tztime(info.publication_expiration_date)))
+
+
+#
+# Generic transition info
+#
+
+@viewlet_config(name='wf-transition-info', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=WorkflowContentTransitionForm, manager=IFormSuffixViewletsManager, weight=10)
+@template_config(template='templates/wf-transition-info.pt')
+class WorkflowContentTransitionFormInfo(Viewlet):
+    """Publication request form info message"""
+
+    @property
+    def previous_step(self):
+        translate = self.request.localizer.translate
+        workflow = IWorkflow(self.context)
+        state = IWorkflowState(self.context)
+        adapter = self.request.registry.queryAdapter(workflow, IWorkflowStateLabel,
+                                                     name=state.state)
+        if adapter is None:
+            adapter = self.request.registry.queryAdapter(workflow, IWorkflowStateLabel)
+        if adapter is not None:
+            state_label = adapter.get_label(self.context, request=self.request)
+        else:
+            security = get_utility(ISecurityManager)
+            state_label = translate(_("{state} by {principal}")).format(
+                state=translate(workflow.get_state_label(state.state)),
+                principal=security.get_principal(state.state_principal).title)
+        return translate(_("{state} {date}")).format(state=state_label,
+                                                     date=format_datetime(state.state_date, request=self.request))
+
+    @property
+    def previous_message(self):
+        workflow = IWorkflow(self.context)
+        state = IWorkflowState(self.context)
+        position = 0
+        history_item = None
+        trigger = SYSTEM
+        while trigger != MANUAL:
+            position -= 1
+            history_item = state.history[position]
+            if history_item.transition_id:
+                trigger = workflow.get_transition_by_id(history_item.transition_id).trigger
+            else:
+                break
+        if history_item:
+            return text_to_html((history_item.comment or '').strip())
+
+    @property
+    def next_step(self):
+        transition = self.__parent__.transition
+        return self.request.localizer.translate(transition.user_data.get('next_step')) \
+            if 'next_step' in transition.user_data else None
+
+
+#
+# Request publication form
+#
+
+class IPublicationRequestButtons(Interface):
+    """Shared content publication request buttons"""
+
+    close = CloseButton(name='close', title=_("Cancel"))
+    action = button.Button(name='action', title=_("Request publication"))
+
+
+@pagelet_config(name='wf-propose.html', context=IWfSharedContent, layer=IPyAMSLayer, permission='pyams.ManageContent')
+class PublicationRequestForm(WorkflowContentTransitionForm):
+    """Shared content publication request form"""
+
+    fields = field.Fields(IWorkflowTransitionInfo) + \
+             field.Fields(IWorkflowPublicationInfo).select('publication_effective_date',
+                                                           'publication_expiration_date') + \
+             field.Fields(IWorkflowRequestUrgencyInfo) + \
+             field.Fields(IWorkflowCommentInfo)
+    buttons = button.Buttons(IPublicationRequestButtons)
+    ajax_handler = 'wf-propose.json'
+
+    def updateWidgets(self, prefix=None):
+        super(PublicationRequestForm, self).updateWidgets(prefix)
+        self.widgets['publication_effective_date'].required = True
+        self.widgets['publication_effective_date'].value = datetime.now().strftime('%d/%m/%y %H:%M')
+        self.widgets['comment'].required = True
+
+    def createAndAdd(self, data):
+        pub_info = IWorkflowPublicationInfo(self.context)
+        pub_info.publication_effective_date = data.get('publication_effective_date')
+        pub_info.publication_expiration_date = data.get('publication_expiration_date')
+        return super(PublicationRequestForm, self).createAndAdd(data)
+
+
+@view_config(name='wf-propose.json', context=IWfSharedContent, request_type=IPyAMSLayer,
+             permission='pyams.ManageContent', renderer='json', xhr=True)
+class PublicationRequestAJAXForm(WorkflowContentTransitionAJAXForm, PublicationRequestForm):
+    """Shared content publication request form, JSON renderer"""
+
+
+@subscriber(IDataExtractedEvent, form_selector=PublicationRequestForm)
+def handle_publication_request_form_data_extraction(event):
+    """Handle publication request form data extraction"""
+    if not event.data.get('publication_effective_date'):
+        event.form.widgets.errors += (Invalid(_("Publication start date is required")), )
+    comment = (event.data.get('comment') or '').strip()
+    if not comment:
+        event.form.widgets.errors += (Invalid(_("A comment is required")), )
+
+
+@viewlet_config(name='wf-propose-owner-warning', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationRequestForm, manager=IFormPrefixViewletsManager, weight=10)
+@template_config(template='templates/wf-owner-warning.pt')
+class PublicationRequestFormWarning(Viewlet):
+    """Publication request form warning message"""
+
+    def __new__(cls, context, request, view, manager):
+        if request.principal.id in context.owner:
+            return None
+        return Viewlet.__new__(cls)
+
+
+@viewlet_config(name='wf-propose-message', context=IWfSharedContent, layer=IPyAMSLayer, view=PublicationRequestForm,
+                manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-propose-message.pt')
+class PublicationRequestFormMessage(Viewlet):
+    """Publication request form info message"""
+
+
+#
+# Cancel publication request form
+#
+
+class IPublicationRequestCancelButtons(Interface):
+    """Shared content publication request cancel buttons"""
+
+    close = CloseButton(name='close', title=_("Cancel"))
+    action = button.Button(name='action', title=_("Cancel publication request"))
+
+
+@pagelet_config(name='wf-cancel-propose.html', context=IWfSharedContent, layer=IPyAMSLayer,
+                permission='pyams.ManageContent')
+class PublicationRequestCancelForm(WorkflowContentTransitionForm):
+    """Shared content publication request cancel form"""
+
+    buttons = button.Buttons(IPublicationRequestCancelButtons)
+    ajax_handler = 'wf-cancel-propose.json'
+
+
+@view_config(name='wf-cancel-propose.json', context=IWfSharedContent, request_type=IPyAMSLayer,
+             permission='pyams.ManageContent', renderer='json', xhr=True)
+class PublicationRequestCancelAJAXForm(WorkflowContentTransitionAJAXForm, PublicationRequestCancelForm):
+    """Shared content publication request cancel form, JSON renderer"""
+
+
+@viewlet_config(name='wf-cancel-propose-owner-warning', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationRequestCancelForm, manager=IFormPrefixViewletsManager, weight=10)
+@template_config(template='templates/wf-owner-warning.pt')
+class PublicationRequestCancelFormWarning(Viewlet):
+    """Publication request cancel form warning message"""
+
+    def __new__(cls, context, request, view, manager):
+        if request.principal.id in context.owner:
+            return None
+        return Viewlet.__new__(cls)
+
+
+@viewlet_config(name='wf-cancel-propose-message', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationRequestCancelForm, manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-cancel-propose-message.pt')
+class PublicationRequestCancelFormMessage(Viewlet):
+    """Publication request cancel form info message"""
+
+
+#
+# Refuse publication form
+#
+
+class IPublicationRequestRefuseButtons(Interface):
+    """Shared content publication request refuse buttons"""
+
+    close = CloseButton(name='close', title=_("Cancel"))
+    action = button.Button(name='action', title=_("Refuse publication request"))
+
+
+@pagelet_config(name='wf-refuse.html', context=IWfSharedContent, layer=IPyAMSLayer,
+                permission='pyams.PublishContent')
+class PublicationRequestRefuseForm(WorkflowContentTransitionForm):
+    """Shared content publication request refuse form"""
+
+    buttons = button.Buttons(IPublicationRequestRefuseButtons)
+    ajax_handler = 'wf-refuse.json'
+
+    def updateWidgets(self, prefix=None):
+        super(PublicationRequestRefuseForm, self).updateWidgets(prefix)
+        self.widgets['comment'].required = True
+
+
+@view_config(name='wf-refuse.json', context=IWfSharedContent, request_type=IPyAMSLayer,
+             permission='pyams.PublishContent', renderer='json', xhr=True)
+class PublicationRequestRefuseAJAXForm(WorkflowContentTransitionAJAXForm, PublicationRequestRefuseForm):
+    """Shared content publication request refuse form, JSON renderer"""
+
+
+@subscriber(IDataExtractedEvent, form_selector=PublicationRequestRefuseForm)
+def handle_publication_request_refuse_form_data_extraction(event):
+    """Handle publication request refuse form data extraction"""
+    comment = (event.data.get('comment') or '').strip()
+    if not comment:
+        event.form.widgets.errors += (Invalid(_("A comment is required")), )
+
+
+@viewlet_config(name='wf-refuse-operator-warning', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationRequestRefuseForm, manager=IFormPrefixViewletsManager, weight=10)
+@template_config(template='templates/wf-operator-warning.pt')
+class PublicationRequestRefuseFormWarning(Viewlet):
+    """Publication request refuse form warning message"""
+
+    def __new__(cls, context, request, view, manager):
+        state = IWorkflowState(context)
+        if state.state_principal in context.owner:
+            return None
+        return Viewlet.__new__(cls)
+
+
+@viewlet_config(name='wf-refuse-message', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationRequestRefuseForm, manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-refuse-propose-message.pt')
+class PublicationRequestRefuseFormMessage(Viewlet):
+    """Publication request refuse form info message"""
+
+
+#
+# Publish form
+#
+
+class IPublicationButtons(Interface):
+    """Shared content publication buttons"""
+
+    close = CloseButton(name='close', title=_("Cancel"))
+    action = button.Button(name='action', title=_("Publish"))
+
+
+@pagelet_config(name='wf-publish.html', context=IWfSharedContent, layer=IPyAMSLayer, permission='pyams.PublishContent')
+class PublicationForm(WorkflowContentTransitionForm):
+    """Shared content publication form"""
+
+    fields = field.Fields(IWorkflowTransitionInfo) + \
+             field.Fields(IWorkflowPublicationInfo).select('publication_effective_date',
+                                                           'publication_expiration_date') + \
+             field.Fields(IWorkflowCommentInfo)
+    buttons = button.Buttons(IPublicationButtons)
+    ajax_handler = 'wf-publish.json'
+
+    def updateWidgets(self, prefix=None):
+        super(PublicationForm, self).updateWidgets(prefix)
+        pub_info = IWorkflowPublicationInfo(self.context)
+        self.widgets['publication_effective_date'].required = True
+        if ('publication_effective_date' in self.widgets) and pub_info.publication_effective_date:
+            self.widgets['publication_effective_date'].value = \
+                tztime(pub_info.publication_effective_date).strftime('%d/%m/%y %H:%M')
+        if ('publication_expiration_date' in self.widgets) and pub_info.publication_expiration_date:
+            self.widgets['publication_expiration_date'].value = \
+                tztime(pub_info.publication_expiration_date).strftime('%d/%m/%y %H:%M')
+
+    def createAndAdd(self, data):
+        pub_info = IWorkflowPublicationInfo(self.context)
+        pub_info.publication_effective_date = data.get('publication_effective_date')
+        pub_info.publication_expiration_date = data.get('publication_expiration_date')
+        return super(PublicationForm, self).createAndAdd(data)
+
+
+@view_config(name='wf-publish.json', context=IWfSharedContent, request_type=IPyAMSLayer,
+             permission='pyams.PublishContent', renderer='json', xhr=True)
+class PublicationAJAXForm(WorkflowContentTransitionAJAXForm, PublicationForm):
+    """Shared content publication form, JSON renderer"""
+
+
+@subscriber(IDataExtractedEvent, form_selector=PublicationForm)
+def handle_publication_form_data_extraction(event):
+    """Handle publication form data extraction"""
+    if not event.data.get('publication_effective_date'):
+        event.form.widgets.errors += (Invalid(_("Publication start date is required")), )
+
+
+@viewlet_config(name='wf-publish-operator-warning', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationForm, manager=IFormPrefixViewletsManager, weight=10)
+@template_config(template='templates/wf-operator-warning.pt')
+class PublicationFormWarning(Viewlet):
+    """Shared content publication form warning message"""
+
+    def __new__(cls, context, request, view, manager):
+        state = IWorkflowState(context)
+        if state.state_principal in context.owner:
+            return None
+        return Viewlet.__new__(cls)
+
+
+@viewlet_config(name='wf-publish-message', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationForm, manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-publish-message.pt')
+class PublicationFormMessage(Viewlet):
+    """Shared content publication form info message"""
+
+
+#
+# Publication retire request form
+#
+
+class IPublicationRetireRequestButtons(Interface):
+    """Shared content publication retire request buttons"""
+
+    close = CloseButton(name='close', title=_("Cancel"))
+    action = button.Button(name='action', title=_("Request retire"))
+
+
+@pagelet_config(name='wf-retiring.html', context=IWfSharedContent, layer=IPyAMSLayer,
+                permission='pyams.ManageContent')
+class PublicationRetireRequestForm(WorkflowContentTransitionForm):
+    """Shared content publication request refuse form"""
+
+    fields = field.Fields(IWorkflowTransitionInfo) + \
+             field.Fields(IWorkflowRequestUrgencyInfo) + \
+             field.Fields(IWorkflowCommentInfo)
+    buttons = button.Buttons(IPublicationRetireRequestButtons)
+    ajax_handler = 'wf-retiring.json'
+
+    def updateWidgets(self, prefix=None):
+        super(PublicationRetireRequestForm, self).updateWidgets(prefix)
+        self.widgets['comment'].required = True
+
+
+@view_config(name='wf-retiring.json', context=IWfSharedContent, request_type=IPyAMSLayer,
+             permission='pyams.ManageContent', renderer='json', xhr=True)
+class PublicationRetireRequestAJAXForm(WorkflowContentTransitionAJAXForm, PublicationRetireRequestForm):
+    """Shared content publication retire request form, JSON renderer"""
+
+
+@subscriber(IDataExtractedEvent, form_selector=PublicationRetireRequestForm)
+def handle_publication_retire_request_form_data_extraction(event):
+    """Handle publication retire request form data extraction"""
+    comment = (event.data.get('comment') or '').strip()
+    if not comment:
+        event.form.widgets.errors += (Invalid(_("A comment is required")), )
+
+
+@viewlet_config(name='wf-retiring-owner-warning', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationRetireRequestForm, manager=IFormPrefixViewletsManager, weight=10)
+@template_config(template='templates/wf-owner-warning.pt')
+class PublicationRetireRequestFormWarning(Viewlet):
+    """Publication retire request form warning message"""
+
+    def __new__(cls, context, request, view, manager):
+        if request.principal.id in context.owner:
+            return None
+        return Viewlet.__new__(cls)
+
+
+@viewlet_config(name='wf-retiring-message', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationRetireRequestForm, manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-retiring-message.pt')
+class PublicationRetireRequestFormMessage(Viewlet):
+    """Publication retire request form info message"""
+
+
+#
+# Publication retire cancel form
+#
+
+class IPublicationRetireCancelButtons(Interface):
+    """Shared content publication retire request cancel buttons"""
+
+    close = CloseButton(name='close', title=_("Cancel"))
+    action = button.Button(name='action', title=_("Cancel retire request"))
+
+
+@pagelet_config(name='wf-cancel-retiring.html', context=IWfSharedContent, layer=IPyAMSLayer,
+                permission='pyams.ManageContent')
+class PublicationRetireCancelForm(WorkflowContentTransitionForm):
+    """Shared content publication retire request cancel form"""
+
+    buttons = button.Buttons(IPublicationRetireCancelButtons)
+    ajax_handler = 'wf-cancel-retiring.json'
+
+
+@view_config(name='wf-cancel-retiring.json', context=IWfSharedContent, request_type=IPyAMSLayer,
+             permission='pyams.ManageContent', renderer='json', xhr=True)
+class PublicationRetireCancelAJAXForm(WorkflowContentTransitionAJAXForm, PublicationRetireCancelForm):
+    """Shared content publication retire request cancel form, JSON renderer"""
+
+
+@viewlet_config(name='wf-cancel-retiring-owner-warning', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationRetireCancelForm, manager=IFormPrefixViewletsManager, weight=10)
+@template_config(template='templates/wf-owner-warning.pt')
+class PublicationRetireCancelFormWarning(Viewlet):
+    """Publication retire request cancel form warning message"""
+
+    def __new__(cls, context, request, view, manager):
+        if request.principal.id in context.owner:
+            return None
+        return Viewlet.__new__(cls)
+
+
+@viewlet_config(name='wf-cancel-retiring-message', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationRetireCancelForm, manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-cancel-retiring-message.pt')
+class PublicationRetireCancelFormMessage(Viewlet):
+    """Publication retire request form info message"""
+
+
+#
+# Publication retire form
+#
+
+class IPublicationRetireButtons(Interface):
+    """Shared content publication retire buttons"""
+
+    close = CloseButton(name='close', title=_("Cancel"))
+    action = button.Button(name='action', title=_("Retire"))
+
+
+@pagelet_config(name='wf-retire.html', context=IWfSharedContent, layer=IPyAMSLayer,
+                permission='pyams.PublishContent')
+class PublicationRetireForm(WorkflowContentTransitionForm):
+    """Shared content publication retire form"""
+
+    buttons = button.Buttons(IPublicationRetireButtons)
+    ajax_handler = 'wf-retire.json'
+
+
+@view_config(name='wf-retire.json', context=IWfSharedContent, request_type=IPyAMSLayer,
+             permission='pyams.PublishContent', renderer='json', xhr=True)
+class PublicationRetireAJAXForm(WorkflowContentTransitionAJAXForm, PublicationRetireForm):
+    """Shared content publication retire form, JSON renderer"""
+
+
+@viewlet_config(name='wf-retire-operator-warning', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationRetireForm, manager=IFormPrefixViewletsManager, weight=10)
+@template_config(template='templates/wf-operator-warning.pt')
+class PublicationRetireFormWarning(Viewlet):
+    """Publication retire form warning message"""
+
+    def __new__(cls, context, request, view, manager):
+        state = IWorkflowState(context)
+        if state.state_principal in context.owner:
+            return None
+        return Viewlet.__new__(cls)
+
+
+@viewlet_config(name='wf-retire-message', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationRetireForm, manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-retire-message.pt')
+class PublicationRetireFormMessage(Viewlet):
+    """Publication retire form info message"""
+
+
+#
+# Publication archive request form
+#
+
+class IPublicationArchiveRequestButtons(Interface):
+    """Shared content publication archive request buttons"""
+
+    close = CloseButton(name='close', title=_("Cancel"))
+    action = button.Button(name='action', title=_("Request archive"))
+
+
+@pagelet_config(name='wf-archiving.html', context=IWfSharedContent, layer=IPyAMSLayer,
+                permission='pyams.ManageContent')
+class PublicationArchiveRequestForm(WorkflowContentTransitionForm):
+    """Shared content publication request archive form"""
+
+    fields = field.Fields(IWorkflowTransitionInfo) + \
+             field.Fields(IWorkflowRequestUrgencyInfo) + \
+             field.Fields(IWorkflowCommentInfo)
+    buttons = button.Buttons(IPublicationArchiveRequestButtons)
+    ajax_handler = 'wf-archiving.json'
+
+    def updateWidgets(self, prefix=None):
+        super(PublicationArchiveRequestForm, self).updateWidgets(prefix)
+        self.widgets['comment'].required = True
+
+
+@view_config(name='wf-archiving.json', context=IWfSharedContent, request_type=IPyAMSLayer,
+             permission='pyams.ManageContent', renderer='json', xhr=True)
+class PublicationArchiveRequestAJAXForm(WorkflowContentTransitionAJAXForm, PublicationArchiveRequestForm):
+    """Shared content publication archive request form, JSON renderer"""
+
+
+@subscriber(IDataExtractedEvent, form_selector=PublicationArchiveRequestForm)
+def handle_archive_request_form_data_extraction(event):
+    """Handle archive request form data extraction"""
+    comment = (event.data.get('comment') or '').strip()
+    if not comment:
+        event.form.widgets.errors += (Invalid(_("A comment is required")), )
+
+
+@viewlet_config(name='wf-archiving-owner-warning', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationArchiveRequestForm, manager=IFormPrefixViewletsManager, weight=10)
+@template_config(template='templates/wf-owner-warning.pt')
+class PublicationArchiveRequestFormWarning(Viewlet):
+    """Publication archive request form warning message"""
+
+    def __new__(cls, context, request, view, manager):
+        if request.principal.id in context.owner:
+            return None
+        return Viewlet.__new__(cls)
+
+
+@viewlet_config(name='wf-archiving-message', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationArchiveRequestForm, manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-archiving-message.pt')
+class PublicationArchiveRequestFormMessage(Viewlet):
+    """Publication archive request form info message"""
+
+
+#
+# Publication archive cancel form
+#
+
+class IPublicationArchiveCancelButtons(Interface):
+    """Shared content publication archive request cancel buttons"""
+
+    close = CloseButton(name='close', title=_("Cancel"))
+    action = button.Button(name='action', title=_("Cancel archive request"))
+
+
+@pagelet_config(name='wf-cancel-archiving.html', context=IWfSharedContent, layer=IPyAMSLayer,
+                permission='pyams.ManageContent')
+class PublicationArchiveCancelForm(WorkflowContentTransitionForm):
+    """Shared content publication archive request cancel form"""
+
+    buttons = button.Buttons(IPublicationArchiveCancelButtons)
+    ajax_handler = 'wf-cancel-archiving.json'
+
+
+@view_config(name='wf-cancel-archiving.json', context=IWfSharedContent, request_type=IPyAMSLayer,
+             permission='pyams.ManageContent', renderer='json', xhr=True)
+class PublicationArchiveCancelAJAXForm(WorkflowContentTransitionAJAXForm, PublicationArchiveCancelForm):
+    """Shared content publication archive request cancel form, JSON renderer"""
+
+
+@viewlet_config(name='wf-cancel-archiving-owner-warning', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationArchiveCancelForm, manager=IFormPrefixViewletsManager, weight=10)
+@template_config(template='templates/wf-owner-warning.pt')
+class PublicationArchiveCancelFormWarning(Viewlet):
+    """Publication archive cancel form warning message"""
+
+    def __new__(cls, context, request, view, manager):
+        if request.principal.id in context.owner:
+            return None
+        return Viewlet.__new__(cls)
+
+
+@viewlet_config(name='wf-cancel-archiving-message', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationArchiveCancelForm, manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-cancel-archiving-message.pt')
+class PublicationArchiveCancelFormMessage(Viewlet):
+    """Publication archive cancel form info message"""
+
+
+#
+# Publication archive form
+#
+
+class IPublicationArchiveButtons(Interface):
+    """Shared content publication archive buttons"""
+
+    close = CloseButton(name='close', title=_("Cancel"))
+    action = button.Button(name='action', title=_("Archive"))
+
+
+@pagelet_config(name='wf-archive.html', context=IWfSharedContent, layer=IPyAMSLayer,
+                permission='pyams.PublishContent')
+class PublicationArchiveForm(WorkflowContentTransitionForm):
+    """Shared content publication archive form"""
+
+    buttons = button.Buttons(IPublicationArchiveButtons)
+    ajax_handler = 'wf-archive.json'
+
+
+@view_config(name='wf-archive.json', context=IWfSharedContent, request_type=IPyAMSLayer,
+             permission='pyams.PublishContent', renderer='json', xhr=True)
+class PublicationArchiveAJAXForm(WorkflowContentTransitionAJAXForm, PublicationArchiveForm):
+    """Shared content publication archive form, JSON renderer"""
+
+
+@viewlet_config(name='wf-archive-operator-warning', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationArchiveForm, manager=IFormPrefixViewletsManager, weight=10)
+@template_config(template='templates/wf-operator-warning.pt')
+class PublicationArchiveFormWarning(Viewlet):
+    """Publication archive form warning message"""
+
+    def __new__(cls, context, request, view, manager):
+        state = IWorkflowState(context)
+        if state.state_principal in context.owner:
+            return None
+        return Viewlet.__new__(cls)
+
+
+@viewlet_config(name='wf-archive-message', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationArchiveForm, manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-archive-message.pt')
+class PublicationArchiveFormMessage(Viewlet):
+    """Publication archive form info message"""
+
+
+#
+# Clone form
+#
+
+class ISharedContentCloneButtons(Interface):
+    """Shared content clone buttons"""
+
+    close = CloseButton(name='close', title=_("Cancel"))
+    action = button.Button(name='action', title=_("Create new version"))
+
+
+@pagelet_config(name='wf-clone.html', context=IWfSharedContent, layer=IPyAMSLayer, permission='pyams.CreateContent')
+class SharedContentCloneForm(WorkflowContentTransitionForm):
+    """Shared content clone form"""
+
+    buttons = button.Buttons(ISharedContentCloneButtons)
+    ajax_handler = 'wf-clone.json'
+
+    def createAndAdd(self, data):
+        info = IWorkflowInfo(self.context)
+        return info.fire_transition_toward(DRAFT, comment=data.get('comment'))
+
+
+@view_config(name='wf-clone.json', context=IWfSharedContent, request_type=IPyAMSLayer,
+             permission='pyams.CreateContent', renderer='json', xhr=True)
+class SharedContentCloneAJAXForm(AJAXAddForm, SharedContentCloneForm):
+    """Shared content clone form, JSON rendener"""
+
+    def get_ajax_output(self, changes):
+        return {'status': 'redirect',
+                'location': absolute_url(changes, self.request, 'admin.html#properties.html')}
+
+
+@viewlet_config(name='wf-clone-owner-warning', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=SharedContentCloneForm, manager=IFormPrefixViewletsManager, weight=10)
+@template_config(template='templates/wf-owner-warning.pt')
+class SharedContentCloneFormWarning(Viewlet):
+    """Shared content clone form warning message"""
+
+    def __new__(cls, context, request, view, manager):
+        if request.principal.id in context.owner:
+            return None
+        return Viewlet.__new__(cls)
+
+
+@viewlet_config(name='wf-clone-message', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=SharedContentCloneForm, manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-clone-message.pt')
+class SharedContentCloneFormMessage(Viewlet):
+    """Shared content clone form info message"""
+
+
+#
+# Delete form
+#
+
+class ISharedContentDeleteButtons(Interface):
+    """Shared content delete form buttons"""
+
+    close = CloseButton(name='close', title=_("Cancel"))
+    action = button.Button(name='action', title=_("Delete version"))
+
+
+@pagelet_config(name='wf-delete.html', context=IWfSharedContent, layer=IPyAMSLayer, permission='pyams.ManageContent')
+class SharedContentDeleteForm(WorkflowContentTransitionForm):
+    """Shared content delete form"""
+
+    buttons = button.Buttons(ISharedContentDeleteButtons)
+    ajax_handler = 'wf-delete.json'
+
+    def createAndAdd(self, data):
+        state = IWorkflowState(self.context)
+        if state.version_id == 1:  # remove the first and only version => remove all
+            content = get_parent(self.context, ISharedContent)
+            self.__target = get_parent(content, ISharedTool)
+            del content.__parent__[content.__name__]
+        else:
+            self.__target = get_parent(self.context, ISharedTool)
+            IWorkflowVersions(self.context).remove_version(state.version_id, state=DELETED, comment=data.get('comment'))
+
+
+@view_config(name='wf-delete.json', context=IWfSharedContent, request_type=IPyAMSLayer,
+             permission='pyams.ManageContent', renderer='json', xhr=True)
+class SharedContentDeleteAJAXForm(AJAXAddForm, SharedContentDeleteForm):
+    """Shared content delete form, JSON rendener"""
+
+    def get_ajax_output(self, changes):
+        return {'status': 'redirect',
+                'location': absolute_url(self._SharedContentDeleteForm__target, self.request, 'admin.html')}
+
+
+@viewlet_config(name='wf-delete-owner-warning', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=SharedContentDeleteForm, manager=IFormPrefixViewletsManager, weight=10)
+@template_config(template='templates/wf-owner-warning.pt')
+class SharedContentDeleteFormWarning(Viewlet):
+    """Shared content delete form warning message"""
+
+    def __new__(cls, context, request, view, manager):
+        if request.principal.id in context.owner:
+            return None
+        return Viewlet.__new__(cls)
+
+
+@viewlet_config(name='wf-delete-message', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=SharedContentDeleteForm, manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-delete-message.pt')
+class SharedContentDeleteFormMessage(Viewlet):
+    """Shared content delete form info message"""