Index: main/plugin-odf/cms-ribbon-content.xml
===================================================================
--- main/plugin-odf/cms-ribbon-content.xml	(revision 31623)
+++ main/plugin-odf/cms-ribbon-content.xml	(working copy)
@@ -90,6 +90,8 @@
 					<medium>
 						<control id="org.ametys.odf.content.container.Edit"/>
 						<control id="org.ametys.cms.Unlock"/>
+						<control id="org.ametys.odf.clipboard.Copy"/>
+                        <control id="org.ametys.odf.clipboard.Paste"/>
 					</medium>
 					<small/>
 				</group>
@@ -133,6 +135,8 @@
 					<medium>
 						<control id="org.ametys.odf.content.container.Edit"/>
 						<control id="org.ametys.cms.Unlock"/>
+						<control id="org.ametys.odf.clipboard.Copy"/>
+                        <control id="org.ametys.odf.clipboard.Paste"/>
 					</medium>
 					<small/>
 				</group>
@@ -164,6 +168,7 @@
                     <large></large>
                     <medium>
                         <control id="org.ametys.cms.ContentConsistencyTest"/>
+                        <layout size="small">
                     </medium>
                     <small/>
                 </group>
@@ -194,7 +199,8 @@
 						</layout>
 					</small>
 				</group>
-                <group label="plugin.odf:RIBBON_TABS_TAB_CONTENT_GROUPS_GROUP_TOOL_LABEL" icon="">
+                <group label="plugin.odf:RIBBON_TABS_TAB_CONTENT_GROUPS_GROUP_
+                        <layout size="small">TOOL_LABEL" icon="">
                     <large>
                         <control id="org.ametys.odfsync.person.Synchronize"/>
                         <control id="org.ametys.cms.userinterface.History"/>
@@ -237,6 +243,7 @@
 					<medium>
 						<control id="org.ametys.odf.content.program.Edit"/>
 						<control id="org.ametys.cms.Unlock"/>
+                        <control id="org.ametys.odf.clipboard.Paste"/>
 					</medium>
 					<small/>
 				</group>
Index: main/plugin-odf/i18n/messages_en.xml
===================================================================
--- main/plugin-odf/i18n/messages_en.xml	(revision 31623)
+++ main/plugin-odf/i18n/messages_en.xml	(working copy)
@@ -1289,7 +1290,24 @@
     
     <message key="PLUGINS_ODF_EXPORT_RNCP_LABEL">RNCP export</message>
     <message key="PLUGINS_ODF_EXPORT_RNCP_DESCRIPTION">Generate RNCP file to .docx format</message>
-    <message key="PLUGINS_ODF_EXPORT_RNCP_FOOTER">See help for details</message>
+    <message key="PLUGINS_ODF_EXPORT_RNCP_FOOTER">See help for further details</message>
     
+    <!-- +
+         | CLIPBOARD
+         + -->
+    <message key="PLUGINS_ODF_CLIPBOARD_COPY_LABEL">Copy</message>
+    <message key="PLUGINS_ODF_CLIPBOARD_COPY_DESC">Copy the hierarchy of the selected subprogram or container.</message>
+    <message key="PLUGINS_ODF_CLIPBOARD_COPY_FOOTER">See help for further details.</message>
+    
+    <message key="PLUGINS_ODF_CLIPBOARD_PASTE_NO_COPY_DESC">This button is disabled because there is no content in the clipboard.</message>
+    <message key="PLUGINS_ODF_CLIPBOARD_PASTE_NO_SELECTION_DESC">This button is disabled because a subprogram/container must be pasted under a program, a subprogram or a container.</message>
+    
+    <message key="PLUGINS_ODF_CLIPBOARD_COPY_LABEL">Paste</message>
+    <message key="PLUGINS_ODF_CLIPBOARD_COPY_DESC">Pastes the hierarchy of the selected subprogram or container.</message>
+    <message key="PLUGINS_ODF_CLIPBOARD_COPY_FOOTER">See help for further details.</message>
+    
+    <message key="PLUGINS_ODF_CLIPBOARD_PASTE_ERROR">An error has occurred: the content hasn't been pasted.</message>
+    <message key="PLUGINS_ODF_CLIPBOARD_PASTE_ERROR_TITLE">Pasting error</message>
+    <message key="PLUGINS_ODF_CLIPBOARD_PASTE_ERROR_ALREADY_EXISTS">The content already exists.</message>
 </catalogue>
 	
\ No newline at end of file
Index: main/plugin-odf/i18n/messages_fr.xml
===================================================================
--- main/plugin-odf/i18n/messages_fr.xml	(revision 31623)
+++ main/plugin-odf/i18n/messages_fr.xml	(working copy)
@@ -1293,5 +1294,23 @@
     <message key="PLUGINS_ODF_EXPORT_RNCP_LABEL">Fiche RNCP</message>
     <message key="PLUGINS_ODF_EXPORT_RNCP_DESCRIPTION">Exporter la fiche RNCP (Répertoire National des Certifications Professionnelles)</message>
     <message key="PLUGINS_ODF_EXPORT_RNCP_FOOTER">Voir l'aide pour plus de détails</message>
+    
+    <!-- +
+         | CLIPBOARD
+         + -->
+    <message key="PLUGINS_ODF_CLIPBOARD_COPY_LABEL">Copier</message>
+    <message key="PLUGINS_ODF_CLIPBOARD_COPY_DESC">Copie l'arborescence du parcours ou de la formation sélectionnée.</message>
+    <message key="PLUGINS_ODF_CLIPBOARD_COPY_FOOTER">Voir l'aide pour plus de détails.</message>
+    
+    <message key="PLUGINS_ODF_CLIPBOARD_PASTE_NO_COPY_DESC">Ce bouton est désactivé car il n'y a aucun contenu dans le presse-papiers.</message>
+    <message key="PLUGINS_ODF_CLIPBOARD_PASTE_NO_SELECTION_DESC">Ce bouton est désactivé car il n'est possible de coller un parcours/conteneur que sous une formation, un parcours ou un conteneur.</message>
+    
+    <message key="PLUGINS_ODF_CLIPBOARD_PASTE_LABEL">Coller</message>
+    <message key="PLUGINS_ODF_CLIPBOARD_PASTE_DESC">Colle l'arborescence du parcours ou de la formation sélectionnée.</message>
+    <message key="PLUGINS_ODF_CLIPBOARD_PASTE_FOOTER">Voir l'aide pour plus de détails.</message>
+    
+    <message key="PLUGINS_ODF_CLIPBOARD_PASTE_ERROR">Une erreur est survenue : le contenu n'a pas été copié.</message>
+    <message key="PLUGINS_ODF_CLIPBOARD_PASTE_ERROR_TITLE">Erreur de copie</message>
+    <message key="PLUGINS_ODF_CLIPBOARD_PASTE_ERROR_ALREADY_EXISTS">Le contenu existe déjà.</message>
 </catalogue>
 	
\ No newline at end of file
Index: main/plugin-odf/plugin.xml
===================================================================
--- main/plugin-odf/plugin.xml	(revision 31623)
+++ main/plugin-odf/plugin.xml	(working copy)
@@ -5116,4 +5116,53 @@
             </extension>
         </extensions>
     </feature>
+    
+    <feature name="clipboard">
+        <extensions>
+            <extension id="org.ametys.odf.clipboard.Copy"
+                       point="org.ametys.cms.workspace.ribbon.RibbonControlsManager"
+                       class="org.ametys.runtime.ui.impl.StaticContextualClientSideElement">
+                <action class="org.ametys.odf.clipboard.button.SubProgramOrContainerActionButton">
+                    <param name="action">org.ametys.odf.clipboard.Copy.act</param>
+
+                    <param name="label" i18n="true">PLUGINS_ODF_CLIPBOARD_COPY_LABEL</param>
+                    <param name="default-description" i18n="true">PLUGINS_ODF_CLIPBOARD_COPY_DESC</param>
+                    <param name="footer" i18n="true">PLUGINS_ODF_CLIPBOARD_COPY_FOOTER</param>
+
+                    <param name="icon-small" file="true">img/actions/export_rncp_16.png</param>
+                    <param name="icon-medium" file="true">img/actions/export_rncp_32.png</param>
+                    <param name="icon-large" file="true">img/actions/export_rncp_48.png</param>
+                </action>
+                <scripts>
+                    <file>js/org/ametys/odf/clipboard/button/SubProgramOrContainerActionButton.js</file>
+                    <file>js/org/ametys/odf/clipboard/ClipboardActions.i18n.js</file>
+                </scripts>  
+                <right>ODF_Rights_Program_Edit</right>   
+            </extension>
+            
+            <extension id="org.ametys.odf.clipboard.Paste"
+                       point="org.ametys.cms.workspace.ribbon.RibbonControlsManager"
+                       class="org.ametys.runtime.ui.impl.StaticContextualClientSideElement">
+                <action class="org.ametys.odf.clipboard.button.PasteActionButton">
+                    <param name="action">org.ametys.odf.clipboard.Paste.act</param>
+
+                    <param name="label" i18n="true">PLUGINS_ODF_CLIPBOARD_PASTE_LABEL</param>
+                    <param name="default-description" i18n="true">PLUGINS_ODF_CLIPBOARD_PASTE_DESC</param>
+                    <param name="footer" i18n="true">PLUGINS_ODF_CLIPBOARD_PASTE_FOOTER</param>
+
+                    <param name="no-copy-description" i18n="true">PLUGINS_ODF_CLIPBOARD_PASTE_NO_COPY_DESC</param>
+                    <param name="no-selection-description" i18n="true">PLUGINS_ODF_CLIPBOARD_PASTE_NO_SELECTION_DESC</param>
+
+                    <param name="icon-small" file="true">img/actions/export_pdf_16.png</param>
+                    <param name="icon-medium" file="true">img/actions/export_pdf_32.png</param>
+                    <param name="icon-large" file="true">img/actions/export_pdf_48.png</param>
+                </action>
+                <scripts>
+                    <file>js/org/ametys/odf/clipboard/button/PasteActionButton.js</file>
+                    <file>js/org/ametys/odf/clipboard/ClipboardActions.i18n.js</file>
+                </scripts>  
+                <right>ODF_Rights_Program_Edit</right>   
+            </extension>
+        </extensions>        
+    </feature>
 </plugin>
Index: main/plugin-odf/resources/js/org/ametys/odf/clipboard/ClipboardActions.i18n.js
===================================================================
--- main/plugin-odf/resources/js/org/ametys/odf/clipboard/ClipboardActions.i18n.js	(revision 0)
+++ main/plugin-odf/resources/js/org/ametys/odf/clipboard/ClipboardActions.i18n.js	(revision 0)
@@ -0,0 +1,93 @@
+/*
+ *  Copyright 2015 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+Ext.namespace('org.ametys.odf.clipboard.Copy');
+Ext.namespace('org.ametys.odf.clipboard.Paste');
+
+/**
+ * Copy the selected subprogram or container
+ */
+org.ametys.odf.clipboard.Copy.act = function(controlId, configuration, pluginName)
+{
+	var targets = org.ametys.ribbon.RibbonManager.getInstance().getCurrentSelectionTargets();
+	var target = org.ametys.messagebus.message.MessageTargetHelper.findFirst(targets, function(target) {return target.getType() == "content" &amp;&amp; 
+																											   (target.getParameters().type == "org.ametys.plugins.odf.Content.subProgram" ||
+																											    target.getParameters().type == "org.ametys.plugins.odf.Content.container");});
+	if (target == null) 
+		return;
+	
+	// Store the reference to the subprogram or container who will possibly be copied
+	org.ametys.odf.clipboard.Copy._subprogramOrContainerToCopy = target.getParameters().id;
+	
+	// Refresh the Paste button
+    var events = [org.ametys.messagebus.bus.MessageBuilder.getInstance().createMessage(org.ametys.ribbon.RibbonManager.EVENTTYPE_SELECTIONCHANGED, null, [target])];
+    org.ametys.messagebus.MessageBus.getInstance().fireMessages(events);
+}
+
+/**
+ * Paste the stored subprogram/container under the selected program/subprogram/container
+ */
+org.ametys.odf.clipboard.Paste.act = function(controlId, configuration, pluginName)
+{
+	var targets = org.ametys.ribbon.RibbonManager.getInstance().getCurrentSelectionTargets();
+	var target = org.ametys.messagebus.message.MessageTargetHelper.findFirst(targets, function(target) {return target.getType() == "content" &amp;&amp;
+																												(target.getParameters().type == "org.ametys.plugins.odf.Content.program" 
+																												|| target.getParameters().type == "org.ametys.plugins.odf.Content.subProgram"
+																												|| target.getParameters().type == "org.ametys.plugins.odf.Content.container");});
+	if (target == null)
+		return;
+    
+	var params = {};
+	params["subprogramOrContainerToCopy"] = org.ametys.odf.clipboard.Copy._subprogramOrContainerToCopy;
+	params["parentId"] = target.getParameters().id;
+	
+	var serverMessage = new org.ametys.servercomm.ServerMessage("odf", 
+	        'clipboard/paste',
+	        params,
+	        org.ametys.servercomm.ServerComm.PRIORITY_MAJOR,
+	        org.ametys.odf.clipboard.Paste._pasteCallback,
+	        this, params, "xml");
+	
+	org.ametys.servercomm.ServerComm.getInstance().send(serverMessage);
+}
+
+/**
+ * Callback for the pasting process
+ */
+org.ametys.odf.clipboard.Paste._pasteCallback = function (response, args)
+{
+	org.ametys.odf.clipboard.Copy._subprogramOrContainerToCopy = null;
+	
+	// Server exception
+	if (org.ametys.servercomm.ServerComm.handleBadResponse("<i18n:text i18n:key='PLUGINS_ODF_CLIPBOARD_PASTE_ERROR'/>", response, "org.ametys.odf.clipboard.Paste.act"))
+    {
+        return;
+    }
+    
+	var newContentId = response.selectSingleNode("ActionResult/contentId")[org.ametys.servercomm.ServerComm.xmlTextContent];
+	var targets = [org.ametys.messagebus.bus.MessageBuilder.getInstance().createTarget("content", {'id': newContentId})];
+
+	var events = [org.ametys.messagebus.bus.MessageBuilder.getInstance().createMessage(org.ametys.ribbon.RibbonManager.EVENTTYPE_CREATED, {'parentId': args.parentId}, targets)];
+	org.ametys.messagebus.MessageBus.getInstance().fireMessages(events);
+}
+
+
+
+
+
+
+
+
Index: main/plugin-odf/resources/js/org/ametys/odf/clipboard/button/PasteActionButton.js
===================================================================
--- main/plugin-odf/resources/js/org/ametys/odf/clipboard/button/PasteActionButton.js	(revision 0)
+++ main/plugin-odf/resources/js/org/ametys/odf/clipboard/button/PasteActionButton.js	(revision 0)
@@ -0,0 +1,63 @@
+/*
+ *  Copyright 2015 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+Ext.namespace('org.ametys.odf.clipboard.button');
+
+/**
+ * Create the control instance.
+ * @param {String} id The unique identifier
+ * @param {Map<id, value>} configuration The configuration of the control instance.
+ * @param {String} pluginName The name of the declaring plugin
+ * @class This class implements the behavior for an action.
+ * @extends org.ametys.ribbon.control.RibbonControl
+ */
+org.ametys.odf.clipboard.button.PasteActionButton = function(id, configuration, pluginName)
+{
+	org.ametys.odf.clipboard.button.PasteActionButton.superclass.constructor.call(this, id, configuration, pluginName);
+}
+
+Ext.extend(org.ametys.odf.clipboard.button.PasteActionButton, org.ametys.odf.program.button.ProgramOrContainerActionButton, {});
+
+org.ametys.odf.clipboard.button.PasteActionButton.prototype._testTarget = function(target)
+{
+	if (org.ametys.odf.clipboard.Copy._subprogramOrContainerToCopy == null)
+	{
+		 var text = this._getTooltip(this.getConfiguration()["no-copy-description"]);
+         this.updateTooltip(text, null, null);
+         return false;
+	}
+	
+	if (!org.ametys.odf.clipboard.button.PasteActionButton.superclass._testTarget.call (this, target))
+	{
+		var text = this._getTooltip(this.getConfiguration()["no-selection-description"]);
+        this.updateTooltip(text, null, null);
+        return false;
+	}
+
+	this.updateTooltip(this.getConfiguration()["default-description"], null, null);
+	return true;
+}
+
+org.ametys.odf.clipboard.button.PasteActionButton.prototype._getTooltip = function (addedText)
+{
+    var tooltip = this.getConfiguration()["default-description"];
+    if (addedText != null && addedText.length > 0)
+    {
+        tooltip += "<br/><br/>";
+        tooltip += addedText;
+    }
+    return tooltip;
+}
\ No newline at end of file
Index: main/plugin-odf/resources/js/org/ametys/odf/clipboard/button/SubProgramOrContainerActionButton.js
===================================================================
--- main/plugin-odf/resources/js/org/ametys/odf/clipboard/button/SubProgramOrContainerActionButton.js	(revision 0)
+++ main/plugin-odf/resources/js/org/ametys/odf/clipboard/button/SubProgramOrContainerActionButton.js	(revision 0)
@@ -0,0 +1,40 @@
+/*
+ *  Copyright 2015 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+Ext.namespace('org.ametys.odf.clipboard.button');
+
+/**
+ * Create the control instance.
+ * @param {String} id The unique identifier
+ * @param {Map<id, value>} configuration The configuration of the control instance.
+ * @param {String} pluginName The name of the declaring plugin
+ * @class This class implements the behavior for an action.
+ * @extends org.ametys.ribbon.control.RibbonControl
+ */
+org.ametys.odf.clipboard.button.SubProgramOrContainerActionButton = function(id, configuration, pluginName)
+{
+	org.ametys.odf.clipboard.button.SubProgramOrContainerActionButton.superclass.constructor.call(this, id, configuration, pluginName);
+}
+
+Ext.extend(org.ametys.odf.clipboard.button.SubProgramOrContainerActionButton, org.ametys.ribbon.control.button.ActionSelectionButton, {});
+
+org.ametys.odf.clipboard.button.SubProgramOrContainerActionButton.prototype._testTarget = function(target)
+{
+	return target.getType() == 'content' && (target.getParameters().type == 'org.ametys.plugins.odf.Content.subProgram' 
+		|| target.getParameters().type == 'org.ametys.plugins.odf.Content.container' 
+	);
+}
+
Index: main/plugin-odf/sitemap.xmap
===================================================================
--- main/plugin-odf/sitemap.xmap	(revision 31623)
+++ main/plugin-odf/sitemap.xmap	(working copy)
@@ -101,6 +101,8 @@
             <map:action name="rename-catalog" src="org.ametys.odf.catalog.actions.CatalogRenameAction" />
             <map:action name="delete-catalog" src="org.ametys.odf.catalog.actions.CatalogDeleteAction" />
             
+            <map:action name="paste-subprogram-or-container" src="org.ametys.odf.clipboard.PasteSubProgramOrContainerAction" />
+            
             <map:action name="set-filename" src="org.ametys.odf.program.actions.SetFileNameAction"/>
             <map:action name="resource-exists" src="org.apache.cocoon.acting.ResourceExistsAction"/>
 		</map:actions>
@@ -1002,6 +1004,18 @@
             </map:match>
             
             <!-- +
+                 | CLIPBOARD
+                 +-->
+            <map:match pattern="clipboard/paste">
+                <map:act type="paste-subprogram-or-container">
+                    <map:generate type="action-result" >
+                        <map:parameter name="contentId" value="{contentId}"/>
+                    </map:generate>
+                    <map:serialize type="xml" />
+                </map:act>
+            </map:match>
+            
+            <!-- +
                  | PDF export 
                  +-->
             <map:match pattern="*/generate-pdf">
Index: main/plugin-odf/src/org/ametys/odf/clipboard/PasteSubProgramOrContainerAction.java
===================================================================
--- main/plugin-odf/src/org/ametys/odf/clipboard/PasteSubProgramOrContainerAction.java	(revision 0)
+++ main/plugin-odf/src/org/ametys/odf/clipboard/PasteSubProgramOrContainerAction.java	(revision 0)
@@ -0,0 +1,162 @@
+package org.ametys.odf.clipboard;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.cocoon.acting.ServiceableAction;
+import org.apache.cocoon.environment.ObjectModelHelper;
+import org.apache.cocoon.environment.Redirector;
+import org.apache.cocoon.environment.SourceResolver;
+
+import org.ametys.cms.repository.Content;
+import org.ametys.cms.repository.ModifiableWorkflowAwareContent;
+import org.ametys.cms.workflow.AbstractContentWorkflowComponent;
+import org.ametys.cms.workflow.CreateContentFunction;
+import org.ametys.odf.courselist.CourseList;
+import org.ametys.odf.courselist.CourseListFactory;
+import org.ametys.odf.program.Container;
+import org.ametys.odf.program.ContainerFactory;
+import org.ametys.odf.program.SubProgram;
+import org.ametys.odf.program.SubProgramFactory;
+import org.ametys.odf.workflow.CreateODFContentFunction;
+import org.ametys.plugins.repository.AmetysObject;
+import org.ametys.plugins.repository.AmetysObjectResolver;
+import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
+import org.ametys.plugins.repository.TraversableAmetysObject;
+import org.ametys.plugins.workflow.Workflow;
+
+import com.opensymphony.workflow.WorkflowException;
+import com.opensymphony.workflow.spi.Step;
+
+/**
+ * Action that effectively pastes a subprogram/container under a program/subprogram/container
+ */
+public class PasteSubProgramOrContainerAction extends ServiceableAction
+{
+    private static final String __PROGRAM_WORKFLOW_NAME = "program";
+
+    /** Component used to resolve Ametys objects */
+    private AmetysObjectResolver _ametysObjectResolver;
+    
+    /** The workflow */
+    private Workflow _workflow;
+    
+    @Override
+    public void service(ServiceManager serviceManager) throws ServiceException
+    {
+        super.service(serviceManager);
+        _ametysObjectResolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
+        _workflow = (Workflow) serviceManager.lookup(Workflow.ROLE);
+    }
+
+    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
+    {
+        @SuppressWarnings("unchecked")
+        Map<String, Object> jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT);
+        Map<String, String> result = new HashMap<String, String> ();
+        
+        String subprogramOrContainerToCopy = (String) jsParameters.get("subprogramOrContainerToCopy");
+        String parentId = (String) jsParameters.get("parentId");
+        
+        ModifiableTraversableAmetysObject ametysObjectToCopy = _ametysObjectResolver.resolveById(subprogramOrContainerToCopy);
+        
+        if (ametysObjectToCopy instanceof SubProgram)
+        {
+            SubProgram newSubProgram = (SubProgram) _createOdfContent(ametysObjectToCopy.getName(), ((SubProgram) ametysObjectToCopy).getTitle(), ((SubProgram) ametysObjectToCopy).getLanguage(), SubProgramFactory.SUBPROGRAM_CONTENT_TYPE, parentId, __PROGRAM_WORKFLOW_NAME, 110);
+            _copyContentChildren(ametysObjectToCopy, newSubProgram);
+            newSubProgram.saveChanges();
+
+            result.put("contentId", newSubProgram.getId());
+        }
+        else if (ametysObjectToCopy instanceof Container) 
+        {
+            Container newContainer = (Container) _createOdfContent(ametysObjectToCopy.getName(), ((Container) ametysObjectToCopy).getTitle(), ((Container) ametysObjectToCopy).getLanguage(), ContainerFactory.CONTAINER_CONTENT_TYPE, parentId, __PROGRAM_WORKFLOW_NAME, 120);
+            _copyContentChildren(ametysObjectToCopy, newContainer);
+            newContainer.saveChanges();
+            
+            result.put("contentId", newContainer.getId());
+        }
+        else
+        {
+            throw new IllegalArgumentException("The selected content to paste is neither a Subprogram nor a Container. The pasting process is aborted");
+        }
+        
+        return result;
+    }
+
+    private Content _createOdfContent(String name, String title, String language, String contentTypeId, String parentId, String workflowName, int actionId) throws WorkflowException
+    {
+        Map<String, Object> params = new HashMap<String, Object>();
+        
+        // Workflow result  
+        Map<String, Object> workflowResult = new HashMap<String, Object>();
+        params.put(AbstractContentWorkflowComponent.RESULT_MAP_KEY, workflowResult);
+        
+        // Workflow parameters
+        params.put("workflowName", workflowName);
+        params.put(CreateContentFunction.CONTENT_NAME_KEY, name.substring(name.indexOf('-')) + 1);
+        params.put(CreateContentFunction.CONTENT_TITLE_KEY, title);
+        params.put(CreateContentFunction.CONTENT_TYPE_KEY, contentTypeId);
+        params.put(CreateContentFunction.CONTENT_LANGUAGE_KEY, language);
+        params.put(CreateODFContentFunction.CONTENT_PARENT_ID, parentId);
+        
+        long workflowId = _workflow.initialize(workflowName, actionId, params);
+        String contentId = (String) workflowResult.get("contentId");
+        ModifiableWorkflowAwareContent content = _ametysObjectResolver.resolveById(contentId);
+        
+        Step currentStep = (Step) _workflow.getCurrentSteps(workflowId).iterator().next();
+        content.setCurrentStepId(currentStep.getStepId());
+        
+        content.setTitle(title);
+        content.setCreationDate(new Date());
+        
+        return content;
+    }
+    
+    /**
+     * Copy the children of a content into another traversable content
+     * @param srcContent the content to copy
+     * @param targetContent the target content
+     */
+    private void _copyContentChildren(TraversableAmetysObject srcContent, ModifiableTraversableAmetysObject targetContent)
+    {
+        for (AmetysObject child : srcContent.getChildren())
+        {
+            if (child instanceof SubProgram)
+            {
+                SubProgram subProgram = (SubProgram) child;
+
+                // prevent infinite loop
+                if (!child.equals(targetContent))
+                {
+                    SubProgram newSubProgram = (SubProgram) subProgram.copyTo(targetContent, null);
+                    _copyContentChildren(subProgram, newSubProgram);
+                }
+            }
+            else if (child instanceof Container)
+            {
+                Container container = (Container) child;
+
+                // prevent infinite loop
+                if (!child.equals(targetContent))
+                {
+                    Container newContainer = (Container) container.copyTo(targetContent, null);
+                    _copyContentChildren(container, newContainer);
+                }
+            }
+            else if (child instanceof CourseList)
+            {
+                CourseList courseList = (CourseList) child;
+                
+                CourseList newCourseList = (CourseList) targetContent.createChild(courseList.getName(), CourseListFactory.COURSE_LIST_NODETYPE);
+                courseList.getMetadataHolder().copyTo(newCourseList.getMetadataHolder());
+                
+                newCourseList.saveChanges();
+            }
+        }
+    }
+}
