From 9d9f83c7a36d0a82f400f2eadfe570a4d8082020 Mon Sep 17 00:00:00 2001
From: Georgios Dagkakis <georgios.dagkakis@nexedi.com>
Date: Mon, 22 Feb 2016 17:18:54 +0000
Subject: [PATCH] BusinessTemplates: Allow export of files like Python Script,
 Web Page, Web Script etc. separately from the xml

---
 product/ERP5/Document/BusinessTemplate.py     | 390 ++++++--
 product/ERP5/Tool/TemplateTool.py             |  68 +-
 product/ERP5/tests/testBusinessTemplate.py    |   4 -
 .../testBusinessTemplateTwoFileExport.py      | 944 ++++++++++++++++++
 4 files changed, 1293 insertions(+), 113 deletions(-)
 create mode 100644 product/ERP5/tests/testBusinessTemplateTwoFileExport.py

diff --git a/product/ERP5/Document/BusinessTemplate.py b/product/ERP5/Document/BusinessTemplate.py
index 7c285c0e63..5778478af6 100644
--- a/product/ERP5/Document/BusinessTemplate.py
+++ b/product/ERP5/Document/BusinessTemplate.py
@@ -62,7 +62,7 @@ from Products.ERP5Type.Utils import readLocalTest, \
 from Products.ERP5Type.Utils import convertToUpperCase
 from Products.ERP5Type import Permissions, PropertySheet, interfaces
 from Products.ERP5Type.XMLObject import XMLObject
-from Products.ERP5Type.dynamic.lazy_class import ERP5BaseBroken
+from Products.ERP5Type.dynamic.lazy_class import ERP5BaseBroken, InitGhostBase
 from Products.ERP5Type.dynamic.portal_type_class import synchronizeDynamicModules
 from Products.ERP5Type.Core.PropertySheet import PropertySheet as PropertySheetDocument
 from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
@@ -102,6 +102,12 @@ try:
 except TypeError:
   pass
 cache_database = threading.local()
+from mimetypes import guess_extension
+from mimetypes import MimeTypes
+from Products.MimetypesRegistry.common import MimeTypeException
+import base64
+import binascii
+import imghdr
 
 # those attributes from CatalogMethodTemplateItem are kept for
 # backward compatibility
@@ -686,19 +692,279 @@ class ObjectTemplateItem(BaseTemplateItem):
         if id != '':
           self._archive["%s/%s" % (tool_id, id)] = None
 
-  def export(self, context, bta, **kw):
+  def getClassNameAndExportedExtensionDict(self):
+    class_name_and_exported_extension_dict = {
+      "Web Page": {"extension": None, "exported_property_type": "text_content"},
+      "Web Style": {"extension": None, "exported_property_type": "text_content"},
+      "Web Script": {"extension": None, "exported_property_type": "text_content"},
+      "ZopePageTemplate": {"extension": ".zpt", "exported_property_type": "_text"},
+      "OOoTemplate": {"extension": ".oot", "exported_property_type": "_text"},
+      "Extension Component": {"extension": ".py", "exported_property_type": "text_content"},
+      "Test Component": {"extension": ".py", "exported_property_type": "text_content"},
+      "Document Component": {"extension": ".py", "exported_property_type": "text_content"},
+      "PythonScript": {"extension": ".py", "exported_property_type": "_body"},
+      "Image": {"extension": None, "exported_property_type": "data"},
+      "File": {"extension": None, "exported_property_type": "data"},
+      "DTMLMethod": {"extension": None, "exported_property_type": "raw"},
+      "SQL": {"extension": '.sql', "exported_property_type": "src"},
+      "Spreadsheet": {"extension": None, "exported_property_type": "data"},
+      "PDF": {"extension": '.pdf', "exported_property_type": "data"}
+      }
+    return class_name_and_exported_extension_dict
+
+  def getPropertyAndExtensionExportedSeparatelyDict(self, document, key):
+    """Returns a dictionary with one element in the type of
+      {exported_property_type: extension}
+
+    exported_property_type is the key of the document where the actual data
+    exists. E.g. for a Extension Component this is 'text_content', while for a
+    PythonScript it is'_body'.
+
+    The extension can be default for some Portal Types, e.g. for PythonScript it
+    is always '.py', but for others like File guessExtensionOfDocument is used
+    to identify it.
+    """
+    class_name = document.__class__.__name__
+    class_name_and_exported_extension_dict = self.getClassNameAndExportedExtensionDict()
+    if class_name in class_name_and_exported_extension_dict.keys():
+      extension = class_name_and_exported_extension_dict[class_name]["extension"]
+      exported_property_type = class_name_and_exported_extension_dict[class_name]["exported_property_type"]
+      if extension:
+        return {exported_property_type: extension}
+      else:
+        extension = self.guessExtensionOfDocument(document, key, exported_property_type)
+        if extension:
+          # if the obtained extension was .xml, change it to ._xml so that it
+          # would not conflict with th .xml metadata document
+          if extension == '.xml':
+            extension = '._xml'
+          return {exported_property_type: extension}
+    return {}
+
+  def getMimetypesRegistryLookupResultOfContenType(self, content_type):
+    """Returns a lookup in mimetypes_registry based on the content_type"""
+    mimetypes_registry = self.getPortalObject().mimetypes_registry
+    try:
+      return mimetypes_registry.lookup(content_type)
+    except MimeTypeException:
+      return
+
+  def guessExtensionOfDocument(self, document, key, exported_property_type=None):
+    """Guesses and returns the extension of an ERP5 document.
+
+    The process followed is:
+    1. Try to guess extension by content type
+    2. Try to guess extension by the id of the document
+    3. Try to guess extension by the title of the document
+    4. Try to guess extension by the reference of the document
+    5. In the case of an image, try to guess extension by Base64 representation
+
+    In case everything fails then:
+    - '.bin' is returned for binary files
+    - '.txt' is returned for text
+    """
+    # XXX Zope items like DTMLMethod would not
+    # implement getContentType method
+    extension = None
+    binary = 'not_identified'
+    if hasattr(document, 'getContentType'):
+      content_type = document.getContentType()
+      lookup_result = self.getMimetypesRegistryLookupResultOfContenType(content_type)
+      if lookup_result:
+        # return first registered Extension (if any)
+        if lookup_result[0].extensions:
+          extension = lookup_result[0].extensions[0]
+        # If there is no extension return the first registered Glob (if any)
+        elif lookup_result[0].globs:
+          extension = str(lookup_result[0].globs[0]).replace('*', '')
+        if hasattr(lookup_result[0], 'binary'):
+          binary = lookup_result[0].binary
+      if extension:
+        if not extension.startswith('.'):
+          extension = '.'+extension
+        return extension
+    # Try to guess the extension based on the id of the document
+    mime = MimeTypes()    
+    mime_type = mime.guess_type(key)
+    if mime_type[0]:
+      extension = guess_extension(mime_type[0])
+      return extension
+    # Try to guess the extension based on the reference of the document
+    mime = MimeTypes()
+    if hasattr(document, 'getReference'):
+      reference = document.getReference()
+      if reference:
+        mime_type = mime.guess_type(reference)
+        if mime_type[0]:
+          extension = guess_extension(mime_type[0])
+          return extension
+    # Try to guess the extension based on the title of the document
+    if hasattr(document, 'title'):
+      mime_type = mime.guess_type(document.title)
+      if mime_type[0]:
+        extension = guess_extension(mime_type[0])
+        return extension
+    # Try to get type of image from its base64 encoding
+    if exported_property_type == 'data':
+      # XXX maybe decoding the whole file just for the extension is an overkill
+      data = str(document.__dict__.get('data'))
+      try:
+        decoded_data = base64.decodestring(data)
+        for test in imghdr.tests:
+          extension = test(decoded_data, None)
+          if extension:
+            return '.'+extension
+      except binascii.Error:
+        LOG('BusinessTemplate ', 0, 'data is not base 64')
+    # in case we could not read binary flag from mimetypes_registry then return
+    # '.bin' for all the Portal Types where exported_property_type is data
+    # (File, Image, Spreadsheet). Otherwise, return .bin if binary was returned
+    # as 1.
+    if (binary == 'not_identified' and exported_property_type == 'data') or \
+       binary == 1:
+      return '.bin'
+    # in all other cases return .txt
+    return '.txt'
+      
+  def export(self, context, bta, catalog_method_template_item = 0, **kw):
     """
       Export the business template : fill the BusinessTemplateArchive with
       objects exported as XML, hierarchicaly organised.
     """
     if len(self._objects.keys()) == 0:
       return
-    path = self.__class__.__name__
+    path = self.__class__.__name__+ '/'
     for key, obj in self._objects.iteritems():
-      # export object in xml
-      f = StringIO()
-      XMLExportImport.exportXML(obj._p_jar, obj._p_oid, f)
-      bta.addObject(f, key, path=path)
+      class_name=obj.__class__.__name__ 
+      property_and_extension_exported_separately_dict = self.getPropertyAndExtensionExportedSeparatelyDict(obj, key)
+      # Back compatibility with filesystem Documents
+      if isinstance(obj, str):
+        if not key.startswith(path):
+          key = path + key
+        bta.addObject(obj, name=key, ext='.py')
+      else:
+        if property_and_extension_exported_separately_dict:
+          for record_id, record in property_and_extension_exported_separately_dict.iteritems():
+            extension = record
+            exported_property_type = record_id
+            if hasattr(obj, exported_property_type):
+              exported_property = getattr(obj, exported_property_type)
+              if isinstance(exported_property, unicode):
+                exported_property = str(exported_property.encode('utf-8'))
+              elif not isinstance(exported_property, str):
+                exported_property = str(exported_property)
+
+              reset_output_encoding = False
+              if hasattr(obj, 'output_encoding'):
+                reset_output_encoding = True
+                output_encoding = obj.output_encoding
+              obj = obj._getCopy(context)
+
+              f = StringIO(exported_property)
+              bta.addObject(f, key, path=path, ext=extension)
+
+              # since we get the obj from context we should
+              # again remove useless properties
+              obj = self.removeProperties(obj, 1, keep_workflow_history = True)
+
+              # in case the related Portal Type does not exist, the object may be broken.
+              # So we cannot delattr, but we can delet the het of its its broken state
+              if isinstance(obj, ERP5BaseBroken):
+                del obj.__Broken_state__[exported_property_type]
+                if reset_output_encoding:
+                  self._objects[obj_key].__Broken_state__['output_encoding'] = output_encoding
+                obj._p_changed = 1
+              else:
+                delattr(obj, exported_property_type)
+                if reset_output_encoding:
+                  obj.output_encoding = output_encoding
+              transaction.savepoint(optimistic=True)
+
+        f = StringIO()
+        XMLExportImport.exportXML(obj._p_jar, obj._p_oid, f)
+        bta.addObject(f, key, path=path)
+        
+      if catalog_method_template_item:
+        # add all datas specific to catalog inside one file
+        xml_data = self.generateXml(key)
+        bta.addObject(xml_data, key + '.catalog_keys', path=path)
+        
+  def _importFile(self, file_name, file_obj, catalog_method_template_item = 0):
+    transactional_variable = getTransactionalVariable()
+    obj_key, file_ext = os.path.splitext(file_name)
+    # id() for installing several bt5 in the same transaction
+    transactional_variable_obj_key = "%s-%s" % (id(self), obj_key)
+    if file_ext != '.xml':
+      # if the document has not been migrated yet (its class is file and
+      # it is not in portal_components) use legacy importer
+      if issubclass(self.__class__, FilesystemDocumentTemplateItem) and file_obj.name.rsplit(os.path.sep, 2)[-2] != 'portal_components':
+        FilesystemDocumentTemplateItem._importFile(self, file_name, file_obj)
+      else:
+        # For ZODB Components: if .xml have been processed before, set the
+        # source code property, otherwise store it in a transactional variable
+        # so that it can be set once the .xml has been processed
+        file_obj_content = file_obj.read()
+        try:
+          obj = self._objects[obj_key]
+        except KeyError:
+          transactional_variable[transactional_variable_obj_key] = file_obj_content
+        else:
+          obj_class_name = obj.__class__.__name__
+          exported_property_type = self.getClassNameAndExportedExtensionDict()[obj_class_name]['exported_property_type']
+          # if we have instance of InitGhostBase, 'unghost' it so we can
+          # identify if it is broken
+          if isinstance(self._objects[obj_key], InitGhostBase):
+            self._objects[obj_key].__class__.loadClass()
+          # in case the Portal Type does not exist, the object may be broken.
+          # So we cannot setattr, but we can change the attribute in its broken state
+          if isinstance(self._objects[obj_key], ERP5BaseBroken):
+            self._objects[obj_key].__Broken_state__[exported_property_type] = file_obj_content
+            self._objects[obj_key]._p_changed = 1
+          else:
+            setattr(self._objects[obj_key], exported_property_type, file_obj_content)
+            self._objects[obj_key] = self.removeProperties(self._objects[obj_key], 1, keep_workflow_history = True)
+    else:
+      connection = self.getConnection(self.aq_parent)
+      __traceback_info__ = 'Importing %s' % file_name
+      if hasattr(cache_database, 'db') and isinstance(file_obj, file):
+        obj = connection.importFile(self._compileXML(file_obj))
+      else:
+        # FIXME: Why not use the importXML function directly? Are there any BT5s
+        # with actual .zexp files on the wild?
+        obj = connection.importFile(file_obj, customImporters=customImporters)
+      self._objects[obj_key] = obj
+
+      if transactional_variable.get(transactional_variable_obj_key, None) != None:
+        obj_class_name = obj.__class__.__name__
+        exported_property_type = self.getClassNameAndExportedExtensionDict()[obj_class_name]['exported_property_type']
+        # if we have instance of InitGhostBase, 'unghost' it so we can
+        # identify if it is broken
+        if isinstance(self._objects[obj_key], InitGhostBase):
+          self._objects[obj_key].__class__.loadClass()
+        # in case the related Portal Type does not exist, the object may be broken.
+        # So we cannot setattr, but we can change the attribute in its broken state
+        if isinstance(self._objects[obj_key], ERP5BaseBroken):
+          self._objects[obj_key].__Broken_state__[exported_property_type] = transactional_variable[transactional_variable_obj_key]
+          self._objects[obj_key]._p_changed = 1
+        else:
+          setattr(self._objects[obj_key], exported_property_type, transactional_variable[transactional_variable_obj_key])
+          self._objects[obj_key] = self.removeProperties(self._objects[obj_key], 1, keep_workflow_history = True)
+
+      # When importing a Business Template, there is no way to determine if it
+      # has been already migrated or not in __init__() when it does not
+      # already exist, therefore BaseTemplateItem.__init__() is called which
+      # does not set _archive with portal_components/ like
+      # ObjectTemplateItem.__init__()
+      # XXX - the above comment is a bit unclear, 
+      # still not sure if this is handled correctly
+      if file_obj.name.rsplit(os.path.sep, 2)[-2] == 'portal_components':
+        self._archive[obj_key] = None
+        try:
+          del self._archive[obj_key[len('portal_components/'):]]
+        except KeyError:
+          pass
+      if catalog_method_template_item:
+        self.removeProperties(obj, 0)
 
   def build_sub_objects(self, context, id_list, url, **kw):
     # XXX duplicates code from build
@@ -820,21 +1086,6 @@ class ObjectTemplateItem(BaseTemplateItem):
         return connection
       obj = obj.aq_parent
 
-  def _importFile(self, file_name, file_obj):
-    # import xml file
-    if not file_name.endswith('.xml'):
-      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
-      return
-    connection = self.getConnection(self.aq_parent)
-    __traceback_info__ = 'Importing %s' % file_name
-    if hasattr(cache_database, 'db') and isinstance(file_obj, file):
-      obj = connection.importFile(self._compileXML(file_obj))
-    else:
-      # FIXME: Why not use the importXML function directly? Are there any BT5s
-      # with actual .zexp files on the wild?
-      obj = connection.importFile(file_obj, customImporters=customImporters)
-    self._objects[file_name[:-4]] = obj
-
   def preinstall(self, context, installed_item, **kw):
     modified_object_list = {}
     upgrade_list = []
@@ -2718,19 +2969,7 @@ class CatalogMethodTemplateItem(ObjectTemplateItem):
     if catalog is None:
       LOG('BusinessTemplate, export', 0, 'no SQL catalog was available')
       return
-
-    if len(self._objects.keys()) == 0:
-      return
-    path = self.__class__.__name__
-    for key in self._objects.keys():
-      obj = self._objects[key]
-      # export object in xml
-      f=StringIO()
-      XMLExportImport.exportXML(obj._p_jar, obj._p_oid, f)
-      bta.addObject(f, key, path=path)
-      # add all datas specific to catalog inside one file
-      xml_data = self.generateXml(key)
-      bta.addObject(xml_data, key + '.catalog_keys', path=path)
+    ObjectTemplateItem.export(self, context, bta, catalog_method_template_item=1)
 
   def install(self, context, trashbin, **kw):
     ObjectTemplateItem.install(self, context, trashbin, **kw)
@@ -2887,14 +3126,9 @@ class CatalogMethodTemplateItem(ObjectTemplateItem):
         else:
           # new style key
           self._method_properties.setdefault(id, PersistentMapping())[key] = 1
-    elif file_name.endswith('.xml'):
-      # just import xml object
-      connection = self.getConnection(self.aq_parent)
-      obj = connection.importFile(file, customImporters=customImporters)
-      self.removeProperties(obj, 0)
-      self._objects[file_name[:-4]] = obj
     else:
-      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
+      ObjectTemplateItem._importFile(self, file_name, file, catalog_method_template_item=1)
+
 
 class ActionTemplateItem(ObjectTemplateItem):
 
@@ -3968,74 +4202,14 @@ class DocumentTemplateItem(FilesystemToZodbTemplateItem):
       wf_history.pop('comment', None)
 
       obj.workflow_history[wf_id] = WorkflowHistoryList([wf_history])
-
+  
+  # XXX temporary should be eliminated from here
   def _importFile(self, file_name, file_obj):
-    """
-    This will be called once for non-migrated Document and twice for ZODB
-    Components (for .xml (metadata) and .py (source code)). This code MUST
-    consider both bt5 folder (everything is on the FS) and tarball (where in
-    case of ZODB Components, .xml may have been processed before .py and vice
-    versa.
-    """
-    tv = getTransactionalVariable()
-    obj_key, file_ext = os.path.splitext(file_name)
-    # id() for installing several bt5 in the same transaction
-    tv_obj_key = "%s-%s" % (id(self), obj_key)
-    if file_ext == '.py':
-      # If this Document has not been migrated yet (eg not matching
-      # "portal_components/XXX.py"), use legacy importer
-      if file_obj.name.rsplit(os.path.sep, 2)[-2] != 'portal_components':
-        FilesystemDocumentTemplateItem._importFile(self, file_name, file_obj)
-      # For ZODB Components: if .xml have been processed before, set the
-      # source code property, otherwise store it in a transactional variable
-      # so that it can be set once the .xml has been processed
-      else:
-        text_content = file_obj.read()
-        try:
-          obj = self._objects[obj_key]
-        except KeyError:
-          tv[tv_obj_key] = text_content
-        else:
-          obj.text_content = text_content
-    elif file_ext == '.xml':
-      ObjectTemplateItem._importFile(self, file_name, file_obj)
-      self._objects[obj_key].text_content = tv.get(tv_obj_key, None)
-
-      # When importing a Business Template, there is no way to determine if it
-      # has been already migrated or not in __init__() when it does not
-      # already exist, therefore BaseTemplateItem.__init__() is called which
-      # does not set _archive with portal_components/ like
-      # ObjectTemplateItem.__init__()
-      self._archive[obj_key] = None
-      del self._archive[obj_key[len('portal_components/'):]]
-    else:
-      LOG('Business Template', 0, 'Skipping file "%s"' % file_name)
-
+    ObjectTemplateItem._importFile(self, file_name, file_obj)
+  
+  # XXX temporary should be eliminated from here
   def export(self, context, bta, **kw):
-    """
-    Export a Document as two files for ZODB Components, one for metadata
-    (.xml) and the other for the Python source code (.py)
-    """
-    path = self.__class__.__name__ + '/'
-    for key, obj in self._objects.iteritems():
-      # Back compatibility with filesystem Documents
-      if isinstance(obj, str):
-        if not key.startswith(path):
-          key = path + key
-        bta.addObject(obj, name=key, ext='.py')
-      else:
-        obj = obj._getCopy(context)
-
-        f = StringIO(obj.text_content)
-        bta.addObject(f, key, path=path, ext='.py')
-
-        del obj.text_content
-        transaction.savepoint(optimistic=True)
-
-        # export object in xml
-        f = StringIO()
-        XMLExportImport.exportXML(obj._p_jar, obj._p_oid, f)
-        bta.addObject(f, key, path=path)
+    ObjectTemplateItem.export(self, context, bta, **kw)  
 
   def getTemplateIdList(self):
     """
diff --git a/product/ERP5/Tool/TemplateTool.py b/product/ERP5/Tool/TemplateTool.py
index 0b552d92e9..dc75196ba3 100644
--- a/product/ERP5/Tool/TemplateTool.py
+++ b/product/ERP5/Tool/TemplateTool.py
@@ -62,7 +62,7 @@ from zLOG import LOG, INFO, WARNING
 from base64 import decodestring
 import subprocess
 import time
-
+from distutils.dir_util import copy_tree
 
 WIN = os.name == 'nt'
 
@@ -461,6 +461,72 @@ class TemplateTool (BaseTool):
       """
       return self.getFilteredDiff(diff).toHTML()
 
+    def _cleanUpTemplateFolder(self, folder_path):
+      for file_object in os.listdir(folder_path):
+        file_object_path = os.path.join(folder_path, file_object)
+        if os.path.isfile(file_object_path):
+          os.unlink(file_object_path)
+        else:
+          shutil.rmtree(file_object_path)
+
+    def _importAndReExportBusinessTemplate(self, template_path):
+      """
+        Imports the template that is in the template_path and exports it to the
+        same path.
+
+        We want to clean this directory, i.e. remove all files before
+        the export. Because this is called as activity though, it could cause
+        the following problem:
+        - Activity imports the template
+        - Activity removes all files from template_path
+        - Activity fails in export.
+        Then the folder contents will be changed, so when retrying the
+        activity may succeed without the user understanding that files were
+        erased. For this reason export is done in 3 steps:
+        - First to a temporary directory
+        - If there was no error delete contents of template_path
+        - Copy the contents of the temporary directory to the template_path
+      """
+      import_template = self.download(url=template_path)
+      export_dir = mkdtemp()
+      try:
+        import_template.export(path=export_dir, local=True)
+        self._cleanUpTemplateFolder(template_path)
+        copy_tree(export_dir, template_path)
+      except:
+        raise
+      finally:
+        shutil.rmtree(export_dir)
+
+    security.declareProtected( 'Import/Export objects', 'importAndReExportBusinessTemplatesFromPath' )
+    def importAndReExportBusinessTemplatesFromPath(self, repository_list, REQUEST=None, **kw):
+      """
+        Migrate business templates to new format where files like .py or .html
+        are exported seprately than the xml.
+      """
+      repository_list = filter(bool, repository_list)
+
+      if REQUEST is None:
+        REQUEST = getattr(self, 'REQUEST', None)
+        
+      if len(repository_list) == 0 and REQUEST:
+        ret_url = self.absolute_url()
+        REQUEST.RESPONSE.redirect("%s?portal_status_message=%s"
+                                  % (ret_url, 'No repository was defined'))
+                                    
+      for repository in repository_list:
+        repository = repository.rstrip('\n')
+        repository = repository.rstrip('\r')
+        for business_template_id in os.listdir(repository):
+          template_path = os.path.join(repository, business_template_id)
+          if os.path.isfile(template_path):
+            LOG(business_template_id,0,'is file, so it is skipped')
+          else:
+            if not os.path.exists((os.path.join(template_path, 'bt'))):
+              LOG(business_template_id,0,'has no bt sub-folder, so it is skipped')
+            else:
+              self.activate(activity='SQLQueue')._importAndReExportBusinessTemplate(template_path)
+
     security.declareProtected(Permissions.ManagePortal, 'getFilteredDiff')
     def getFilteredDiff(self, diff):
       """
diff --git a/product/ERP5/tests/testBusinessTemplate.py b/product/ERP5/tests/testBusinessTemplate.py
index 9b7a619e65..b099c0773c 100644
--- a/product/ERP5/tests/testBusinessTemplate.py
+++ b/product/ERP5/tests/testBusinessTemplate.py
@@ -7546,10 +7546,6 @@ class TestDocumentTemplateItem(BusinessTemplateMixin):
       self.assertNotEqual(wf_history_dict.get('actor'), None)
       self.assertNotEqual(wf_history_dict.get('comment'), None)
 
-  def stepRemoveZodbDocument(self, sequence=None, **kw):
-    self.getPortalObject().portal_components.deleteContent(
-      sequence['document_id'])
-
   def stepCheckZodbDocumentExistsAndValidated(self, sequence=None, **kw):
     component = getattr(self.getPortalObject().portal_components,
                         sequence['document_id'], None)
diff --git a/product/ERP5/tests/testBusinessTemplateTwoFileExport.py b/product/ERP5/tests/testBusinessTemplateTwoFileExport.py
new file mode 100644
index 0000000000..98690ffb05
--- /dev/null
+++ b/product/ERP5/tests/testBusinessTemplateTwoFileExport.py
@@ -0,0 +1,944 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved.
+#          Aurelien Calonne <aurel@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsibility of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# guarantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
+from App.config import getConfiguration
+import shutil
+import os
+import tempfile
+
+class TestBusinessTemplateTwoFileExport(ERP5TypeTestCase):
+  """
+    Test export and import of business templates with files (e.g. Web Script)
+
+    - Create a template
+
+    - Create a file
+
+    - Build and export the template
+
+    - Check that the expected files are exported
+
+    - Import the exported template and install it
+
+    - Check that the files are imported properly
+  """
+
+  def getBusinessTemplateList(self):
+    return ['erp5_core_proxy_field_legacy',
+                            'erp5_property_sheets',
+                            'erp5_jquery',
+                            'erp5_full_text_mroonga_catalog',
+                            'erp5_base',
+                            'erp5_core',
+                            'erp5_ingestion_mysql_innodb_catalog',
+                            'erp5_ingestion',
+                            'erp5_xhtml_style',
+                            'erp5_web',
+                            'erp5_hal_json_style',
+                            'erp5_dms',
+                            'erp5_web_renderjs_ui'
+                            ]
+
+  def afterSetUp(self):
+    self.cfg = getConfiguration()
+    self.export_dir = tempfile.mkdtemp()
+    self.template_tool = self.getTemplateTool()
+    self.template = self._createNewBusinessTemplate(self.template_tool)
+
+  def beforeTearDown(self):
+    export_dir_path = os.path.join(self.cfg.instancehome, self.export_dir)
+    if os.path.exists(export_dir_path):
+      shutil.rmtree(self.export_dir)
+
+  def _createNewBusinessTemplate(self, template_tool):
+    template = template_tool.newContent(portal_type='Business Template')
+    self.assertTrue(template.getBuildingState() == 'draft')
+    self.assertTrue(template.getInstallationState() == 'not_installed')
+    template.edit(title ='test_template',
+                  version='1.0',
+                  description='bt for unit_test')
+    return template
+
+  def _buildAndExportBusinessTemplate(self):
+    self.tic()
+    self.template.build()
+    self.tic()
+
+    self.template.export(path=self.export_dir, local=True)
+    self.tic()
+
+  def _importBusinessTemplate(self):
+    template_id = self.template.getId()
+    template_path = os.path.join(self.cfg.instancehome, self.export_dir)
+    self.template_tool.manage_delObjects(template_id)
+
+    import_template = self.template_tool.download(url='file:'+template_path)
+
+    self.assertFalse(import_template is None)
+    self.assertEqual(import_template.getPortalType(), 'Business Template')
+
+    return import_template
+
+  def _exportAndReImport(self, xml_document_path,
+                        file_document_path, data, removed_property_list):
+
+    self._buildAndExportBusinessTemplate()
+    self.assertTrue(os.path.exists(xml_document_path))
+    self.assertTrue(os.path.exists(file_document_path))
+    test_file=open(file_document_path,'r+')
+    self.assertEqual(test_file.read(), data)
+    test_file.close()
+    xml_file=open(xml_document_path,'r+')
+    xml_file_content = xml_file.read()
+    xml_file.close()
+    for exported_property in removed_property_list:
+      self.assertFalse('<string>'+exported_property+'</string>' in xml_file_content)
+
+    import_template = self._importBusinessTemplate()
+    return import_template
+
+  def test_twoFileImportExportForTestDocument(self):
+    """Test Business Template Import And Export With Test Document"""
+    test_component_kw = {"title": "foo",
+                         "text_content": "def dummy(): pass",
+                         "portal_type": "Test Component"}
+
+    test_document_page = self.portal.portal_components.newContent(**test_component_kw)
+    test_component_kw['id'] = test_component_id = test_document_page.getId()
+
+    self.template.edit(template_test_id_list=['portal_components/'+test_component_id,])
+
+    test_component_path = os.path.join(self.cfg.instancehome, self.export_dir,
+                                       'TestTemplateItem', 'portal_components',
+                                       test_component_id)
+    import_template = self._exportAndReImport(
+                                  test_component_path + ".xml",
+                                  test_component_path +".py",
+                                  test_component_kw["text_content"],
+                                  ['text_content'])
+
+    self.portal.portal_components.manage_delObjects([test_component_id])
+
+    import_template.install()
+
+    test_page = self.portal.portal_components[test_component_id]
+
+    for property_id, property_value in test_component_kw.iteritems():
+      self.assertEqual(test_page.getProperty(property_id), property_value)
+
+  def test_twoFileImportExportForWebPage(self):
+    """Test Business Template Import And Export With Web Page"""
+    html_document_kw = {"title": "foo", "text_content": "<html></html>",
+                        "portal_type": "Web Page"}
+    html_page = self.portal.web_page_module.newContent(**html_document_kw)
+    js_document_kw = {"title": "foo.js", "text_content": "// JavaScript",
+                      "portal_type": "Web Script"}
+    js_page = self.portal.web_page_module.newContent(**js_document_kw)
+    css_document_kw = {"title": "foo.css", "text_content": "<style></style>",
+                       "portal_type": "Web Style"}
+    css_page = self.portal.web_page_module.newContent(**css_document_kw)
+    html_document_kw['id'] = html_file_id = html_page.getId()
+    js_document_kw['id'] = js_file_id = js_page.getId()
+    css_document_kw['id'] = css_file_id = css_page.getId()
+
+    self.template.edit(template_path_list=['web_page_module/'+html_file_id,
+                                      'web_page_module/'+js_file_id,
+                                      'web_page_module/'+css_file_id,])
+
+    self._buildAndExportBusinessTemplate()
+
+    web_page_module_path = os.path.join(self.cfg.instancehome, self.export_dir,
+                                        'PathTemplateItem', 'web_page_module')
+
+    for web_file in [(html_file_id, '.html', html_document_kw),
+                     (js_file_id, '.js', js_document_kw),
+                     (css_file_id, '.css', css_document_kw)]:
+      xml_document_path = os.path.join(web_page_module_path, web_file[0]+'.xml')
+      file_document_path = os.path.join(web_page_module_path,
+                                        web_file[0]+web_file[1])
+      self.assertTrue(os.path.exists(xml_document_path))
+      self.assertTrue(os.path.exists(file_document_path))
+      file_content=open(file_document_path,'r+')
+      self.assertEqual(file_content.read(), web_file[2]["text_content"])
+      xml_file=open(xml_document_path,'r+')
+      self.assertFalse('<string>text_content</string>' in xml_file.read())
+
+    import_template = self._importBusinessTemplate()
+
+    self.portal.web_page_module.manage_delObjects([html_file_id])
+    self.portal.web_page_module.manage_delObjects([js_file_id])
+    self.portal.web_page_module.manage_delObjects([css_file_id])
+
+    import_template.install()
+
+    for web_file in [(html_file_id, html_document_kw),
+                     (js_file_id, js_document_kw),
+                     (css_file_id, css_document_kw)]:
+      web_page = self.portal.web_page_module[web_file[0]]
+      for property_id, property_value in web_file[1].iteritems():
+        self.assertEqual(web_page.getProperty(property_id), property_value)
+
+  def test_twoFileImportExportForPythonScript(self):
+    """Test Business Template Import And Export With PythonScript"""
+    skin_folder_id = 'dummy_test_folder'
+    if skin_folder_id in self.portal.portal_skins.objectIds():
+      self.portal.portal_skins.manage_delObjects([skin_folder_id])
+
+    self.portal.portal_skins.manage_addProduct['OFSP'].manage_addFolder(skin_folder_id)
+    skin_folder = self.portal.portal_skins[skin_folder_id]
+
+    python_script_id = 'dummy_test_script'
+    if python_script_id in skin_folder.objectIds():
+      skin_folder.manage_delObjects([python_script_id])
+    skin_folder.manage_addProduct['PythonScripts'].manage_addPythonScript(id=python_script_id)
+    python_script = skin_folder[python_script_id]
+    python_script.ZPythonScript_edit('', "context.setTitle('foo')")
+
+    python_script_kw = {"_body": "context.setTitle('foo')\n",}
+
+    self.template.edit(template_skin_id_list=[skin_folder_id+'/'+python_script_id,])
+
+    python_script_path = os.path.join(self.cfg.instancehome, self.export_dir,
+                                             'SkinTemplateItem', 'portal_skins',skin_folder_id,python_script_id)
+
+
+    import_template = self._exportAndReImport(
+                                  python_script_path+".xml",
+                                  python_script_path+".py",
+                                  python_script_kw["_body"],
+                                  ['_body','_code'])
+
+    self.portal.portal_skins[skin_folder_id].manage_delObjects([python_script_id])
+
+    import_template.install()
+
+    python_script_page = self.portal.portal_skins[skin_folder_id][python_script_id]
+
+    python_script_content = python_script_page.read()
+    self.assertTrue(python_script_content.endswith(python_script_kw['_body']))
+
+  def _checkTwoFileImportExportForImageInImageModule(self,
+                                                    image_document_kw,
+                                                    extension):
+    image_page = self.portal.image_module.newContent(**image_document_kw)
+    image_document_kw['id'] = image_file_id = image_page.getId()
+
+    self.template.edit(template_path_list=['image_module/'+image_file_id,])
+
+
+    image_document_path = os.path.join(self.cfg.instancehome, self.export_dir,
+                                     'PathTemplateItem', 'image_module',image_file_id)
+
+    import_template = self._exportAndReImport(
+                                  image_document_path+".xml",
+                                  image_document_path+extension,
+                                  image_document_kw["data"],
+                                  ['data'])
+
+    self.portal.image_module.manage_delObjects([image_file_id])
+
+    import_template.install()
+
+    image_page = self.portal.image_module[image_file_id]
+    for property_id, property_value in image_document_kw.iteritems():
+      self.assertEqual(image_page.getProperty(property_id), property_value)
+
+
+  def test_twoFileImportExportForImageIdentifyingTypeByBase64(self):
+    """
+      Test Business Template Import And Export With Image In Image Module
+      where extension is found by Base64 representation
+    """
+    image_data = """iVBORw0KGgoAAAANSUhEUgAAAAUA
+AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
+9TXL0Y4OHwAAAABJRU5ErkJggg=="""
+    image_document_kw = {"title": "foo", "data": image_data,
+                         "portal_type": "Image"}
+
+    self._checkTwoFileImportExportForImageInImageModule(image_document_kw, '.png')
+
+
+  def test_twoFileImportExportForImageIdentifyingTypeByContentType(self):
+    """
+      Test Business Template Import And Export With Image In Image Module
+      where extension (.pjpg) is found by content_type
+    """
+    image_data = """MalformedBase64HereiVBORw0KGgoAAAANSUhEUgAAAAUA
+AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
+9TXL0Y4OHwAAAABJRU5ErkJggg=="""
+    image_document_kw = {"title": "foo", "data": image_data,
+                         "portal_type": "Image", "content_type": "image/jpeg"}
+
+    self._checkTwoFileImportExportForImageInImageModule(image_document_kw,
+                                                        '.pjpg')
+
+  def test_twoFileImportExportForImageNotIdentifyingType(self):
+    """
+      Test Business Template Import And Export With Image In Image Module
+      where extension is not identified, so it is exported as '.bin'
+    """
+    image_data = """MalformedBase64HereiVBORw0KGgoAAAANSUhEUgAAAAUA
+AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
+9TXL0Y4OHwAAAABJRU5ErkJggg=="""
+    image_document_kw = {"title": "foo", "data": image_data,
+                         "portal_type": "Image"}
+
+    self._checkTwoFileImportExportForImageInImageModule(image_document_kw,
+                                                        '.bin')
+
+  def _checkTwoFileImportExportForDocumentInDocumentModule(self,
+                                                            file_document_kw,
+                                                            extension):
+    file_page = self.portal.document_module.newContent(**file_document_kw)
+    file_document_kw['id'] = file_id = file_page.getId()
+
+    self.template.edit(template_path_list=['document_module/'+file_id,])
+
+    file_document_path = os.path.join(self.cfg.instancehome, self.export_dir,
+                                     'PathTemplateItem', 'document_module',
+                                      file_id)
+
+    import_template = self._exportAndReImport(
+                                  file_document_path+".xml",
+                                  file_document_path+extension,
+                                  file_document_kw["data"],
+                                  ['data'])
+
+    self.portal.document_module.manage_delObjects([file_id])
+
+    import_template.install()
+
+    file_page = self.portal.document_module[file_id]
+
+    for property_id, property_value in file_document_kw.iteritems():
+      self.assertEqual(getattr(file_page, property_id), property_value)
+
+  def test_twoFileImportExportForFileIdentifyingTypeByContentTypeJS(self):
+    """
+      Test Business Template Import And Export With File
+      where extension (.js) is identified by the content_type
+    """
+    file_content = "a test file"
+    file_content_type = "text/javascript"
+    file_title = "foo"
+
+    file_document_kw = {"title": file_title, "data": file_content,
+                        "content_type": file_content_type,
+                        "portal_type": "File"}
+
+    self._checkTwoFileImportExportForDocumentInDocumentModule(file_document_kw,
+                                                              '.js')
+
+  def test_twoFileImportExportForFileIdentifyingTypeByContentTypeObj(self):
+    """
+      Test Business Template Import And Export With File
+      where extension (.obj) is identified by the content_type
+    """
+    file_content = "a test file"
+    file_content_type = "application/octet-stream"
+    file_title = "foo"
+    file_document_kw = {"title": file_title, "data": file_content,
+                        "content_type": file_content_type,
+                        "portal_type": "File"}
+
+    self._checkTwoFileImportExportForDocumentInDocumentModule(file_document_kw,
+                                                              '.obj')
+
+  def test_twoFileImportExportForFileIdentifyingTypeByContentTypeEpub(self):
+    """
+      Test Business Template Import And Export With File
+      where extension (.epub) is identified by the content_type
+    """
+    file_content = "a test file"
+    file_content_type = "application/epub+zip"
+    file_title = "foo"
+    file_document_kw = {"title": file_title, "data": file_content,
+                        "content_type": file_content_type,
+                        "portal_type": "File"}
+
+    self._checkTwoFileImportExportForDocumentInDocumentModule(file_document_kw,
+                                                              '.epub')
+
+  def test_twoFileImportExportForFileIdentifyingTypeByTitleJS(self):
+    """
+      Test Business Template Import And Export With File
+      where extension (.js) is identified by the title
+    """
+    file_content = "a test file"
+    file_title = "foo.js"
+    file_document_kw = {"title": file_title, "data": file_content,
+                        "portal_type": "File"}
+
+    self._checkTwoFileImportExportForDocumentInDocumentModule(file_document_kw,
+                                                              '.js')
+
+  def test_twoFileImportExportForFileIdentifyingTypeByReferenceJS(self):
+    """
+      Test Business Template Import And Export With File
+      where extension (.js) is identified by the reference
+    """
+    file_content = "<script> ... </script>"
+    file_title = "foo"
+    file_document_kw = {"title": file_title, "data": file_content,
+                        "default_reference": file_title+".js",
+                        "portal_type": "File"}
+
+    self._checkTwoFileImportExportForDocumentInDocumentModule(file_document_kw,
+                                                              '.js')
+
+  def test_twoFileImportExportForFileNotIdentifyingTypeEmptyContentType(self):
+    """
+      Test Business Template Import And Export With File
+      where extension is not identified, so it is exported as .bin
+    """
+    file_content = "a test file"
+    file_content_type = None
+    file_title = "foo"
+    file_document_kw = {"title": file_title, "data": file_content,
+                        "content_type": file_content_type,
+                        "portal_type": "File"}
+
+    self._checkTwoFileImportExportForDocumentInDocumentModule(file_document_kw,
+                                                              '.bin')
+
+  def test_twoFileImportExportForFileNotIdentifyingTypeBinaryContentType(self):
+    """
+      Test Business Template Import And Export With File
+      where extension is not identified by content_type (video/wavelet)
+      but it is identified as binary in the mimetypes_registry so it is
+      exported as .bin.
+    """
+    file_content = "a test file"
+    file_content_type = 'video/wavelet'
+    file_title = "foo"
+    file_document_kw = {"title": file_title, "data": file_content,
+                        "content_type": file_content_type,
+                        "portal_type": "File"}
+
+    self._checkTwoFileImportExportForDocumentInDocumentModule(file_document_kw,
+                                                              '.bin')
+
+  def test_twoFileImportExportForFileNotIdentifyingTypeNonBinaryContentType(self):
+    """
+      Test Business Template Import And Export With File
+      where extension is not identified by content_type (text/x-uri)
+      but it is identified as non-binary in the mimetypes_registry so it is
+      exported as .txt.
+    """
+    file_content = "a test file"
+    file_content_type = 'text/x-uri'
+    file_title = "foo"
+    file_document_kw = {"title": file_title, "data": file_content,
+                        "content_type": file_content_type,
+                        "portal_type": "File"}
+
+    self._checkTwoFileImportExportForDocumentInDocumentModule(file_document_kw,
+                                                              '.txt')
+
+  def test_twoFileImportExportForFileIdentifyingTypeByTitleXML(self):
+    """
+      Test Business Template Import And Export With File in portal skins
+      where extension (.xml, exported as ._xml to avoid conflict with the meta-data file)
+      is identified by the title
+    """
+    file_content = """<person>
+<name>John</name>
+<surname>Doe</surname>
+</person>
+    """
+    file_title = "foo.xml"
+    file_content_type = None
+    file_document_kw = {"title": file_title, "data": file_content,
+                        "content_type": file_content_type,
+                        "portal_type": "File"}
+
+    self._checkTwoFileImportExportForDocumentInDocumentModule(file_document_kw,
+                                                              '._xml')
+
+  def test_twoFileImportExportForFileInPortalSkinsIdentifyingTypeByTitleXML(self):
+    """
+      Test Business Template Import And Export With File in portal skins
+      where extension (.xml, exported as ._xml to avoid conflict with the meta-data file)
+      is identified by the title
+    """
+    file_content = """<person>
+<name>John</name>
+<surname>Doe</surname>
+</person>
+    """
+    file_title = "foo.xml"
+    file_document_kw = {"title": file_title, "data": file_content,}
+
+
+    skin_folder_id = 'dummy_test_folder'
+    if skin_folder_id in self.portal.portal_skins.objectIds():
+      self.portal.portal_skins.manage_delObjects([skin_folder_id])
+
+    self.portal.portal_skins.manage_addProduct['OFSP'].\
+                                    manage_addFolder(skin_folder_id)
+    skin_folder = self.portal.portal_skins[skin_folder_id]
+
+    test_file_id = 'dummy_file_id'
+    if test_file_id in self.portal.objectIds():
+      self.portal.manage_delObjects([test_file_id])
+
+    skin_folder.manage_addProduct['OFSP'].manage_addFile(id=test_file_id)
+    zodb_file = skin_folder._getOb(test_file_id)
+    zodb_file.manage_edit(title=file_title,
+                          content_type='',
+                          filedata=file_content)
+
+    self.template.edit(template_skin_id_list=[skin_folder_id+'/'+test_file_id,])
+
+    file_document_path = os.path.join(self.cfg.instancehome, self.export_dir,
+                                             'SkinTemplateItem', 'portal_skins',
+                                              skin_folder_id,test_file_id)
+
+    import_template = self._exportAndReImport(
+                                  file_document_path+".xml",
+                                  file_document_path+"._xml",
+                                  file_document_kw["data"],
+                                  ['data'])
+
+    self.portal.portal_skins[skin_folder_id].manage_delObjects([test_file_id])
+
+    import_template.install()
+
+    file_page = self.portal.portal_skins[skin_folder_id][test_file_id]
+
+    for property_id, property_value in file_document_kw.iteritems():
+      self.assertEqual(getattr(file_page, property_id), property_value)
+
+  def test_twoFileImportExportForPDF(self):
+    """Test Business Template Import And Export With A PDF Document"""
+    pdf_data = """pdf content, maybe should update for base64 sample"""
+
+    pdf_document_kw = {"title": "foo.pdf", "data": pdf_data,
+                         "portal_type": "PDF"}
+
+    self._checkTwoFileImportExportForDocumentInDocumentModule(pdf_document_kw,
+                                                              '.pdf')
+
+  def test_twoFileImportExportForCatalogMethodInCatalog(self):
+    """Test Business Template Import And Export With Catalog Method In Catalog"""
+    catalog_tool = self.getCatalogTool()
+    catalog = catalog_tool.getSQLCatalog()
+    catalog_id = catalog.id
+
+    self.assertTrue(catalog is not None)
+    method_id = "z_another_dummy_method"
+    if method_id in catalog.objectIds():
+      catalog.manage_delObjects([method_id])
+
+    method_document_kw = {'id': method_id, 'title': 'dummy_method_title',
+                         'connection_id': 'erp5_sql_connection',
+                         'arguments_src': 'args', 'src': 'dummy_method_template'}
+
+    addSQLMethod = catalog.manage_addProduct['ZSQLMethods'].manage_addZSQLMethod
+    addSQLMethod(id=method_id, title='dummy_method_title',
+                 connection_id='erp5_sql_connection',
+                 template='dummy_method_template',
+                 arguments = 'args'
+                )
+    zsql_method = catalog._getOb(method_id, None)
+    self.assertTrue(zsql_method is not None)
+
+    self.template.edit(template_catalog_method_id_list=[catalog_id+'/'+method_id])
+    self._buildAndExportBusinessTemplate()
+
+    method_document_path = os.path.join(self.cfg.instancehome, self.export_dir,
+                                     'CatalogMethodTemplateItem',
+                                     'portal_catalog',
+                                      catalog_id, method_id)
+
+    import_template = self._exportAndReImport(
+                                  method_document_path + ".xml",
+                                  method_document_path +".sql",
+                                  'dummy_method_template',
+                                  ['src'])
+
+    catalog.manage_delObjects([method_id])
+
+    import_template.install()
+
+    method_page = catalog[method_id]
+
+    for property_id, property_value in method_document_kw.iteritems():
+      self.assertEqual(getattr(method_page, property_id), property_value)
+
+  def test_twoFileImportExportForCatalogMethodInPortalSkins(self):
+    """Test Business Template Import And Export With Catalog Method In Portal Skins"""
+
+    method_id = "z_another_dummy_method"
+    method_document_kw = {'id': method_id, 'title': 'dummy_method_title',
+                         'connection_id': 'erp5_sql_connection',
+                         'arguments_src': 'args', 'src': 'dummy_method_template'}
+
+    skin_folder_id = 'dummy_test_folder'
+    if skin_folder_id in self.portal.portal_skins.objectIds():
+      self.portal.portal_skins.manage_delObjects([skin_folder_id])
+
+    self.portal.portal_skins.manage_addProduct['OFSP'].\
+                                  manage_addFolder(skin_folder_id)
+    skin_folder = self.portal.portal_skins[skin_folder_id]
+
+    if method_id in self.portal.objectIds():
+      self.portal.manage_delObjects([method_id])
+
+    addSQLMethod = skin_folder.manage_addProduct['ZSQLMethods'].manage_addZSQLMethod
+    addSQLMethod(id=method_id, title='dummy_method_title',
+                 connection_id='erp5_sql_connection',
+                 template='dummy_method_template',
+                 arguments = 'args'
+                )
+
+    self.template.edit(template_skin_id_list=[skin_folder_id+'/'+method_id,])
+
+    method_document_path = os.path.join(self.cfg.instancehome, self.export_dir,
+                                             'SkinTemplateItem', 'portal_skins',
+                                              skin_folder_id, method_id)
+    import_template = self._exportAndReImport(
+                                  method_document_path+".xml",
+                                  method_document_path+".sql",
+                                  'dummy_method_template',
+                                  ['src'])
+
+    self.portal.portal_skins[skin_folder_id].manage_delObjects([method_id])
+
+    import_template.install()
+
+    method_page = skin_folder[method_id]
+
+    for property_id, property_value in method_document_kw.iteritems():
+      self.assertEqual(getattr(method_page, property_id), property_value)
+
+  def test_twoFileImportExportForZopePageTemplate(self):
+    """Test Business Template Import And Export With ZopePageTemplate"""
+    skin_folder_id = 'dummy_test_folder'
+    if skin_folder_id in self.portal.portal_skins.objectIds():
+      self.portal.portal_skins.manage_delObjects([skin_folder_id])
+
+    self.portal.portal_skins.manage_addProduct['OFSP'].\
+                                  manage_addFolder(skin_folder_id)
+    skin_folder = self.portal.portal_skins[skin_folder_id]
+
+    page_template_id = 'dummy_page_template'
+    if page_template_id in skin_folder.objectIds():
+      skin_folder.manage_delObjects([page_template_id])
+    page_template_text = '<html></html>'
+    page_template_kw = {"id": page_template_id,
+                         "_text": page_template_text,
+                         "content_type": "text/html",
+                         "output_encoding": "utf-8"}
+    skin_folder.manage_addProduct['PageTemplates'].\
+                      manage_addPageTemplate(id=page_template_id,
+                                             text=page_template_text)
+
+    self.template.edit(template_skin_id_list=[skin_folder_id+'/'+page_template_id,])
+
+    page_template_path = os.path.join(self.cfg.instancehome, self.export_dir,
+                                             'SkinTemplateItem', 'portal_skins',skin_folder_id,page_template_id)
+
+    import_template = self._exportAndReImport(
+                                  page_template_path+".xml",
+                                  page_template_path+".zpt",
+                                  page_template_kw['_text'],
+                                  ['_text'])
+
+    self.portal.portal_skins[skin_folder_id].manage_delObjects([page_template_id])
+
+    import_template.install()
+
+    page_template_page = self.portal.portal_skins[skin_folder_id][page_template_id]
+
+    for property_id, property_value in page_template_kw.iteritems():
+      self.assertEqual(getattr(page_template_page, property_id), property_value)
+
+  def test_twoFileImportExportForDTMLMethodIdentifyingTypeByTitle(self):
+    """
+      Test Business Template Import And Export With DTMLMethod where the
+      extension is identified by the title
+    """
+    skin_folder_id = 'dummy_test_folder'
+    if skin_folder_id in self.portal.portal_skins.objectIds():
+      self.portal.portal_skins.manage_delObjects([skin_folder_id])
+
+    self.portal.portal_skins.manage_addProduct['OFSP'].manage_addFolder(skin_folder_id)
+    skin_folder = self.portal.portal_skins[skin_folder_id]
+
+    dtml_method_id = 'dummy_dtml_method'
+    dtml_method_title = 'dummy_dtml_method.js'
+    dtml_method_data = 'dummy content'
+
+    dtml_method_kw = {"__name__": dtml_method_id,
+                         "title": dtml_method_title,
+                         "raw": dtml_method_data}
+
+    if dtml_method_id in skin_folder.objectIds():
+      skin_folder.manage_delObjects([dtml_method_id])
+
+    skin_folder.manage_addProduct['DTMLMethods'].\
+                          manage_addDTMLMethod(id = dtml_method_id,
+                          title = dtml_method_title)
+    dtml_method = skin_folder[dtml_method_id]
+    dtml_method.manage_edit(data=dtml_method_data, title = dtml_method_title)
+
+    self.template.edit(template_skin_id_list=[skin_folder_id+'/'+dtml_method_id,])
+
+    dtml_method_path = os.path.join(self.cfg.instancehome,
+                                    self.export_dir,
+                                    'SkinTemplateItem',
+                                    'portal_skins',
+                                    skin_folder_id,dtml_method_id)
+
+    import_template = self._exportAndReImport(
+                                  dtml_method_path+".xml",
+                                  dtml_method_path+".js",
+                                  dtml_method_kw['raw'],
+                                  ['raw'])
+
+    self.portal.portal_skins[skin_folder_id].manage_delObjects([dtml_method_id])
+
+    import_template.install()
+
+    dtml_method_page = self.portal.portal_skins[skin_folder_id][dtml_method_id]
+
+    for property_id, property_value in dtml_method_kw.iteritems():
+      self.assertEqual(getattr(dtml_method_page, property_id), property_value)
+
+  def test_twoFileImportExportForDTMLMethodNotIdentifyingType(self):
+    """
+      Test Business Template Import And Export With DTMLMethod where the
+      extension is not identified, so it is exported as '.txt'
+    """
+    skin_folder_id = 'dummy_test_folder'
+    if skin_folder_id in self.portal.portal_skins.objectIds():
+      self.portal.portal_skins.manage_delObjects([skin_folder_id])
+
+    self.portal.portal_skins.manage_addProduct['OFSP'].\
+                                  manage_addFolder(skin_folder_id)
+    skin_folder = self.portal.portal_skins[skin_folder_id]
+
+    dtml_method_id = 'dummy_dtml_method'
+    dtml_method_title = 'dummy_dtml_method'
+    dtml_method_data = 'dummy content'
+
+    dtml_method_kw = {"__name__": dtml_method_id,
+                         "title": dtml_method_title,
+                         "raw": dtml_method_data}
+
+    if dtml_method_id in skin_folder.objectIds():
+      skin_folder.manage_delObjects([dtml_method_id])
+
+    skin_folder.manage_addProduct['DTMLMethods'].\
+                                manage_addDTMLMethod(id = dtml_method_id,
+                                                     title = dtml_method_title)
+    dtml_method = skin_folder[dtml_method_id]
+    dtml_method.manage_edit(data=dtml_method_data, title = dtml_method_title)
+
+    self.template.edit(template_skin_id_list=[skin_folder_id+'/'+dtml_method_id,])
+
+    dtml_method_path = os.path.join(self.cfg.instancehome, self.export_dir,
+                                             'SkinTemplateItem', 'portal_skins',
+                                              skin_folder_id,dtml_method_id)
+
+    import_template = self._exportAndReImport(
+                                  dtml_method_path+".xml",
+                                  dtml_method_path+".txt",
+                                  dtml_method_kw['raw'],
+                                  ['raw'])
+
+    self.portal.portal_skins[skin_folder_id].manage_delObjects([dtml_method_id])
+
+    import_template.install()
+
+    dtml_method_page = self.portal.portal_skins[skin_folder_id][dtml_method_id]
+
+    for property_id, property_value in dtml_method_kw.iteritems():
+      self.assertEqual(getattr(dtml_method_page, property_id), property_value)
+
+  def test_twoFileImportExportForOOoTemplate(self):
+    """Test Business Template Import And Export With OOoTemplate"""
+    skin_folder_id = 'dummy_test_folder'
+    if skin_folder_id in self.portal.portal_skins.objectIds():
+      self.portal.portal_skins.manage_delObjects([skin_folder_id])
+
+    self.portal.portal_skins.manage_addProduct['OFSP'].\
+                                manage_addFolder(skin_folder_id)
+    skin_folder = self.portal.portal_skins[skin_folder_id]
+
+    OOo_template_id = 'dummy_OOo_template'
+    OOo_template_data = 'dummy OOotemplate content'
+
+    OOo_template_kw = {"id": OOo_template_id,
+                         "_text": OOo_template_data,
+                         "output_encoding": "utf-8",
+                         "content_type": "text/html"}
+
+    if OOo_template_id in skin_folder.objectIds():
+      skin_folder.manage_delObjects([OOo_template_id])
+
+    addOOoTemplate =skin_folder.manage_addProduct['ERP5OOo'].addOOoTemplate
+    addOOoTemplate(id=OOo_template_id, title=OOo_template_data)
+
+    self.template.edit(template_skin_id_list=[skin_folder_id+'/'+OOo_template_id,])
+
+    OOo_template_path = os.path.join(self.cfg.instancehome, self.export_dir,
+                                             'SkinTemplateItem', 'portal_skins',
+                                              skin_folder_id, OOo_template_id)
+
+    import_template = self._exportAndReImport(
+                                  OOo_template_path+".xml",
+                                  OOo_template_path+".oot",
+                                  OOo_template_kw['_text'],
+                                  ['_text'])
+
+    self.portal.portal_skins[skin_folder_id].manage_delObjects([OOo_template_id])
+
+    import_template.install()
+
+    OOo_template_page = self.portal.portal_skins[skin_folder_id][OOo_template_id]
+
+    for property_id, property_value in OOo_template_kw.iteritems():
+      self.assertEqual(getattr(OOo_template_page, property_id), property_value)
+
+  def test_twoFileImportExportForSpreadsheetNotIdentifyingType(self):
+    """
+      Test Business Template Import And Export With Spreadsheed where the
+      extension is not identified, so it is exported as '.bin'
+    """
+    # XXX addding a dummy string in data leads to 'NotConvertedError'
+    spreadsheet_data = ''
+
+    spreadsheet_document_kw = {"title": "foo", "data": spreadsheet_data,
+                         "portal_type": "Spreadsheet"}
+
+    self._checkTwoFileImportExportForDocumentInDocumentModule(spreadsheet_document_kw,
+                                                              '.bin')
+
+  def test_twoFileImportExportForSpreadsheetIdentifyingTypeByContentType(self):
+    """
+      Test Business Template Import And Export With Spreadsheed where the
+      extension is identified by content_type, so it is exported as '.ods'
+    """
+    # XXX addding a dummy string in data leads to 'NotConvertedError'
+    spreadsheet_data = ''
+
+    spreadsheet_document_kw = {"title": "foo", "data": spreadsheet_data,
+              "portal_type": "Spreadsheet",
+              "content_type": "application/vnd.oasis.opendocument.spreadsheet"}
+
+    self._checkTwoFileImportExportForDocumentInDocumentModule(spreadsheet_document_kw,
+                                                              '.ods')
+
+  def test_twoFileImportExportForSpreadsheetIdentifyingTypeByTitle(self):
+    """
+      Test Business Template Import And Export With Spreadsheed where the
+      extension is identified by title, so it is exported as '.xlsx'
+    """
+    # XXX addding a dummy string in data leads to 'NotConvertedError'
+    spreadsheet_data = ''
+
+    spreadsheet_document_kw = {"title": "foo.xlsx", "data": spreadsheet_data,
+                         "portal_type": "Spreadsheet"}
+
+    self._checkTwoFileImportExportForDocumentInDocumentModule(spreadsheet_document_kw,
+                                                              '.xlsx')
+
+  def test_templateFolderIsCleanedUpInImportEndReexport(self):
+    """
+      Test that when TemplateTool.importAndReExportBusinessTemplatesFromPath is
+      invoked the template folder is cleaned.
+
+      1. Create a bt and export it in a temporary folder
+      2. Add to the folder a text file
+      3. Add to the folder a sub-folder
+      4. Add to the sub-folder a second text file
+      5. Add a third file to portal_templates folder
+      6. Invoke TemplateTool.importAndReExportBusinessTemplatesFromPath in the
+      template folder
+      7. Assert that only the template elements are present
+    """
+    test_component_kw = {"title": "foo",
+                         "text_content": "def dummy(): pass",
+                         "portal_type": "Test Component"}
+
+    test_document_page = self.portal.\
+                              portal_components.newContent(**test_component_kw)
+    test_component_kw['id'] = test_component_id = test_document_page.getId()
+
+    self.template.edit(template_test_id_list=['portal_components/'+test_component_id,])
+
+    test_component_path = os.path.join(self.cfg.instancehome, self.export_dir,
+                                       'TestTemplateItem', 'portal_components',
+                                        test_component_id)
+
+    self._buildAndExportBusinessTemplate()
+
+    self.assertTrue(os.path.exists(os.path.join(self.export_dir, 'bt')))
+    self.assertTrue(os.path.exists(test_component_path+'.xml'))
+    self.assertTrue(os.path.exists(test_component_path+'.py'))
+
+    # create a text file in the root
+    text_file_name = "text_file.txt"
+    text_file_path = os.path.join(self.export_dir, text_file_name)
+    text_file = open(text_file_path, "w")
+    text_file.close()
+    self.assertTrue(os.path.exists(text_file_path))
+    # create a sub_folder
+    sub_folder_name = "subfolder"
+    sub_folder_path = os.path.join(self.export_dir, sub_folder_name)
+    os.mkdir(sub_folder_path)
+    self.assertTrue(os.path.exists(sub_folder_path))
+    # create another text file in the subfolder
+    text_file_in_sub_folder_name = "text_file_in_sub_folder.txt"
+    text_file_in_sub_folder_path = os.path.join(self.export_dir,
+                                              text_file_in_sub_folder_name)
+    text_file_in_sub_folder = open(text_file_in_sub_folder_path, "w")
+    text_file_in_sub_folder.close()
+    self.assertTrue(os.path.exists(text_file_in_sub_folder_path))
+    # create another text file inside portal components
+    text_file_in_portal_components_name = "text_file_in_sub_folder.txt"
+    text_file_in_portal_components_path = os.path.join(self.export_dir,
+                                            text_file_in_portal_components_name)
+    text_file_in_portal_components = open(text_file_in_sub_folder_path, "w")
+    text_file_in_portal_components.close()
+    self.assertTrue(os.path.exists(text_file_in_portal_components_path))
+    # invoke importAndReExportBusinessTemplatesFromPath
+    self.template_tool.importAndReExportBusinessTemplatesFromPath(repository_list=['/tmp'])
+    self.tic()
+    # assert that unrelated objects were deleted
+    self.assertFalse(os.path.exists(text_file_path))
+    self.assertFalse(os.path.exists(sub_folder_path))
+    self.assertFalse(os.path.exists(text_file_in_sub_folder_path))
+    self.assertFalse(os.path.exists(text_file_in_portal_components_path))
+    # assert that related objects exist
+    self.assertTrue(os.path.exists(os.path.join(self.export_dir, 'bt')))
+    self.assertTrue(os.path.exists(test_component_path+'.xml'))
+    self.assertTrue(os.path.exists(test_component_path+'.py'))
-- 
2.30.9