Index: main/plugin-cms/src/org/ametys/cms/file/FileHelper.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/file/FileHelper.java	(revision 0)
+++ main/plugin-cms/src/org/ametys/cms/file/FileHelper.java	(revision 0)
@@ -0,0 +1,293 @@
+package org.ametys.cms.file;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.avalon.framework.component.Component;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.service.Serviceable;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceResolver;
+import org.apache.excalibur.source.SourceUtil;
+import org.apache.excalibur.source.impl.FileSource;
+
+import org.ametys.core.ui.Callable;
+
+/**
+ * Helper for managing files and folders
+ */
+public final class FileHelper implements Component, Serviceable
+{
+    /** The Avalon role name */
+    public final static String ROLE = FileHelper.class.getName();
+    
+    private static SourceResolver _srcResolver;
+
+    public void service(ServiceManager serviceManager) throws ServiceException
+    {
+        _srcResolver = (org.apache.excalibur.source.SourceResolver) serviceManager.lookup(org.apache.excalibur.source.SourceResolver.ROLE);        
+    }
+    
+    /**
+     * Saves the text in file 
+     * @param rootDirURI the root folder URI
+     * @param relFileURI the file URI, relative to the root
+     * @param text the file content to save
+     * @return The result map
+     * @throws IOException If an error occurred while saving 
+     */
+    @Callable
+    public Map<String, Object> saveFile(String rootDirURI, String relFileURI, String text) throws IOException 
+    {
+        Map<String, Object> result = new HashMap<>();
+
+        FileSource file = (FileSource) _srcResolver.resolveURI(rootDirURI + (relFileURI.length() > 0 ? "/" + relFileURI : ""));
+        
+        if (file.exists())
+        {
+            InputStream is = IOUtils.toInputStream(text, "UTF-8");
+            SourceUtil.copy(is, file.getOutputStream());
+            
+            if (file.getName().startsWith("messages"))
+            {
+                result.put("msg", "wait");
+            }
+        }
+        else
+        {
+            result.put("msg", "unknown-file");      
+        }
+        
+        return result;
+    }
+    
+    /**
+     * Create a folder
+     * @param rootDirURI the root folder URI
+     * @param relParentURI the parent URI, relative to the root
+     * @param name the name of the new folder.
+     * @param renameIfExists 
+     * @return A map with the "path" and "name" of the new folder, and the "parentPath" of the parent folder
+     * @throws IOException 
+     */
+    public Map<String, String> addFolder(String rootDirURI, String relParentURI, String name, Boolean renameIfExists) throws IOException
+    {
+        Map<String, String> result = new HashMap<>();
+        
+        FileSource rootDir = (FileSource) _srcResolver.resolveURI(rootDirURI);
+        FileSource parentDir = (FileSource) _srcResolver.resolveURI(rootDirURI + (relParentURI.length() > 0 ? "/" + relParentURI : ""));
+        
+        int index = 2;
+        String folderName = name;
+
+        if (!renameIfExists && parentDir.getChild(folderName).exists())
+        {
+            result.put("msg", "already-exist");
+            return result;
+        }
+        
+        while (parentDir.getChild(folderName).exists())
+        {
+            folderName = name + " (" + index + ")";
+            index++;
+        }
+        
+        FileSource folder = (FileSource) parentDir.getChild(folderName);
+        folder.makeCollection();
+        
+        String path = folder.getURI().substring(rootDir.getURI().length());
+
+        if (path.endsWith("/"))
+        {
+            path = path.substring(0, path.length() - 1);
+        }
+
+        String parentPath = parentDir.getURI().substring(rootDir.getURI().length());
+        
+        if (parentPath.endsWith("/"))
+        {
+            parentPath = parentPath.substring(0, parentPath.length() - 1);
+        }
+        
+        result.put("name", folder.getName());
+        result.put("path", path);
+        result.put("parentPath", parentPath);
+        
+        return result;
+    }
+    
+    /**
+     * Remove a folder or a file
+     * @param rootDirURI the root folder URI
+     * @param relFileURI the context file path
+     * @return a map with error messages
+     * @throws IOException If an error occurs while removing the folder
+     */
+    public Map<String, String> deleteFile(String rootDirURI, String relFileURI) throws IOException
+    {
+        Map<String, String> result = new HashMap<>();
+        
+        FileSource file = (FileSource) _srcResolver.resolveURI(rootDirURI + (relFileURI.length() > 0 ? "/" + relFileURI : ""));
+        
+        if (file.exists())
+        {
+            FileUtils.deleteQuietly(file.getFile());
+        }
+        else
+        {
+            result.put("msg", "no-exists");
+        }
+        
+        return result;
+    }
+    
+    /**
+    * Rename a file or a folder 
+    * @param rootDirURI the root folder URI
+    * @param relSourcePath the relative URI of the file or folder to rename
+    * @param name the new name of the file/folder
+    * @return A map with the "path" and "name" of the new folder. Returns "error" on failure
+    * @throws IOException if an error occurs while renaming the file/folder
+    */
+    public Map<String, String> renameFile (String rootDirURI, String relSourcePath, String name) throws IOException 
+    {
+        Map<String, String> result = new HashMap<>();
+           
+        FileSource rootDir = (FileSource) _srcResolver.resolveURI(rootDirURI);
+        FileSource file = (FileSource) _srcResolver.resolveURI(rootDirURI + (relSourcePath.length() > 0 ? "/" + relSourcePath : ""));
+        FileSource parentDir = (FileSource) file.getParent();
+           
+        if (parentDir.getChild(name).exists())
+        {
+            result.put("message", "already-exist");
+        }
+        else
+        {
+            Source dest = _srcResolver.resolveURI(parentDir.getURI() + name);
+            file.moveTo(dest);
+           
+            String newPath = dest.getURI().substring(rootDir.getURI().length());
+            if (newPath.endsWith("/"))
+            {
+                newPath = newPath.substring(0, newPath.length() - 1);
+            }
+            
+            result.put("path", newPath);
+            result.put("name", name);
+        }
+        
+        return result;
+    }
+    
+    /**
+    * Check if a file with same name already exist
+    * @param rootDirURI the root folder URI
+    * @param relSourcePath the relative URI of the file or folder to check
+    * @param name the name of the file 
+    * @return Boolean true if the file exists
+    * @throws IOException if an error occurred
+    */
+    public Boolean fileExists(String rootDirURI, String relSourcePath, String name) throws IOException
+    {
+        FileSource currentDir = (FileSource) _srcResolver.resolveURI(rootDirURI + (relSourcePath.length() > 0 ? "/" + relSourcePath : ""));
+       
+        return currentDir.getChild(name).exists();
+    }
+    
+    
+    /**
+     * Copy a file or folder 
+     * @param rootDirURI The root folder URI
+     * @param relSourcePath the source path relative to the root
+     * @param relTargetPath the folder where the source will be copied
+     * @return a map with the path of the new file or folder
+     * @throws IOException
+     */
+    public Map<String, String> copySource(String rootDirURI, String relSourcePath, String relTargetPath) throws IOException
+    {
+        Map<String, String> result = new HashMap<>();
+
+        FileSource srcFile = (FileSource) _srcResolver.resolveURI(rootDirURI + (relSourcePath.length() > 0 ? "/" + relSourcePath : ""));
+        FileSource rootDir = (FileSource) _srcResolver.resolveURI(rootDirURI);
+        
+        if (!srcFile.exists())
+        {
+            result.put("msg", "no-exists");
+            return result;
+        }
+        
+        String fileName = srcFile.getName();
+        FileSource target = (FileSource) _srcResolver.resolveURI(rootDirURI + relTargetPath + (fileName.length() > 0 ? "/" + fileName : ""));
+        
+        // Find unique file name
+        int index = 1;
+        while (target.exists())
+        {
+            fileName = srcFile.getName() + " (" + index + ")";
+            target = (FileSource) _srcResolver.resolveURI(rootDirURI + relTargetPath + (fileName.length() > 0 ? "/" + fileName : ""));
+            index++;
+        }
+        
+        if (srcFile.getFile().isDirectory())
+        {
+            FileUtils.copyDirectory(srcFile.getFile(), target.getFile());
+        }
+        else
+        {
+            FileUtils.copyFile(srcFile.getFile(), target.getFile());
+        }
+        
+        result.put("name", target.getName());
+        result.put("path", target.getURI().substring(rootDir.getURI().length()));
+        
+        return result;
+    }
+    
+    /**
+     * Move a file or folder 
+     * @param rootDirURI The root folder URI
+     * @param relSourcePath the source path relative to the root
+     * @param relTargetPath the folder where the source will be copied
+     * @return a map with the path of the new file or folder
+     * @throws IOException
+     */
+    public Map<String, String> moveSource(String rootDirURI, String relSourcePath, String relTargetPath) throws IOException
+    {
+        Map<String, String> result = new HashMap<>();
+        
+        FileSource rootDir = (FileSource) _srcResolver.resolveURI(rootDirURI);
+        FileSource srcFile = (FileSource) _srcResolver.resolveURI(rootDirURI + (relSourcePath.length() > 0 ? "/" + relSourcePath : ""));
+        FileSource targetDir = (FileSource) _srcResolver.resolveURI(rootDirURI + (relTargetPath.length() > 0 ? "/" + relTargetPath : ""));
+        FileSource targetFile = (FileSource) _srcResolver.resolveURI(rootDirURI + (relTargetPath.length() > 0 ? "/" + relTargetPath : "") + "/" + srcFile.getName());
+        
+        if (!srcFile.exists())
+        {
+            result.put("msg", "no-exists");
+            return result;
+        }
+        if (targetFile.exists())
+        {
+            result.put("msg", "already-exists");
+            return result;
+        }
+        
+        FileUtils.moveToDirectory(srcFile.getFile(), targetDir.getFile(), false);
+        
+        result.put("name", targetFile.getName());
+        String path = targetFile.getURI().substring(rootDir.getURI().length());
+        if (path.endsWith("/"))
+        {
+            path = path.substring(0, path.length() - 1);
+        }
+        result.put("path", path);
+        
+        return result;
+    }
+    
+    
+}
Index: main/plugin-cms/src/org/ametys/cms/parameters/ParametersClientSideElement.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/parameters/ParametersClientSideElement.java	(revision 32835)
+++ main/plugin-cms/src/org/ametys/cms/parameters/ParametersClientSideElement.java	(working copy)
@@ -16,19 +16,14 @@
 package org.ametys.cms.parameters;
 
 import java.io.IOException;
-import java.io.InputStream;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
 import org.apache.avalon.framework.service.ServiceException;
 import org.apache.avalon.framework.service.ServiceManager;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.IOUtils;
-import org.apache.excalibur.source.Source;
-import org.apache.excalibur.source.SourceUtil;
-import org.apache.excalibur.source.impl.FileSource;
 
+import org.ametys.cms.file.FileHelper;
 import org.ametys.core.right.RightsManager.RightResult;
 import org.ametys.core.ui.Callable;
 import org.ametys.core.ui.StaticClientSideElement;
@@ -38,14 +33,14 @@
  */
 public class ParametersClientSideElement extends StaticClientSideElement
 {
-    /** Excalibur's source resolver */
-    private org.apache.excalibur.source.SourceResolver _srcResolver;
+    /** The file helper */
+    private FileHelper _fileHelper;
     
     @Override
     public void service(ServiceManager serviceManager) throws ServiceException
     {
         super.service(serviceManager);
-        _srcResolver = (org.apache.excalibur.source.SourceResolver) serviceManager.lookup(org.apache.excalibur.source.SourceResolver.ROLE);
+        _fileHelper = (FileHelper) serviceManager.lookup(FileHelper.ROLE);
     }
 
     /**
@@ -58,26 +53,7 @@
     @Callable
     public Map<String, Object> saveParameters(String path, String text) throws IOException 
     {
-        Map<String, Object> result = new HashMap<>();
-
-        FileSource file = (FileSource) _srcResolver.resolveURI("context://WEB-INF/param/" + path);
-        
-        if (file.exists())
-        {
-            InputStream is = IOUtils.toInputStream(text, "UTF-8");
-            SourceUtil.copy(is, file.getOutputStream());
-            
-            if (file.getName().startsWith("messages"))
-            {
-                result.put("msg", "wait");            
-            }
-        }
-        else
-        {
-            result.put("msg", "unknown-file");      
-        }
-        
-        return result;
+        return _fileHelper.saveFile("context://WEB-INF/param/", path, text);
     }
     
     /**
@@ -88,47 +64,14 @@
      * @throws IOException If an error occurred while creating folder
      */
     @Callable
-    public Map<String, Object> addFolder(String filePath, String name) throws IOException 
+    public Map<String, String> addFolder(String filePath, String name) throws IOException 
     {
-        Map<String, Object> result = new HashMap<>();
-        
         // Check user can access this feature
         _checkUserRight("CMS_Rights_EditParameterFile", "/contributor");
         
-        FileSource paramsDir = (FileSource) _srcResolver.resolveURI("context://WEB-INF/param");
-        FileSource currentDir = (FileSource) _srcResolver.resolveURI("context://WEB-INF/param" + (filePath.length() > 0 ? "/" + filePath : ""));
-        
-        int index = 2;
-        String folderName = name;
-
-        while (currentDir.getChild(folderName).exists())
-        {
-            folderName = name + " (" + index + ")";
-            index++;
-        }
-        
-        FileSource folder = (FileSource) currentDir.getChild(folderName);
-        folder.makeCollection();
-        
-        String path = folder.getURI().substring(paramsDir.getURI().length());
-
-        if (path.endsWith("/"))
-        {
-            path = path.substring(0, path.length() - 1);
-        }
-
-        String parentPath = currentDir.getURI().substring(paramsDir.getURI().length());
-        
-        if (parentPath.endsWith("/"))
-        {
-            parentPath = parentPath.substring(0, parentPath.length() - 1);
-        }
-        
-        result.put("name", folder.getName());
-        result.put("path", path);
-        result.put("parentPath", parentPath);
+        Map<String, String> folderData = _fileHelper.addFolder("context://WEB-INF/param", filePath, name, true);
 
-        return result;
+        return folderData;
     }
     
     /**
@@ -143,12 +86,7 @@
         // Check user can access this feature
         _checkUserRight("CMS_Rights_EditParameterFile", "/contributor");
        
-        FileSource file = (FileSource) _srcResolver.resolveURI("context://WEB-INF/param" + (filePath.length() > 0 ? "/" + filePath : ""));
-       
-        if (file.exists())
-        {
-            FileUtils.deleteQuietly(file.getFile());
-        }
+        _fileHelper.deleteFile("context://WEB-INF/param", filePath);
        
         return Collections.EMPTY_MAP;
     }
@@ -161,37 +99,14 @@
     * @throws IOException if an error occurs while renaming the file/folder
     */
     @Callable
-    public Map<String, Object> renameFile (String path, String name) throws IOException 
+    public Map<String, String> renameFile (String path, String name) throws IOException 
     {
         // Check user can access this feature
         _checkUserRight ("CMS_Rights_EditParameterFile", "/contributor"); 
-           
-        Map<String, Object> result = new HashMap<>();
-           
-        FileSource paramsDir = (FileSource) _srcResolver.resolveURI("context://WEB-INF/param");
-        FileSource file = (FileSource) _srcResolver.resolveURI("context://WEB-INF/param" + (path.length() > 0 ? "/" + path : ""));
-        FileSource parentDir = (FileSource) file.getParent();
-           
-        if (parentDir.getChild(name).exists())
-        {
-            result.put("message", "already-exist");
-        }
-        else
-        {
-            Source dest = _srcResolver.resolveURI(parentDir.getURI() + name);
-            file.moveTo(dest);
-           
-            String newPath = dest.getURI().substring(paramsDir.getURI().length());
-            if (newPath.endsWith("/"))
-            {
-                newPath = newPath.substring(0, newPath.length() - 1);
-            }
-            
-            result.put("path", newPath);
-            result.put("name", name);
-        }
         
-        return result;
+        Map<String, String> fileInfo = _fileHelper.renameFile("context://WEB-INF/param", path, name);
+        
+        return fileInfo;
     }
     
     /**
@@ -199,25 +114,21 @@
     * @param path the path where the file will be added
     * @param name the name of the file 
     * @return result the server's response in JSON
-    * @throws Exception if an error occured
+    * @throws Exception if an error occurred
     */
     @Callable
     public Map<String, Object> fileExists(String path, String name) throws Exception
     {
         Map<String, Object> result = new HashMap<>();
        
-        FileSource currentDir = (FileSource) _srcResolver.resolveURI("context://WEB-INF/param" + (path.length() > 0 ? "/" + path : ""));
-       
-        boolean exist = currentDir.getChild(name).exists();
-       
-        result.put("exists", String.valueOf(exist));
+        result.put("exists", _fileHelper.fileExists("context://WEB-INF/param", path, name));
        
         return result;
     }
     
     /**
      * Check if the user right to access the feature
-     * @param rightId The right ide
+     * @param rightId The right id
      * @param context The right context
      * @throws IllegalStateException if the user has no right
      */
