Index: main/plugin-cms/i18n/messages_en.xml
===================================================================
--- main/plugin-cms/i18n/messages_en.xml	(revision 32835)
+++ main/plugin-cms/i18n/messages_en.xml	(working copy)
@@ -2068,6 +2068,9 @@
     <message key="PLUGINS_CMS_HANDLE_TAGS_MOVE_ERROR">An error occurred: the tag or tags have not been moved.</message>
     <message key="PLUGINS_CMS_UITOOL_TAGS_SELECTION_MULTI_FORBIDDEN">Several tags are currently selected.&lt;br/&gt;Select one tag to interact with.</message>
     
+    <message key="PLUGINS_CMS_UITOOL_TAG_LABEL">Set a tag</message>
+    <message key="PLUGINS_CMS_UITOOL_TAG_DESCRIPTION">Set a tag on selected page or content.</message>
+
     <!-- +
          | TAGS WIDGET
          + -->
@@ -2341,5 +2344,36 @@
          | WIDGETS
          + -->
 	<message key="WIDGET_COMBOBOX_ALL_OPTIONS">- All -</message>
+
+    <!-- +
+         | IMPORT
+         + -->
+    <message key="PLUGINS_WEB_UITOOL_TAGS_IMPORT_XML_LABEL">Import a file</message>
+    <message key="PLUGINS_WEB_UITOOL_TAGS_IMPORT_XML_DESC">Import tags from a CSV file.&lt;br/&gt;Tags are imported under the selected tag.</message>
+    <message key="PLUGINS_WEB_UITOOL_TAGS_IMPORT_XML_DIALOG_TITLE">Import tags from a XML file</message>
+    <message key="PLUGINS_WEB_UITOOL_TAGS_IMPORT_XML_DIALOG_HINT">Please select a XML file from your local storage containing the tags to import.&lt;br/&gt;The tags will imported under the currently selected tag in the tree.</message>
+    <message key="PLUGINS_WEB_UITOOL_TAGS_IMPORT_XML_DIALOG_FILE_LABEL">File</message>
+    <message key="PLUGINS_WEB_UITOOL_TAGS_IMPORT_XML_DIALOG_FILE_DESC">XML file containing the tags to import</message>
+    <message key="PLUGINS_WEB_UITOOL_TAGS_IMPORT_XML_DIALOG_FILE_EMPTY_TEXT">Please select a XML file</message>
+    <message key="PLUGINS_WEB_UITOOL_TAGS_IMPORT_XML_DIALOG_FILE_BUTTON">Browse</message>
+    <message key="PLUGINS_WEB_UITOOL_TAGS_IMPORT_XML_DIALOG_BUTTON_OK">OK</message>
+    <message key="PLUGINS_WEB_UITOOL_TAGS_IMPORT_XML_DIALOG_BUTTON_CANCEL">Cancel</message>
+    <message key="PLUGINS_WEB_UITOOL_TAGS_IMPORT_XML_DIALOG_WAIT_TITLE">Import tags</message>
+    <message key="PLUGINS_WEB_UITOOL_TAGS_IMPORT_XML_DIALOG_WAIT_MSG">Importing the tags, please wait...</message>
+    <message key="PLUGINS_TAGS_IMPORT_XML_SUCCESS_TITLE">Import finished</message>
+    <message key="PLUGINS_TAGS_IMPORT_XML_SUCCESS_START">The import has ended:&lt;br/&gt;</message>
+    <message key="PLUGINS_TAGS_IMPORT_XML_CREATE_TAG_SUCCESS">1 tag was successfully imported.&lt;br/&gt;</message>
+    <message key="PLUGINS_TAGS_IMPORT_XML_CREATE_TAGS_SUCCESS"> tags were successfully imported.&lt;br/&gt;</message>
+    <message key="PLUGINS_TAGS_IMPORT_XML_UPDATE_TAG_SUCCESS">1 tag was updated.&lt;br/&gt;</message>
+    <message key="PLUGINS_TAGS_IMPORT_XML_UPDATE_TAGS_SUCCESS"> tags were updated.&lt;br/&gt;</message>
+    <message key="PLUGINS_TAGS_IMPORT_XML_TAG_ERROR">1 tag was not imported.</message>
+    <message key="PLUGINS_TAGS_IMPORT_XML_TAGS_ERROR"> tags were not imported.</message>
+    <message key="PLUGINS_TAGS_IMPORT_XML_ERROR_TITLE">Error importing the tags</message>
+    <message key="PLUGINS_TAGS_IMPORT_XML_ERROR_INVALID_FILE">You must select a file with .xml extension.</message>
+    <message key="PLUGINS_TAGS_IMPORT_XML_ERROR_REJECTED_FILE">The file is too large. The tags were not imported.</message>
+    <message key="PLUGINS_TAGS_IMPORT_XML_ERROR">An unexpected error has occurred : The tags were not imported.</message>
+    <message key="PLUGINS_TAGS_IMPORT_GET_TAGS_ERROR">An error occurred: the list of tags was not retrieved.</message>
+    <message key="PLUGINS_TAGS_IMPORT_XML_ERROR_ID_DOESNOT_MATCH">Id does not match.</message>
+    
 	    
 </catalogue>
Index: main/plugin-cms/i18n/messages_fr.xml
===================================================================
--- main/plugin-cms/i18n/messages_fr.xml	(revision 32835)
+++ main/plugin-cms/i18n/messages_fr.xml	(working copy)
@@ -2066,6 +2066,9 @@
     <message key="PLUGINS_CMS_HANDLE_TAGS_MOVE_ERROR">Une erreur est survenue : le ou les étiquette(s) n'ont pas été déplacées.</message>
     <message key="PLUGINS_CMS_UITOOL_TAGS_SELECTION_MULTI_FORBIDDEN">Plusieurs étiquettes sont actuellement sélectionnées.&lt;br/&gt;Sélectionnez une étiquette pour interagir avec.</message>
     
+    <message key="PLUGINS_CMS_UITOOL_TAG_LABEL">Etiqueter</message>
+    <message key="PLUGINS_CMS_UITOOL_TAG_DESCRIPTION">Ce bouton permet d'étiqueter la page et/ou le contenu sélectionné.</message>
+
     <!-- +
          | TAGS WIDGET
          + -->
@@ -2338,4 +2341,35 @@
          | WIDGETS
          + -->
 	<message key="WIDGET_COMBOBOX_ALL_OPTIONS">- Tous -</message>
+
+    <!-- +
+         | IMPORT
+         + -->
+    <message key="PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_LABEL">Importer des étiquettes</message>
+    <message key="PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DESC">Importer des étiquettes depuis un fichier xml.&lt;br&gt;Les étiquettes sont importées au niveau de l'étiquette sélectionnée.</message>
+    <message key="PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DIALOG_TITLE">Importer des étiquettes depuis un fichier xml</message>
+    <message key="PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DIALOG_HINT">Veuillez sélectionner un fichier .xml de votre disque dur contenant les étiquettes à importer.&lt;br&gt;Les étiquettes seront importées au niveau de l'étiquette actuellement sélectionnée dans l'arbre.</message>
+    <message key="PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DIALOG_FILE_LABEL">Fichier</message>
+    <message key="PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DIALOG_FILE_DESC">Fichier XML contenant les étiquettes à importer</message>
+    <message key="PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DIALOG_FILE_EMPTY_TEXT">Veuillez sélectionner un fichier XML</message>
+    <message key="PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DIALOG_FILE_BUTTON">Parcourir</message>
+    <message key="PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DIALOG_BUTTON_OK">OK</message>
+    <message key="PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DIALOG_BUTTON_CANCEL">Annuler</message>
+    <message key="PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DIALOG_WAIT_TITLE">Import des étiquettes</message>
+    <message key="PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DIALOG_WAIT_MSG">Import des étiquettes en cours, veuillez patienter...</message>
+    <message key="PLUGINS_CMS_TAGS_IMPORT_XML_SUCCESS_TITLE">Import terminé</message>
+    <message key="PLUGINS_CMS_TAGS_IMPORT_XML_SUCCESS_START">L'import est terminé :&lt;br/&gt;</message>
+    <message key="PLUGINS_CMS_TAGS_IMPORT_XML_CREATE_TAG_SUCCESS">1 étiquette a été importée avec succès.&lt;br/&gt;</message>
+    <message key="PLUGINS_CMS_TAGS_IMPORT_XML_CREATE_TAGS_SUCCESS"> étiquettes ont été importées avec succès.&lt;br/&gt;</message>
+    <message key="PLUGINS_CMS_TAGS_IMPORT_XML_UPDATE_TAG_SUCCESS">1 étiquette a été mise à jour.&lt;br/&gt;</message>
+    <message key="PLUGINS_CMS_TAGS_IMPORT_XML_UPDATE_TAGS_SUCCESS"> étiquettes ont été mise à jour.&lt;br/&gt;</message>
+    <message key="PLUGINS_CMS_TAGS_IMPORT_XML_TAG_ERROR">1 étiquette n'a pu être importée.</message>
+    <message key="PLUGINS_CMS_TAGS_IMPORT_XML_TAGS_ERROR"> étiquettes n'ont pu être importées.</message>
+    <message key="PLUGINS_CMS_TAGS_IMPORT_XML_ERROR_TITLE">Importer des étiquettes</message>
+    <message key="PLUGINS_CMS_TAGS_IMPORT_XML_ERROR_INVALID_FILE">Vous devez sélectionner un fichier XML</message>
+    <message key="PLUGINS_CMS_TAGS_IMPORT_XML_ERROR_REJECTED_FILE">Le fichier est trop volumineux. Les étiquettes n'ont pas pu être importées.</message>
+    <message key="PLUGINS_CMS_TAGS_IMPORT_XML_ERROR">Une erreur inattendue est survenue : les étiquettes n'ont pas pu être importées.</message>
+    <message key="PLUGINS_CMS_TAGS_IMPORT_GET_TAGS_ERROR">Une erreur est survenue: la liste des étiquettes n'a pu être récupéré.</message>
+    <message key="PLUGINS_CMS_TAGS_IMPORT_XML_ERROR_ID_DOESNOT_MATCH">L'id ne correspond pas.</message>
+    
 </catalogue>
Index: main/plugin-cms/pages/tags/import-result-json.xsl
===================================================================
--- main/plugin-cms/pages/tags/import-result-json.xsl	(revision 0)
+++ main/plugin-cms/pages/tags/import-result-json.xsl	(revision 0)
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Copyright 2014 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.
+   -->
+<xsl:stylesheet version="1.0" 
+                xmlns:i18n="http://apache.org/cocoon/i18n/2.1"
+                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    
+    <xsl:template match="/ActionResult">
+        <json>
+        <xsl:choose>
+            <xsl:when test="success = 'true'">
+{
+    success: true,
+    createdCount: <xsl:value-of select="createdCount"/>,
+    updatedCount: <xsl:value-of select="updatedCount"/>,
+    errorCount: <xsl:value-of select="errorCount"/>,
+    rootId: "<xsl:value-of select="rootId"/>"
+}
+            </xsl:when>
+            <xsl:otherwise>
+{
+    success: false,
+    error: "<xsl:value-of select="error"/>"
+}
+            </xsl:otherwise>
+        </xsl:choose>
+        </json>
+    </xsl:template>
+    
+</xsl:stylesheet>
Index: main/plugin-cms/plugin.xml
===================================================================
--- main/plugin-cms/plugin.xml	(revision 32835)
+++ main/plugin-cms/plugin.xml	(working copy)
@@ -7419,6 +7419,27 @@
                     <file>js/Ametys/plugins/cms/tag/TagActions.js</file>
                 </scripts>
             </extension>
+
+            <extension id="org.ametys.cms.tag.ImportXml"
+                       point="org.ametys.core.ui.RibbonControlsManager" 
+                       class="org.ametys.core.ui.StaticClientSideElement">
+                <class name="Ametys.ribbon.element.ui.ButtonController">
+                    <action>Ametys.plugins.cms.tag.TagActions.importXml</action>
+                    <selection-target-type>^tag(-root)?$</selection-target-type>
+                    
+                    <label type="i18n">PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_LABEL</label>
+                    <default-description type="i18n">PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DESC</default-description>
+
+                    <icon-small type="file">img/tag/import_xml_16.png</icon-small>
+                    <icon-medium type="file">img/tag/import_xml_32.png</icon-medium>
+                    <icon-large type="file">img/tag/import_xml_48.png</icon-large>
+                </class>
+                <scripts>
+                    <file>js/Ametys/plugins/cms/tag/TagActions.js</file>
+                    <file>js/Ametys/plugins/cms/tag/TagsImportDialog.js</file>
+                </scripts>
+                <right>CMS_Rights_Tags_HandleTag</right>
+            </extension>
     	</extensions>
     </feature>
     
Index: main/plugin-cms/resources/js/Ametys/cms/uihelper/ChooseTag.js
===================================================================
--- main/plugin-cms/resources/js/Ametys/cms/uihelper/ChooseTag.js	(revision 32835)
+++ main/plugin-cms/resources/js/Ametys/cms/uihelper/ChooseTag.js	(working copy)
@@ -55,10 +55,19 @@
     /**
      * @property {Boolean} allowCreation=false If set to 'true', a link at the dialog bottom proposes to add a new tag (defaults to false).
      */
+    
+     /**
+      * @property {Boolean} targetType=null The type of tags to display. If set to a value, display the value tags. If set to null, display all tags.
+      */
+     /**
+      * @property {Boolean} filterTarget=null Filter on target type. If set to a value, disable the value tags. If set to null, enable all tags.
+      */
 
     /**
      * Configure and open the dialog box
      * @param {Object} config The configuration options :
+     * @param {String} config.plugin The plugin to get the tags informations.
+     * @param {String} config.url The url to get the tags informations.
      * @param {String} config.icon The full path to icon (16x16 pixels) for the dialog box.
      * @param {String} config.title The title of the dialog box.
      * @param {String} config.helpMessage The help message to display on top of dialog box.
@@ -68,6 +77,8 @@
      * @param {Boolean} config.allowProviders=false `true` to allow selecting providers.
      * @param {Boolean} config.allowCreation=false `true` to allow the creation of new tags from the dialog box.
      * @param {Boolean} config.onlyTagsWithChildren=false `true` to prevent selection of tags without child.
+     * @param {String} config.targetType=null The type of tags to display. Set to target value to display only target tags. Set to null to display all tags
+     * @param {String[]} config.filterTarget=null Filter on target type. Set to target value to disable target tags. Set to null to enable all tags.
      * @param {Function} config.callback The callback function invoked when terms are selected. The callback function will received the following parameters:
      */
     open: function (config)
@@ -81,13 +92,18 @@
         this.allowProviders = config.allowProviders || false;
         this.allowCreation = config.allowCreation || false;
         this.onlyTagsWithChildren = config.onlyTagsWithChildren || false;
+        this.targetType = config.targetType;
+        this.filterTarget = config.filterTarget;
         
         this.values = config.values || [];
         this._clickEventRegistered = false;
         
         this.helpMessage = config.helpMessage;
         this._createDialogBox(config.icon || Ametys.getPluginResourcesPrefix('cms') + (this.multiple ? '/img/widgets/tag/tags_16.png' : '/img/widgets/tag/tag_16.png'),
-                              config.title || "<i18n:text i18n:key='PLUGINS_CMS_HELPER_CHOOSETAG_TITLE'/>");
+                              config.title || "<i18n:text i18n:key='PLUGINS_CMS_HELPER_CHOOSETAG_TITLE'/>",
+                              config.plugin || "cms",
+                              config.url || "tags.json",
+                              config.messageTargets);
         this._box.show();
     },
     
@@ -97,10 +113,20 @@
      * @param {String} title The title of the dialog box.
      * @private
      */
-    _createDialogBox: function (icon, title)
+    _createDialogBox: function (icon, title, plugin, url, messageTargets)
     {
+        var messageTargetIds = [];
+        for (var i = 0; i < messageTargets.length; i++) 
+        {
+            var target = messageTargets[i];
+            if (target != null)
+            {
+                messageTargetIds.push(target.getParameters().id);
+            }
+        }
+
+
         var me = this;
-        var contentTarget = Ametys.message.MessageBus.getCurrentSelectionMessage().getTarget(Ametys.message.MessageTarget.CONTENT);
         this._tree = Ext.create('Ametys.plugins.cms.tag.TagsTreePanel', {
             width: 320,
             height: 450,
@@ -109,8 +135,12 @@
             allowProviders: this.allowProviders,
             allowCreation: this.allowCreation,
             onlyTagsWithChildren: this.onlyTagsWithChildren,
+            targetType: this.targetType,
+            filterTarget: this.filterTarget,
             values: this.values,
-            contentTarget: contentTarget,
+            targetsIds: messageTargetIds,
+            plugin: plugin,
+            url: url,
             
             checkMode: this.multiple,
             
@@ -186,11 +216,14 @@
      * Initialize the "ok" button of the dialog box on the first load.
      * @private
      */
-    _onLoad: function(loader, node, response)
+    _onLoad: function(loader, records)
     {
-        if (node.isRoot())
+        for (var i = 0; i < records.length; i++) 
         {
-            this._box.getDockedItems('toolbar[dock="bottom"]')[0].items.get(0).setDisabled(true);
+            if (records[i].isRoot())
+            {
+                this._box.getDockedItems('toolbar[dock="bottom"]')[0].items.get(0).setDisabled(true);
+            }
         }
     },
     
Index: main/plugin-cms/resources/js/Ametys/plugins/cms/tag/TagActions.js
===================================================================
--- main/plugin-cms/resources/js/Ametys/plugins/cms/tag/TagActions.js	(revision 32835)
+++ main/plugin-cms/resources/js/Ametys/plugins/cms/tag/TagActions.js	(working copy)
@@ -58,7 +58,28 @@
     		this.act('edit', targets[0].getParameters().id);
         }  
     },
-    
+
+    /**
+     * Function called to import a tag
+     * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling the function
+     */
+    importXml: function (controller)
+    {
+        var targets = controller.getMatchingTargets();
+        if (targets.length > 0)
+        {
+            var dialog = Ext.create('Ametys.plugins.cms.TagsImportDialog', 
+                {
+                    callback: Ext.bind(this._doImport, this),
+                    tagId: targets[0].getParameters().id,
+                    forceProvider: controller.forceProvider || null
+                }
+            );
+
+        dialog.show();
+        }
+    },
+
     /**
      *  Function called to move one or more tags.
      *  @param {String} target The target id for the move action.
@@ -589,7 +610,7 @@
                 Ametys.data.ServerComm.callMethod({
         			role: "org.ametys.cms.tag.jcr.JCRTagsDAO",
         			methodName: "deleteTag",
-        			parameters: [tagId],
+        			parameters: [tagId, Ametys.getAppParameters()],
         			callback: {
         				scope: this,
         				handler: this._deleteTagCb
@@ -627,6 +648,122 @@
                  }
              }
          });
-    }
+    },
+
+    /**
+     * Import the list of tags if the user pressed the "Ok" button
+     * @param {Ext.form.Basic} form The import form
+     * @private
+     */
+    _doImport: function(form)
+    {
+        var contextualParameters = Ametys.getAppParameters();
+        form.submit({
+            url: Ametys.getPluginDirectPrefix("cms") + '/tags/import',
+            params: { contextualParameters: Ext.JSON.encode(contextualParameters) },
+            
+            waitTitle: "<i18n:text i18n:key='PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DIALOG_WAIT_TITLE'/>",
+            waitMsg: "<i18n:text i18n:key='PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DIALOG_WAIT_MSG'/>",
+            
+            success: this._importSuccess,
+            failure: this._importFailure,
+            
+            errorReader: new Ext.data.XmlReader({
+                record: 'field',
+                success: '@success'
+            })
+        });
+    },
+
+    /**
+     * Callback invoked after successful import of a list of tags
+     * @param {Object} form The form submitted
+     * @param {Object} action The action executed
+     * @private
+     */
+     _importSuccess: function(form, action)
+     {
+        var message = "<i18n:text i18n:key='PLUGINS_CMS_TAGS_IMPORT_XML_SUCCESS_START'/>";
+        
+        if (action.result.createdCount == 1)
+        {
+            message += "<i18n:text i18n:key='PLUGINS_CMS_TAGS_IMPORT_XML_CREATE_TAG_SUCCESS'/>";
+        }
+        else if (action.result.createdCount > 1)
+        {
+            message += action.result.createdCount + "<i18n:text i18n:key='PLUGINS_CMS_TAGS_IMPORT_XML_CREATE_TAGS_SUCCESS'/>";
+        }
+        
+        if (action.result.updatedCount == 1)
+        {
+            message += "<i18n:text i18n:key='PLUGINS_CMS_TAGS_IMPORT_XML_UPDATE_TAG_SUCCESS'/>";
+        }
+        else if (action.result.updatedCount > 1)
+        {
+            message += action.result.updatedCount + "<i18n:text i18n:key='PLUGINS_CMS_TAGS_IMPORT_XML_UPDATE_TAGS_SUCCESS'/>";
+        }
+        
+        if (action.result.errorCount == 1)
+        {
+            message += "<i18n:text i18n:key='PLUGINS_CMS_TAGS_IMPORT_XML_TAG_ERROR'/>";
+        }
+        else if (action.result.errorCount > 1)
+        {
+            message += action.result.errorCount + "<i18n:text i18n:key='PLUGINS_CMS_TAGS_IMPORT_XML_TAGS_ERROR'/>";
+        }
+        
+        Ext.Msg.show({
+            title: "<i18n:text i18n:key='PLUGINS_CMS_TAGS_IMPORT_XML_SUCCESS_TITLE'/>",
+            msg: message,
+            buttons: Ext.Msg.OK,
+            icon: Ext.MessageBox.INFO
+        });
 
+        Ext.create("Ametys.message.Message", {
+            type: Ametys.message.Message.MODIFIED,
+            
+            targets: {
+                type: Ametys.message.MessageTarget.TAG_ROOT,
+                parameters: {
+                    'id': action.result.rootId
+                }
+            }
+        });
+     },
+
+     /**
+     * Callback invoked after failing to import a list of tags
+     * @param {Object} form The form submitted
+     * @param {Object} action The action executed
+     * @private
+     */
+     _importFailure: function(form, action)
+     {
+        var message = '';
+        var error = action.result.error;
+        
+        if (error == 'invalid-extension')
+        {
+            message = "<i18n:text i18n:key='PLUGINS_CMS_TAGS_IMPORT_XML_ERROR_INVALID_FILE'/>";
+        }
+        else if (error == 'rejected-file')
+        {
+            message = "<i18n:text i18n:key='PLUGINS_CMS_TAGS_IMPORT_XML_ERROR_REJECTED_FILE'/>";
+        }
+        else if (error == 'id-not-match')
+        {
+            message = "<i18n:text i18n:key='PLUGINS_CMS_TAGS_IMPORT_XML_ERROR_ID_DOESNOT_MATCH'/>";
+        }
+        else
+        {
+            message = "<i18n:text i18n:key='PLUGINS_CMS_TAGS_IMPORT_XML_ERROR'/>";
+        }
+        
+        Ext.Msg.show({
+            title: "<i18n:text i18n:key='PLUGINS_CMS_TAGS_IMPORT_XML_ERROR_TITLE'/>",
+            msg: message,
+            buttons: Ext.Msg.OK,
+            icon: Ext.MessageBox.ERROR
+        });
+     }
 });
\ No newline at end of file
Index: main/plugin-cms/resources/js/Ametys/plugins/cms/tag/TagNode.js
===================================================================
--- main/plugin-cms/resources/js/Ametys/plugins/cms/tag/TagNode.js	(revision 32835)
+++ main/plugin-cms/resources/js/Ametys/plugins/cms/tag/TagNode.js	(working copy)
@@ -26,6 +26,7 @@
                'visibility',
                'title',
                'description', 
+               'target',
                {
                    name : 'text',
                    mapping : 'title'
@@ -42,13 +43,9 @@
                    }
                },
                {
-                   name: 'disabled',
-                   type: 'boolean',
-                   mapping: 'authorized',
-                   convert: function (value, node)
-                   {
-                       return value === false;
-                   }
+                  name: 'disabled',
+                  type: 'boolean',
+                  defaultValue: false
                },
                {
                    name: 'cls',
Index: main/plugin-cms/resources/js/Ametys/plugins/cms/tag/TagsImportDialog.js
===================================================================
--- main/plugin-cms/resources/js/Ametys/plugins/cms/tag/TagsImportDialog.js	(revision 0)
+++ main/plugin-cms/resources/js/Ametys/plugins/cms/tag/TagsImportDialog.js	(revision 0)
@@ -0,0 +1,140 @@
+/*
+ *  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.
+ */
+
+/**
+ * Dialog to import a list of tags from an xml file
+ */
+Ext.define("Ametys.plugins.cms.TagsImportDialog",
+    {
+        extend: "Ametys.window.DialogBox",
+
+		/**
+         * Construstor
+         * @param {Object} config The configuration object
+         * @param {Function} config.callback The callback function
+         * @param {String} config.tagId The id of the parent tag selected
+         * @param {String} config.forceProvider The provider used by the import. Set to null to use the default provider
+         */
+        constructor: function (config)
+        {
+            this._cbFn = config.callback;
+            this._formPanel = this._createFormPanel(config.tagId, config.forceProvider);
+            
+            config = Ext.applyIf({
+                layout: 'fit',
+                
+                title: "<i18n:text i18n:key='PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DIALOG_TITLE'/>",
+                icon: Ametys.getPluginResourcesPrefix("cms") + '/img/tag/import_xml_16.png',
+                
+                width: 520,
+                height: 180, 
+                autoScroll: false,
+                
+                items: [this._formPanel],
+                
+                closeAction: 'hide',
+                buttons: [{
+                    text: "<i18n:text i18n:key='PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DIALOG_BUTTON_OK'/>",
+                    handler: Ext.bind(this._validate, this)
+                }, {
+                    text: "<i18n:text i18n:key='PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DIALOG_BUTTON_CANCEL'/>",
+                    handler: Ext.bind(this._cancel, this)
+                }]        
+            }, config);
+            
+            this.callParent(arguments);
+        },
+
+       /**
+         * Callback for the "ok" button of the dialog.
+         */
+        _validate: function ()
+        {
+            var form = this._formPanel.getForm();
+            if (form.isValid())
+            {
+                if (Ext.isFunction (this._cbFn))
+                {
+                    this._cbFn(form);
+                }
+
+                this.close();
+            }
+        },
+        
+        /**
+         * Callback for the "cancel" button of the dialog. Close the dialog.
+         */
+        _cancel: function ()
+        {
+            this.close();
+        },
+
+        /**
+         * @private
+         * Create the form panel
+         * @return {Ext.form.FormPanel} the form panel
+         */
+        _createFormPanel: function(tagId, forceProvider)
+        {
+            var formPanel = new Ext.form.FormPanel( {
+                labelWidth: 80,
+                defaultType: 'textfield',
+                fileUpload: true,
+                
+                defaults: {
+                    cls: 'ametys',
+                    labelAlign: 'right',
+                    labelSeparator: '',
+                    labelWidth: 80,
+                    width: 470
+                },
+                
+                items: [
+                    {
+                        xtype: 'hidden',
+                        name: 'tagId',
+                        value: tagId
+                    },
+                    {
+                        xtype: 'hidden',
+                        name: 'forceProvider',
+                        value: forceProvider
+                    },
+                    {
+                        xtype: 'container',
+                        html: "<i18n:text i18n:key='PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DIALOG_HINT'/>",
+                        cls: 'dialog-text-hint'
+                    },
+                    {
+                        xtype: 'filefield',
+                        name: 'importFile',
+                        fieldLabel: "<i18n:text i18n:key='PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DIALOG_FILE_LABEL'/> *",
+                        ametysDescription: "<i18n:text i18n:key='PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DIALOG_FILE_DESC'/>",
+                        allowBlank: false,
+                        emptyText: "<i18n:text i18n:key='PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DIALOG_FILE_EMPTY_TEXT'/>",
+                        buttonText: "<i18n:text i18n:key='PLUGINS_CMS_UITOOL_TAGS_IMPORT_XML_DIALOG_FILE_BUTTON'/>"
+                    }
+                ],
+                
+                bodyStyle: 'padding:10px',
+                autoScroll: true
+            });
+            
+            return formPanel;
+        },
+	}
+);
\ No newline at end of file
Index: main/plugin-cms/resources/js/Ametys/plugins/cms/tag/TagsTreePanel.js
===================================================================
--- main/plugin-cms/resources/js/Ametys/plugins/cms/tag/TagsTreePanel.js	(revision 32835)
+++ main/plugin-cms/resources/js/Ametys/plugins/cms/tag/TagsTreePanel.js	(working copy)
@@ -26,9 +26,9 @@
     cls: 'tags-tree',
     
     /**
-     * @property {Ametys.message.MessageTarget} contentTarget The content message target. Can be null.
+     * @property {String[]} targetsIds The targets to tag.
      */
-    contentTarget: null,
+    targetsIds: [],
     
     /**
      * @cfg {Boolean} checkMode To select tags by checkboxes. Defaults to 'false'.
@@ -54,6 +54,28 @@
 	 * @cfg {Boolean} onlyCustomTags=false 'true' to get only tags from the JCR provider
 	 */
 	onlyCustomTags: false,
+    /**
+     * @cfg {String} targetType=null The type of tag to display. Set to target type to display only matching tags. Set to null to display all tags
+     */
+    targetType: null,    
+    /**
+     * @cfg {String/String[]} filterTarget Filters on target type. 
+     *  Set to 'TARGET_TYPE' to disable tags with the target type. 
+     *  Set to 'TARGET_TYPE_PRIVATE' to disable private tags with the target type.
+     *  Set to null or empty array to enable all tags.
+     */
+    filterTarget: null,
+
+    /**
+     * @cfg {String} The plugin for the store.
+     */
+    plugin: null,
+
+    /**
+     * @cfg {String} The url for the store.
+     */
+    url: null,
+
 	/**
 	 * @cfg {String} content=null The current content id to be tagged, used to check user rights on private tags. Can be null.
 	 */
@@ -82,6 +104,7 @@
         this.on('load', this._onLoad, this);
         this.on('checkchange', this._onCheckChange, this);
         this.on('itemexpand', this._onItemExpand, this);
+        this.on('viewready', this._onViewReady, this);
         
         this.callParent();
     },
@@ -157,8 +180,8 @@
             model : 'Ametys.plugins.cms.tag.TagNode',
             proxy: {
                 type: 'ametys',
-                plugin: 'cms',
-                url: 'tags.json',
+                plugin: this.plugin,
+                url: this.url,
                 reader: {
                     type: 'json'
                 }
@@ -173,6 +196,28 @@
     
     /**
      * @private
+     * Listener for the view, set an interceptor on check for disabled node
+     */
+    _onViewReady: function()
+    {
+        var view = this.getView();
+        
+        // onCheckChange interceptor. When this function return false, the onCheckChange method of the view is not called, hence the value of the checkbox does not change.
+        var onCheckChangeInterceptor = function(node)
+        {
+            if (node.get('disabled'))
+            {
+                return false;
+            }
+        };
+        
+        Ext.apply(view, {
+            onCheckChange: Ext.Function.createInterceptor(view.onCheckChange, onCheckChangeInterceptor)
+        });
+    },
+    
+    /**
+     * @private
      * Set the request parameters before loading the store.
      * @param {Ext.data.Store} store The store.
      * @param {Ext.data.operation.Operation} operation The Ext.data.operation.Operation object that will be passed to the Proxy to load the Store.
@@ -184,7 +229,9 @@
         	nodeName: (operation.node !== undefined ? operation.node.get('name') : ""),
         	checkMode: this.checkMode,
         	onlyCustomTags: this.onlyCustomTags,
-        	contentTargetId: ((this.contentTarget && this.contentTarget.getParameters().getId() !== undefined) ? this.contentTarget.getParameters().getId() : null)
+        	objectTargetIds: this.targetsIds,
+            targetType: this.targetType,
+            contextualParameters: Ametys.getAppParameters()
         }));
     },
     
@@ -201,20 +248,21 @@
     {
     	this._setInfos(node);
     	
-    	if (this.checkMode === true)
-    	{
-    	    this._setCheckboxes(node);
-    	}
-    	else if (node.isRoot() && this.values.length > 0)
-    	{
-    	    var values = (Ext.isArray(this.values) ? this.values : [ this.values ]);
+        if (this.checkMode === true)
+        {
+            this._setCheckboxes(node);
+        }
+        
+        if (node.isRoot())
+        {
+            var values = (Ext.isArray(this.values) ? this.values : [ this.values ]);
             Ametys.data.ServerComm.callMethod({
                 role: "org.ametys.cms.tag.TagsDAO", 
                 methodName: 'getTagPaths', 
                 parameters: [ values, Ametys.getAppParameters()],
                 errorMessage: "<i18n:text i18n:key='PLUGINS_CMS_HANDLE_TAGS_TAG_ERROR'/>",
                 callback: {
-                    handler: this._getTagPathsCb,
+                    handler: this._loadValues,
                     scope: this
                 },
                 waitMessage: {
@@ -222,7 +270,53 @@
                     msg: "<i18n:text i18n:key='PLUGINS_CORE_UI_LOADMASK_DEFAULT_MESSAGE' i18n:catalogue='plugin.core-ui'/>"
                 }
             });
-    	}
+        }
+
+        // TO REMOVE
+    	// else if (node.isRoot() && this.values.length > 0)
+    	// {
+    	//     var values = (Ext.isArray(this.values) ? this.values : [ this.values ]);
+     //        Ametys.data.ServerComm.callMethod({
+     //            role: "org.ametys.cms.tag.TagsDAO", 
+     //            methodName: 'getTagPaths', 
+     //            parameters: [ values, Ametys.getAppParameters()],
+     //            errorMessage: "<i18n:text i18n:key='PLUGINS_CMS_HANDLE_TAGS_TAG_ERROR'/>",
+     //            callback: {
+     //                handler: this._getTagPathsCb,
+     //                scope: this
+     //            },
+     //            waitMessage: {
+     //                target: this,
+     //                msg: "<i18n:text i18n:key='PLUGINS_CORE_UI_LOADMASK_DEFAULT_MESSAGE' i18n:catalogue='plugin.core-ui'/>"
+     //            }
+     //        });
+    	// }
+    }, 
+
+    _loadValues: function(response)
+    {
+        var paths = response.paths;
+        
+        var me = this;
+        
+        Ext.Array.each (paths, function (path) {
+            // don't expand last node
+            path = path.substr(0, path.lastIndexOf("/"));
+
+            me.expandNodeByPath(path, function (sucessfull, node) {
+                if (sucessfull)
+                {
+                    if (me.multiple) 
+                    {
+                        node.set('checked', true);
+                    }
+                    else
+                    {
+                        me.getSelectionModel().select(node);
+                    }
+                }
+            });
+        });
     },
     
     /**
@@ -526,20 +620,18 @@
         for (var i=0; i < paths.length; i++)
         {
             var path = paths[i].substr(0, paths[i].lastIndexOf("/"));
-            this.expandPath (rootNode.getPath('name') + '/' + path, 'name', null, Ext.bind (this._filterPaths, this, [paths, rootNode, node], true));
+            this.expandPath (rootNode.getPath('name') + '/' + path, 'name', null, Ext.bind (this._filterPaths, this, [paths, rootNode, node], false));
         }   
     },
     
     /**
      * Filter nodes by path once the last expand has been processed
-     * @param {Boolean} success true if the expand was successful
-     * @param {Ext.data.Model} lastNode the last node that was expanded
      * @param {String[]} paths The path to filter by
      * @param {Ext.data.Model} rootNode The concerned root node
      * @param {Ext.data.Model} node The node from which apply filter
      * @private
      */
-    _filterPaths: function (success, lastNode, paths, rootNode, node)
+    _filterPaths: function (paths, rootNode, node)
     {
         // only execute the filterBy after the last expandPath()
         if (--this._counter[rootNode.getId()] == 0)
@@ -735,7 +827,41 @@
         {
             this.expandNode(node);
         }
+
+        if (this.filterTarget != null && this._isFilteredTarget(node))
+        {
+            node.set("disabled", true);
+        }
+
+        if (node.get("disabled") == true)
+        {
+            var cls = node.get('cls');
+            cls += (cls ? ' ' : '') + 'tree-cell-disabled';
+            node.set('cls', cls);
+        }
     },
+
+    /**
+     * @private
+     * Check if a node match the specified filters for the target type
+     * @param {Ext.data.Model} node The node to initialize
+     * @return {Boolean} returns true if the filter is matched
+     */
+    _isFilteredTarget: function(node)
+    {
+        for (var i = 0; i < this.filterTarget.length; i++) 
+        {
+            var target = node.get("target");
+            var filter = this.filterTarget[i];
+
+            if (target == filter || (node.get('visibility') == "PRIVATE" && target + "_PRIVATE" == filter))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    },
     
     /**
      * Iterate through the node childs, and call #_setCheckbox
Index: main/plugin-cms/resources/js/Ametys/plugins/cms/tag/tool/TagsTool.js
===================================================================
--- main/plugin-cms/resources/js/Ametys/plugins/cms/tag/tool/TagsTool.js	(revision 32835)
+++ main/plugin-cms/resources/js/Ametys/plugins/cms/tag/tool/TagsTool.js	(working copy)
@@ -96,7 +96,7 @@
             },
             rootVisible : true,
             selModel: {
-            	mode: "MULTI"
+            	mode: "SINGLE"
             },
 
             viewConfig: {
@@ -379,6 +379,17 @@
                 }
     		});
         }
+        var tagRootTarget = message.getTarget("tag-root");
+        if (tagRootTarget != null)
+        {
+            var tagRootNode = this._tree.getStore().getNodeById(tagRootTarget.getParameters().id);
+            if (tagRootNode != null)
+            {
+                this._tree.getStore().load({
+                    node: tagRootNode
+                });
+            }
+        }
     },
     
     /**
@@ -547,7 +558,7 @@
         {
             return {
                 type: Ametys.message.MessageTarget.TAG,
-                parameters: { ids: [record.getId()] }
+                parameters: { ids: [record.getId()] , names: [record.get("name")] }
             };
         }
         else
Index: main/plugin-cms/ribbon/cms-ribbon-tags.xml
===================================================================
--- main/plugin-cms/ribbon/cms-ribbon-tags.xml	(revision 32835)
+++ main/plugin-cms/ribbon/cms-ribbon-tags.xml	(working copy)
@@ -27,7 +27,7 @@
 							<control id="org.ametys.cms.tag.Edit"/>
 							<control id="org.ametys.cms.tag.Delete"/>
 						</layout>
-<!--                         <control id="org.ametys.cms.tag.ImportXml"/> -->
+                        <control id="org.ametys.cms.tag.ImportXml"/>
 					</large>
 					<medium>
 						<layout size="small">
@@ -35,7 +35,7 @@
 							<control id="org.ametys.cms.tag.Edit"/>
 							<control id="org.ametys.cms.tag.Delete"/>
 						</layout>
-<!--                         <control id="org.ametys.cms.tag.ImportXml"/> -->
+                        <control id="org.ametys.cms.tag.ImportXml"/>
 					</medium>
 					<small>
 						<control id="org.ametys.cms.tag.Add"/>
@@ -43,7 +43,7 @@
 							<control id="org.ametys.cms.tag.Edit"/>
 							<control id="org.ametys.cms.tag.Delete"/>
 						</layout>
-<!--                         <control id="org.ametys.cms.tag.ImportXml"/> -->
+                        <control id="org.ametys.cms.tag.ImportXml"/>
 					</small>
 				</group>
 			</groups>
Index: main/plugin-cms/sitemap.xmap
===================================================================
--- main/plugin-cms/sitemap.xmap	(revision 32835)
+++ main/plugin-cms/sitemap.xmap	(working copy)
@@ -99,7 +99,9 @@
 			<map:action name="add-update-param-file" src="org.ametys.cms.parameters.AddOrUpdateFileAction"/>
 			
 			<map:action name="set-header" src="org.apache.cocoon.acting.HttpHeaderAction" logger="workspace.action.setheader"/>		
+
+            <map:action name="import-tags" src="org.ametys.cms.tag.jcr.ImportTagsAction" />
         </map:actions>
         
         <map:readers>
@@ -778,6 +780,21 @@
                     <map:read type="json"/>
                 </map:act>
             </map:match>     
+
+            <map:match pattern="tags/import">
+                <map:act type="import-tags">
+                    <map:generate type="action-result">
+                        <map:parameter name="error" value="{error}"/>
+                        <map:parameter name="createdCount" value="{createdCount}"/>
+                        <map:parameter name="errorCount" value="{errorCount}"/>
+                        <map:parameter name="updatedCount" value="{updatedCount}"/>
+                        <map:parameter name="success" value="{success}"/>
+                        <map:parameter name="rootId" value="{rootId}"/>
+                    </map:generate>
+                     <map:transform src="pages/tags/import-result-json.xsl" type="xslt"/>
+                     <map:serialize type="text" mime-type="text/html"/> 
+                </map:act>
+            </map:match>
         </map:pipeline>
         
         <!-- +
Index: main/plugin-cms/src/org/ametys/cms/repository/ContentDAO.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/repository/ContentDAO.java	(revision 32835)
+++ main/plugin-cms/src/org/ametys/cms/repository/ContentDAO.java	(working copy)
@@ -64,10 +64,12 @@
      * Tag a list of contents
      * @param contentIds The ids of contents to tag
      * @param tagNames The tags
+     * @param contextualParameters The contextual parameters
+     * @param keepOldTags If false, erase the old tags before adding the news tags.
      * @return the result
      */
     @Callable
-    public Map<String, Object> tag (List<String> contentIds, List<String> tagNames, Map<String, Object> contextualParameters)
+    public Map<String, Object> tag (List<String> contentIds, List<String> tagNames, Map<String, Object> contextualParameters, Boolean keepOldTags)
     {
         Map<String, Object> result = new HashMap<>();
         
@@ -113,22 +115,28 @@
                 
                 // First delete old tags
                 Set<String> oldTags = mContent.getTags();
-                for (String tagName : oldTags)
+                if (!keepOldTags)
                 {
-                    mContent.untag(tagName);
+                    for (String tagName : oldTags)
+                    {
+                        mContent.untag(tagName);
+                    }
                 }
                 
                 // Then set new tags
                 for (String tagName : tagNames)
                 {
-                    if (_isTagValid(tagName, contextualParameters))
-                    {
-                        mContent.tag(tagName);
-                    }
-                    else
+                    if (!keepOldTags || !oldTags.contains(tagName))
                     {
-                        List<String> invalidTags = (List<String>) result.get("invalid-tags");
-                        invalidTags.add(tagName);
+                        if (_isTagValid(tagName, contextualParameters))
+                        {
+                            mContent.tag(tagName);
+                        }
+                        else
+                        {
+                            List<String> invalidTags = (List<String>) result.get("invalid-tags");
+                            invalidTags.add(tagName);
+                        }
                     }
                 }
                 
Index: main/plugin-cms/src/org/ametys/cms/tag/GetTagsAction.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/tag/GetTagsAction.java	(revision 32835)
+++ main/plugin-cms/src/org/ametys/cms/tag/GetTagsAction.java	(working copy)
@@ -44,6 +44,7 @@
 import org.ametys.core.right.RightsManager;
 import org.ametys.core.right.RightsManager.RightResult;
 import org.ametys.core.user.CurrentUserProvider;
+import org.ametys.plugins.repository.AmetysObject;
 import org.ametys.plugins.repository.AmetysObjectResolver;
 import org.ametys.plugins.repository.AmetysRepositoryException;
 
@@ -86,16 +87,13 @@
         String parentId = (String) jsParameters.get("node");
         String nodeName = (String) jsParameters.get("nodeName");
         boolean onlyCustomTags = (Boolean) jsParameters.get("onlyCustomTags");
-        String contentTargetId = (String) jsParameters.get("contentTargetId");
+        
+        List<String> objectTargetIds = (List<String>) jsParameters.get("objectTargetIds");
+        
+        String targetType = (String) jsParameters.get("targetType");
         
         Map<String, Object> contextualParameters = (Map<String, Object>) jsParameters.get("contextualParameters");
         
-        Content content = null;
-        if (StringUtils.isNotEmpty(contentTargetId))
-        {
-            content = _resolver.resolveById(contentTargetId);
-        }
-
         List<Map<String, Object>> tags = new ArrayList<>();
 
         if (StringUtils.isEmpty(parentId) || parentId.equals("tag-root"))
@@ -105,7 +103,7 @@
             for (String id : extensionsIds)
             {
                 TagProvider tagProvider = _tagProviderExtPt.getExtension(id);
-                tags.add(providerToJSON(tagProvider, contextualParameters));
+                tags.add(providerToJSON(tagProvider, contextualParameters, targetType));
             }
         }
         else if (nodeName.startsWith("provider_"))
@@ -115,12 +113,12 @@
             
             if (tagProvider != null)
             {
-                Map<String, Tag> childs = tagProvider.getTags(contextualParameters);
+                Map<String, Tag> childs = tagProvider.getTags(contextualParameters, targetType);
                 if (childs != null)
                 {
                     for (Tag tag : childs.values())
                     {
-                        tags.add(tagToJSON(tag, tagProvider, content));
+                        tags.add(tagToJSON(tag, tagProvider, objectTargetIds));
                     }
                 }                
             }
@@ -137,7 +135,10 @@
                     Map<String, Tag> childs = parent.getTags();
                     for (Tag tag : childs.values())
                     {
-                        tags.add(tagToJSON(tag, tagProvider, content));
+                        if (targetType == null || tag.getTarget().getName().equals(targetType))
+                        {
+                            tags.add(tagToJSON(tag, tagProvider, objectTargetIds));
+                        }
                     }
                 }
             }
@@ -183,10 +184,10 @@
      * Get tag's properties to JSON format
      * @param tag The tag
      * @param tagProvider the tag provider
-     * @param content the current content, can be null
+     * @param objectTargetIds the list of ametys object ids, can be empty
      * @return The tag properties
      */
-    protected Map<String, Object> tagToJSON(Tag tag, TagProvider tagProvider, Content content)
+    protected Map<String, Object> tagToJSON(Tag tag, TagProvider tagProvider, List<String> objectTargetIds)
     {
         Map<String, Object> infos = new HashMap<>();
 
@@ -195,7 +196,7 @@
         infos.put("title", tag.getTitle());
         infos.put("description", tag.getDescription());
         
-        boolean authorized = isUserAuthorized(tag, content);
+        boolean authorized = isUserAuthorized(tag, objectTargetIds);
         infos.put("authorized", authorized);
         infos.put("creationAllowed", isCreationAllowed(tagProvider, tag));
         
@@ -239,10 +240,12 @@
     /**
      * Get tag provider's properties to JSON format
      * @param tagProvider the tag provider
+     * @param contextualParameters The contextual parameters
+     * @param targetType 
      * @return The tag providers properties
      * @throws AmetysRepositoryException If an error occurred
      */
-    protected Map<String, Object> providerToJSON(TagProvider tagProvider, Map<String, Object> contextualParameters)
+    protected Map<String, Object> providerToJSON(TagProvider tagProvider, Map<String, Object> contextualParameters, String targetType)
     {
         Map<String, Object> infos = new HashMap<>();
 
@@ -277,7 +280,7 @@
         infos.put("iconMedium", "/plugins/cms/resources/img/tag/tags_32.png");
         infos.put("iconLarge", "/plugins/cms/resources/img/tag/tags_50.png");
 
-        if (tagProvider.getTags(contextualParameters).size() == 0)
+        if (tagProvider.getTags(contextualParameters, targetType).size() == 0)
         {
             infos.put("leaf", true);
         }
@@ -287,22 +290,30 @@
     /**
      * Test if a tag is visible to the current user.
      * @param tag the Tag object.
-     * @param content the current content (can be null).
+     * @param objectTargetIds the ids of current Ametys objects
      * @return true if the user has access to the tag, false otherwise.
      */
-    protected boolean isUserAuthorized(Tag tag, Content content)
+    protected boolean isUserAuthorized(Tag tag, List<String> objectTargetIds)
     {
-        if (content != null)
+        if (objectTargetIds != null)
         {
-            String context = _getContentContext(content);
-            if (isPrivate(tag))
+            for (String objectId : objectTargetIds)
             {
-                return _rightsManager.hasRight(_getCurrentUser(), "CMS_Rights_Content_Private_Tag", context) == RightResult.RIGHT_OK;
-            }
-            else
-            {
-                return _rightsManager.hasRight(_getCurrentUser(), "CMS_Rights_Content_Tag", context) == RightResult.RIGHT_OK
-                        || _rightsManager.hasRight(_getCurrentUser(), "CMS_Rights_Content_Private_Tag", context) == RightResult.RIGHT_OK;
+                AmetysObject ao = _resolver.resolveById(objectId);
+                
+                if (ao instanceof Content && tag.getTarget().getName().equals("CONTENT"))
+                {
+                    String context = _getContentContext((Content) ao);
+                    if (isPrivate(tag))
+                    {
+                        return _rightsManager.hasRight(_getCurrentUser(), "CMS_Rights_Content_Private_Tag", context) == RightResult.RIGHT_OK;
+                    }
+                    else
+                    {
+                        return _rightsManager.hasRight(_getCurrentUser(), "CMS_Rights_Content_Tag", context) == RightResult.RIGHT_OK
+                                || _rightsManager.hasRight(_getCurrentUser(), "CMS_Rights_Content_Private_Tag", context) == RightResult.RIGHT_OK;
+                    }
+                }
             }
         }
         
Index: main/plugin-cms/src/org/ametys/cms/tag/StaticTagProvider.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/tag/StaticTagProvider.java	(revision 32835)
+++ main/plugin-cms/src/org/ametys/cms/tag/StaticTagProvider.java	(working copy)
@@ -20,6 +20,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 
 import org.apache.avalon.framework.configuration.Configurable;
 import org.apache.avalon.framework.configuration.Configuration;
@@ -131,6 +132,31 @@
     }
     
     @Override
+    public Map<String, Tag> getTags(Map<String, Object> contextualParameters, String targetType)
+    {
+        Map<String, Tag> tags = new HashMap<>();
+
+        Map<String, Tag> allTags = getTags(contextualParameters);
+        
+        if (targetType != null)
+        {
+            for (Entry<String, Tag> entry : allTags.entrySet())
+            {
+                if (entry.getValue().getTarget().getName().equals(targetType))
+                {
+                    tags.put(entry.getKey(), entry.getValue());
+                }
+            }
+
+            return tags;
+        }
+        else
+        {
+            return allTags;
+        }
+    }
+    
+    @Override
     public boolean hasTag(String tagID, Map<String, Object> contextualParameters)
     {
         return getTag(tagID, contextualParameters) != null;
Index: main/plugin-cms/src/org/ametys/cms/tag/TagProvider.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/tag/TagProvider.java	(revision 32835)
+++ main/plugin-cms/src/org/ametys/cms/tag/TagProvider.java	(working copy)
@@ -51,6 +51,14 @@
     public Map<String, Tag> getTags (Map<String, Object> contextualParameters);
     
     /**
+     * Returns the provider's tags.
+     * @param contextualParameters contextual parameters
+     * @param targetType If not null, returns only tags matching this target type
+     * @return the provider's tags.
+     */
+    public Map getTags(Map<String, Object> contextualParameters, String targetType);
+
+    /**
      * Returns the provider's tag.
      * @param tagName The tag name
      * @param contextualParameters contextual parameters
@@ -73,4 +81,5 @@
      * @return true if the tag exists.
      */
     public boolean hasTag (String tagName, Map<String, Object> contextualParameters);
+
 }
Index: main/plugin-cms/src/org/ametys/cms/tag/jcr/ImportTagsAction.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/tag/jcr/ImportTagsAction.java	(revision 32835)
+++ main/plugin-cms/src/org/ametys/cms/tag/jcr/ImportTagsAction.java	(working copy)
@@ -17,6 +17,7 @@
 
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.util.Collections;
 import java.util.HashMap;
@@ -40,7 +41,7 @@
 import org.apache.cocoon.servlet.multipart.PartOnDisk;
 import org.apache.cocoon.servlet.multipart.RejectedPart;
 import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.xml.sax.XMLReader;
 
 import org.ametys.cms.ObservationConstants;
@@ -53,6 +54,7 @@
 import org.ametys.core.observation.AbstractNotifierAction;
 import org.ametys.core.observation.Event;
 import org.ametys.core.util.I18nizableText;
+import org.ametys.core.util.JSONUtils;
 import org.ametys.plugins.repository.AmetysObjectResolver;
 import org.ametys.plugins.repository.TraversableAmetysObject;
 import org.ametys.plugins.repository.jcr.DefaultTraversableAmetysObject;
@@ -70,6 +72,8 @@
     protected AmetysObjectResolver _resolver;
     /** Target types */
     protected TagTargetTypeExtensionPoint _targetTypeEP;
+    /** The JSon Utils Component */
+    protected JSONUtils _jsonUtils;
     
     /** The count of new tags **/
     private int _createdTagsCount;
@@ -85,6 +89,7 @@
         _tagProviderExtPt = (TagProviderExtensionPoint) smanager.lookup(TagProviderExtensionPoint.ROLE);
         _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
         _targetTypeEP = (TagTargetTypeExtensionPoint) smanager.lookup(TagTargetTypeExtensionPoint.ROLE);
+        _jsonUtils = (JSONUtils) smanager.lookup(JSONUtils.ROLE);
     }
     
     @Override
@@ -95,6 +100,11 @@
         Request request = ObjectModelHelper.getRequest(objectModel);
 
         String parentId = request.getParameter("tagId");
+        String forceProvider = request.getParameter("forceProvider");
+        if (StringUtils.isEmpty(forceProvider))
+        {
+            forceProvider = JCRTagProvider.class.getName();
+        }
         
         Part part = (Part) request.get("importFile");
         if (part instanceof RejectedPart)
@@ -116,12 +126,11 @@
             return Collections.singletonMap("error", "no-file");
         }
         
-        // FIXME
-        String paramsAsStr = request.getParameter("parameters");
-        Map<String, Object> contextualParameters = new HashMap<>();
+        String contextualParametersAsStr = request.getParameter("contextualParameters");
+        Map<String, Object> contextualParameters = _jsonUtils.convertJsonToMap(contextualParametersAsStr);
         
         Configuration configuration = initializeTags(uploadedFile.getPath());
-        Map<String, Tag> tags = configureTags(configuration, null, "plugin.web");
+        Map<String, Tag> tags = configureTags(configuration, null, "plugin.cms");
         
         _createdTagsCount = 0;
         _updatedTagsCount = 0;
@@ -130,7 +139,7 @@
         DefaultTraversableAmetysObject<?> parent = _resolver.resolveById(parentId);
         createOrUpdateTags(parent, tags, contextualParameters);
         
-        JCRTagProvider provider = (JCRTagProvider) _tagProviderExtPt.getExtension(JCRTagProvider.class.getName());
+        JCRTagProvider provider = (JCRTagProvider) _tagProviderExtPt.getExtension(forceProvider);
         TraversableAmetysObject rootNode = provider.getRootNode(contextualParameters);
 
         result.put("success", "true");
@@ -146,6 +155,7 @@
      * Save or update a tag
      * @param parent the JCR parent tag or provider
      * @param tags the list of tags to add or update
+     * @param contextualParameters Contextual parameters transmitted by the environment.
      */
     protected void createOrUpdateTags(DefaultTraversableAmetysObject<?> parent, Map<String, Tag> tags, Map<String, Object> contextualParameters)
     {
@@ -172,6 +182,7 @@
                 jcrTag.setTitle(tag.getTitle().getLabel());
                 jcrTag.setDescription(tag.getDescription().getLabel());
                 jcrTag.setVisibility(tag.getVisibility());
+                jcrTag.setTargetType(tag.getTarget());
                 
                 // recursive call to save/update
                 createOrUpdateTags(jcrTag, tag.getTags(), contextualParameters);
@@ -210,9 +221,7 @@
      */
     protected Configuration initializeTags(String path) throws Exception
     {
-        InputStream is = new FileInputStream(path);
-        
-        try
+        try (InputStream is = new FileInputStream(path))
         {
             SAXParserFactory factory = SAXParserFactory.newInstance();
             
@@ -222,9 +231,9 @@
 
             return confBuilder.build(is);
         }
-        finally
+        catch (IOException e)
         {
-            IOUtils.closeQuietly(is);
+            return null;
         }
     }
     
Index: main/plugin-cms/src/org/ametys/cms/tag/jcr/JCRTagsDAO.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/tag/jcr/JCRTagsDAO.java	(revision 32835)
+++ main/plugin-cms/src/org/ametys/cms/tag/jcr/JCRTagsDAO.java	(working copy)
@@ -41,12 +41,14 @@
 import org.ametys.cms.tag.TagProvider;
 import org.ametys.cms.tag.TagProviderExtensionPoint;
 import org.ametys.cms.tag.TagTargetTypeExtensionPoint;
+import org.ametys.cms.tag.TagsDAO;
 import org.ametys.core.observation.Event;
 import org.ametys.core.observation.ObservationManager;
 import org.ametys.core.right.RightsManager;
 import org.ametys.core.right.RightsManager.RightResult;
 import org.ametys.core.ui.Callable;
 import org.ametys.core.user.CurrentUserProvider;
+import org.ametys.plugins.repository.AmetysObject;
 import org.ametys.plugins.repository.AmetysObjectResolver;
 import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
 import org.ametys.plugins.repository.UnknownAmetysObjectException;
@@ -70,6 +72,8 @@
     protected RightsManager _rightsManager;
     /** Target types */
     protected TagTargetTypeExtensionPoint _targetTypeEP;
+    /** Tags DAO */
+    protected TagsDAO _tagsDAO;
     
     @Override
     public void service(ServiceManager serviceManager) throws ServiceException
@@ -80,10 +84,13 @@
         _currentUserProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE);
         _rightsManager = (RightsManager) serviceManager.lookup(RightsManager.ROLE);
         _targetTypeEP = (TagTargetTypeExtensionPoint) serviceManager.lookup(TagTargetTypeExtensionPoint.ROLE);
+        _tagsDAO = (TagsDAO) serviceManager.lookup(TagsDAO.ROLE);
     }
     
     /**
      * Get the root node for tags
+     * @param tagProviderId The tag provider id
+     * @param contextualParameters Contextual parameters transmitted by the environment.
      * @return The root node in key "id"
      * @throws ProcessingException If an error occured in the repository
      */
@@ -116,6 +123,7 @@
      * @param description The tag's description
      * @param visibility The tag's visibility
      * @param targetType The tag's target type
+     * @param contextualParameters Contextual parameters transmitted by the environment.
      * @return The result map
      */
     @Callable
@@ -271,6 +279,7 @@
     /**
      * Deletes a JCR tag
      * @param tagId The tag's id
+     * @param contextualParameters Contextual parameters transmitted by the environment.
      * @return the result map
      */
     @Callable
@@ -332,6 +341,7 @@
     /**
      * Find a unique name for the tag
      * @param originalName The requested name
+     * @param contextualParameters Contextual parameters transmitted by the environment.
      * @return A unique name
      */
     protected String _findUniqueName(String originalName, Map<String, Object> contextualParameters)
@@ -353,6 +363,7 @@
      * Determines if a tag with given name already exists
      * @param providers The tag providers
      * @param name The name of the tag
+     * @param contextualParameters Contextual parameters transmitted by the environment.
      * @return true if the tag exists
      */
     protected boolean _hasTag(Set<TagProvider> providers, String name, Map<String, Object> contextualParameters)
@@ -441,4 +452,29 @@
     
         return result;
     }
+    
+    /**
+     * Retrieve a list of tags, from a list of tagged objects
+     * @param objectIds The tagged objects ids
+     * @return The list of tags
+     */
+    @Callable
+    public List<String> getTags(List<String> objectIds)
+    {
+        List<String> result = new ArrayList<>();
+        
+        for (String objectid : objectIds)
+        {
+            AmetysObject ao = _resolver.resolveById(objectid);
+            
+            if (ao instanceof JCRAmetysObject)
+            {
+                Set<String> tags = TaggableAmetysObjectHelper.getTags((JCRAmetysObject) ao);
+                
+                result.addAll(tags);
+            }
+        }
+        
+        return result;
+    }
 }
