diff --git a/client/css/icons/style.css b/client/css/icons/style.css
index 90f70bd022f65b24335883a7fcdd899db7d42c7a..995a6b2a6d7272a2ba770be7f7a77f7e31dae74b 100644
--- a/client/css/icons/style.css
+++ b/client/css/icons/style.css
@@ -596,6 +596,9 @@
 .icon-language1:before {
   content: "\e2a8";
 }
+.icon-vm4vmp:before {
+  content: "\e2a8";
+}
 .icon-layers:before {
   content: "\e1d3";
 }
diff --git a/client/javascript/app/directives/workspaceListDrtv.js b/client/javascript/app/directives/workspaceListDrtv.js
index 6da6cf43c6439b60c045ff651c0bbfdd1ee3b9a2..f16ccb8e9b199c234322e0cf8aae1ad166428bf4 100755
--- a/client/javascript/app/directives/workspaceListDrtv.js
+++ b/client/javascript/app/directives/workspaceListDrtv.js
@@ -187,6 +187,9 @@ vitisApp.appWorkspaceListDrtv = function ($timeout, $rootScope, $templateRequest
                                     // Rafraîchit le formulaire.
                                     scope.$broadcast('$$rebind::refresh');
                                     scope.$apply();
+                                    // Rafraîchit les listes du formulaire.
+                                    var oFormScope = angular.element($('#' + envSrvc["oFormDefinition"][envSrvc["sFormDefinitionName"]]["name"]).children()).scope();
+                                    oFormScope['ctrl'].extractFormDefinitionInfos();
                                 }
                             }
                         });
diff --git a/client/javascript/app/services/formSrvc.js b/client/javascript/app/services/formSrvc.js
index ea6c95d42a8297b55bc48a2cf018081df3c214a1..0bae8fcd8b256b94e96e24373a1fe7581425f47c 100755
--- a/client/javascript/app/services/formSrvc.js
+++ b/client/javascript/app/services/formSrvc.js
@@ -51,44 +51,63 @@ vitisApp.formSrvc = function (envSrvc, propertiesSrvc, sessionSrvc, formReaderSe
                             case "upload":
                             case "file_wsdata":
                             case "image_wsdata":
-                                if (document.getElementById(aFormRowElementsList[ifieldIndex]["id"]) != null) {
-                                    var bContainFiles = true;
-                                    var oFiles = document.getElementById(aFormRowElementsList[ifieldIndex]["id"]).files;
-                                    if (goog.isDefAndNotNull(oFiles)) {
-                                        if (oFiles.length > 0) {
-                                            oFormKeysValues[aFormRowElementsList[ifieldIndex]["name"]] = oFiles[0];
-                                            if (oFiles.length > 1) {
-                                                oFormKeysValues[aFormRowElementsList[ifieldIndex]["name"]] = oFiles;
-                                            }
-                                            if (goog.isDefAndNotNull(aFormRowElementsList[ifieldIndex]["width"]) && goog.isDefAndNotNull(aFormRowElementsList[ifieldIndex]["height"])) {
-                                                oFormKeysValues[aFormRowElementsList[ifieldIndex]["name"] + "_width"] = aFormRowElementsList[ifieldIndex]["width"];
-                                                oFormKeysValues[aFormRowElementsList[ifieldIndex]["name"] + "_height"] = aFormRowElementsList[ifieldIndex]["height"];
+
+                                var bContainFiles = false;
+                                var oFiles = null;
+                                if (document.getElementById(aFormRowElementsList[ifieldIndex]['id']) != null) {
+                                    bContainFiles = true;
+                                    oFiles = document.getElementById(aFormRowElementsList[ifieldIndex]['id']).files;
+                                } else {
+                                    var oElemValue = aFormValues[aFormRowElementsList[ifieldIndex]['name']];
+                                    if (goog.isArray(oElemValue)) {
+                                        if (goog.isDefAndNotNull(oElemValue[0])) {
+                                            if (goog.isDefAndNotNull(oElemValue[0]['name'])) {
+                                                oFiles = [];
+                                                for (var i = 0; i < oElemValue.length; i++) {
+                                                    if (goog.isDefAndNotNull(oElemValue[i]['name'])) {
+                                                        oFiles.push(oElemValue[i]);
+                                                    }
+                                                }
                                             }
-                                        } else {
-                                            bContainFiles = false;
+                                        }
+                                    }
+                                }
+
+                                if (goog.isDefAndNotNull(oFiles)) {
+                                    if (oFiles.length > 0) {
+                                        oFormKeysValues[aFormRowElementsList[ifieldIndex]['name']] = oFiles[0];
+                                        if (oFiles.length > 1) {
+                                            oFormKeysValues[aFormRowElementsList[ifieldIndex]['name']] = oFiles;
+                                        }
+                                        if (goog.isDefAndNotNull(aFormRowElementsList[ifieldIndex]['width']) && goog.isDefAndNotNull(aFormRowElementsList[ifieldIndex]['height'])) {
+                                            oFormKeysValues[aFormRowElementsList[ifieldIndex]['name'] + '_width'] = aFormRowElementsList[ifieldIndex]['width'];
+                                            oFormKeysValues[aFormRowElementsList[ifieldIndex]['name'] + '_height'] = aFormRowElementsList[ifieldIndex]['height'];
                                         }
                                     } else {
                                         bContainFiles = false;
                                     }
+                                } else {
+                                    bContainFiles = false;
+                                }
 
-                                    if (!bContainFiles) {
-                                        var oElemValue = aFormValues[aFormRowElementsList[ifieldIndex]["name"]];
-                                        if (goog.isDefAndNotNull(oElemValue)) {
-                                            if (goog.isDefAndNotNull(oElemValue['aFiles'])) {
-                                                if (goog.isDefAndNotNull(oElemValue['aFiles'][0])) {
-                                                    oFormKeysValues[aFormRowElementsList[ifieldIndex]["name"]] = oElemValue['aFiles'][0];
-                                                    if (oElemValue['aFiles'].length > 1) {
-                                                        oFormKeysValues[aFormRowElementsList[ifieldIndex]["name"]] = oElemValue['aFiles'];
-                                                    }
-                                                    if (goog.isDefAndNotNull(aFormRowElementsList[ifieldIndex]["width"]) && goog.isDefAndNotNull(aFormRowElementsList[ifieldIndex]["height"])) {
-                                                        oFormKeysValues[aFormRowElementsList[ifieldIndex]["name"] + "_width"] = aFormRowElementsList[ifieldIndex]["width"];
-                                                        oFormKeysValues[aFormRowElementsList[ifieldIndex]["name"] + "_height"] = aFormRowElementsList[ifieldIndex]["height"];
-                                                    }
+                                if (!bContainFiles) {
+                                    var oElemValue = aFormValues[aFormRowElementsList[ifieldIndex]['name']];
+                                    if (goog.isDefAndNotNull(oElemValue)) {
+                                        if (goog.isDefAndNotNull(oElemValue['aFiles'])) {
+                                            if (goog.isDefAndNotNull(oElemValue['aFiles'][0])) {
+                                                oFormKeysValues[aFormRowElementsList[ifieldIndex]['name']] = oElemValue['aFiles'][0];
+                                                if (oElemValue['aFiles'].length > 1) {
+                                                    oFormKeysValues[aFormRowElementsList[ifieldIndex]['name']] = oElemValue['aFiles'];
+                                                }
+                                                if (goog.isDefAndNotNull(aFormRowElementsList[ifieldIndex]['width']) && goog.isDefAndNotNull(aFormRowElementsList[ifieldIndex]['height'])) {
+                                                    oFormKeysValues[aFormRowElementsList[ifieldIndex]['name'] + '_width'] = aFormRowElementsList[ifieldIndex]['width'];
+                                                    oFormKeysValues[aFormRowElementsList[ifieldIndex]['name'] + '_height'] = aFormRowElementsList[ifieldIndex]['height'];
                                                 }
                                             }
                                         }
                                     }
                                 }
+
                                 break;
                                 // Si double liste : valeurs séparé par un champ.
                             case "double_select":
@@ -149,6 +168,15 @@ vitisApp.formSrvc = function (envSrvc, propertiesSrvc, sessionSrvc, formReaderSe
                 iRowIndex++;
             }
 
+            // vitis_deleted_files
+            if (goog.isDefAndNotNull(aFormValues['vitis_deleted_files'])) {
+                oFormKeysValues['vitis_deleted_files'] = JSON.stringify(aFormValues['vitis_deleted_files']);
+            }
+            // vitis_unchanged_files
+            if (goog.isDefAndNotNull(aFormValues['vitis_unchanged_files'])) {
+                oFormKeysValues['vitis_unchanged_files'] = JSON.stringify(aFormValues['vitis_unchanged_files']);
+            }
+
             // Retourne un objet json ou un objet "FormData".
             if (bReturnJson === true) {
                 oFormData = oFormKeysValues;
@@ -161,14 +189,45 @@ vitisApp.formSrvc = function (envSrvc, propertiesSrvc, sessionSrvc, formReaderSe
                 }
             } else {
                 // Sauve les clés et valeurs dans un objet "FormData".
-                oFormData = new FormData();
-                var aFormKeys = Object.keys(oFormKeysValues);
-                var i = 0;
-                while (i < aFormKeys.length) {
-                    oFormData.append(aFormKeys[i], oFormKeysValues[aFormKeys[i]]);
-                    i++;
+                oFormData = this['getFormDataFromValues'](oFormKeysValues);
+            }
+
+            return oFormData;
+        },
+        "getFormDataFromValues": function (oValues) {
+
+            var oFormData = new FormData();
+
+            for (var key in oValues) {
+
+                var bIsMultipleFiles = false;
+                if (goog.isObject(oValues[key])) {
+                    if (goog.isDefAndNotNull(oValues[key].length)) {
+                        bIsMultipleFiles = true
+                    }
+                }
+
+                // Fichiers multiples
+                if (bIsMultipleFiles) {
+                    for (var i = 0; i < oValues[key].length; i++) {
+                        if (oValues[key][i]._modified !== false) {
+                            // Ajoute les fichiers
+                            if (goog.isDefAndNotNull(oValues[key][i]['name'])) {
+                                oFormData.append(key + '[]', oValues[key][i], oValues[key][i]['name']);
+                            } else {
+                                oFormData.append(key + '[]', oValues[key][i]);
+                            }
+                        }
+                    }
+                }
+                // Fichier simple
+                else {
+                    if (oValues[key]._modified !== false) {
+                        oFormData.append(key, oValues[key]);
+                    }
                 }
             }
+
             return oFormData;
         },
         /**
diff --git a/client/javascript/externs/formReader/component/file_picker/file_picker.css b/client/javascript/externs/formReader/component/file_picker/file_picker.css
new file mode 100644
index 0000000000000000000000000000000000000000..14bac1a02f071760a18454cc26398d549791570d
--- /dev/null
+++ b/client/javascript/externs/formReader/component/file_picker/file_picker.css
@@ -0,0 +1,128 @@
+.file_picker .preview-images-zone {
+    width: 100%;
+    border: 1px solid #ddd;
+    min-height: 90px;
+    padding: 5px 5px 0px 5px;
+    position: relative;
+    overflow:auto;
+}
+.file_picker .preview-images-zone > .preview-image-small {
+    height: 90px !important;
+    width: 90px !important;
+}
+.file_picker .preview-images-zone > .preview-image {
+    height: 185px;
+    width: 185px;
+    position: relative;
+    margin-right: 5px;
+    float: left;
+    margin-bottom: 5px;
+}
+.file_picker .preview-images-zone > .preview-image > .image-zone {
+    width: 100%;
+    height: 100%;
+}
+.file_picker .preview-images-zone > .preview-image > .image-add {
+    text-align: center;
+    font-size: 30px;
+    padding-top: calc(50% - 20px);
+}
+.file_picker .preview-images-zone > .preview-image > .image-zone > img {
+    width: 100%;
+    height: 100%;
+}
+.file_picker .preview-images-zone > .preview-image > .tools-edit-image {
+    position: absolute;
+    z-index: 100;
+    color: #fff;
+    bottom: 0;
+    width: 100%;
+    text-align: center;
+    margin-bottom: calc(50% - 20px);
+    display: none;
+}
+.file_picker .preview-images-zone > .preview-image > .tools-edit-image > a > .icon-zoom-in{
+    font-size: 22px;
+}
+.file_picker .preview-images-zone > .preview-image > .image-cancel {
+    font-size: 18px;
+    position: absolute;
+    top: 0;
+    right: 0;
+    font-weight: bold;
+    margin-right: 10px;
+    cursor: pointer;
+    display: none;
+    z-index: 100;
+}
+.file_picker .preview-documents-zone > .preview-document > .document-cancel {
+    font-weight: bold;
+    margin-left: 15px;
+    cursor: pointer;
+    display: none;
+}
+.file_picker .preview-image:hover > .image-add,
+.file_picker .preview-image:hover > .image-zone {
+    cursor: pointer;
+    opacity: .5;
+}
+.preview-image:hover > .tools-edit-image,
+.file_picker .preview-image:hover > .image-cancel {
+    display: block !important;
+}
+.file_picker .preview-document:hover > .document-cancel {
+    display: inline !important;
+}
+.file_picker .preview-document:hover > .document-link {
+    text-decoration: underline;
+}
+.file_picker .ui-sortable-helper {
+    width: 90px !important;
+    height: 90px !important;
+}
+.modal-image-zone {
+    text-align: center;
+    max-height: 600px;
+    overflow-y: auto;
+}
+.modal-image-zone > img {
+    height: auto;
+    width: 100%;
+    max-width: 350px;
+    max-height: 350px;
+}
+
+.file_picker .container {
+    padding-top: 50px;
+}
+
+.filepicker-image-zoom-modal .filepicker-image-zoom-modal-button-container {
+    padding-top: 15px;
+    text-align: right;
+    border-top: 1px solid #e5e5e5;
+}
+.filepicker-image-zoom-modal .filepicker-image-zoom-modal-tools-container {
+    position: absolute;
+    top: 0px;
+    width: calc(100% - 30px);
+    height: calc(100% - 64px);
+}
+.filepicker-image-zoom-modal .filepicker-image-zoom-modal-tools-container > .filepicker-image-zoom-modal-tools-prev,
+.filepicker-image-zoom-modal .filepicker-image-zoom-modal-tools-container > .filepicker-image-zoom-modal-tools-next {
+    position: absolute;
+    top: calc(50% - 35px);
+    font-size: 70px;
+    cursor: pointer;
+    -webkit-text-stroke: 1px white;
+    display: none;
+}
+.filepicker-image-zoom-modal .filepicker-image-zoom-modal-tools-container:hover > .filepicker-image-zoom-modal-tools-prev,
+.filepicker-image-zoom-modal .filepicker-image-zoom-modal-tools-container:hover > .filepicker-image-zoom-modal-tools-next {
+    display: block;
+}
+.filepicker-image-zoom-modal .filepicker-image-zoom-modal-tools-container > .filepicker-image-zoom-modal-tools-prev {
+    left: 0px;
+}
+.filepicker-image-zoom-modal .filepicker-image-zoom-modal-tools-container > .filepicker-image-zoom-modal-tools-next {
+    right: 0px;
+}
diff --git a/client/javascript/externs/formReader/component/file_picker/file_picker.html b/client/javascript/externs/formReader/component/file_picker/file_picker.html
new file mode 100644
index 0000000000000000000000000000000000000000..4a3243d8b3ef881c2a5b1e69cd1309c8f4dec536
--- /dev/null
+++ b/client/javascript/externs/formReader/component/file_picker/file_picker.html
@@ -0,0 +1,73 @@
+
+<link rel="stylesheet" ng-href="{{ folderUrl }}/file_picker.css">
+
+<div class="file_picker">
+
+    <label for="{{:refresh:field.id}}" id="{{:refresh:field.id}}_label" class="control-label" data-translate="{{:refresh:field.label}}"></label>
+
+    <!-- Visioneuse Images -->
+    <div ng-if="field.type === 'image_wsdata'"
+         ng-show="aPreviewImages.length > 0"
+         class="preview-images-zone">
+        <div ng-repeat="image in aPreviewImages"
+             ng-class="{'preview-image-small' : aPreviewImages.length !== 1}"
+             class="preview-image preview-show-{{$index}}">
+            <div class="image-cancel" data-no="{{$index}}" ng-if="!field.displayOnly" ng-click="removeFile(image)">
+                <span class="icon-trash"></span>
+            </div>
+            <div class="image-zone"><img ng-src="{{image.src}}"></div>
+            <div class="tools-edit-image">
+                <a href="javascript:void(0)"
+                   data-no="{{$index}}"
+                   class="btn btn-light btn-edit-image"
+                   ng-click="showImageModal(image)">
+                    <span class="icon-zoom-in"></span>
+                </a>
+            </div>
+        </div>
+    </div>
+
+    <!-- Visioneuse documents -->
+    <div ng-if="field.type === 'file_wsdata'" class="preview-documents-zone">
+        <div ng-repeat="document in aPreviewDocuments"
+             class="preview-document preview-show-{{$index}}">
+            <a class="document-link" href="javascript:void(0)" ng-click="downloadDocument(document)">{{document.name}}</a>
+            <span class="document-cancel" data-no="{{$index}}" ng-click="removeFile(document)"><span class="icon-trash"></span></span>
+         </div>
+    </div>
+
+    <!-- Fichiers à envoyer -->
+    <div class="input-group input-file-xxs" ng-if="!field.displayOnly" ng-click="searchNewFile()">
+        <div tabindex="500" class="form-control file-caption  kv-fileinput-caption">
+            <div ng-show="aFiles.length > 0" class="file-caption-name" title="7 fichiers sélectionné(s)"><span class="glyphicon glyphicon-file kv-caption-icon"></span> {{aFiles.length}} fichier(s)</div>
+            <div ng-show="aFiles.length == 0" class="file-caption-name" title="7 fichiers sélectionné(s)"><span class="glyphicon glyphicon-file kv-caption-icon"></span> Aucun fichier disponible</div>
+        </div>
+        <div class="input-group-btn">
+            <div tabindex="500" class="btn btn-primary btn-file"><i class="glyphicon glyphicon-folder-open"></i> &nbsp;Ajouter…</div>
+        </div>
+    </div>
+
+    <!-- Bouton caché input -->
+    <div class="hide"
+         ng-if="!field.displayOnly">
+        <!-- Simple -->
+        <input ng-if="!field.multiple"
+               type="file"
+               id="{{:refresh:field.id}}_hidden"
+               class="file"
+               name="{{:refresh:field.name}}"
+               class="form-control"
+               ng-attr-accept="{{ field.type === 'image_wsdata' ? 'image/*' : (field.formats.length > 0 ? getFormatsRestrict() : '*') }}"
+               ng-required="field.required">
+        <!-- Multiple -->
+        <input ng-if="field.multiple"
+               type="file"
+               id="{{:refresh:field.id}}_hidden"
+               class="file"
+               name="{{:refresh:field.name}}"
+               class="form-control"
+               ng-attr-accept="{{ field.type === 'image_wsdata' ? 'image/*' : (field.formats.length > 0 ? getFormatsRestrict() : '*') }}"
+               ng-required="field.required" multiple>
+    </div>
+
+</div>
diff --git a/client/javascript/externs/formReader/component/file_picker/file_picker.js b/client/javascript/externs/formReader/component/file_picker/file_picker.js
new file mode 100644
index 0000000000000000000000000000000000000000..6ae3324962dfceca6e2d9012e0c5959ed05771af
--- /dev/null
+++ b/client/javascript/externs/formReader/component/file_picker/file_picker.js
@@ -0,0 +1,723 @@
+/* global goog, ol, nsVitisComponent, vitisApp, formReader, bootbox */
+
+/**
+ * @author: Anthony Borghi, Armand Bahi
+ * @Description: Fichier contenant la classe nsVitisComponent.Map
+ * Permet d'instancier un composant OpenLayers3
+ */
+
+'use strict';
+
+goog.provide('nsVitisComponent.FilePicker');
+
+goog.require('formReader');
+goog.require('nsVitisComponent.Map');
+
+/**
+ * FilePicker Directive
+ * @param {type} $timeout
+ * @returns {nsVitisComponent.FilePickerDirective}
+ * @ngInject
+ */
+nsVitisComponent.FilePickerDirective = function ($timeout, $translate, propertiesSrvc, formReaderService, $log, $q) {
+    return {
+        restrict: 'A',
+        scope: {
+            'field': '=appField',
+            'oFormDefinition': '=appFormDef',
+            'sFormDefinitionName': '=appFormDefName',
+            'oFormValues': '=appFormVal'
+        },
+        controllerAs: 'ctrl',
+        templateUrl: window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port : '') + "/" + sessionStorage["appEnv"] + '/javascript/externs/formReader/component/file_picker/file_picker.html',
+        link: function (scope, element, attributes, controller, transcludeFn) {
+            $log.log("formReader.FilePickerDirective.link");
+
+            var fileInputLoaded = false;
+
+            /**
+             * URL vers le dossier fliepicker
+             */
+            scope['folderUrl'] = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port : '') + "/" + sessionStorage["appEnv"] + '/javascript/externs/formReader/component/file_picker';
+
+            /**
+             * Fichiers à envoyer
+             */
+            scope['aFiles'] = [];
+
+            /**
+             * Fichiers à supprimer
+             */
+            scope['aDeletedFiles'] = [];
+
+            /**
+             * Liste des URL des images à afficher
+             */
+            scope['aPreviewImages'] = [];
+
+            /**
+             * Liste des URL des documents à afficher
+             */
+            scope['aPreviewDocuments'] = [];
+
+            /**
+             * Initialise le composant
+             */
+            scope['initComponent'] = function() {
+                $log.log("formReader.FilePickerDirective.initComponent");
+
+                // Réinitialise les variables
+                scope['resetFileInput']();
+
+                // Initialise l'élément bootstrap fileInput
+                scope['initBootstrapFileInputs']().then(function(oInput) {
+
+                    // Écoute les changements pour afficher les images dans la liste
+                    if (!fileInputLoaded) {
+                        $(oInput).change(function(e){
+
+                            // Vide la liste quand on est pas en mode multiple
+                            if (scope['field']['multiple'] !== true) {
+                                scope['aFiles'] = [];
+                            }
+
+                            // Ajoute les fichiers
+                            scope['addFiles'](e);
+
+                            // Affichage des images/documents dans l'espace de prévisualisation
+                            scope['initPreview']();
+
+                            // Set la nouvelle valeur de l'attibut
+                            setTimeout(function () {
+                                oInput[0]['files_vitis'] = scope['aFiles'];
+
+                                // Sauvegarde les fichiers sur oFormValues
+                                scope['saveFiles']();
+                            });
+                        });
+                    }
+
+                    // Affiche les images présentes sur le serveur
+                    var aAvaliableFiles = scope["oFormValues"][scope["sFormDefinitionName"]][scope['field'].name];
+
+                    // Rétrocompatibilité API
+                    if (goog.isString(aAvaliableFiles)) {
+                        aAvaliableFiles = [aAvaliableFiles];
+                    }
+
+                    if (goog.isDefAndNotNull(aAvaliableFiles)) {
+                        scope['addFilesFromURL'](aAvaliableFiles).then(function(){
+
+                            // Initialise l'affichage des images
+                            scope['initPreview']();
+
+                            // Sauvegarde les fichiers sur oFormValues
+                            scope['saveFiles']();
+                        });
+                    }
+
+                    fileInputLoaded = true;
+                });
+            }
+
+            /**
+             * Réinitialise les variables du composant
+             *
+             * @return {type}  description
+             */
+            scope['resetFileInput'] = function() {
+                $log.log("formReader.FilePickerDirective.resetFileInput");
+
+                scope.$applyAsync(function(){
+                    scope['aFiles'] = [];
+                    scope['aPreviewImages'] = [];
+                    scope['aPreviewDocuments'] = [];
+                    scope['aDeletedFiles'] = [];
+                    scope['initPreview']();
+                });
+            }
+
+            /**
+             * Add the added files to scope.aFiles
+             * @param  {type} event
+             */
+            scope['addFiles'] = function(event) {
+                $log.log("formReader.FilePickerDirective.addFiles");
+
+                var files = event['target']['files'];
+                for (var i = 0; i < files.length; i++) {
+                    if (scope['isFilePresent'](files[i])) {
+                        console.error('file already present');
+                    } else {
+                        scope['aFiles'].push(files[i]);
+                    }
+                }
+            }
+
+            /**
+             * Lit les images à partir de leur URL
+             * @param  {array} aAvaliableFiles tableau d'URL
+             * @return {promise}
+             */
+            scope['addFilesFromURL'] = function(aAvaliableFiles) {
+                $log.log("formReader.FilePickerDirective.addFilesFromURL");
+
+                var deferred = $q.defer();
+                var iCounter = 0;
+
+                if (goog.isArray(aAvaliableFiles)) {
+
+                    // Assure que le ajaxLoader soit activé pendant le chargement
+                    for (var i = 0; i < 100; i++) {
+                        setTimeout(function () {
+                            if (iCounter < aAvaliableFiles.length) {
+                                showAjaxLoader();
+                            }
+                        }, i * 100);
+                    }
+
+                    for (var i = 0; i < aAvaliableFiles.length; i++) {
+
+                        scope['downloadFileBlob'](aAvaliableFiles[i], true, 3).then(
+                        function success(oBlob){
+                            scope['aFiles'].push(oBlob);
+                            iCounter++;
+                            if (iCounter === aAvaliableFiles.length) {
+                                deferred.resolve();
+                            }
+                        },
+                        function error(){
+                            iCounter++;
+                            if (iCounter === aAvaliableFiles.length) {
+                                deferred.resolve();
+                            }
+                        });
+                    }
+                }
+
+                return deferred.promise;
+            }
+
+            /**
+             * Télécharge le blob d'un fichier depuis son URL
+             *
+             * @param  {string} sFileUrl URL du fichier
+             * @param  {number|undefined} iRetry Nombre de fois à retenter en cas de problème
+             * @param  {boolean|undefined} bThumbnail True pour télécharger la thumbnail en priorité
+             * @return {promise} fonction prenant le blob en paramètre
+             */
+            scope['downloadFileBlob'] = function(sFileUrl, bThumbnail, iRetry) {
+                $log.log("formReader.FilePickerDirective.downloadFileBlob " + iRetry);
+
+                var deferred = $q.defer();
+                var bThumbnail = goog.isDefAndNotNull(bThumbnail) ? bThumbnail : false;
+                var oParams = {
+                    'type': scope['field']['type'] === 'image_wsdata' ? 'image' : 'document',
+                    'thumbnail' : bThumbnail === true ? true : false
+                };
+
+                if (goog.isString(sFileUrl)) {
+                    ajaxRequest({
+                        'method': 'GET',
+                        'url': sFileUrl,
+                        'headers': {
+                            'Accept': 'application/x-vm-json'
+                        },
+                        'params': oParams,
+                        'ajaxLoader': true,
+                        'responseType': 'blob',
+                        'success': function(response) {
+                            if (goog.isDefAndNotNull(response['data'])) {
+
+                                var oBlob = response['data'];
+
+                                // Trouve le nom du fichier
+                                var sFileName = scope['findFileNameInHeaders'](response['headers']);
+
+                                if (scope['field']['type'] === 'image_wsdata') {
+                                    // Vérification du type
+                                    if(response['headers']['content-type'] === 'application/x-vm-json; charset=UTF-8'){
+                                        if (bThumbnail === true) {
+                                            bThumbnail = false;
+                                            sFileName = null;
+                                        }
+                                    }
+                                }
+
+                                if (goog.isDefAndNotNull(sFileName)) {
+                                    oBlob['name'] = sFileName;
+                                    oBlob._url = sFileUrl;
+                                    oBlob._modified = false;
+                                    oBlob._thumbnail = bThumbnail;
+                                    deferred.resolve(oBlob);
+                                } else {
+                                    console.error('cannot get name from headers : ', response['headers']);
+
+                                    // Retry
+                                    if (goog.isDefAndNotNull(iRetry)) {
+                                        if (iRetry > 0) {
+                                            iRetry--;
+                                            scope['downloadFileBlob'](sFileUrl, bThumbnail, iRetry).then(
+                                                function success(oBlob){
+                                                    deferred.resolve(oBlob);
+                                                },
+                                                function error(){
+                                                    deferred.reject();
+                                                }
+                                            );
+                                        } else {
+                                            oBlob['name'] = sFileUrl.split('/')[sFileUrl.split('/').length - 1];
+                                            oBlob._url = sFileUrl;
+                                            oBlob._modified = false;
+                                            oBlob._thumbnail = bThumbnail;
+                                            deferred.resolve(oBlob);
+                                        }
+                                    }
+                                }
+
+                            } else {
+                                deferred.reject();
+                            }
+                        },
+                        'error': function(response) {
+                            deferred.reject();
+                        }
+                    });
+                } else {
+                    setTimeout(function () {
+                        deferred.reject();
+                    });
+                }
+
+                return deferred.promise;
+            }
+
+            /**
+             * Récupère le nom du fichier depuis les headers
+             *
+             * @param  {object} oHeaders Headers
+             * @return {string}          Nom du fichier
+             */
+            scope['findFileNameInHeaders'] = function(oHeaders) {
+                var sName;
+
+                if (goog.isDefAndNotNull(oHeaders)) {
+
+                    var sContentDisp = oHeaders['content-disposition'];
+
+                    // IE
+                    if (!goog.isDefAndNotNull(sContentDisp) &&
+                        goog.isDefAndNotNull(oHeaders['Content-disposition'])) {
+                        sContentDisp = oHeaders['Content-disposition'];
+                    }
+
+                    if (goog.isString(sContentDisp)) {
+                        var aContentDisp = sContentDisp.split(';');
+                        for (var i = 0; i < aContentDisp.length; i++) {
+                            if(aContentDisp[i].indexOf('filename=') !== -1){
+                                sName = aContentDisp[i].split('"')[1];
+                            }
+                        }
+                    }
+                }
+
+                if (goog.isDefAndNotNull(sName)) {
+                    sName = decodeURI(sName);
+                }
+
+                return sName;
+            }
+
+            /**
+             * Fonction pour télécharger les documents de type non image
+             *
+             * @param  {object} oFile
+             */
+            scope['downloadDocument'] = function(oFile) {
+                $log.log("formReader.FilePickerDirective.downloadDocument");
+
+                if (goog.isDefAndNotNull(oFile._url)) {
+                    scope['downloadFileBlob'](oFile._url).then(function(oBlob){
+                        scope['downloadFile'](oFile, oBlob);
+                    });
+                } else {
+                    scope['downloadFile'](oFile);
+                }
+            }
+
+            /**
+             * Download the given file
+             * @param  {object} oFile
+             * @param  {object|undefined} oBlob
+             */
+            scope['downloadFile'] = function(oFile, oBlob) {
+                $log.log("formReader.FilePickerDirective.downloadFile");
+
+                var sFileName = oFile['name'];
+
+                if (!goog.isDefAndNotNull(oBlob)) {
+                    // Trouve le fichier correspondant
+                    for (var i = 0; i < scope['aFiles'].length; i++) {
+                        if(scope['aFiles'][i]['name'] === oFile['name']){
+                            oBlob = scope['aFiles'][i];
+                        }
+                    }
+                }
+
+                // Télécharge le fichier
+                if (goog.isDefAndNotNull(oBlob)) {
+
+                    // IE
+                    if (window.navigator['msSaveOrOpenBlob']) {
+                        window.navigator['msSaveOrOpenBlob'](oBlob, sFileName);
+                    }
+                    // Others
+                    else {
+                        var a = document.createElement("a");
+                        var url = window.URL.createObjectURL(oBlob);
+                        document.body.appendChild(a);
+                        a.style = "display: none";
+                        a.href = url;
+                        a.download = sFileName;
+                        a.click();
+                        window.URL.revokeObjectURL(url);
+                    }
+                }
+            }
+
+            /**
+             * Initialise l'élément bootstrap fileInput
+             */
+            scope['initBootstrapFileInputs'] = function() {
+                $log.log("formReader.FilePickerDirective.initBootstrapFileInputs");
+
+                var deferred = $q.defer();
+                setTimeout(function () {
+                    var oInput = $(element).find('#' + scope['field']['id'] + '_hidden');
+                    deferred.resolve(oInput);
+                });
+
+                return deferred.promise;
+            }
+
+            /**
+             * Init the preview in scope.aPreviewImages or scope.aPreviewDocuments
+             */
+            scope['initPreview'] = function() {
+                $log.log("formReader.FilePickerDirective.initPreview");
+
+                var sPreviewContainer = 'aPreviewDocuments';
+                if (scope['field']['type'] === 'image_wsdata') {
+                    sPreviewContainer = 'aPreviewImages';
+                }
+
+                // Vide les images qui ont étés supprimées de aFiles
+                var bIsPresent;
+                for (var i = scope[sPreviewContainer].length - 1; i >= 0; i--) {
+                    bIsPresent = false;
+                    for (var ii = 0; ii < scope['aFiles'].length; ii++) {
+                        if (!bIsPresent) {
+                            if(scope[sPreviewContainer][i]['name'] === scope['aFiles'][ii]['name']){
+                                bIsPresent = true;
+                            }
+                        }
+                    }
+                    if (!bIsPresent) {
+                        scope[sPreviewContainer].splice(i, 1);
+                    }
+                }
+
+                // Ajoute les nouvelles images
+                var oFile;
+                for (var i = 0; i < scope['aFiles'].length; i++) {
+                    bIsPresent = false;
+                    oFile = scope['aFiles'][i];
+
+                    if (scope['field']['type'] === 'image_wsdata') {
+                        if (!oFile.type.match('image')) continue;
+                    }
+
+                    // Vérifie que l'image ne soit pas déjà ajoutée
+                    for (var ii = 0; ii < scope[sPreviewContainer].length; ii++) {
+                        if(scope['aFiles'][i]['name'] === scope[sPreviewContainer][ii]['name']){
+                            bIsPresent = true;
+                        }
+                    }
+
+                    // Ajoute les nouvelles images
+                    if (!bIsPresent) {
+                        var picReader = new FileReader();
+                        picReader.addEventListener('load', angular.bind(this, function (oFile, event) {
+                            var picFile = event.target;
+                            scope.$applyAsync(function(){
+                                scope[sPreviewContainer].push({
+                                    'name': oFile['name'],
+                                    'src': picFile['result'],
+                                    _url: oFile._url,
+                                    _thumbnail: oFile._thumbnail
+                                });
+                            });
+                        }, oFile));
+                        picReader.readAsDataURL(oFile);
+                    }
+                }
+
+                // Rafraichit la vue
+                scope.$applyAsync(function(){
+                    scope[sPreviewContainer] = scope[sPreviewContainer];
+                });
+            }
+
+            /**
+             * Test if a file is already present
+             * @param  {object} oFile file to test
+             * @return {boolean} true if the file is already present
+             */
+            scope['isFilePresent'] = function(oFile) {
+                $log.log("formReader.FilePickerDirective.isFilePresent");
+
+                var bIsFilePresent = false;
+                for (var i = 0; i < scope['aFiles'].length; i++) {
+                    if (scope['aFiles'][i]['name'] === oFile['name']) {
+                        bIsFilePresent = true;
+                    }
+                }
+                return bIsFilePresent;
+            }
+
+            /**
+             * Remove the given file
+             * @param  {object} oImage
+             */
+            scope['removeFile'] = function(oFile) {
+                $log.log("formReader.FilePickerDirective.removeFile");
+
+                bootbox['confirm']({
+                    'message': "Supprimer <b>" + oFile['name'] + "</b> ?",
+                    'buttons': {
+                        'confirm': {
+                            'label': 'Oui',
+                            'className': 'btn-danger'
+                        },
+                        'cancel': {
+                            'label': 'Non',
+                            'className': 'btn-default'
+                        }
+                    },
+                    'callback': function (result) {
+                        if (result) {
+
+                            scope['aDeletedFiles'].push(oFile['name']);
+
+                            for (var i = 0; i < scope['aFiles'].length; i++) {
+                                if(scope['aFiles'][i]['name'] === oFile['name']){
+                                    scope['aFiles'].splice(i, 1);
+                                }
+                            }
+
+                            // Affichage des images
+                            setTimeout(function () {
+
+                                // Initialise l'affichage des images
+                                scope['initPreview']();
+
+                                // Sauvegarde les fichiers sur oFormValues
+                                scope['saveFiles']();
+                            });
+                        }
+                    }
+                });
+            }
+
+            /**
+             * Show the finder to search no files to add
+             */
+            scope['searchNewFile'] = function() {
+                $log.log("formReader.FilePickerDirective.searchNewFile");
+
+                var oInput = $(element).find('#' + scope['field']['id'] + '_hidden');
+                $(oInput).click();
+            }
+
+            /**
+             * Show the given image on a modal
+             * @param  {object} oImage
+             * @param  {object|undefined} oBlob
+             */
+            scope['showImageModal'] = function(oImage, oBlob) {
+                $log.log("formReader.FilePickerDirective.showImageModal");
+
+                var iImageIndex;
+                for (var i = 0; i < scope['aPreviewImages'].length; i++) {
+                    if(scope['aPreviewImages'][i]['name'] === oImage['name']){
+                        iImageIndex = angular.copy(i);
+                    }
+                }
+
+                var domModalcontent = $('<div class="modal-image-zone"></div>');
+                var domImage = $('<img src="' + oImage['src'] + '">');
+
+                // Boutons suivant/précédent
+                var domImageToolsContainer = $('<div class="filepicker-image-zoom-modal-tools-container"></div>');
+                var domImageToolsPrev = $('<div class="filepicker-image-zoom-modal-tools-prev"><span class="icon-keyboard_arrow_left"></span></div>');
+                var domImageToolsNext = $('<div class="filepicker-image-zoom-modal-tools-next"><span class="icon-keyboard_arrow_right"></span></div>');
+
+                // Bouton télécharger
+                var domDownloadButtonContainer = $('<div class="filepicker-image-zoom-modal-button-container"></div>');
+                var domDownloadButton = $('<button type="button" class="btn btn-primary"><span class="glyphicon glyphicon-download-alt"></span></button>');
+
+                /**
+                 * Téléchargement de l'image
+                 */
+                $(domDownloadButton).click(function(){
+                    scope['downloadDocument'](oImage);
+                });
+
+                /**
+                 * Image suivante
+                 */
+                $(domImageToolsNext).click(function(){
+                    scope['showNextImageModal'](oImage, dialog);
+                });
+
+                /**
+                 * Image précédente
+                 */
+                $(domImageToolsPrev).click(function(){
+                    scope['showNextImageModal'](oImage, dialog, true);
+                });
+
+                // Image
+                $(domModalcontent).append(domImage);
+
+                // Boutons suivant/précédent
+                if (goog.isDefAndNotNull(iImageIndex)) {
+                    if (iImageIndex > 0) {
+                        $(domImageToolsContainer).append(domImageToolsPrev);
+                    }
+                    if (iImageIndex < scope['aPreviewImages'].length - 1) {
+                        $(domImageToolsContainer).append(domImageToolsNext);
+                    }
+                }
+
+                // Bouton téléchargement
+                $(domDownloadButtonContainer).append(domDownloadButton);
+
+                // Containers
+                $(domModalcontent).append(domDownloadButtonContainer);
+                $(domModalcontent).append(domImageToolsContainer);
+
+
+                var dialog = bootbox['dialog']({
+                    'title': oImage['name'],
+                    'className': 'filepicker-image-zoom-modal',
+                    'animate': false,
+                    'message': domModalcontent
+                });
+            }
+
+            /**
+             * Affiche l'image suivante
+             *
+             * @param  {object} oImage
+             * @param  {object} oBootbox
+             * @param  {boolean} bPrev true pour affiche l'ímage suivante
+             */
+            scope['showNextImageModal'] = function(oImage, oBootbox, bPrev) {
+                $log.log("formReader.FilePickerDirective.showNextImageModal");
+
+                if (!goog.isDefAndNotNull(oImage)) {
+                    console.error('oImage not defined or null');
+                    return 0;
+                }
+
+                if (goog.isDefAndNotNull(oImage['name'])) {
+
+                    var iImageIndex;
+                    for (var i = 0; i < scope['aPreviewImages'].length; i++) {
+                        if(scope['aPreviewImages'][i]['name'] === oImage['name']){
+                            iImageIndex = angular.copy(i);
+                        }
+                    }
+
+                    if (goog.isDefAndNotNull(iImageIndex)) {
+
+                        var oNextImage;
+                        if (bPrev === true) {
+                            oNextImage = scope['aPreviewImages'][iImageIndex - 1];
+                        } else {
+                            oNextImage = scope['aPreviewImages'][iImageIndex + 1];
+                        }
+
+                        if (goog.isDefAndNotNull(oNextImage)) {
+                            if (goog.isDefAndNotNull(oNextImage['name']) &&
+                                goog.isDefAndNotNull(oNextImage['src'])) {
+                                oBootbox['modal']('hide');
+                                scope['showImageModal'](oNextImage);
+                            }
+                        }
+
+                    } else {
+                        console.error('iImageIndex not founded');
+                    }
+                }
+
+            }
+
+            /**
+             * Sauvegarde les fichiers sur oFormValues
+             */
+            scope['saveFiles'] = function() {
+                $log.log("formReader.FilePickerDirective.saveFiles");
+
+                // Fichiers inchangés à ne pas re-envoyer
+                var aUnchangedFiles = [];
+                for (var i = 0; i < scope['aFiles'].length; i++) {
+                    if (scope['aFiles'][i]._modified === false) {
+                        aUnchangedFiles.push(scope['aFiles'][i]['name']);
+                    }
+                }
+                if (!goog.isDefAndNotNull(scope['oFormValues'][scope['sFormDefinitionName']]['vitis_unchanged_files'])) {
+                    scope['oFormValues'][scope['sFormDefinitionName']]['vitis_unchanged_files'] = {}
+                }
+                scope['oFormValues'][scope['sFormDefinitionName']]['vitis_unchanged_files'][scope['field'].name] = aUnchangedFiles;
+
+                // Fichiers à supprimer
+                if (!goog.isDefAndNotNull(scope['oFormValues'][scope['sFormDefinitionName']]['vitis_deleted_files'])) {
+                    scope['oFormValues'][scope['sFormDefinitionName']]['vitis_deleted_files'] = {}
+                }
+                scope['oFormValues'][scope['sFormDefinitionName']]['vitis_deleted_files'][scope['field'].name] = scope['aDeletedFiles'];
+
+                // Fichiers à envoyer
+                scope['oFormValues'][scope['sFormDefinitionName']][scope['field'].name] = scope['aFiles'];
+            }
+
+            /**
+             * Retourne les restrictions de type input=file
+             */
+            scope['getFormatsRestrict'] = function() {
+
+                var sRestrictedFormats;
+                var aImgExtencions = [''];
+
+                if (goog.isString(scope.field.formats)) {
+
+                    var aRestrictedFormats = [];
+                    var aFormats = scope.field.formats.split('|');
+
+                    for (var i = 0; i < aFormats.length; i++) {
+                        aRestrictedFormats.push('.' + aFormats[i]);
+                    }
+
+                    sRestrictedFormats = aRestrictedFormats.join(', ');
+
+                } else {
+                    sRestrictedFormats = scope.field.formats;
+                }
+
+                return sRestrictedFormats;
+            }
+        }
+    };
+};
+formReader.module.directive('appFilePicker', nsVitisComponent.FilePickerDirective);
diff --git a/client/javascript/externs/formReader/component/map.js b/client/javascript/externs/formReader/component/map.js
index 0482db946269fcaebace4fb649834a3e949861c0..84e8ba3cc709ec0c973afeee8a915f6be0de8a41 100755
--- a/client/javascript/externs/formReader/component/map.js
+++ b/client/javascript/externs/formReader/component/map.js
@@ -117,6 +117,10 @@ nsVitisComponent.Map = function (opt_option) {
      * @private
      */
     this.$log_ = angular.element(vitisApp.appHtmlFormDrtv).injector().get(["$log"]);
+    /**
+     * @private
+     */
+    this.$q_ = angular.element(vitisApp.appHtmlFormDrtv).injector().get(["$q"]);
 
     var this_ = this;
 
@@ -355,7 +359,31 @@ nsVitisComponent.Map = function (opt_option) {
         }
     }
 
-    /****************************************************Map*******************************/
+    var hiddenFeatures = angular.copy($("#" + this.hiddenFieldId).val());
+
+    // Initialise la carte
+    this.initMap(options).then(function(){
+        // Initialise l'étendue de la carte
+        this_.initExtent(options);
+        // Initialise le CSS de la carte
+        this_.initCSS(options);
+        // Initialise les features de la carte
+        this_.initFeatureOverlay(options, hiddenFeatures);
+    });
+
+    return this;
+};
+
+/**
+ * Initialise la carte
+ *
+ * @param  {object} options
+ */
+nsVitisComponent.Map.prototype.initMap = function (options) {
+    this.$log_.info('nsVitisComponent.Map.initMap');
+
+    var deferred = this.$q_.defer();
+    var this_ = this;
 
     this.MapObject = new ol.Map({
         target: this_.Target,
@@ -372,16 +400,54 @@ nsVitisComponent.Map = function (opt_option) {
         this.setExtent(options["center"]["extent"]);
     }
 
-    if (options["type"] === "vmap" && options["tree"]) {
-        this['aTree'] = this.loadTree(options["tree"]);
+    if (options["type"] === "vmap") {
+        if (options["tree"]) {
+            this['aTree'] = this.loadTree(options["tree"]);
+        } else if (options["map_id"]) {
+            this.getAjaxVmapMap(options["map_id"]).then(function(oMap){
+
+                // Set map
+                this_['aTree'] = this_.loadTree(oMap);
+
+                // Set proj_
+                this_.proj_ = this_.MapObject.getView().getProjection().getCode();
+
+                deferred.resolve(this_['aTree']);
+            },
+            // Cas où carte non trouvée
+            function(){
+                this_.MapObject.addLayer(new ol.layer.Tile({
+                    source: new ol.source.OSM()
+                }));
+                this_['aTree'] = [{
+                    'service': options["type"],
+                    'layers': this_.Layers
+                }];
+                setTimeout(function () {
+                    deferred.resolve(this_['aTree']);
+                });
+            });
+        }
     } else {
         this['aTree'] = [{
-                'service': options["type"],
-                'layers': this.Layers
-            }];
+            'service': options["type"],
+            'layers': this.Layers
+        }];
+        setTimeout(function () {
+            deferred.resolve(this_['aTree']);
+        });
     }
 
-    /****************************************Extent***************************************/
+    return deferred.promise;
+}
+
+/**
+ * Initialise l'étendue de la carte
+ *
+ * @param  {object} options
+ */
+nsVitisComponent.Map.prototype.initExtent = function (options) {
+    this.$log_.info('nsVitisComponent.Map.initExtent');
 
     // Vérifie que l'étendue soit valable
     var projExtent = ol.proj.get(options["proj"]).getExtent();
@@ -393,20 +459,34 @@ nsVitisComponent.Map = function (opt_option) {
         console.error('map extent oversized');
         this.MapObject.getView().fit(projExtent);
     }
+}
 
+/**
+ * Initialise lle CSS de la carte
+ *
+ * @param  {object} options
+ */
+nsVitisComponent.Map.prototype.initCSS = function (options) {
+    this.$log_.info('nsVitisComponent.Map.initCSS');
 
-    //****************************************CSS******************************************/
     $(".ol-mouse-position").css("bottom", "8px");
     $(".ol-mouse-position").css("top", "auto");
     $(".ol-mouse-position").css("background", this.appColor);
     $(".ol-mouse-position").css("color", "#ffffff");
     $(".ol-mouse-position").css("border-radius", "4px");
-
     $(".ol-scale-line").css("background", this.appColor);
-
     $(".ol-current-projection-studio").css("background", this.appColor);
+}
+
+/**
+ * Initialise les features de la carte
+ *
+ * @param  {object} options
+ */
+nsVitisComponent.Map.prototype.initFeatureOverlay = function (options, hiddenFeatures) {
+    this.$log_.info('nsVitisComponent.Map.initFeatureOverlay');
 
-    /****************************************Feature Overlay********************************/
+    var this_ = this;
 
     options["draw_color"] = goog.isDef(options["draw_color"]) ? options["draw_color"] : "rgba(54,184,255,0.6)";
     options["contour_color"] = goog.isDef(options["contour_color"]) ? options["contour_color"] : "rgba(0,0,0,0.4)";
@@ -464,40 +544,72 @@ nsVitisComponent.Map = function (opt_option) {
         source: this_.FeatureOverlay.getSource()
     })
 
-    var hiddenFeatures = $("#" + this.hiddenFieldId).val();
+    // Met en place les Features
+    this.setFeatures(hiddenFeatures);
+
+    // Écrit les features dans le champ hidden
+    this.Features.on("change", function () {
+        setTimeout(function () {
+            this_.saveFeatures();
+            this_.MapObject.dispatchEvent('moveend');
+        });
+    });
+}
+
+/**
+ * Set the map features
+ *
+ * @param  {string} hiddenFeatures GeoJSON features
+ */
+nsVitisComponent.Map.prototype.setFeatures = function (hiddenFeatures) {
+    this.$log_.info('nsVitisComponent.Map.setFeatures');
+
+    // Vide les précédentes features
+    this.FeatureOverlay.getSource().forEachFeature(function(oFeature){
+        this.FeatureOverlay.getSource().removeFeature(oFeature);
+    }, this);
 
     // Lit les features écrites dans le champ hidden
     if (goog.isDef(hiddenFeatures)) {
         if (!goog.string.isEmpty(hiddenFeatures) && hiddenFeatures !== "[object Object]") {
 
-            var aFeatures = this_.getFeaturesByString(hiddenFeatures);
+            var aFeatures = this.getFeaturesByString(hiddenFeatures);
 
             // Ajoute les features
             for (var i = 0; i < aFeatures.length; i++) {
                 this.FeatureOverlay.getSource().addFeature(aFeatures[i]);
             }
-            var featuresExtent = this.FeatureOverlay.getSource().getExtent();
-            this.MapObject.getView().fit(featuresExtent);
 
-            // En cas de simple point
-            if (aFeatures.length === 1) {
-                if (aFeatures[0].getGeometry().getType() === 'Point') {
-                    this.MapObject.getView().setZoom(12);
-                }
-            }
+            // Zoom sur les features
+            this.zoomOnFeatures();
+
+            // Sauvegarde les features
+            this.saveFeatures();
         }
     }
+}
 
-    // Écrit les features dans le champ hidden
-    this.Features.on("change", function () {
-        setTimeout(function () {
-            this_.saveFeatures();
-            this_.MapObject.dispatchEvent('moveend');
-        });
-    });
+/**
+ * Zoom sur les features de this.FeatureOverlay
+ */
+nsVitisComponent.Map.prototype.zoomOnFeatures = function () {
+    this.$log_.info('nsVitisComponent.Map.zoomOnFeatures');
 
-    return this;
-};
+    var aFeatures = this.FeatureOverlay.getSource().getFeatures();
+    var featuresExtent = this.FeatureOverlay.getSource().getExtent();
+
+    if (aFeatures.length > 0) {
+
+        this.MapObject.getView().fit(featuresExtent);
+
+        // En cas de simple point
+        if (aFeatures.length === 1) {
+            if (aFeatures[0].getGeometry().getType() === 'Point') {
+                this.MapObject.getView().setZoom(15);
+            }
+        }
+    }
+}
 
 /**
  * Save the current features
@@ -526,14 +638,20 @@ nsVitisComponent.Map.prototype.saveFeatures = function () {
 /**
  * Parse the string (GeoJSON, WKT) and return the features
  * @param {string} sFeatures
+ * @param {string} sFormat
  * @returns {Array}
+ * @export
  */
-nsVitisComponent.Map.prototype.getFeaturesByString = function (sFeatures) {
+nsVitisComponent.Map.prototype.getFeaturesByString = function (sFeatures, sFormat) {
     this.$log_.info('nsVitisComponent.Map.getFeaturesByString');
 
     var aFeatures = [];
 
-    if (this.outputFormat_ === 'wkt' || this.outputFormat_ === 'ewkt') {
+    if (!goog.isDefAndNotNull(sFormat)) {
+        sFormat = this.outputFormat_;
+    }
+
+    if (sFormat === 'wkt' || sFormat === 'ewkt') {
         if (ol.isEWKTGeom(sFeatures)) {
             aFeatures = ol.getFeaturesFromEWKT(sFeatures, this.proj_);
         } else {
@@ -547,7 +665,7 @@ nsVitisComponent.Map.prototype.getFeaturesByString = function (sFeatures) {
                 console.error('cannot read the geometry on format wkt');
             }
         }
-    } else if (this.outputFormat_ === 'geojson') {
+    } else if (sFormat === 'geojson') {
         try {
             aFeatures = this.geoJSONFormat.readFeatures(sFeatures, {
                 dataProjection: this.baseProj_,
@@ -1318,10 +1436,19 @@ nsVitisComponent.Map.prototype.activeInteraction = function (sInteraction, bForc
     this_.MapObject.addInteraction(this.snapInteraction_);
 };
 
+/**
+ * Get the formReaderScope
+ * @return {object} scope
+ */
 nsVitisComponent.Map.prototype.getFormReaderScope_ = function (){
     return angular.element(this.Target).scope();
 }
 
+/**
+ * Show the divide segment form
+ * @param  {string} sFeature1
+ * @param  {string} sFeature2
+ */
 nsVitisComponent.Map.prototype.showDividedSegmentForm_ = function (sFeature1, sFeature2){
 
     var this_ = this;
@@ -1674,6 +1801,62 @@ nsVitisComponent.Map.prototype.removeFeature = function (oFeature) {
     this.Features.changed();
 };
 
+/**
+ * Requête Ajax pour répupérer la définition d'une carte
+ * @param  {string} sMapId map_id
+ * @return {object} map json
+ */
+nsVitisComponent.Map.prototype.getAjaxVmapMap = function (sMapId) {
+
+    var deferred = this.$q_.defer();
+
+    if (!angular.isDefined(sMapId) || sMapId == '') {
+        console.error('map_id non valide');
+        deferred.reject();
+    } else {
+
+        // Récupère la liste des rapports disponibles
+        ajaxRequest({
+            'method': 'GET',
+            'url': this.$propertiesSrvc_["web_server_name"] + "/" + this.$propertiesSrvc_["services_alias"] + '/vmap/mapjsons/' + sMapId,
+            'headers': {
+                'Accept': 'application/x-vm-json'
+            },
+            'success': function (response) {
+                if (!goog.isDefAndNotNull(response['data'])) {
+                    console.error('response.data undefined: ', response);
+                    deferred.reject();
+                    return 0;
+                }
+                if (!goog.isDefAndNotNull(response['data']['data'])) {
+                    console.error('Aucune carte disponible pour ' + sMapId);
+                    deferred.reject();
+                    return 0;
+                }
+                if (!goog.isDefAndNotNull(response['data']['data'][0])) {
+                    console.error('Aucune carte disponible pour ' + sMapId);
+                    deferred.reject();
+                    return 0;
+                }
+                if (!goog.isDefAndNotNull(response['data']['data'][0]['children'])) {
+                    console.error('Aucune carte disponible pour ' + sMapId);
+                    deferred.reject();
+                    return 0;
+                }
+                if (!goog.isDefAndNotNull(response['data']['data'][0]['children'][1])) {
+                    console.error('Aucune carte disponible pour ' + sMapId);
+                    deferred.reject();
+                    return 0;
+                }
+                var oMap = response['data']['data'][0];
+                deferred.resolve(oMap);
+            }
+        });
+    }
+
+    return deferred.promise;
+}
+
 /**
  * Load a Map.json to set layers and view of the map
  * @param {Object} tree Json tree to set the map (he's generated by Vmap)
diff --git a/client/javascript/externs/formReader/component/map_workbench/map_workbench.js b/client/javascript/externs/formReader/component/map_workbench/map_workbench.js
index 32ed53d696a53063fe3dd4020ed207461e8f235a..76b09c686bef4b3411631f3042d7f1c92ef4929c 100755
--- a/client/javascript/externs/formReader/component/map_workbench/map_workbench.js
+++ b/client/javascript/externs/formReader/component/map_workbench/map_workbench.js
@@ -365,7 +365,7 @@ nsVitisComponent.MapWorkbenchDirective = function ($timeout) {
                                     break;
                             }
                         });
-                    });
+                    }, 500);
 
                     // Intancie le subform
                     setTimeout(function () {
@@ -745,7 +745,7 @@ nsVitisComponent.MapWorkbenchController.prototype.addFeature = function () {
         });
     });
 
-    // Désactive la possibilité d'ajouter des features    
+    // Désactive la possibilité d'ajouter des features
     this.unactiveDrawInteractions_();
 
     // Ajoute la/les nouvelle interaction
@@ -811,7 +811,7 @@ nsVitisComponent.MapWorkbenchController.prototype.setStyleFromType_ = function (
                             // La chaine commence et se finit par {{ et }} ?
                             if (oStyle[key1][key2].substr(0, 2) === '{{' && oStyle[key1][key2].substr(-2, 2) === '}}') {
 
-                                // Le contenu des balises est un des champs du formulaire ?                                
+                                // Le contenu des balises est un des champs du formulaire ?
                                 var sTag = oStyle[key1][key2].substr(2, oStyle[key1][key2].length - 4);
                                 if (goog.isDefAndNotNull(oAttributes[sTag])) {
                                     oStyle[key1][key2] = oAttributes[sTag];
@@ -887,4 +887,4 @@ nsVitisComponent.MapWorkbenchController.prototype.unactiveDrawInteractions_ = fu
 //            this.$scope_['oMap']['setInteractions'](this.$scope_['field']['map_options']['interactions']);
 //        }
 //    }
-};
\ No newline at end of file
+};
diff --git a/client/javascript/externs/formReader/formReader.html b/client/javascript/externs/formReader/formReader.html
index bec175d509bb7cc60b97fc19e66fa200af6d4c44..1640c7e5ca712e67e7df0335ddbfb45d697c7cea 100755
--- a/client/javascript/externs/formReader/formReader.html
+++ b/client/javascript/externs/formReader/formReader.html
@@ -368,25 +368,12 @@
                                     </div>
                                     <!-- image ws_data -->
                                     <div ng-switch-when="image_wsdata">
-                                        <label for="{{:refresh:field.id}}" id="{{:refresh:field.id}}_label" class="control-label" data-translate="{{:refresh:field.label}}"></label>
-                                        <!--Si l'image n'existe pas-->
-                                        <div ng-if="!field.displayOnly" ng-show="!oFormValues[sFormDefinitionName][field.name].length > 0">
-                                            <input ng-if="!field.multiple" data-app-form-field-specific-params="" type="file" id="{{:refresh:field.id}}" class="file" name="{{:refresh:field.name}}" class="form-control" ng-required="field.required">
-                                            <input ng-if="field.multiple" data-app-form-field-specific-params="" type="file" id="{{:refresh:field.id}}" class="file" name="{{:refresh:field.name}}" class="form-control" ng-required="field.required" multiple>
-                                        </div>
-                                        <!--Si l'image existe-->
-                                        <div ng-show="oFormValues[sFormDefinitionName][field.name].length > 0">
-                                            <img id="{{:refresh:field.id}}_img" ng-src="{{oFormValues[sFormDefinitionName][field.name]}}" alt="image" class="img-responsive" width="{{field.display_width}}"/>
-                                            <div ng-if="!field.displayOnly">
-                                                <div style="float: left; width:95%">
-                                                    <input ng-if="!field.multiple" data-app-form-field-specific-params="" type="file" id="{{:refresh:field.id}}" class="file" name="{{:refresh:field.name}}" class="form-control" ng-required="field.required">
-                                                    <input ng-if="field.multiple" data-app-form-field-specific-params="" type="file" id="{{:refresh:field.id}}" class="file" name="{{:refresh:field.name}}" class="form-control" ng-required="field.required" multiple>
-                                                </div>
-                                                <div style="float: right; width:5%">
-                                                    <a ng-href="{{oFormValues[sFormDefinitionName][field.name]}}" download class="btn btn-xs btn-success"><span class="glyphicon glyphicon-download"></span></a>
-                                                </div>
-                                            </div>
-                                        </div>
+                                        <div id="{{:refresh:field.id}}_image_picker"
+                                             data-app-file-picker=""
+                                             data-app-field="field"
+                                             data-app-form-def="oFormDefinition"
+                                             data-app-form-def-name="sFormDefinitionName"
+                                             data-app-form-val="oFormValues"></div>
                                     </div>
                                     <!-- Boutons -->
                                     <div ng-switch-when="button">
@@ -474,14 +461,12 @@
                                     </div>
                                     <!-- File WS_Data -->
                                     <div ng-switch-when="file_wsdata">
-                                        <label for="{{:refresh:field.id}}" id="{{:refresh:field.id}}_label" class="control-label" data-translate="{{:refresh:field.label}}"></label>
-                                        <div>
-                                            <a ng-href="{{oFormValues[sFormDefinitionName][field.name]}}" download>{{getLinkFileName(oFormValues[sFormDefinitionName][field.name])}}</a>
-                                        </div>
-                                        <div ng-if="!field.displayOnly && !field.disabled">
-                                            <input ng-if="!field.multiple" data-app-form-field-specific-params="" type="file" id="{{:refresh:field.id}}" class="file" name="{{:refresh:field.name}}" class="form-control" ng-required="field.required">
-                                            <input ng-if="field.multiple" data-app-form-field-specific-params="" type="file" id="{{:refresh:field.id}}" class="file" name="{{:refresh:field.name}}" class="form-control" ng-required="field.required" multiple>
-                                        </div>
+                                        <div id="{{:refresh:field.id}}_image_picker"
+                                             data-app-file-picker=""
+                                             data-app-field="field"
+                                             data-app-form-def="oFormDefinition"
+                                             data-app-form-def-name="sFormDefinitionName"
+                                             data-app-form-val="oFormValues"></div>
                                     </div>
                                     <!-- UI Grid -->
                                     <div ng-switch-when="ui_grid">
diff --git a/client/javascript/externs/formReader/formReaderDrtv.js b/client/javascript/externs/formReader/formReaderDrtv.js
index d49d434d947387edf9a9a3012541f8a1ff759652..7764774d42b8e94d95a855d92806dc03b85b10bd 100644
--- a/client/javascript/externs/formReader/formReaderDrtv.js
+++ b/client/javascript/externs/formReader/formReaderDrtv.js
@@ -7,6 +7,7 @@
  */
 goog.provide('formReader.directive.formReaderDirective');
 goog.require('nsVitisComponent.MapWorkbench');
+goog.require('nsVitisComponent.FilePicker');
 goog.require('formReader');
 
 /**
@@ -717,6 +718,13 @@ formReader.formReaderDirective = function ($q, formReaderService, propertiesSrvc
                 $(element).find(".file").each(function () {
                     $(this)['fileinput']('clear');
                 });
+
+                setTimeout(function () {
+                    $(element).find('[data-app-file-picker=""]').each(function(index, elem){
+                    	var oFilePickerScope = angular.element($(elem).children()).scope();
+                        oFilePickerScope['initComponent']();
+                    })
+                }, 500);
             };
 
             /**
@@ -899,88 +907,88 @@ formReader.appFormFieldSpecificParamsDrtv = function ($timeout, $translate, prop
                     // Intégration du plugin "bootstrap-fileinput" dans tous les champ d'upload de fichiers.
                     $(element)["fileinput"](oOptions);
                     break;
-                case 'file_wsdata':
-
-                    // Évite la multiplication des champs depuis le studio
-                    // quand on coche/décoche "Multiple documents"
-                    if ($(element).parent().find('.file-input').length > 0) {
-                        $(element).remove();
-                        break;
-                    }
-
-                    // Si sélection d'un fichier à uploader : sauve ses paramètres.
-                    element[0].addEventListener("change", function () {
-                        var oFileList = {
-                            "aFiles": this.files
-                        };
-                        scope["oFormValues"][scope["sFormDefinitionName"]][element[0].name] = oFileList;
-                    }, false);
-                    // Extensions de fichiers autorisées.
-                    if (typeof(scope["field"]["formats"]) != "undefined" && typeof(scope["field"]["extensions"]) == "undefined")
-                        scope["field"]["extensions"] = scope["field"]["formats"];
-                    if (goog.isDefAndNotNull(scope["field"]["extensions"])) {
-                        var aFilesExtensions = scope["field"]["extensions"].split('|');
-                        for (var iFileExtensionIndex in aFilesExtensions)
-                            aFilesExtensions[iFileExtensionIndex] = "." + aFilesExtensions[iFileExtensionIndex];
-                        element[0].setAttribute("accept", aFilesExtensions.join(","));
-                    }
-                    // Options
-                    var oOptions = {
-                        "showPreview": false,
-                        "showRemove": false,
-                        "showUpload": false,
-                    };
-
-                    if (typeof (scope["field"]["options"]) !== "undefined")
-                        goog.object.extend(oOptions, scope["field"]["options"]);
-
-                    oOptions["mainClass"] = "input-file-" + scope["oFormDefinition"][scope["sFormDefinitionName"]]["input_size"];
-                    // Langue.
-                    if (propertiesSrvc["language"] != "en")
-                        oOptions["language"] = propertiesSrvc["language"];
-                    // Intégration du plugin "bootstrap-fileinput" dans tous les champ d'upload de fichiers.
-                    $(element)["fileinput"](oOptions);
-                    break;
-                case 'image_wsdata':
-
-                    // Évite la multiplication des champs depuis le studio
-                    // quand on coche/décoche "Multiple documents"
-                    if ($(element).parent().find('.file-input').length > 0) {
-                        $(element).remove();
-                        break;
-                    }
-
-                    if (!goog.isDefAndNotNull(scope["field"]["display_width"])) {
-                        scope["field"]["display_width"] = "100%";
-                    }
-                    // Si sélection d'un fichier à uploader : sauve ses paramètres.
-                    element[0].addEventListener("change", function () {
-                        var oFileList = {
-                            "aFiles": this.files
-                        };
-                        if (goog.isDefAndNotNull(scope["field"]["width"]) && goog.isDefAndNotNull(scope["field"]["height"])) {
-                            oFileList["width"] = scope["field"]["width"];
-                            oFileList["height"] = scope["field"]["height"];
-                        }
-
-                        scope["oFormValues"][scope["sFormDefinitionName"]][element[0].name] = oFileList;
-                    }, false);
-                    // Passage de paramètres de définition du champ d'upload ?
-                    var oOptions = {
-                        "showPreview": false,
-                        "showRemove": false,
-                        "showUpload": false,
-                        "allowedFileTypes": ['image']
-                    };
-                    oOptions["mainClass"] = "input-file-" + scope["oFormDefinition"][scope["sFormDefinitionName"]]["input_size"];
-
-                    // Langue.
-                    if (propertiesSrvc["language"] != "en")
-                        oOptions["language"] = propertiesSrvc["language"];
-
-                    // Intégration du plugin "bootstrap-fileinput" dans tous les champ d'upload de fichiers.
-                    $(element)["fileinput"](oOptions);
-                    break;
+                // case 'file_wsdata':
+                //
+                //     // Évite la multiplication des champs depuis le studio
+                //     // quand on coche/décoche "Multiple documents"
+                //     if ($(element).parent().find('.file-input').length > 0) {
+                //         $(element).remove();
+                //         break;
+                //     }
+                //
+                //     // Si sélection d'un fichier à uploader : sauve ses paramètres.
+                //     element[0].addEventListener("change", function () {
+                //         var oFileList = {
+                //             "aFiles": this.files
+                //         };
+                //         scope["oFormValues"][scope["sFormDefinitionName"]][element[0].name] = oFileList;
+                //     }, false);
+                //     // Extensions de fichiers autorisées.
+                //     if (typeof(scope["field"]["formats"]) != "undefined" && typeof(scope["field"]["extensions"]) == "undefined")
+                //         scope["field"]["extensions"] = scope["field"]["formats"];
+                //     if (goog.isDefAndNotNull(scope["field"]["extensions"])) {
+                //         var aFilesExtensions = scope["field"]["extensions"].split('|');
+                //         for (var iFileExtensionIndex in aFilesExtensions)
+                //             aFilesExtensions[iFileExtensionIndex] = "." + aFilesExtensions[iFileExtensionIndex];
+                //         element[0].setAttribute("accept", aFilesExtensions.join(","));
+                //     }
+                //     // Options
+                //     var oOptions = {
+                //         "showPreview": false,
+                //         "showRemove": false,
+                //         "showUpload": false,
+                //     };
+                //
+                //     if (typeof (scope["field"]["options"]) !== "undefined")
+                //         goog.object.extend(oOptions, scope["field"]["options"]);
+                //
+                //     oOptions["mainClass"] = "input-file-" + scope["oFormDefinition"][scope["sFormDefinitionName"]]["input_size"];
+                //     // Langue.
+                //     if (propertiesSrvc["language"] != "en")
+                //         oOptions["language"] = propertiesSrvc["language"];
+                //     // Intégration du plugin "bootstrap-fileinput" dans tous les champ d'upload de fichiers.
+                //     $(element)["fileinput"](oOptions);
+                //     break;
+                // case 'image_wsdata':
+                //
+                //     // Évite la multiplication des champs depuis le studio
+                //     // quand on coche/décoche "Multiple documents"
+                //     if ($(element).parent().find('.file-input').length > 0) {
+                //         $(element).remove();
+                //         break;
+                //     }
+                //
+                //     if (!goog.isDefAndNotNull(scope["field"]["display_width"])) {
+                //         scope["field"]["display_width"] = "100%";
+                //     }
+                //     // Si sélection d'un fichier à uploader : sauve ses paramètres.
+                //     element[0].addEventListener("change", function () {
+                //         var oFileList = {
+                //             "aFiles": this.files
+                //         };
+                //         if (goog.isDefAndNotNull(scope["field"]["width"]) && goog.isDefAndNotNull(scope["field"]["height"])) {
+                //             oFileList["width"] = scope["field"]["width"];
+                //             oFileList["height"] = scope["field"]["height"];
+                //         }
+                //
+                //         scope["oFormValues"][scope["sFormDefinitionName"]][element[0].name] = oFileList;
+                //     }, false);
+                //     // Passage de paramètres de définition du champ d'upload ?
+                //     var oOptions = {
+                //         "showPreview": false,
+                //         "showRemove": false,
+                //         "showUpload": false,
+                //         "allowedFileTypes": ['image']
+                //     };
+                //     oOptions["mainClass"] = "input-file-" + scope["oFormDefinition"][scope["sFormDefinitionName"]]["input_size"];
+                //
+                //     // Langue.
+                //     if (propertiesSrvc["language"] != "en")
+                //         oOptions["language"] = propertiesSrvc["language"];
+                //
+                //     // Intégration du plugin "bootstrap-fileinput" dans tous les champ d'upload de fichiers.
+                //     $(element)["fileinput"](oOptions);
+                //     break;
                 case 'email':
                     scope["field"]["pattern"] = "^(([a-zA-Z]|[0-9])|([-]|[_]|[.]))+[@](([a-zA-Z]|[0-9])|([-]|[_]|[.])){2,63}[.](([a-zA-Z0-9]){2,63})+$";
                     // Utile dans le studio pour mémoriser la patern
@@ -1375,7 +1383,9 @@ formReader.appFormFieldSpecificParamsDrtv = function ($timeout, $translate, prop
                         scope['oFormValues'][scope['sFormDefinitionName']][scope['field']['name']] = 0;
                     // Quand le slider change par l'action de l'utilisateur
                     $(element).on('slide', function () {
-                        scope['oFormValues'][scope['sFormDefinitionName']][scope['field']['name']] = $(element)['slider']('getValue');
+                        scope.$applyAsync(function(){
+                            scope['oFormValues'][scope['sFormDefinitionName']][scope['field']['name']] = $(element)['slider']('getValue');
+                        });
                     });
                     // Quand la valeur dans oFormValues du slider change
                     scope.$watch('oFormValues.' + scope['sFormDefinitionName'] + '["' + scope['field']['name'] + '"]', function () {
diff --git a/client/javascript/externs/mapJSON/MapJSON.js b/client/javascript/externs/mapJSON/MapJSON.js
index 0d98c0487f4de44920d87be6070a95f0702f16da..1f631eb3536f9f76a3bd0b8a29c2a790df270b98 100755
--- a/client/javascript/externs/mapJSON/MapJSON.js
+++ b/client/javascript/externs/mapJSON/MapJSON.js
@@ -597,8 +597,11 @@ MapJSON.prototype.getLayerFromLayerDef_ = function (oMapDefinition, oLayerDef, o
     }
     if (goog.isDefAndNotNull(oLayerDef['is_filtered'])) {
         if (goog.isDefAndNotNull(oLayerDef['filter_form'])) {
-            if (goog.isString(oLayerDef['filter_form']) && oLayerDef['filter_form'].length !== 0) {
-                oLayerDef['filter_form'] = JSON.parse(oLayerDef['filter_form']);
+            if ((goog.isString(oLayerDef['filter_form']) && oLayerDef['filter_form'].length !== 0) ||
+                goog.isObject(oLayerDef['filter_form'])) {
+                if (goog.isString(oLayerDef['filter_form'])) {
+                    oLayerDef['filter_form'] = JSON.parse(oLayerDef['filter_form']);
+                }
                 oLayerDef['filter_values'] = this.getFilterFormValues_(oLayerDef['filter_form']);
                 layer.set('filter_form', oLayerDef['filter_form']);
                 layer.set('filter_form_embedjs', oLayerDef['filter_form_embedjs']);
diff --git a/client/modules/vitis/forms/user/user_vitis_user.json b/client/modules/vitis/forms/user/user_vitis_user.json
index a5dacd7e5ccd46a776a8381a46c298adce8ab53f..6042656ced48e36bbf7436f68eb19e85f3acec83 100644
--- a/client/modules/vitis/forms/user/user_vitis_user.json
+++ b/client/modules/vitis/forms/user/user_vitis_user.json
@@ -139,7 +139,7 @@
                         "type": "radio",
                         "name": "dataencrypt",
                         "label": "FORM_DATAENCRYPT_USER_USER",
-                        "nb_cols": 5,
+                        "nb_cols": 3,
                         "options": {
                             "choices": [
                                 {
@@ -152,22 +152,31 @@
                                 }
                             ]
                         },
-                        "default_value": false
+                        "default_value": false,
+                        "visible": "oProperties.enableEncryptionForUsers == true ? true : false"
                     },
                     {
                         "type": "hidden",
                         "name": "encrypted_secretkey",
                         "label": "",
                         "required": false,
-                        "nb_cols": 1
+                        "nb_cols": 1,
+                        "visible": "oProperties.enableEncryptionForUsers == true ? true : false"
                     },
                     {
-                        "type": "text",
+                        "type": "password",
                         "name": "secretkey",
                         "label": "FORM_SECRETKEY_USER_USER",
                         "required": false,
                         "pattern": "^[^\t\r\n]+$",
-                        "nb_cols": 6
+                        "visible": "= {{dataencrypt}} === true && oProperties.enableEncryptionForUsers",
+                        "tooltip": {
+                                "title":"FORM_DATAENCRYPT_TOOLTIP_USER_TITLE",
+                                "content":"FORM_DATAENCRYPT_TOOLTIP_USER_USER",
+                                "container": "body",
+                                "html": true
+                        },
+                        "nb_cols": 8
                     }
                 ]
             },
diff --git a/client/modules/vitis/forms/users/users_vitis_users.json b/client/modules/vitis/forms/users/users_vitis_users.json
index 6e19d03d238b88849bb454975ecb262f80d9e886..39aa385fab9aadd53d3a3194c903da6c631e32e8 100644
--- a/client/modules/vitis/forms/users/users_vitis_users.json
+++ b/client/modules/vitis/forms/users/users_vitis_users.json
@@ -646,8 +646,7 @@
                             "parameters":{
                                 "order_by":"billinggroup"
                             }
-                        },
-                        "default_value": -1
+                        }
                     }
                 ]
             },
diff --git a/client/modules/vitis/javascript/script_module.js b/client/modules/vitis/javascript/script_module.js
index 716b8de908bc4dfb057afe25e838d92aa2463531..b15867c294f4eb7a6c96f0103c68d1c1af9c2b04 100644
--- a/client/modules/vitis/javascript/script_module.js
+++ b/client/modules/vitis/javascript/script_module.js
@@ -34,13 +34,11 @@ vitisApp.on('appMainDrtvLoaded', function () {
         // Cryptage de la clé secrète.
         var oFormValues = envSrvc["oFormValues"][envSrvc["sFormDefinitionName"]];
         oFormValues['encrypted_secretkey'] = "";
-        if (oFormValues['dataencrypt'] === true && (goog.isDefAndNotNull(oFormValues['secretkey']) && oFormValues['secretkey'] != "" && oFormValues['secretkey'].substr(0, 2) !== "0x")) {
+        if ((goog.isDefAndNotNull(oFormValues['secretkey']) && oFormValues['secretkey'] != "" && oFormValues['secretkey'].substr(0, 2) !== "0x")) {
             var key = oFormValues['login'];
             var ciphertext = des(key, oFormValues['secretkey'], 1, 0);
             oFormValues['encrypted_secretkey'] = stringToHex(ciphertext);
         }
-        else if (oFormValues['dataencrypt'] === false)
-            oFormValues['secretkey'] = "";
         // Changement de mot de passe ?
         var oPasswordInput = document.querySelector("form[name='" + envSrvc["oFormDefinition"][envSrvc["sFormDefinitionName"]]["name"] + "'] input[name='password']");
         var oPasswordConfirmInput = document.querySelector("form[name='" + envSrvc["oFormDefinition"][envSrvc["sFormDefinitionName"]]["name"] + "'] input[name='password_confirm']");
@@ -101,7 +99,7 @@ vitisApp.on('appMainDrtvLoaded', function () {
             }
         }
         // Décryptage de la clé secrète.
-        if (oFormValues['dataencrypt'] === true && (goog.isDefAndNotNull(oFormValues['secretkey']) && oFormValues['secretkey'] != "" && oFormValues['secretkey'].substr(0, 2) === "0x")) {
+        if ((goog.isDefAndNotNull(oFormValues['secretkey']) && oFormValues['secretkey'] != "" && oFormValues['secretkey'].substr(0, 2) === "0x")) {
             var key = oFormValues['login'];
             // Décrypte et Suprimme les caractères "NULL".
             oFormValues['secretkey'] = des(key, hexToString(oFormValues['secretkey']), 0, 0).replace(/\x00/g, "");
@@ -284,7 +282,7 @@ vitisApp.on('appMainDrtvLoaded', function () {
             link: function (scope, element, attrs) {
                 // 1er affichage ou tri de la liste : maj de la mise en forme.
                 var clearObserver = attrs.$observe("appUserRoleColumn", function (value) {
-                    // Si le champ est vide : supprime l'icône.  
+                    // Si le champ est vide : supprime l'icône.
                     if (scope["row"]["entity"][scope["col"]["field"]] === null || scope["row"]["entity"][scope["col"]["field"]] === "")
                         element[0].className = "";
                     else {
@@ -700,35 +698,33 @@ vitisApp.on('appMainDrtvLoaded', function () {
                 }
             }
             // Chargement de la clé secrète (le web service vitis/users ne doit pas la retourner).
-            if (oFormValues['dataencrypt'] === true) {
-                var oUrlParams = {
-                    "schema": "s_vitis",
-                    "table": "user",
-                    "filter": {
-                        "relation": "AND",
-                        "operators": [{
-                                "column": "user_id",
-                                "compare_operator": "=",
-                                "value": oFormValues["user_id"]
-                            }]
-                    },
-                    "attributs": "secretkey"
-                };
-                ajaxRequest({
-                    "method": "GET",
-                    "url": propertiesSrvc["web_server_name"] + "/" + propertiesSrvc["services_alias"] + "/vitis/genericquerys/user",
-                    "params": oUrlParams,
-                    "success": function (response) {
-                        if (response["data"]["status"] != 0) {
-                            // Décryptage de la clé secrète.
-                            var oUserSecretKey = envSrvc["extractWebServiceData"]("genericquerys", response["data"])[0];
-                            if (goog.isDefAndNotNull(oUserSecretKey['secretkey']) && oUserSecretKey['secretkey'] != "" && oUserSecretKey['secretkey'].substr(0, 2) === "0x") {
-                                oFormValues['secretkey'] = des (oFormValues['login'], hexToString(oUserSecretKey['secretkey']), 0, 0).replace(/\x00/g, "");
-                            }
-                        }
-                    }
-                });
-            }
+              var oUrlParams = {
+                  "schema": "s_vitis",
+                  "table": "user",
+                  "filter": {
+                      "relation": "AND",
+                      "operators": [{
+                              "column": "user_id",
+                              "compare_operator": "=",
+                              "value": oFormValues["user_id"]
+                          }]
+                  },
+                  "attributs": "secretkey"
+              };
+              ajaxRequest({
+                  "method": "GET",
+                  "url": propertiesSrvc["web_server_name"] + "/" + propertiesSrvc["services_alias"] + "/vitis/genericquerys/user",
+                  "params": oUrlParams,
+                  "success": function (response) {
+                      if (response["data"]["status"] != 0) {
+                          // Décryptage de la clé secrète.
+                          var oUserSecretKey = envSrvc["extractWebServiceData"]("genericquerys", response["data"])[0];
+                          if (goog.isDefAndNotNull(oUserSecretKey['secretkey']) && oUserSecretKey['secretkey'] != "" && oUserSecretKey['secretkey'].substr(0, 2) === "0x") {
+                              oFormValues['secretkey'] = des (oFormValues['login'], hexToString(oUserSecretKey['secretkey']), 0, 0).replace(/\x00/g, "");
+                          }
+                      }
+                  }
+              });
         });
     };
 
@@ -1693,9 +1689,9 @@ vitisApp.on('appMainDrtvLoaded', function () {
 
         envSrvc["oFormDefinition"][envSrvc["sFormDefinitionName"]]["rows"].push(aMethodFields);
         envSrvc["oFormValues"][envSrvc["sFormDefinitionName"]]["tr_status_method"] = sMethod;
-        // l'event submit du formulaire sera appelé après cette fonction 
+        // l'event submit du formulaire sera appelé après cette fonction
     };
-    
+
     /**
      * loadVitisExploitation function.
      * Chargement des sections du mode "exploitation".
@@ -1709,7 +1705,7 @@ vitisApp.on('appMainDrtvLoaded', function () {
         // Passage en mode "Update" (pour afficher les sections).
         envSrvc["sMode"] = "update";
     };
-    
+
     /**
      * initExploitationSharedDirectoryForm function.
      * Chargement de l'arborescence du répertoire partagé.
@@ -1738,7 +1734,7 @@ vitisApp.on('appMainDrtvLoaded', function () {
             document.getElementById(oFormElementDefinition["id"] + "_treeview").style.height = document.getElementById("container_mode_exploitation").clientHeight - 221 + "px";
         });
     };
-    
+
     /**
      * refreshExploitationSharedDirectoryTreeview function.
      * Recharge les données du treeview du répertoire partagé.
@@ -1761,7 +1757,7 @@ vitisApp.on('appMainDrtvLoaded', function () {
             document.getElementById(oFormElementDefinition["id"] + "_treeview").style.height = document.getElementById("container_mode_exploitation").parentElement.clientHeight - 221 + "px";
         });
     };
-    
+
     /**
      * loadExploitationSharedDirectoryTreeviewData function.
      * Charge les données du treeview du répertoire partagé.
@@ -1788,7 +1784,7 @@ vitisApp.on('appMainDrtvLoaded', function () {
         });
         return promise;
     };
-    
+
     /**
      * deleteSharedDirectoryFiles function.
      * Charge les données du treeview du répertoire partagé.
@@ -1846,7 +1842,7 @@ vitisApp.on('appMainDrtvLoaded', function () {
             $rootScope["modalWindow"]("confirm", "", oOptions);
         }
     };
-    
+
     /**
      * createSharedDirectoryFolder function.
      * Création d'un répertoire dans le répertoire partagé.
@@ -1897,7 +1893,7 @@ vitisApp.on('appMainDrtvLoaded', function () {
             $rootScope["modalWindow"]("dialog", "FORM_NO_DIRECTORY_EXPLOITATION_EXPLOITATION", {"className": "modal-danger"});
         }
     };
-    
+
     /**
      * uploadSharedDirectoryFile function.
      * Upload d'un fichier dans le répertoire partagé.
@@ -1952,4 +1948,4 @@ vitisApp.on('appMainDrtvLoaded', function () {
             $rootScope["modalWindow"]("dialog", "FORM_NO_FILE_EXPLOITATION_EXPLOITATION", {"className": "modal-danger"});
         }
     };
-});
\ No newline at end of file
+});
diff --git a/client/modules/vitis/lang/lang-en.json b/client/modules/vitis/lang/lang-en.json
index dc98a487ebd474dde8d906521ce10687fd6b5a2f..b383a8afabdd1545ff87faa5fea21f8ec3df13d8 100644
--- a/client/modules/vitis/lang/lang-en.json
+++ b/client/modules/vitis/lang/lang-en.json
@@ -152,6 +152,8 @@
     "FORM_PHONE_USER_USER" : "Telephone number (for SMS) in international E.164 format (+33 6 xx xx xx xx)",
     "FORM_TIMEZONE_ID_USER_USER" : "Time Zone",
     "FORM_FORMATDATE_ID_USER_USER" : "Date display format",
+    "FORM_DATAENCRYPT_TOOLTIP_USER_TITLE" : "Data encryption",
+    "FORM_DATAENCRYPT_TOOLTIP_USER_USER" : "The results produced by GTF will be encrypted using the \"AES 256\" algorithm, you will need a suitable tool to open these files. (ex: 7zip)",
     "": "",
     "USER_ROLE_TOOLTIP_TITLE_USERS_USER": "Description",
     "USER_ROLE_ADMIN_TOOLTIP_CONTENT_USERS_USER": "Administrator",
diff --git a/client/modules/vitis/lang/lang-fr.json b/client/modules/vitis/lang/lang-fr.json
index 9a882aa04bad00781d3d5434d5ee6f052a6bd3c3..7ed0e0437dbb44de208982f66b059dcdaf04ff1b 100644
--- a/client/modules/vitis/lang/lang-fr.json
+++ b/client/modules/vitis/lang/lang-fr.json
@@ -147,13 +147,15 @@
     "FORM_INFORMATIONS_USER_USER": "Informations",
     "FORM_NEW_PASSWORD_USER_USER": "Nouveau mot de passe",
     "ACCOUNT_UNSUBSCRIBE" : "Désactiver votre compte",
-    "FORM_DATAENCRYPT_USER_USER" : "Cryptage des données",
-    "FORM_SECRETKEY_USER_USER" : "Clé secrète",
+    "FORM_DATAENCRYPT_USER_USER" : "Cryptage des documents générés",
+    "FORM_SECRETKEY_USER_USER" : "Clé de chiffrement",
     "FORM_BILLINGGROUP_ID_USERS_USER" : "Groupe de facturation",
     "FORM_ACCEPTNOTIFICATION_USER_USER" : "Acceptez-vous de recevoir des notifications par SMS ?",
     "FORM_PHONE_USER_USER" : "N° de téléphone (pour SMS) au format international E.164 (+33 6 xx xx xx xx)",
     "FORM_TIMEZONE_ID_USER_USER" : "Fuseau horaire",
     "FORM_FORMATDATE_ID_USER_USER" : "Format d'affichage des dates",
+    "FORM_DATAENCRYPT_TOOLTIP_USER_TITLE" : "Cryptage des données",
+    "FORM_DATAENCRYPT_TOOLTIP_USER_USER" : "Le cryptage des données entraîne le chiffrement avec l’algorithme AES-256 des jeux de données générés par GTF et téléchargeables. Si la clef de chiffrement personnelle n'est pas définie, une clé est générée aléatoirement à chaque traitement et envoyée à l'utilisateur avec le mail optionnel de notification. Cette clef ne peut pas être retrouvée et le message ne peut pas être renvoyé. Les utilisateurs doivent donc demander a être notifiés de la fin du traitement (fonctionnement par défaut) si leur clé de chiffrement n'est pas définie.",
     "": "",
     "USER_ROLE_TOOLTIP_TITLE_USERS_USER": "Description",
     "USER_ROLE_ADMIN_TOOLTIP_CONTENT_USERS_USER": "Administrateur",
diff --git a/vas/rest/class/vmlib/files/Files_manager.class.inc b/vas/rest/class/vmlib/files/Files_manager.class.inc
index a3f047854d7d7cf08b11267e193b2382b37f07e3..df8a89bcac44e25f15f3aa2dc9f91c906791b541 100644
--- a/vas/rest/class/vmlib/files/Files_manager.class.inc
+++ b/vas/rest/class/vmlib/files/Files_manager.class.inc
@@ -40,6 +40,7 @@ class Files_manager{
                 break;
         }
     }
+
     /**
      *This method allow to upload a file on a server.
      *@file vmlib/phpUtil.inc
@@ -48,7 +49,7 @@ class Files_manager{
      *@param $sServerPath New path of the file.
      *@param $sMaxSize Maximal size of the file.
      *@param $aFileValues File structure generated by extractFileStruct.
-     *@return $sErrorMsg The error message or the final file path on success.
+     *@return $aReturn
      */
     private function uploadFile($sNomObjet, $sFileType, $sServerPath, $sMaxSize, $aFileValues) {
         loadLang($this->sFolderLib, $this->oProperties["language"], $this->sFolderLib . "/");
@@ -63,10 +64,12 @@ class Files_manager{
         );
         $aForbiddenExtension = explode('|', str_replace("*.", "", $this->oProperties['forbidden_extension']));
         $sTmpFile = "";
+        $sFileName = "";
         $sErrorMsg = "";
         // si pas de aValues il y a eu une erreur pendant l'upload dans tmp du PUT
         if (!empty($aFileValues)){
             $sTmpFile = $aFileValues['tmp_name'];
+            $sFileName = $aFileValues['name'];
 
             // Si l'utilisateur n'a indiqué aucun fichier à uploader, il ne se passe rien
             if ($sTmpFile == '') {
@@ -133,12 +136,31 @@ class Files_manager{
                 }
 
                 // si c'est une image avec les infos de taille final on resample
-                if($sFileType === "image" && (isset($aFileValues["width"]) && isset($aFileValues["height"]))){
-                    $sTmpFile = pictureResampler($sTmpFile, $aFileValues["width"], $aFileValues["height"], 0, 0, 0, 0, $sFileExtension);
-                    if(!$sTmpFile){
-                        $bAllowUpload = false;
-                        writeToErrorLog(ERROR_COPYING_FILE . $aFileValues['name'] . ON_SERVER_PHPUTIL . ', while resampling picture, ' . $sServerPath);
-                        $sErrorMsg = ERROR_COPYING_FILE . $aFileValues['name'] . ON_SERVER_PHPUTIL . '.';
+                if(isset($aFileValues["width"]) && isset($aFileValues["height"])){
+
+                    // Si le fichier est une image
+                    if(@is_array(getimagesize($sTmpFile))){
+
+                        // Resample l'image
+                        $sTmpFile = pictureResampler($sTmpFile, $aFileValues["width"], $aFileValues["height"], 0, 0, 0, 0, $sFileExtension);
+
+                        // Change l'extension de la destination
+                        $sMinFileExtension = $this->extension($sTmpFile);
+                        $aDestPathExt = explode('.', $sServerPath);
+                        array_pop($aDestPathExt);
+                        array_push($aDestPathExt, 'jpg');
+                        $sServerPath = implode('.', $aDestPathExt);
+
+                        $aFileExt = explode('.', $sFileName);
+                        array_pop($aFileExt);
+                        array_push($aFileExt, 'jpg');
+                        $sFileName = implode('.', $aFileExt);
+
+                        if(!$sTmpFile){
+                            $bAllowUpload = false;
+                            writeToErrorLog(ERROR_COPYING_FILE . $aFileValues['name'] . ON_SERVER_PHPUTIL . ', while resampling picture, ' . $sServerPath);
+                            $sErrorMsg = ERROR_COPYING_FILE . $aFileValues['name'] . ON_SERVER_PHPUTIL . '.';
+                        }
                     }
                 }
 
@@ -158,7 +180,11 @@ class Files_manager{
             $sErrorMsg = FILE_LABEL_PHPUTIL . "File" . ERROR_DOWNLOAD_SERVER . '.';
         }
 
-        return $sErrorMsg;
+        return array(
+            'message' => $sMessage,
+            'file_name' => $sFileName,
+            'file_path' => $sServerPath
+        );
     }
 
     /**
@@ -172,23 +198,37 @@ class Files_manager{
         $aFileStruc = array();
         if (!isset($aValues[$sField . "_name"])){
             // Extract From Post $File Struct
-            $aFileStruc = array(
-                "name" => $_FILES[$sField]['name'],
-                "tmp_name" => $_FILES[$sField]['tmp_name'],
-                "error" => $_FILES[$sField]['error'],
-                "size" => $_FILES[$sField]['size']
-            );
+            if (!empty($_FILES[$sField]['name'])) {
+                $aFileStruc = array(
+                    "name" => $_FILES[$sField]['name'],
+                    "tmp_name" => $_FILES[$sField]['tmp_name'],
+                    "error" => $_FILES[$sField]['error'],
+                    "size" => $_FILES[$sField]['size']
+                );
+                if (!empty($_FILES[$sField]['width'])) {
+                    $aFileStruc['width'] = $_FILES[$sField]['width'];
+                }
+                if (!empty($_FILES[$sField]['height'])) {
+                    $aFileStruc['height'] = $_FILES[$sField]['height'];
+                }
+            }
 
             // Fichiers multiples
-            if (!empty($_FILES[$sField][0])) {
-                $aFileStruc = [];
+            if (!empty($_FILES[$sField][0]['name'])) {
                 for ($i=0; $i < count($_FILES[$sField]); $i++) {
-                    $aFileStruc[] = array(
+                    $aTmpFileStruc = array(
                         "name" => $_FILES[$sField][$i]['name'],
                         "tmp_name" => $_FILES[$sField][$i]['tmp_name'],
                         "error" => $_FILES[$sField][$i]['error'],
                         "size" => $_FILES[$sField][$i]['size']
                     );
+                    if (!empty($_FILES[$sField][$i]['width'])) {
+                        $aTmpFileStruc['width'] = $_FILES[$sField][$i]['width'];
+                    }
+                    if (!empty($_FILES[$sField]['height'])) {
+                        $aTmpFileStruc['height'] = $_FILES[$sField][$i]['height'];
+                    }
+                    array_push($aFileStruc, $aTmpFileStruc);
                 }
             }
 
@@ -219,6 +259,70 @@ class Files_manager{
         return $aFileStruc;
     }
 
+    /**
+     * Upload la miniature du fichier si il s'agit l'image ou retourne false
+     *
+     * @param  {array} $aFileStruct    Structure du fichier
+     * @param  {string} $sDestPath     Répertoire de destination
+     * @param  {string} $sField        Champ en base de données
+     * @param  {string} $sMinDestPath  Répertoire de destination du fichier min
+     * @param  {integer} $iMaxSize     Taille maximale du fichier
+     * @return {boolean}               true en cas de succes
+     */
+    private function uploadImageThumbnail($aFileStruct, $sDestPath, $sField, $sMinDestPath, $iMaxSize = -1) {
+
+        $aImgSize = getimagesize($aFileStruct['tmp_name']);
+
+        // Si image
+        if(@is_array($aImgSize)){
+
+            $aThumbnailFileStruct = $aFileStruct;
+            $aThumbnailFileStruct["width"] = 200;
+            $aThumbnailFileStruct["height"] = 200;
+
+            if (is_int($this->oProperties['filesystem_thumbnails_max_width']) &&
+                is_int($this->oProperties['filesystem_thumbnails_max_height'])) {
+
+                $imgWidth = $aImgSize[0];
+                $imgHeight = $aImgSize[1];
+
+                // Redimentionne la largeur en conservant le ratio
+                if ($imgWidth > $this->oProperties['filesystem_thumbnails_max_width']) {
+                    $imgHeight = round($imgHeight * $this->oProperties['filesystem_thumbnails_max_width'] / $imgWidth);
+                    $imgWidth = $this->oProperties['filesystem_thumbnails_max_width'];
+                }
+
+                // Redimentionne la hauteur en conservant le ratio
+                if ($imgHeight > $this->oProperties['filesystem_thumbnails_max_height']) {
+                    $imgWidth = round($imgWidth * $this->oProperties['filesystem_thumbnails_max_height'] / $imgHeight);
+                    $imgHeight = $this->oProperties['filesystem_thumbnails_max_height'];
+                }
+
+                $aThumbnailFileStruct["height"] = $imgHeight;
+                $aThumbnailFileStruct["width"] = $imgWidth;
+            }
+
+            $aThumbnailFileStruct['tmp_name'] = $aThumbnailFileStruct['tmp_name'] . '.min';
+            $aDestPathExt = explode('.', $sDestPath);
+
+            $aDestPathExt = explode('.', $sDestPath);
+
+            // Ajoute .min sur la destination
+            array_splice($aDestPathExt, -1, 0, 'min');
+            $sMinDestPath = implode('.', $aDestPathExt);
+
+            // Copie le fichier dans tmp
+            copy($aFileStruct['tmp_name'], $aThumbnailFileStruct['tmp_name']);
+
+            // Upload le fichier .min.jpg
+            $this->uploadFile($sField, "image", $sMinDestPath, $iMaxSize, $aThumbnailFileStruct);
+
+            return true;
+        } else {
+            return false;
+        }
+    }
+
     /**
      *This method upload a file in ws_data.
      *@param $sModule Name of the module.
@@ -230,11 +334,12 @@ class Files_manager{
      *@param $iMaxSize Maximum size to upload on server. (set to -1 to disable this control)
      *@param $sFileTypeCtrl Type of the document. (set to all to disable this control)
      *@param $aFileStruct File structure to upload
-     *@return $sErrorMsg The error message.
+     *@param $bCreateThumbnailImage true to create automatically a thumnail image
+     *@return $aReturn
      */
-    public function uploadInWsDataDir($sModule, $sObject, $mId, $sField, $sContainer = "", $iMaxSize = -1, $sFileTypeCtrl = "all", $aFileStruct = null){
+    public function uploadInWsDataDir($sModule, $sObject, $mId, $sField, $sContainer = "", $iMaxSize = -1, $sFileTypeCtrl = "all", $aFileStruct = null, $bCreateThumbnail = false){
 
-        // on controle les attributs pour éviter les mauvais placements
+        // controle les attributs pour éviter les mauvais placements
         if (strpos($sModule, '/') > -1){
             writeToErrorLog("Module can't contain path : " . $sModule);
             return "Module can't contain path : "  . $sModule;
@@ -268,9 +373,9 @@ class Files_manager{
         // Fichiers multiples
         if (!empty($aFileStruct[0]['name'])) {
             $aReturn = array();
+            // Appel récursif
             for ($i=0; $i < count($aFileStruct); $i++) {
-                // Appel récursif
-                $aResult = $this->uploadInWsDataDir($sModule, $sObject, $mId, $sField, $sContainer, $iMaxSize, $sFileTypeCtrl, $aFileStruct[$i]);
+                $aResult = $this->uploadInWsDataDir($sModule, $sObject, $mId, $sField, $sContainer, $iMaxSize, $sFileTypeCtrl, $aFileStruct[$i], $bCreateThumbnail);
                 $aReturn['message'][] = $aResult['message'];
                 $aReturn['file_name'][] = $aResult['file_name'];
                 $aReturn['file_path'][] = $aResult['file_path'];
@@ -298,10 +403,12 @@ class Files_manager{
             writeToErrorLog("This function doesn't accept relative reference : " . $sDestPath);
             return "This function doesn't accept relative reference : " . $sDestPath;
         }
+
         // si taille max vaut -1 alors taille max = taille fichier + 1
         if ($iMaxSize == -1){
             $iMaxSize = $aFileStruct["size"] + 1;
         }
+
         // création du fichier si besoin
         if (!is_dir($sDestDir)){
             if(!mkdir($sDestDir, 0777, true)){
@@ -309,16 +416,25 @@ class Files_manager{
                 return 'ERROR_CREATING_DIRECTORY ' . $sDestDir;
             }
         }
+
+        // Image miniature
+        $sMinDestPath = null;
+        if ($bCreateThumbnail === true && file_exists($aFileStruct['tmp_name'])) {
+            $this->uploadImageThumbnail($aFileStruct, $sDestPath, $sField, $sMinDestPath, $iMaxSize);
+        }
+
         // Upload du fichier
-        $sMessage = $this->uploadFile($sField, $sFileTypeCtrl, $sDestPath, $iMaxSize, $aFileStruct);
+        $aUploadReturn = $this->uploadFile($sField, $sFileTypeCtrl, $sDestPath, $iMaxSize, $aFileStruct);
 
         return array(
-            'message' => $sMessage,
-            'field_value' => $aFileStruct["name"],
-            'file_name' => $aFileStruct["name"],
-            'file_path' => $sDestPath
+            'message' => $aUploadReturn['message'],
+            'field_value' => $aUploadReturn["file_name"],
+            'file_name' => $aUploadReturn["file_name"],
+            'file_path' => $sDestPath,
+            'file_min_path' => $sMinDestPath
         );
     }
+
     /**
      *This method upload a file in Public.
      *@file vmlib/phpUtil.inc
@@ -368,8 +484,10 @@ class Files_manager{
           }
       }
       // Upload du fichier
-      return $this->uploadFile($sField, $sFileTypeCtrl, $sDestPath, $iMaxSize, $aFileStruct);
+      $aUploadReturn = $this->uploadFile($sField, $sFileTypeCtrl, $sDestPath, $iMaxSize, $aFileStruct);
+      return $aUploadReturn['message'];
     }
+
     /**
      *This method upload a file in Upload.
      *@file vmlib/phpUtil.inc
@@ -419,8 +537,126 @@ class Files_manager{
           }
       }
       // Upload du fichier
-      return $this->uploadFile($sField, $sFileTypeCtrl, $sDestPath, $iMaxSize, $aFileStruct);
+      $aUploadReturn = $this->uploadFile($sField, $sFileTypeCtrl, $sDestPath, $iMaxSize, $aFileStruct);
+      return $aUploadReturn['message'];
     }
+
+    /**
+     *This method clean the objects ws_data dir .
+     *@param $sModule Name of the module.
+     *@param $sObject Name of the object.
+     *@param $mId Id of the current object.
+     *@param $sField field name (generally DB column name).
+     *@param $sContainer folder between $sObject and $mId
+     *@param $aFilesToDelete array of files names to delete, false to delete all the files
+     *@param $aExceptions array of files names to preserve, false to delete all the files
+     *@return true if the disrectory has benn cleaned
+     */
+    public function cleanWsDataDir($sModule, $sObject, $mId, $sField, $sContainer = "", $aFilesToDelete = false, $aExceptions = false){
+
+        // controle les attributs pour éviter les mauvais placements
+        if (strpos($sModule, '/') > -1){
+            writeToErrorLog("Module can't contain path : " . $sModule);
+            return "Module can't contain path : "  . $sModule;
+        }
+
+        if (strpos($sObject, "/") > -1){
+            writeToErrorLog("Object can't contain path : " . $sObject);
+            return "Object can't contain path : "  . $sObject;
+        }
+
+        if (strpos($mId, "/") > -1){
+            writeToErrorLog("Id can't contain path : " . $mId);
+            return "Id can't contain path : "  . $mId;
+        }
+
+        if (strpos($sField, "/") > -1){
+            writeToErrorLog("Field can't contain path : " . $sField);
+            return "Field can't contain path : "  . $sField;
+        }
+
+        if (strpos($sContainer, "/") > -1){
+            writeToErrorLog("Container can't contain path : " . $sContainer);
+            return "Container can't contain path : "  . $sContainer;
+        }
+
+        // Fichiers à supprimer et exceptions
+        $bIsFilesToDelete = false;
+        if ($aFilesToDelete !== false) {
+            $bIsFilesToDelete = true;
+        } else {
+            $bIsFilesToDelete = false;
+            $aFilesToDelete = [];
+        }
+        $bIsExceptions = false;
+        if ($aExceptions !== false) {
+            $bIsExceptions = true;
+        } else {
+            $bIsExceptions = false;
+            $aExceptions = [];
+        }
+
+        // Génère la destination
+        $sDestDir = $this->oProperties['ws_data_dir'] . "/" . $sModule . "/" . $sObject;
+
+        if(!empty($sContainer)){
+            $sDestDir .= "/" . $sContainer;
+        }
+        if(!empty($mId)){
+            $sDestDir .= "/" . $mId;
+        }
+        if(!empty($sField)){
+            $sDestDir .= "/" . $sField;
+        }
+
+        // Ajoute les miniatures aux exceptions
+        $aMinExceptions = [];
+        if ($bIsExceptions !== false) {
+            for ($i=0; $i < count($aExceptions); $i++) {
+                $aMinException = explode('.', $aExceptions[$i]);
+                array_pop($aMinException);
+                array_push($aMinException, 'min');
+                array_push($aMinException, 'jpg');
+                array_push($aMinExceptions, implode('.', $aMinException));
+            }
+            for ($i=0; $i < count($aMinExceptions); $i++) {
+                array_push($aExceptions, $aMinExceptions[$i]);
+            }
+        }
+
+        // Ajoute les miniatures aux fichiers à supprimer
+        $aMinFilesToDelete = [];
+        if ($bIsFilesToDelete !== false) {
+            for ($i=0; $i < count($aFilesToDelete); $i++) {
+                $aMinFileToDelete = explode('.', $aFilesToDelete[$i]);
+                array_pop($aMinFileToDelete);
+                array_push($aMinFileToDelete, 'min');
+                array_push($aMinFileToDelete, 'jpg');
+                array_push($aMinFilesToDelete, implode('.', $aMinFileToDelete));
+            }
+            for ($i=0; $i < count($aMinFilesToDelete); $i++) {
+                array_push($aFilesToDelete, $aMinFilesToDelete[$i]);
+            }
+        }
+
+        // création du fichier si besoin
+        if (is_dir($sDestDir)){
+            $files = glob($sDestDir . '/*'); // get all file names
+            foreach($files as $file){ // iterate files
+                if(is_file($file)){
+                    if (in_array(basename($file), $aFilesToDelete) || $bIsFilesToDelete == false) {
+                        if (!in_array(basename($file), $aExceptions) || $bIsExceptions == false) {
+                            unlink($file);
+                        }
+                    }
+                }
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+
     /**
      *This method return the extension of a file.
      *@file vmlib/phpUtil.inc
diff --git a/vas/rest/class/vmlib/files/Local_files.class.inc b/vas/rest/class/vmlib/files/Local_files.class.inc
index d4543cab5de7f17fc297d92c932b57160b2beb45..ed9febd5c0bb42d10bbbdc78d70a241e8ce0af71 100644
--- a/vas/rest/class/vmlib/files/Local_files.class.inc
+++ b/vas/rest/class/vmlib/files/Local_files.class.inc
@@ -195,12 +195,12 @@ class Local_files extends Files_common implements Files{
      */
     public function getProxyPassUrl ($sFilePath){
         $date = new DateTime();
-        $sDataUrl = $this->oProperties['web_server_name'] . "/" . $this->oProperties['services_alias'] . "/vitis/file_downloader?key=[KEY]&eTag=[ETAG]&d=" . $date->getTimestamp();
+        $sDataUrl = $this->oProperties['web_server_name'] . "/" . $this->oProperties['services_alias'] . "/vitis/FileDownloader?key=[KEY]&eTag=[ETAG]&d=" . $date->getTimestamp();
 
         $sKey = str_replace($this->oProperties['ws_data_dir'], "ws_data" , $sFilePath);
         $sKey = str_replace($this->oProperties['dir_export'], "public" , $sKey);
         $sKey = str_replace($this->oProperties['upload_dir'], "upload" , $sKey);
-        $sKey = str_replace($properties['vas_home'] . '/shared', "shared" , $sKey);
+        $sKey = str_replace($this->oProperties['vas_home'] . '/shared', "shared" , $sKey);
 
         $sFileUrl = str_replace("[KEY]", urlencode($sKey), $sDataUrl);
         $sFileUrl = str_replace("[ETAG]", sha1(file_get_contents($sFilePath), false), $sFileUrl);
diff --git a/vas/rest/class/vmlib/files/S3_files.class.inc b/vas/rest/class/vmlib/files/S3_files.class.inc
index 47299130714a74817ba59aad8c112b6a2a93f444..fd16f7559686e386b894494771237494fc7a6c6f 100644
--- a/vas/rest/class/vmlib/files/S3_files.class.inc
+++ b/vas/rest/class/vmlib/files/S3_files.class.inc
@@ -445,7 +445,7 @@ class S3_files extends Files_common implements Files{
         $sEtag = $this->getFileEtag($sFilePath);
 
         $date = new DateTime();
-        $sDataUrl = $this->oProperties['web_server_name'] . "/" . $this->oProperties['services_alias'] . "/vitis/file_downloader?key=[KEY]&eTag=[ETAG]&d=" . $date->getTimestamp();
+        $sDataUrl = $this->oProperties['web_server_name'] . "/" . $this->oProperties['services_alias'] . "/vitis/FileDownloader?key=[KEY]&eTag=[ETAG]&d=" . $date->getTimestamp();
 
         $sKey = str_replace($this->oProperties['ws_data_dir'], "ws_data" , $sFilePath);
         $sKey = str_replace($this->oProperties['dir_export'], "public" , $sKey);
diff --git a/vas/rest/class/vmlib/phpUtil.inc b/vas/rest/class/vmlib/phpUtil.inc
index bac8eeaa4c6aa6df98f4dbe63d60bc5e1272b968..79b6523549cae25246eec3639a6e45b9a1b06194 100755
--- a/vas/rest/class/vmlib/phpUtil.inc
+++ b/vas/rest/class/vmlib/phpUtil.inc
@@ -552,7 +552,7 @@ function stripslashes_deep($aString) {
 
      $aFileName = explode("/", $sFilePath);
      $date = new DateTime();
-     $sDataUrl = $properties['web_server_name'] . "/rest/vitis/file_downloader?key=[KEY]&eTag=[ETAG]&d=" . $date->getTimestamp();
+     $sDataUrl = $properties['web_server_name'] . "/rest/vitis/FileDownloader?key=[KEY]&eTag=[ETAG]&d=" . $date->getTimestamp();
 
      $sFileUrl = str_replace("[KEY]", str_replace($properties['ws_data_dir'], "ws_data" , $sFilePath), $sDataUrl);
      $sFileUrl = str_replace("[ETAG]", sha1(file_get_contents($sFilePath), false), $sFileUrl);
@@ -602,7 +602,7 @@ function stripslashes_deep($aString) {
        ));
 
        $date = new DateTime();
-       $sDataUrl = $properties['web_server_name'] . "/rest/vitis/file_downloader?key=[KEY]&eTag=[ETAG]&d=" . $date->getTimestamp();
+       $sDataUrl = $properties['web_server_name'] . "/rest/vitis/FileDownloader?key=[KEY]&eTag=[ETAG]&d=" . $date->getTimestamp();
        $aPath = explode("/", $sDirectoryPath);
        $aTree = array(
                "filename"=> end($aPath),
diff --git a/vas/rest/conf/properties_server.inc b/vas/rest/conf/properties_server.inc
index 494c13e697f984b236ba8e59c84bd894572e9cf1..9b34fdadd7ae222fac65ec5f264f1377555cd898 100644
--- a/vas/rest/conf/properties_server.inc
+++ b/vas/rest/conf/properties_server.inc
@@ -49,6 +49,8 @@ $properties["notifierAccessKeyAccount"] = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
 $properties["notifierSecretKeyAccount"] = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
 // utilisation de s3 pour remplacer le système de Fichier
 $properties['filesystem'] = 'fs'; // s3
+$properties['filesystem_thumbnails_max_width'] = 350; // taille maximale des images .min.jpg
+$properties['filesystem_thumbnails_max_height'] = 350; // taille maximale des images .min.jpg
 $properties['AWSCredentialsFilePath'] = ""; // .../.aws/credentials
 $properties["fileS3UploaderProfil"] = "";
 $properties["fileS3UploaderBucket"] = "";
diff --git a/vas/rest/index.vhtml b/vas/rest/index.vhtml
index 4e0e614d1bd2d65f7ec472036138c5efa867865a..44fcbfafc04970d93a27ba07d5f48bf0eae08435 100755
--- a/vas/rest/index.vhtml
+++ b/vas/rest/index.vhtml
@@ -1,4 +1,8 @@
 <?php
-	header('Content-type: '.$aValues['output'].'; charset='.$properties["page_encoding"]);
+	ob_end_flush();
+	$aCurrentHeaders = apache_response_headers();
+	if(!array_key_exists('Content-Type-Set-By', $aCurrentHeaders)){
+		header('Content-type: '.$aValues['output'].'; charset='.$properties["page_encoding"]);
+	}
 	echo $sMessage;
-?>
\ No newline at end of file
+?>
diff --git a/vas/rest/ws/vitis/File_downloader.class.inc b/vas/rest/ws/vitis/FileDownloader.class.inc
similarity index 69%
rename from vas/rest/ws/vitis/File_downloader.class.inc
rename to vas/rest/ws/vitis/FileDownloader.class.inc
index fcc86a89c7145276ab0bf659a3a63bda15404e95..c2f2e825c66a8686562611730132344501797afa 100755
--- a/vas/rest/ws/vitis/File_downloader.class.inc
+++ b/vas/rest/ws/vitis/FileDownloader.class.inc
@@ -1,8 +1,8 @@
 <?php
 
 /**
- * \file File_downloader.class.inc
- * \class File_downloader
+ * \file FileDownloader.class.inc
+ * \class FileDownloader
  *
  * \author Anthony Borghi <anthony.borghi@veremes.com>.
  *
@@ -14,17 +14,18 @@
 require_once 'Vitis.class.inc';
 require_once ("aws_sdk/aws-autoloader.php");
 require_once ("vmlib/phpUtil.inc");
+require_once "vmlib/files/Files_manager.class.inc";
 
-class File_downloader extends Vitis {
+class FileDownloader extends Vitis {
     /**
      * @SWG\Definition(
-     *   definition="/file_downloader",
+     *   definition="/FileDownloader",
      *   allOf={
-     *     @SWG\Schema(ref="#/definitions/file_downloader")
+     *     @SWG\Schema(ref="#/definitions/FileDownloader")
      *   }
      * )
      * * @SWG\Tag(
-     *   name="File_downloader",
+     *   name="FileDownloader",
      *   description="Download a file"
      * )
      */
@@ -38,14 +39,14 @@ class File_downloader extends Vitis {
      * @param type $oConnection connection object
      */
     function __construct($aPath, $aValues, $properties, $bShortcut = false, $oConnection = false) {
-      $this->aValues = $aValues;
-      $this->aProperties = $properties;
-      $this->oFilesManager = new Files_manager($properties);
+        $this->aValues = $aValues;
+        $this->aProperties = $properties;
+        $this->oFilesManager = new Files_manager($this->aProperties);
     }
 
     /**
-     * @SWG\Get(path="/file_downloader",
-     *   tags={"File_downloader"},
+     * @SWG\Get(path="/FileDownloader",
+     *   tags={"FileDownloader"},
      *   summary="Get File Content",
      *   description="Request to get File",
      *   operationId="GET",
@@ -74,7 +75,7 @@ class File_downloader extends Vitis {
      *   @SWG\Response(
      *         response=200,
      *         description="Blob of the file",
-     *         @SWG\Schema(ref="#/definitions/file_downloader")
+     *         @SWG\Schema(ref="#/definitions/FileDownloader")
      *     )
      *  )
      */
@@ -103,15 +104,36 @@ class File_downloader extends Vitis {
             $sMessage = array("status" => 0, "sMessage" => "This directory can't be replaced by our replacer");
             return json_encode($sMessage);
           }
+
           if($this->oFilesManager->oFileInterface->file_exists($sPath)){
               if($this->oFilesManager->oFileInterface->getFileEtag($sPath) === $this->aValues["eTag"]){
                   $sFileName = $this->oFilesManager->getFileName($sPath);
                   $sContentType = $this->oFilesManager->oFileInterface->mime_content_type($sPath);
-                  header("Content-Type: " . $sContentType);
-                  if (in_array($sContentType, array("text/plain", 'application/pdf'))){
-                      header("Content-disposition: inline; filename=\"" . $sFileName . "\"");
+
+                  // Utilisation thumnail
+                  if ($this->aValues["thumbnail"] === 'true') {
+                      $sMinFilePath = $sPath;
+                      $aMinFilePath = explode('.', $sMinFilePath);
+                      array_pop($aMinFilePath);
+                      array_push($aMinFilePath, 'min');
+                      array_push($aMinFilePath, 'jpg');
+                      $sMinFilePath = implode('.', $aMinFilePath);
+                      if ($this->oFilesManager->oFileInterface->file_exists($sMinFilePath)) {
+                          $sPath = $sMinFilePath;
+                      }
+                  }
+
+                  if ($this->aValues["thumbnail"] === 'true' && $this->aValues["type"] === 'document') {
+                      header("Content-disposition: attachment; filename=\"" . rawurlencode($sFileName) . "\"");
+                      return array("status" => 1, "fileName" => $sFileName);
+                  }
+
+                  header("Content-Type: " . $sContentType .'; charset='. $this->aProperties["page_encoding"]);
+                  header("Content-Type-Set-By: vitis/filedownloader");
+                  if (in_array($sContentType, array("text/plain", 'application/pdf', 'text/html', 'application/xml', 'application/json'))){
+                      header("Content-disposition: inline; filename=\"" . rawurlencode($sFileName) . "\"");
                   } else {
-                      header("Content-disposition: attachment; filename=\"" . $sFileName . "\"");
+                      header("Content-disposition: attachment; filename=\"" . rawurlencode($sFileName) . "\"");
                   }
                   header('Content-Length: ' . $this->oFilesManager->oFileInterface->filesize($sPath));
                   if ($sContentType === "application/octet-stream") {
diff --git a/vas/rest/ws/vitis/GenericQuerys.class.inc b/vas/rest/ws/vitis/GenericQuerys.class.inc
index ae3ade89fea7b65a19aed0869f12b0357b914be6..7394491a162f775ffb0d7ec7731e124bbb5621c9 100644
--- a/vas/rest/ws/vitis/GenericQuerys.class.inc
+++ b/vas/rest/ws/vitis/GenericQuerys.class.inc
@@ -457,7 +457,6 @@ class GenericQuerys extends Vitis {
                 $this->aValues['password'] = '';
             }
         }
-
         $this->oBd = new BD($this->aValues['login'], $this->aValues['password'], $this->aValues['database'], $this->aValues['server'], $this->aValues['port'], $this->aValues['sgbd'], $this->aValues['encoding']);
         if ($this->oBd->erreurRencontree) {
             writeToErrorLog(ERROR_CONNECTION_PROBLEM);
@@ -525,6 +524,28 @@ class GenericQuerys extends Vitis {
         $this->oBd = new BD($this->aValues['login'], $this->aValues['password'], $sDatabase, $this->aValues['server'], $this->aValues['port'], $this->aValues['sgbd'], $this->aValues['encoding']);
         $sSql = $this->aSql[$this->aValues['sgbd']]['getSchemas'];
         $aSQLParams = array();
+        //order by
+        if (!empty($this->aValues['order_by'])) {
+            $aOrder = explode("|", $this->aValues['order_by']);
+            foreach ($aOrder as $value) {
+                $sColumnKey = 'column_' . vitisUniqId();
+                if (strpos($sSql, "ORDER BY") == FALSE)
+                    $sSql .= " ORDER BY " . str_replace("=", " ", "[" . $sColumnKey . "]");
+                else
+                    $sSql .= ", " . str_replace("=", " ", "[" . $sColumnKey . "]");
+                $aSQLParams[$sColumnKey] = array('value' => $value, 'type' => 'column_name');
+            }
+        }
+        // sort_order
+        if (!empty($this->aValues['sort_order'])) {
+            switch (strtoupper($this->aValues['sort_order'])) {
+                case 'DESC':
+                    $sSql .= " DESC";
+                    break;
+                default:
+                    $sSql .= " ASC";
+            }
+        }
         $oPDOresult = $this->oBd->executeWithParams($sSql, $aSQLParams);
         if ($this->oBd->enErreur()) {
             $oError = new VitisError(1, $this->oBd->getBDMessage());
diff --git a/vas/rest/ws/vitis/Users.class.inc b/vas/rest/ws/vitis/Users.class.inc
index 56ec511d7a516ff8aba5dce2358a5c15e95c5444..dbd30e03ec789141d57883e48ff65d690be7ac3e 100755
--- a/vas/rest/ws/vitis/Users.class.inc
+++ b/vas/rest/ws/vitis/Users.class.inc
@@ -271,6 +271,10 @@ class Users extends Vitis {
                         $aXmlRacineAttribute['status'] = 0;
                         $sMessage = $oError->asDocument('', 'vitis', $this->aValues['sEncoding'], True, $aXmlRacineAttribute, $this->aValues['sSourceEncoding'], $this->aValues['output']);
                     } else {
+                        // suppression des valeur permettant d'activer l'encryption lors du post pour que l'administrateur ne puisse pas l'activer à travers l'API
+                        unset($this->aValues['secretkey']);
+                        unset($this->aValues['dataencrypt']);
+                        unset($this->aValues['encrypted_secretkey']);
                         // insert user in table
                         $iId = $this->oConnection->oBd->insert($this->aProperties['schema_framework'], 'v_user', $this->aValues, $this->aProperties['schema_framework'] . '.seq_common', 'user_id');
                         if ($this->oConnection->oBd->enErreur()) {
@@ -416,10 +420,12 @@ class Users extends Vitis {
             $sLogin = mb_strtolower($this->aValues["login"], 'UTF-8');
             $sLogin = str_replace(array('à', 'â', 'ä', 'á', 'ã', 'å', 'î', 'ï', 'ì', 'í', 'ô', 'ö', 'ò', 'ó', 'õ', 'ø', 'ù', 'û', 'ü', 'ú', 'é', 'è', 'ê', 'ë', 'ç', 'ÿ', 'ñ'), array('a', 'a', 'a', 'a', 'a', 'a', 'i', 'i', 'i', 'i', 'o', 'o', 'o', 'o', 'o', 'o', 'u', 'u', 'u', 'u', 'e', 'e', 'e', 'e', 'c', 'y', 'n',), $sLogin);
             unset($this->aValues['login']);
-            if (!empty($this->aValues['encrypted_secretkey']))
-                $this->aValues['secretkey'] = $this->aValues['encrypted_secretkey'];
-            else
-                $this->aValues['secretkey'] = null;
+            $this->aValues['secretkey'] = $this->aValues['encrypted_secretkey'];
+            if($this->aProperties['enableEncryptionForUsers'] !== true){
+                unset($this->aValues['secretkey']);
+                unset($this->aValues['dataencrypt']);
+                unset($this->aValues['encrypted_secretkey']);
+            }
             $aReturn = $this->genericPut($this->aProperties['schema_framework'], 'v_user', 'user_id');
             $aXmlRacineAttribute['status'] = $aReturn['sStatus'];
             $sMessage = $aReturn['sMessage'];
diff --git a/vas/rest/ws/vitis/Vitis.class.inc b/vas/rest/ws/vitis/Vitis.class.inc
index 8a5c4da59651c0c09d1dd64ad8a91f9d2926a22a..0cfa33e45373d8146d7043148a37136fbb11ba1b 100644
--- a/vas/rest/ws/vitis/Vitis.class.inc
+++ b/vas/rest/ws/vitis/Vitis.class.inc
@@ -82,12 +82,14 @@ class Vitis extends DbClass {
 
     /**
      * Generic function which get fields of the object stored in the database
-     * @param type $sSchema
-     * @param type $sTable
-     * @param type $sIdField
-     *
+     * @param $sSchema
+     * @param $sTable
+     * @param $sIdField
+     * @param $sVitisObjectName Name of the vitis object, if provide and the object contains files stored in ws_data/vitis/$sVitisObjectName the values will be url formed
+     * @param $sVitisModuleName for upload files : module name
+     * @param $sVitisPathComplement for upload files : complement folder on put files (ws_data/vitis/.../documents/...)
      */
-    function getFields($sSchema, $sTable, $sIdField) {
+    function getFields($sSchema, $sTable, $sIdField, $sVitisObjectName = "", $sVitisModuleName = "vitis", $sVitisPathComplement = "documents") {
 
         $this->aSqlParams = array();
 
@@ -139,12 +141,48 @@ class Vitis extends DbClass {
                 $aData = array();
                 while ($aObject = $this->oConnection->oBd->ligneSuivante($oResult)) {
                     foreach ($aObject as $sParamKey => $sParamValue) {
-
                         $aData[$sParamKey] = $sParamValue;
                     }
                 }
+
+                // Vérifie si il y a des fichiers à renvoyer
+                if ($sVitisObjectName != "") {
+
+                    // check dans ws_data
+                    $sDataDir = $this->aProperties['ws_data_dir'] . '/'.$sVitisModuleName.'/' . $sVitisObjectName;
+                    if(!empty($sVitisPathComplement)){
+                        $sDataDir .= '/' . $sVitisPathComplement;
+                    }
+                    $sDataDir .=  '/' . $aData[$sIdField];
+
+                    if ($this->oFilesManager->oFileInterface->is_dir($sDataDir)) {
+                        // Remplace le nom du fichier par son url
+                        foreach ($aData as $key => $value) {
+                            if ($this->oFilesManager->oFileInterface->is_dir($sDataDir . "/" . $key)) {
+                                $aFiles = explode('|', $value);
+                                if (count($aFiles) > 1) {
+                                    $aData[$key] = [];
+                                    for ($i=0; $i < count($aFiles); $i++) {
+                                        if (!empty($aFiles[$i])) {
+                                            if (file_exists($sDataDir . "/" . $key . "/" . $aFiles[$i])) {
+                                                array_push($aData[$key], $this->oFilesManager->oFileInterface->getProxyPassUrl($sDataDir . "/" . $key . "/" . $aFiles[$i]));
+                                            }
+                                        }
+                                    }
+                                } else {
+                                    if (!empty($value)) {
+                                        if (file_exists($sDataDir . "/" . $key . "/" . $value)) {
+                                            $aData[$key] = $this->oFilesManager->oFileInterface->getProxyPassUrl($sDataDir . "/" . $key . "/" . $value);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
             }
         }
+
         return $aData;
     }
 
@@ -206,7 +244,6 @@ class Vitis extends DbClass {
         return $aColumn;
     }
 
-    // fonction get générique à tous les objets
     /**
      * Generic function which get data of the objects in tables
      * @param $sSchema schema of the table
@@ -214,6 +251,8 @@ class Vitis extends DbClass {
      * @param $sIdField name of the id field
      * @param $bOnlyReturnStatus
      * @param $sVitisObjectName Name of the vitis object, if provide and the object contains files stored in ws_data/vitis/$sVitisObjectName the values will be url formed
+     * @param $sVitisModuleName for upload files : module name
+     * @param $sVitisPathComplement for upload files : complement folder on put files (ws_data/vitis/.../documents/...)
      * @return the array of objects
      */
     function genericGet($sSchema, $sTable, $sIdField, $bOnlyReturnStatus = false, $sVitisObjectName = "", $sVitisModuleName = "vitis", $sVitisPathComplement = "documents") {
@@ -268,7 +307,23 @@ class Vitis extends DbClass {
                             // Remplace le nom du fichier par son url
                             foreach ($oObject->aFields as $key => $value) {
                                 if ($this->oFilesManager->oFileInterface->is_dir($sDataDir . "/" . $key)) {
-                                    $oObject->aFields[$key] = $this->oFilesManager->oFileInterface->getProxyPassUrl($sDataDir . "/" . $key . "/" . $value);
+                                    $aFiles = explode('|', $value);
+                                    if (count($aFiles) > 1) {
+                                        $oObject->aFields[$key] = [];
+                                        for ($i=0; $i < count($aFiles); $i++) {
+                                            if (!empty($aFiles[$i])) {
+                                                if (file_exists($sDataDir . "/" . $key . "/" . $aFiles[$i])) {
+                                                    array_push($oObject->aFields[$key], $this->oFilesManager->oFileInterface->getProxyPassUrl($sDataDir . "/" . $key . "/" . $aFiles[$i]));
+                                                }
+                                            }
+                                        }
+                                    } else {
+                                        if (!empty($value)) {
+                                            if (file_exists($sDataDir . "/" . $key . "/" . $value)) {
+                                                $oObject->aFields[$key] = $this->oFilesManager->oFileInterface->getProxyPassUrl($sDataDir . "/" . $key . "/" . $value);
+                                            }
+                                        }
+                                    }
                                 }
                             }
                         }
@@ -527,15 +582,18 @@ class Vitis extends DbClass {
     }
 
     /**
-     *
-     * @param type $sSchema schema containing the table
-     * @param type $sTable table to update
-     * @param type $sSequence  used sequence to get the id
-     * @param type $sIdField id field name
-     * @param type $sPrivilegeParent privilege to do the insertion
+     * Generic function to insert data using $this->aValues
+     * @param $sSchema schema containing the table
+     * @param $sTable table to update
+     * @param $sSequence  used sequence to get the id
+     * @param $sIdField id field name
+     * @param $aUploadFiles : uploaded files array (more info on function genericPutFiles)
+     * @param $sVitisObjectName for upload files : object name
+     * @param $sVitisModuleName for upload files : module name
+     * @param $sVitisPathComplement for upload files : complement folder on put files (ws_data/vitis/.../documents/...)
      * @return array containing the status and the message
      */
-    function genericPost($sSchema, $sTable, $sSequence, $sIdField) {
+    function genericPost($sSchema, $sTable, $sSequence, $sIdField, $aUploadFiles = false, $sVitisObjectName = "", $sVitisModuleName = "vitis", $sVitisPathComplement = "documents") {
         if (isset($this->aValues['sEncoding']))
             $sEncoding = $this->aValues['sEncoding'];
         else
@@ -550,10 +608,10 @@ class Vitis extends DbClass {
             $aXmlRacineAttribute['status'] = 0;
             $sMessage = $oError->asDocument('', 'vitis', $sEncoding, True, $aXmlRacineAttribute, $sSourceEncoding, $this->aValues['output']);
         } else {
-            // verify if the user is framework_admin
+            // verify if the user privileges
             $this->getTablePrivileges($sSchema, $sTable);
-            //$aPrivileges = array_intersect($this->oConnection->aPrivileges, $this->aTablePrivileges) ;
             if (in_array('INSERT', $this->aTablePrivileges)) {
+
                 // insert user in table
                 $iId = $this->oConnection->oBd->insert($sSchema, $sTable, $this->aValues, $sSequence, $sIdField);
                 if ($this->oConnection->oBd->enErreur()) {
@@ -564,6 +622,13 @@ class Vitis extends DbClass {
                     $this->aFields[$sIdField] = $iId;
                     $this->aValues['my_vitis_id'] = $iId;
 
+                    // Upload de fichiers
+                    if (($aUploadFiles !== false) && ($sVitisObjectName != "")) {
+                        // Upload the files in $_FILES and $aUploadFiles
+                        // Updade $this->aValues
+                        $this->genericPut($sSchema, $sTable, $sIdField, $aUploadFiles, $sVitisObjectName, $sVitisModuleName, $sVitisPathComplement);
+                    }
+
                     $aXmlRacineAttribute['status'] = 1;
                     $sMessage = $this->asDocument('', 'vitis', $sEncoding, True, $aXmlRacineAttribute, $sSourceEncoding, $this->aValues['output']);
                 }
@@ -578,14 +643,17 @@ class Vitis extends DbClass {
     }
 
     /**
-     *
-     * @param type $sSchema schema containing the table
-     * @param type $sTable table to update
-     * @param type $sIdField id field name
-     * @param type $sPrivilegeParent privilege to do the insertion
+     * Generic function to update data using $this->aValues
+     * @param $sSchema schema containing the table
+     * @param $sTable table to update
+     * @param $sIdField id field name
+     * @param $aUploadFiles : uploaded files array (more info on function genericPutFiles)
+     * @param $sVitisObjectName Name of the vitis object, if provide and the object contains files stored in ws_data/vitis/$sVitisObjectName the values will be url formed
+     * @param $sVitisModuleName for upload files : module name
+     * @param $sVitisPathComplement for upload files : complement folder on put files (ws_data/vitis/.../documents/...)
      * @return array containing the status and the message
      */
-    function genericPut($sSchema, $sTable, $sIdField) {
+    function genericPut($sSchema, $sTable, $sIdField, $aUploadFiles = false, $sVitisObjectName = "", $sVitisModuleName = "vitis", $sVitisPathComplement = "documents") {
         if (isset($this->aValues['sEncoding']))
             $sEncoding = $this->aValues['sEncoding'];
         else
@@ -594,14 +662,26 @@ class Vitis extends DbClass {
             $sSourceEncoding = $this->aValues['sSourceEncoding'];
         else
             $sSourceEncoding = null;
+
+        // Test connection
         if ($this->oConnection->oError != null) {
             $oError = $this->oConnection->oError;
             $aXmlRacineAttribute['status'] = 0;
             $sMessage = $oError->asDocument('', 'vitis', $sEncoding, True, $aXmlRacineAttribute, $sSourceEncoding, $this->aValues['output']);
         } else {
+
+            // Test privilèges
             $this->getTablePrivileges($sSchema, $sTable);
-            //$aPrivileges = array_intersect($this->oConnection->aPrivileges, $this->aTablePrivileges) ;
             if (in_array('UPDATE', $this->aTablePrivileges)) {
+
+                // Upload de fichiers
+                if (($aUploadFiles !== false) && ($sVitisObjectName != "")) {
+                    // Upload the files in $_FILES and $aUploadFiles
+                    // Updade $this->aValues
+                    $this->genericPutFiles($aUploadFiles, $sVitisObjectName, $sVitisModuleName, $sVitisPathComplement);
+                }
+
+                // Upload DBB
                 $iId = $this->oConnection->oBd->update($sSchema, $sTable, $this->aValues, $sIdField, $this->aValues['my_vitis_id'], "text");
                 if ($this->oConnection->oBd->enErreur()) {
                     $oError = new VitisError(1, $this->oConnection->oBd->getBDMessage());
@@ -1431,5 +1511,109 @@ class Vitis extends DbClass {
         else
             return $sField;
     }
+
+    /**
+     * Put the files present on $_FILES and aValues on the specified dir (ws_data, S3 ...)
+     * @param $aUploadFiles for upload files : two dimensional array of uploaded files columns or * to allow everyting
+     * ex : $aUploadFiles = [
+     *      'image_col' => [
+     *          'width' => 800
+     *          'height' => 600
+     *      ],
+     *      'doc_col' => []
+     * ]
+     * @param $sVitisObjectName for upload files : object name (ex : user)
+     * @param $sVitisModuleName for upload files : module name (ex : vitis)
+     * @param $sVitisPathComplement for upload files : complement folder on put files (ws_data/vitis/.../documents/...)
+     */
+    function genericPutFiles($aUploadFiles, $sVitisObjectName, $sVitisModuleName = 'vitis', $sVitisPathComplement = 'documents') {
+
+        if (empty($aUploadFiles) ||
+            empty($sVitisObjectName) ||
+            empty($sVitisModuleName) ||
+            empty($sVitisPathComplement)) {
+            return false;
+        }
+
+        // Noms des colonnes à uploader
+        $aUploadFilesCols = [];
+        if (is_array($aUploadFiles)) {
+            $aUploadFilesCols = array_keys($aUploadFiles);
+        }
+
+        // Fichiers inchangés à ne pas supprimer
+        if (!empty($this->aValues['vitis_unchanged_files'])) {
+            $aUnchangedFiles = json_decode($this->aValues['vitis_unchanged_files'], true);
+        }
+
+        // Fichiers à supprimer ?
+        if (!empty($this->aValues['vitis_deleted_files'])) {
+            $aDeletedFiles = json_decode($this->aValues['vitis_deleted_files'], true);
+            foreach ($aDeletedFiles as $sFieldName => $aFile) {
+
+                // Vérifie la présence du champ
+                if (in_array($sFieldName, $aUploadFilesCols) || $aUploadFiles === '*') {
+
+                    // Supprime les fichiers
+                    $this->oFilesManager->cleanWsDataDir($sVitisModuleName, $sVitisObjectName, $this->aValues['my_vitis_id'], $sFieldName, $sVitisPathComplement, $aDeletedFiles[$sFieldName], false);
+
+                    // Set aValues avec les fichiers restants
+                    $this->aValues[$sFieldName] = null;
+                    if (!empty($aUnchangedFiles[$sFieldName])) {
+                        $this->aValues[$sFieldName] = implode('|', $aUnchangedFiles[$sFieldName]);
+                    }
+                }
+            }
+        }
+
+        // Fichiers à uploader ?
+        if (!empty($_FILES) && !empty($this->aValues['my_vitis_id'])) {
+            foreach ($_FILES as $sFieldName => $aFile) {
+
+                // Vérifie la présence du champ
+                if (in_array($sFieldName, $aUploadFilesCols) || $aUploadFiles === '*') {
+
+                    // width / height
+                    if (!empty($aUploadFiles[$sFieldName]['width']) &&
+                        !empty($aUploadFiles[$sFieldName]['height'])) {
+
+                        // Fichier unique
+                        if (!empty($_FILES[$sFieldName]['name'])) {
+                            $_FILES[$sFieldName]['width'] = $aUploadFiles[$sFieldName]['width'];
+                            $_FILES[$sFieldName]['height'] = $aUploadFiles[$sFieldName]['height'];
+                        }
+
+                        // Fichiers multiples
+                        if (!empty($_FILES[$sFieldName][0]['name'])) {
+                            for ($i=0; $i < count($_FILES[$sFieldName]); $i++) {
+                                $_FILES[$sFieldName][$i]['width'] = $aUploadFiles[$sFieldName]['width'];
+                                $_FILES[$sFieldName][$i]['height'] = $aUploadFiles[$sFieldName]['height'];
+                            }
+                        }
+                    }
+
+                    // Fichiers à ne pas supprimer
+                    $aExceptions = [];
+                    if (!empty($aUnchangedFiles[$sFieldName])) {
+                        $aExceptions = $aUnchangedFiles[$sFieldName];
+                    }
+
+                    // Nettoyage de l'espace ws_data
+                    $this->oFilesManager->cleanWsDataDir($sVitisModuleName, $sVitisObjectName, $this->aValues['my_vitis_id'], $sFieldName, $sVitisPathComplement, false, $aExceptions);
+
+                    // Écriture des fichiers
+                    $aUploadReturn = $this->oFilesManager->uploadInWsDataDir($sVitisModuleName, $sVitisObjectName, $this->aValues['my_vitis_id'], $sFieldName, $sVitisPathComplement, -1, "all", null, true);
+                    $this->aValues[$sFieldName] = $aUploadReturn['field_value'];
+
+                    // Re-inscrit en base les fichiers à ne pas supprimer
+                    $aFiles = explode('|', $this->aValues[$sFieldName]);
+                    for ($i=0; $i < count($aExceptions); $i++) {
+                        array_unshift($aFiles, $aExceptions[$i]);
+                    }
+                    $this->aValues[$sFieldName] = implode('|', $aFiles);
+                }
+            }
+        }
+    }
 }
 ?>
diff --git a/vas/sql/sqlQueries.xml b/vas/sql/sqlQueries.xml
index 131ae69f427361a1b80dc1a59aa57106e0385884..ad70051512d7ab830bb6e8b9a007d4e3ea0f6a35 100644
--- a/vas/sql/sqlQueries.xml
+++ b/vas/sql/sqlQueries.xml
@@ -955,5 +955,15 @@
 				]]>
 			</code>
 		</query>
+		<query>
+			<type>update</type>
+			<version>2019.01.01</version>
+			<code>
+				<![CDATA[
+					-- Frédéric le 14/02/2019 à 15:53
+				 ALTER TABLE s_vitis."user" ALTER COLUMN billinggroup_id drop default;
+				]]>
+			</code>
+		</query>
 	</queriesCollection>
 </sqlQueries>