diff --git a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/jio.js.js b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/jio.js.js
index 089619d8cee28bfd3592586efe00c820f7bcd8d4..4310ca3443dfa098f6f9097197d60a8cbf2a2c55 100644
--- a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/jio.js.js
+++ b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/jio.js.js
@@ -7469,6 +7469,11 @@ return new Parser;
     };
   }
 
+  /* Safari does not define DOMError */
+  if (window.DOMError === undefined) {
+    window.DOMError = {};
+  }
+
   var util = {},
     jIO;
 
@@ -8434,6 +8439,11 @@ return new Parser;
     return rusha.digestFromString(content);
   }
 
+  function generateHashFromArrayBuffer(content) {
+    // XXX Improve performance by moving calculation to WebWorker
+    return rusha.digestFromArrayBuffer(content);
+  }
+
   function ReplicateStorage(spec) {
     this._query_options = spec.query || {};
 
@@ -8494,6 +8504,36 @@ return new Parser;
     if (this._check_remote_deletion === undefined) {
       this._check_remote_deletion = true;
     }
+    this._check_local_attachment_modification =
+      spec.check_local_attachment_modification;
+    if (this._check_local_attachment_modification === undefined) {
+      this._check_local_attachment_modification = false;
+    }
+    this._check_local_attachment_creation =
+      spec.check_local_attachment_creation;
+    if (this._check_local_attachment_creation === undefined) {
+      this._check_local_attachment_creation = false;
+    }
+    this._check_local_attachment_deletion =
+      spec.check_local_attachment_deletion;
+    if (this._check_local_attachment_deletion === undefined) {
+      this._check_local_attachment_deletion = false;
+    }
+    this._check_remote_attachment_modification =
+      spec.check_remote_attachment_modification;
+    if (this._check_remote_attachment_modification === undefined) {
+      this._check_remote_attachment_modification = false;
+    }
+    this._check_remote_attachment_creation =
+      spec.check_remote_attachment_creation;
+    if (this._check_remote_attachment_creation === undefined) {
+      this._check_remote_attachment_creation = false;
+    }
+    this._check_remote_attachment_deletion =
+      spec.check_remote_attachment_deletion;
+    if (this._check_remote_attachment_deletion === undefined) {
+      this._check_remote_attachment_deletion = false;
+    }
   }
 
   ReplicateStorage.prototype.remove = function (id) {
@@ -8520,6 +8560,32 @@ return new Parser;
     return this._local_sub_storage.get.apply(this._local_sub_storage,
                                              arguments);
   };
+  ReplicateStorage.prototype.getAttachment = function () {
+    return this._local_sub_storage.getAttachment.apply(this._local_sub_storage,
+                                                       arguments);
+  };
+  ReplicateStorage.prototype.allAttachments = function () {
+    return this._local_sub_storage.allAttachments.apply(this._local_sub_storage,
+                                                        arguments);
+  };
+  ReplicateStorage.prototype.putAttachment = function (id) {
+    if (id === this._signature_hash) {
+      throw new jIO.util.jIOError(this._signature_hash + " is frozen",
+                                  403);
+    }
+    return this._local_sub_storage.putAttachment.apply(this._local_sub_storage,
+                                                       arguments);
+  };
+  ReplicateStorage.prototype.removeAttachment = function (id) {
+    if (id === this._signature_hash) {
+      throw new jIO.util.jIOError(this._signature_hash + " is frozen",
+                                  403);
+    }
+    return this._local_sub_storage.removeAttachment.apply(
+      this._local_sub_storage,
+      arguments
+    );
+  };
   ReplicateStorage.prototype.hasCapacity = function () {
     return this._local_sub_storage.hasCapacity.apply(this._local_sub_storage,
                                                      arguments);
@@ -8538,6 +8604,333 @@ return new Parser;
     // Do not sync the signature document
     skip_document_dict[context._signature_hash] = null;
 
+
+    function propagateAttachmentDeletion(skip_attachment_dict,
+                                         destination,
+                                         id, name) {
+      return destination.removeAttachment(id, name)
+        .push(function () {
+          return context._signature_sub_storage.removeAttachment(id, name);
+        })
+        .push(function () {
+          skip_attachment_dict[name] = null;
+        });
+    }
+
+    function propagateAttachmentModification(skip_attachment_dict,
+                                             destination,
+                                             blob, hash, id, name) {
+      return destination.putAttachment(id, name, blob)
+        .push(function () {
+          return context._signature_sub_storage.putAttachment(id, name,
+                                                              JSON.stringify({
+              hash: hash
+            }));
+        })
+        .push(function () {
+          skip_attachment_dict[name] = null;
+        });
+    }
+
+    function checkAndPropagateAttachment(skip_attachment_dict,
+                                         status_hash, local_hash, blob,
+                                         source, destination, id, name,
+                                         conflict_force, conflict_revert,
+                                         conflict_ignore) {
+      var remote_blob;
+      return destination.getAttachment(id, name)
+        .push(function (result) {
+          remote_blob = result;
+          return jIO.util.readBlobAsArrayBuffer(remote_blob);
+        })
+        .push(function (evt) {
+          return generateHashFromArrayBuffer(
+            evt.target.result
+          );
+        }, function (error) {
+          if ((error instanceof jIO.util.jIOError) &&
+              (error.status_code === 404)) {
+            remote_blob = null;
+            return null;
+          }
+          throw error;
+        })
+        .push(function (remote_hash) {
+          if (local_hash === remote_hash) {
+            // Same modifications on both side
+            if (local_hash === null) {
+              // Deleted on both side, drop signature
+              return context._signature_sub_storage.removeAttachment(id, name)
+                .push(function () {
+                  skip_attachment_dict[id] = null;
+                });
+            }
+
+            return context._signature_sub_storage.putAttachment(id, name,
+              JSON.stringify({
+                hash: local_hash
+              }))
+              .push(function () {
+                skip_document_dict[id] = null;
+              });
+          }
+
+          if ((remote_hash === status_hash) || (conflict_force === true)) {
+            // Modified only locally. No conflict or force
+            if (local_hash === null) {
+              // Deleted locally
+              return propagateAttachmentDeletion(skip_attachment_dict,
+                                                 destination,
+                                                 id, name);
+            }
+            return propagateAttachmentModification(skip_attachment_dict,
+                                         destination, blob,
+                                         local_hash, id, name);
+          }
+
+          // Conflict cases
+          if (conflict_ignore === true) {
+            return;
+          }
+
+          if ((conflict_revert === true) || (local_hash === null)) {
+            // Automatically resolve conflict or force revert
+            if (remote_hash === null) {
+              // Deleted remotely
+              return propagateAttachmentDeletion(skip_attachment_dict,
+                                                 source, id, name);
+            }
+            return propagateAttachmentModification(
+              skip_attachment_dict,
+              source,
+              remote_blob,
+              remote_hash,
+              id,
+              name
+            );
+          }
+
+          // Minimize conflict if it can be resolved
+          if (remote_hash === null) {
+            // Copy remote modification remotely
+            return propagateAttachmentModification(skip_attachment_dict,
+                                         destination, blob,
+                                         local_hash, id, name);
+          }
+          throw new jIO.util.jIOError("Conflict on '" + id +
+                                      "' with attachment '" +
+                                      name + "'",
+                                      409);
+        });
+    }
+
+    function checkAttachmentSignatureDifference(skip_attachment_dict,
+                                                queue, source,
+                                                destination, id, name,
+                                                conflict_force,
+                                                conflict_revert,
+                                                conflict_ignore,
+                                                is_creation, is_modification) {
+      var blob,
+        status_hash;
+      queue
+        .push(function () {
+          // Optimisation to save a get call to signature storage
+          if (is_creation === true) {
+            return RSVP.all([
+              source.getAttachment(id, name),
+              {hash: null}
+            ]);
+          }
+          if (is_modification === true) {
+            return RSVP.all([
+              source.getAttachment(id, name),
+              context._signature_sub_storage.getAttachment(
+                id,
+                name,
+                {format: 'json'}
+              )
+            ]);
+          }
+          throw new jIO.util.jIOError("Unexpected call of"
+                                      + " checkAttachmentSignatureDifference",
+                                      409);
+        })
+        .push(function (result_list) {
+          blob = result_list[0];
+          status_hash = result_list[1].hash;
+          return jIO.util.readBlobAsArrayBuffer(blob);
+        })
+        .push(function (evt) {
+          var array_buffer = evt.target.result,
+            local_hash = generateHashFromArrayBuffer(array_buffer);
+
+          if (local_hash !== status_hash) {
+            return checkAndPropagateAttachment(skip_attachment_dict,
+                                               status_hash, local_hash, blob,
+                                               source, destination, id, name,
+                                               conflict_force, conflict_revert,
+                                               conflict_ignore);
+          }
+        });
+    }
+
+    function checkAttachmentLocalDeletion(skip_attachment_dict,
+                                queue, destination, id, name, source,
+                                conflict_force, conflict_revert,
+                                conflict_ignore) {
+      var status_hash;
+      queue
+        .push(function () {
+          return context._signature_sub_storage.getAttachment(id, name,
+                                                              {format: 'json'});
+        })
+        .push(function (result) {
+          status_hash = result.hash;
+          return checkAndPropagateAttachment(skip_attachment_dict,
+                                   status_hash, null, null,
+                                   source, destination, id, name,
+                                   conflict_force, conflict_revert,
+                                   conflict_ignore);
+        });
+    }
+
+    function pushDocumentAttachment(skip_attachment_dict, id, source,
+                                    destination, options) {
+      var queue = new RSVP.Queue();
+
+      return queue
+        .push(function () {
+          return RSVP.all([
+            source.allAttachments(id)
+              .push(undefined, function (error) {
+                if ((error instanceof jIO.util.jIOError) &&
+                    (error.status_code === 404)) {
+                  return {};
+                }
+                throw error;
+              }),
+            context._signature_sub_storage.allAttachments(id)
+              .push(undefined, function (error) {
+                if ((error instanceof jIO.util.jIOError) &&
+                    (error.status_code === 404)) {
+                  return {};
+                }
+                throw error;
+              })
+          ]);
+        })
+        .push(function (result_list) {
+          var local_dict = {},
+            signature_dict = {},
+            is_modification,
+            is_creation,
+            key;
+          for (key in result_list[0]) {
+            if (result_list[0].hasOwnProperty(key)) {
+              if (!skip_attachment_dict.hasOwnProperty(key)) {
+                local_dict[key] = null;
+              }
+            }
+          }
+          for (key in result_list[1]) {
+            if (result_list[1].hasOwnProperty(key)) {
+              if (!skip_attachment_dict.hasOwnProperty(key)) {
+                signature_dict[key] = null;
+              }
+            }
+          }
+
+          for (key in local_dict) {
+            if (local_dict.hasOwnProperty(key)) {
+              is_modification = signature_dict.hasOwnProperty(key)
+                && options.check_modification;
+              is_creation = !signature_dict.hasOwnProperty(key)
+                && options.check_creation;
+              if (is_modification === true || is_creation === true) {
+                checkAttachmentSignatureDifference(skip_attachment_dict,
+                                                   queue, source,
+                                                   destination, id, key,
+                                                   options.conflict_force,
+                                                   options.conflict_revert,
+                                                   options.conflict_ignore,
+                                                   is_creation,
+                                                   is_modification);
+              }
+            }
+          }
+          if (options.check_deletion === true) {
+            for (key in signature_dict) {
+              if (signature_dict.hasOwnProperty(key)) {
+                if (!local_dict.hasOwnProperty(key)) {
+                  checkAttachmentLocalDeletion(skip_attachment_dict,
+                                               queue, destination, id, key,
+                                               source,
+                                               options.conflict_force,
+                                               options.conflict_revert,
+                                               options.conflict_ignore);
+                }
+              }
+            }
+          }
+        });
+    }
+
+
+    function repairDocumentAttachment(id) {
+      var skip_attachment_dict = {};
+      return new RSVP.Queue()
+        .push(function () {
+          if (context._check_local_attachment_modification ||
+              context._check_local_attachment_creation ||
+              context._check_local_attachment_deletion) {
+            return pushDocumentAttachment(
+              skip_attachment_dict,
+              id,
+              context._local_sub_storage,
+              context._remote_sub_storage,
+              {
+                conflict_force: (context._conflict_handling ===
+                                 CONFLICT_KEEP_LOCAL),
+                conflict_revert: (context._conflict_handling ===
+                                  CONFLICT_KEEP_REMOTE),
+                conflict_ignore: (context._conflict_handling ===
+                                  CONFLICT_CONTINUE),
+                check_modification:
+                  context._check_local_attachment_modification,
+                check_creation: context._check_local_attachment_creation,
+                check_deletion: context._check_local_attachment_deletion
+              }
+            );
+          }
+        })
+        .push(function () {
+          if (context._check_remote_attachment_modification ||
+              context._check_remote_attachment_creation ||
+              context._check_remote_attachment_deletion) {
+            return pushDocumentAttachment(
+              skip_attachment_dict,
+              id,
+              context._remote_sub_storage,
+              context._local_sub_storage,
+              {
+                use_revert_post: context._use_remote_post,
+                conflict_force: (context._conflict_handling ===
+                                 CONFLICT_KEEP_REMOTE),
+                conflict_revert: (context._conflict_handling ===
+                                  CONFLICT_KEEP_LOCAL),
+                conflict_ignore: (context._conflict_handling ===
+                                  CONFLICT_CONTINUE),
+                check_modification:
+                  context._check_remote_attachment_modification,
+                check_creation: context._check_remote_attachment_creation,
+                check_deletion: context._check_remote_attachment_deletion
+              }
+            );
+          }
+        });
+    }
+
     function propagateModification(source, destination, doc, hash, id,
                                    options) {
       var result,
@@ -8553,6 +8946,33 @@ return new Parser;
             post_id = new_id;
             return source.put(post_id, doc);
           })
+          .push(function () {
+            // Copy all attachments
+            // This is not related to attachment replication
+            // It's just about not losing user data
+            return source.allAttachments(id);
+          })
+          .push(function (attachment_dict) {
+            var key,
+              copy_queue = new RSVP.Queue();
+
+            function copyAttachment(name) {
+              copy_queue
+                .push(function () {
+                  return source.getAttachment(id, name);
+                })
+                .push(function (blob) {
+                  return source.putAttachment(post_id, name, blob);
+                });
+            }
+
+            for (key in attachment_dict) {
+              if (attachment_dict.hasOwnProperty(key)) {
+                copyAttachment(key);
+              }
+            }
+            return copy_queue;
+          })
           .push(function () {
             return source.remove(id);
           })
@@ -8585,9 +9005,27 @@ return new Parser;
     }
 
     function propagateDeletion(destination, id) {
-      return destination.remove(id)
+      // Do not delete a document if it has an attachment
+      // ie, replication should prevent losing user data
+      // Synchronize attachments before, to ensure
+      // all of them will be deleted too
+      return repairDocumentAttachment(id)
         .push(function () {
-          return context._signature_sub_storage.remove(id);
+          return destination.allAttachments(id);
+        })
+        .push(function (attachment_dict) {
+          if (JSON.stringify(attachment_dict) === "{}") {
+            return destination.remove(id)
+              .push(function () {
+                return context._signature_sub_storage.remove(id);
+              });
+          }
+        }, function (error) {
+          if ((error instanceof jIO.util.jIOError) &&
+              (error.status_code === 404)) {
+            return;
+          }
+          throw error;
         })
         .push(function () {
           skip_document_dict[id] = null;
@@ -8921,7 +9359,7 @@ return new Parser;
         // Keep it like this until the bulk API is stabilized
         var use_bulk_get = false;
         try {
-          use_bulk_get = context._remote_sub_storage.hasCapacity("bulk");
+          use_bulk_get = context._remote_sub_storage.hasCapacity("bulk_get");
         } catch (error) {
           if (!((error instanceof jIO.util.jIOError) &&
                (error.status_code === 501))) {
@@ -8946,6 +9384,34 @@ return new Parser;
               check_deletion: context._check_remote_deletion
             });
         }
+      })
+      .push(function () {
+        if (context._check_local_attachment_modification ||
+            context._check_local_attachment_creation ||
+            context._check_local_attachment_deletion ||
+            context._check_remote_attachment_modification ||
+            context._check_remote_attachment_creation ||
+            context._check_remote_attachment_deletion) {
+          // Attachments are synchronized if and only if their parent document
+          // has been also marked as synchronized.
+          return context._signature_sub_storage.allDocs()
+            .push(function (result) {
+              var i,
+                repair_document_queue = new RSVP.Queue();
+
+              function repairDocument(id) {
+                repair_document_queue
+                  .push(function () {
+                    return repairDocumentAttachment(id);
+                  });
+              }
+
+              for (i = 0; i < result.data.total_rows; i += 1) {
+                repairDocument(result.data.rows[i].id);
+              }
+              return repair_document_queue;
+            });
+        }
       });
   };
 
@@ -10879,7 +11345,7 @@ return new Parser;
   ERP5Storage.prototype.hasCapacity = function (name) {
     return ((name === "list") || (name === "query") ||
             (name === "select") || (name === "limit") ||
-            (name === "sort"));
+            (name === "sort")) || (name === "bulk_get");
   };
 
   function isSingleLocalRoles(parsed_query) {
@@ -12839,4 +13305,4 @@ return new Parser;
 
   jIO.addStorage('websql', WebSQLStorage);
 
-}(jIO, RSVP, Blob, openDatabase));
\ No newline at end of file
+}(jIO, RSVP, Blob, openDatabase));