diff --git a/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testBusinessTemplate.py b/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testBusinessTemplate.py index 03a2ce925183a0411dadb68f88224994af71c281..d0c344e905448689c969c3d9871f81192bc00ac1 100644 --- a/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testBusinessTemplate.py +++ b/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testBusinessTemplate.py @@ -6899,17 +6899,17 @@ class TestBusinessTemplate(BusinessTemplateMixin): Updating from: - portal_categories/test_category/modified - portal_categories/test_category/modified/container_in_which_child_is_added - portal_categories/test_category/modified/container_in_which_child_is_added/child_kept - portal_categories/test_category/modified/removed + portal_categories/test_category/modified_category + portal_categories/test_category/modified_category/container_in_which_child_is_added + portal_categories/test_category/modified_category/container_in_which_child_is_added/child_kept + portal_categories/test_category/modified_category/removed to: - portal_categories/test_category/modified <-- this will be modified - portal_categories/test_category/modified/container_in_which_child_is_added - portal_categories/test_category/modified/container_in_which_child_is_added/child_kept - portal_categories/test_category/modified/container_in_which_child_is_added/added + portal_categories/test_category/modified_category <-- this will be modified + portal_categories/test_category/modified_category/container_in_which_child_is_added + portal_categories/test_category/modified_category/container_in_which_child_is_added/child_kept + portal_categories/test_category/modified_category/container_in_which_child_is_added/added was causing when test_category was added both as a base category and as paths. @@ -6920,7 +6920,7 @@ class TestBusinessTemplate(BusinessTemplateMixin): portal_categories.manage_delObjects(['test_category']) base_category = portal_categories.newContent(portal_type='Base Category', id='test_category') - parent_category = base_category.newContent(portal_type='Category', id='modified') + parent_category = base_category.newContent(portal_type='Category', id='modified_category') parent_category.newContent(portal_type='Category', id='container_in_which_child_is_added') parent_category.newContent(portal_type='Category', id='removed') parent_category.container_in_which_child_is_added.newContent(portal_type='Category', id='child_kept') @@ -6945,7 +6945,7 @@ class TestBusinessTemplate(BusinessTemplateMixin): shutil.rmtree(export_dir) # Apply the changes and build a second version of business template - parent_category.setTitle('modified') + parent_category.setTitle('modified_category') parent_category.container_in_which_child_is_added.newContent(portal_type='Category', id='added') parent_category.manage_delObjects(['removed']) @@ -6959,7 +6959,7 @@ class TestBusinessTemplate(BusinessTemplateMixin): self.tic() new_business_template_version_1.install() self.tic() - portal_categories.test_category.modified.container_in_which_child_is_added.setTitle( + portal_categories.test_category.modified_category.container_in_which_child_is_added.setTitle( 'This path should not be reinstalled during update' ) self.tic() @@ -6971,7 +6971,7 @@ class TestBusinessTemplate(BusinessTemplateMixin): self.tic() self.assertEqual( 'This path should not be reinstalled during update', - portal_categories.test_category.modified.container_in_which_child_is_added.getTitle()) + portal_categories.test_category.modified_category.container_in_which_child_is_added.getTitle()) def test_update_business_template_with_template_keep_path_list_catalog_method(self): """Tests for `template_keep_path_list` feature for the special case of catalog methods diff --git a/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testERP5Core.py b/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testERP5Core.py index 6a3bb620b255f48cfa4630ffe76885a091d08946..255b8db2bcccc1e44761c632240b5607c8702923 100644 --- a/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testERP5Core.py +++ b/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testERP5Core.py @@ -576,7 +576,7 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional): if i.getId() not in ('portal_uidhandler',) and 0 != i.getUid() != i.getProperty('uid')]) - def test_site_manager_and_translation_migration(self): + def test_04_site_manager_and_translation_migration(self): from zope.site.hooks import getSite, setSite from zope.component import queryUtility from zope.i18n.interfaces import ITranslationDomain @@ -594,25 +594,27 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional): self.assertEqual(queryUtility(ITranslationDomain, 'ui'), None) # now let's simulate a site just migrated from Zope 2.8 that's being # accessed for the first time: - old_site = getSite() - try: + from Products.ERP5 import ERP5Site + if 1: # BBB setSite() # Sites from Zope2.8 don't have a site_manager yet. del self.portal._components + self.assertIsNotNone(ERP5Site._missing_tools_registered) + ERP5Site._missing_tools_registered = None + self.commit() # check that we can't get any translation utility self.assertEqual(queryUtility(ITranslationDomain, 'erp5_ui'), None) # Now simulate first access. Default behaviour from # ObjectManager is to raise a ComponentLookupError here: + setSite(self.portal) + self.commit() + self.assertIsNotNone(ERP5Site._missing_tools_registered) # This should have automatically reconstructed the i18n utility # registrations: self.assertEqual(queryUtility(ITranslationDomain, 'erp5_ui'), erp5_ui_catalog) self.assertEqual(queryUtility(ITranslationDomain, 'ui'), erp5_ui_catalog) - finally: - # clean everything up, we don't want to mess the test environment - self.abort() - setSite(old_site) def test_BasicAuthenticateDesactivated(self): """Make sure Unauthorized error does not lead to Basic auth popup in browser""" diff --git a/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testERP5Type.py b/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testERP5Type.py index 517377c83fd97bd09dfc0278adc13a42eca19b44..371241a22d4a3b49df0732f68873173422232221 100644 --- a/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testERP5Type.py +++ b/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testERP5Type.py @@ -52,6 +52,7 @@ from AccessControl.ZopeGuards import guarded_getattr, guarded_hasattr from Products.ERP5Type.tests.utils import createZODBPythonScript from Products.ERP5Type.tests.utils import removeZODBPythonScript from Products.ERP5Type import Permissions +from DateTime import DateTime class PropertySheetTestCase(ERP5TypeTestCase): """Base test case class for property sheets tests. @@ -2478,8 +2479,9 @@ class TestERP5Type(PropertySheetTestCase, LogInterceptor): portal = self.getPortalObject() folder = self.getOrganisationModule() obj = folder.newContent(portal_type='Organisation') - self.assertNotEquals(obj.getCreationDate(), portal.CreationDate()) - self.assertNotEquals(obj.getCreationDate(), folder.getCreationDate()) + self.assertIsInstance(portal.creation_date, DateTime) + self.assertLess(portal.creation_date, obj.getCreationDate()) + self.assertIsNone(folder.getCreationDate()) def test_copyWithoutModificationRight(self): """ diff --git a/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testFields.py b/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testFields.py index 8329ac300b3215a19c3c6afd609cc03e24647b1f..cafec033a5a88c4bab8aa1f59e8277fe1c26820a 100644 --- a/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testFields.py +++ b/bt5/erp5_core_test/TestTemplateItem/portal_components/test.erp5.testFields.py @@ -638,9 +638,6 @@ class TestProxyField(ERP5TypeTestCase): self.container = Folder('container').__of__(self.portal) self.container.manage_addProduct['ERP5Form'].addERP5Form('Base_viewProxyFieldLibrary', 'Proxys') self.container.manage_addProduct['ERP5Form'].addERP5Form('Base_view', 'View') - from Products.CMFCore.tests.base.utils import _setUpDefaultTraversable - _setUpDefaultTraversable() - def addField(self, form, id_, title, field_type): form.manage_addField(id_, title, field_type) diff --git a/bt5/erp5_corporate_identity/SkinTemplateItem/portal_skins/erp5_corporate_identity/WebPage_finishPdfCreation.py b/bt5/erp5_corporate_identity/SkinTemplateItem/portal_skins/erp5_corporate_identity/WebPage_finishPdfCreation.py index 0fdc074e8cb0aaf10a7fac25c0ee6566e7552526..0fa3ce91d22bcd4bc2e638ea40e22e18572f5c34 100644 --- a/bt5/erp5_corporate_identity/SkinTemplateItem/portal_skins/erp5_corporate_identity/WebPage_finishPdfCreation.py +++ b/bt5/erp5_corporate_identity/SkinTemplateItem/portal_skins/erp5_corporate_identity/WebPage_finishPdfCreation.py @@ -17,6 +17,8 @@ Save, download or return generated PDF Document # doc_pdf_file pdf content to store # doc_aggregate_list not applicable (only used for events) +from io import BytesIO + if doc_save: dms_module = getattr(context, 'document_module', None) if dms_module is not None: @@ -31,7 +33,7 @@ if doc_save: ) document.edit( source_reference=''.join([doc_reference, '.pdf']), - file=doc_pdf_file + file=BytesIO(doc_pdf_file) ) document.setContentType("application/pdf") diff --git a/bt5/erp5_csv_style/ActionTemplateItem/portal_types/portal_properties/csv_export.xml b/bt5/erp5_csv_style/ActionTemplateItem/portal_types/portal_actions/csv_export.xml similarity index 100% rename from bt5/erp5_csv_style/ActionTemplateItem/portal_types/portal_properties/csv_export.xml rename to bt5/erp5_csv_style/ActionTemplateItem/portal_types/portal_actions/csv_export.xml diff --git a/bt5/erp5_csv_style/ActionTemplateItem/portal_types/portal_properties/csv_import.xml b/bt5/erp5_csv_style/ActionTemplateItem/portal_types/portal_actions/csv_import.xml similarity index 100% rename from bt5/erp5_csv_style/ActionTemplateItem/portal_types/portal_properties/csv_import.xml rename to bt5/erp5_csv_style/ActionTemplateItem/portal_types/portal_actions/csv_import.xml diff --git a/bt5/erp5_csv_style/bt/template_action_path_list b/bt5/erp5_csv_style/bt/template_action_path_list index 4a6477999a630ef06025973d24becb6e6c5690b2..371e5950e7812c5313677d5a5ea9e35efe0939a9 100644 --- a/bt5/erp5_csv_style/bt/template_action_path_list +++ b/bt5/erp5_csv_style/bt/template_action_path_list @@ -1,2 +1,2 @@ -portal_properties | csv_export -portal_properties | csv_import \ No newline at end of file +portal_actions | csv_export +portal_actions | csv_import \ No newline at end of file diff --git a/bt5/erp5_dms/TestTemplateItem/portal_components/test.erp5.testDms.py b/bt5/erp5_dms/TestTemplateItem/portal_components/test.erp5.testDms.py index 51ffc7c1e4a7e6063e72516ed879078b3cfa0a30..8d2698e18d6c3521af70f52b5932dff75ae3f0d9 100644 --- a/bt5/erp5_dms/TestTemplateItem/portal_components/test.erp5.testDms.py +++ b/bt5/erp5_dms/TestTemplateItem/portal_components/test.erp5.testDms.py @@ -672,10 +672,9 @@ class TestDocument(TestDocumentMixin): for document_type in portal.getPortalDocumentTypeList(): module = portal.getDefaultModule(document_type) obj = module.newContent(portal_type=document_type) - self.assertNotEquals(obj.getCreationDate(), - module.getCreationDate()) - self.assertNotEquals(obj.getCreationDate(), - portal.CreationDate()) + self.assertIsInstance(portal.creation_date, DateTime) + self.assertLess(portal.creation_date, obj.getCreationDate()) + self.assertIsNone(module.getCreationDate()) def test_06_ProcessingStateOfAClonedDocument(self): """ @@ -2865,7 +2864,7 @@ class TestDocumentWithSecurity(TestDocumentMixin): reference='Foo_001', title='Foo_OO1') f = makeFileUpload('Foo_001.odt') - text_document.edit(file=f.read()) + text_document.edit(file=f) f.close() self.tic() @@ -2967,4 +2966,4 @@ def test_suite(): return suite -# vim: syntax=python shiftwidth=2 \ No newline at end of file +# vim: syntax=python shiftwidth=2 diff --git a/bt5/erp5_dms/TestTemplateItem/portal_components/test.erp5.testWebDavSupport.py b/bt5/erp5_dms/TestTemplateItem/portal_components/test.erp5.testWebDavSupport.py index ea84e7aa07da915226972d29a22f98dc85cca0d1..fbe605bfa521151c2722bc39b0265dfdcb11b577 100644 --- a/bt5/erp5_dms/TestTemplateItem/portal_components/test.erp5.testWebDavSupport.py +++ b/bt5/erp5_dms/TestTemplateItem/portal_components/test.erp5.testWebDavSupport.py @@ -186,12 +186,14 @@ class TestWebDavSupport(ERP5TypeTestCase): """ iso_text_content = text_content.decode('utf-8').encode('iso-8859-1') path = web_page_module.getPath() - response = self.publish('%s/%s' % (path, filename), - request_method='PUT', - stdin=StringIO(iso_text_content), - basic=self.authentication) - self.assertEqual(response.getStatus(), httplib.NO_CONTENT) - self.assertEqual(web_page_module[filename].getData(), iso_text_content) + for _ in xrange(2): # Run twice to check the code that compares + # old & new data when setting file attribute. + response = self.publish('%s/%s' % (path, filename), + request_method='PUT', + stdin=StringIO(iso_text_content), + basic=self.authentication) + self.assertEqual(response.getStatus(), httplib.NO_CONTENT) + self.assertEqual(web_page_module[filename].getData(), iso_text_content) # Convert to base format and run conversion into utf-8 self.tic() # Content-Type header is replaced if sonversion encoding succeed diff --git a/bt5/erp5_forge/TestTemplateItem/portal_components/test.erp5.testTemplateTool.py b/bt5/erp5_forge/TestTemplateItem/portal_components/test.erp5.testTemplateTool.py index fd7bd45009f23b9356c2c6ac4972b113f5b6fa1c..487f6ea1dbceba5d7b17d66fbbb7d5cdef2d34b6 100644 --- a/bt5/erp5_forge/TestTemplateItem/portal_components/test.erp5.testTemplateTool.py +++ b/bt5/erp5_forge/TestTemplateItem/portal_components/test.erp5.testTemplateTool.py @@ -34,6 +34,7 @@ import random import tempfile from xml.dom.minidom import getDOMImplementation from App.config import getConfiguration +from Products.CMFCore.ActionsTool import ActionsTool from Products.ERP5.Document.BusinessTemplate import \ BusinessTemplateMissingDependency @@ -51,7 +52,6 @@ class TestTemplateTool(ERP5TypeTestCase): return ('erp5_core_proxy_field_legacy', 'erp5_full_text_mroonga_catalog', 'erp5_base', - 'erp5_csv_style', 'erp5_crm', 'erp5_forge') @@ -140,7 +140,7 @@ class TestTemplateTool(ERP5TypeTestCase): self.assertEqual(test_web.getTitle(), 'test_web') self.assertEqual(len(test_web.getRevision()), 28) - def test_updateBusinessTemplateFromUrl_simple(self): + def test_00_updateBusinessTemplateFromUrl_simple(self): """ Test updateBusinessTemplateFromUrl method @@ -148,13 +148,42 @@ class TestTemplateTool(ERP5TypeTestCase): the new bt5 is not installed, only imported. """ self._svn_setup_ssl() + global PropertiesTool + class PropertiesTool(ActionsTool): + id = 'portal_properties' + cls = PropertiesTool + + # Assign a fake properties tool to the portal + tool = PropertiesTool() + self.portal._setObject(tool.id, tool, set_owner=False, suppress_events=True) + del tool + self.commit() + template_tool = self.portal.portal_templates + + url = 'https://svn.erp5.org/repos/public/erp5/trunk/bt5/erp5_csv_style' + template_tool.updateBusinessTemplateFromUrl(url) old_bt = template_tool.getInstalledBusinessTemplate('erp5_csv_style') # fake different revision old_bt.setRevision('') - url = 'https://svn.erp5.org/repos/public/erp5/trunk/bt5/erp5_csv_style' - template_tool.updateBusinessTemplateFromUrl(url) - new_bt = template_tool.getInstalledBusinessTemplate('erp5_csv_style') + + # Break the properties tool + self.assertIs(self.portal.portal_properties.__class__, cls) + self.commit() + self.portal._p_jar.cacheMinimize() + del PropertiesTool + self.assertIsNot(self.portal.portal_properties.__class__, cls) + + # Remove portal.portal_properties + from Products.ERP5Type.dynamic.portal_type_class import \ + _bootstrapped, synchronizeDynamicModules + _bootstrapped.remove(self.portal.id) + synchronizeDynamicModules(self.portal, force=True) + + # The bt from this repo + url = self._getBTPathAndIdList(('erp5_csv_style',))[0][0] + + new_bt = template_tool.updateBusinessTemplateFromUrl(url) self.assertNotEquals(old_bt, new_bt) self.assertEqual('erp5_csv_style', new_bt.getTitle()) diff --git a/product/ERP5/Document/BusinessTemplate.py b/product/ERP5/Document/BusinessTemplate.py index 85f8b6d2052d0a2d2a36bad687668196ccfc113b..8dae64b4693eac711d8395593d02a958d3c976b6 100644 --- a/product/ERP5/Document/BusinessTemplate.py +++ b/product/ERP5/Document/BusinessTemplate.py @@ -1869,6 +1869,7 @@ class ToolTemplateItem(PathTemplateItem): def install(self, context, trashbin, **kw): """ When we install a tool that is a type provider not registered on types tool, register it into the type provider. + We also need to register the tool on the site manager """ PathTemplateItem.install(self, context, trashbin, **kw) portal = context.getPortalObject() @@ -1879,6 +1880,8 @@ class ToolTemplateItem(PathTemplateItem): type_container_id not in types_tool.type_provider_list): types_tool.type_provider_list = tuple(types_tool.type_provider_list) + \ (type_container_id,) + tool_id_list = list(set(self._objects.keys()) & set(portal._registry_tool_id_list)) + portal._registerTools(tool_id_list) def uninstall(self, context, **kw): """ When we uninstall a tool, unregister it from the type provider. """ @@ -2121,7 +2124,7 @@ class RegisteredSkinSelectionTemplateItem(BaseTemplateItem): update_dict = kw.get('object_to_update') force = kw.get('force') portal = context.getPortalObject() - skin_tool = getToolByName(portal, 'portal_skins') + skin_tool = portal.portal_skins for skin_folder_id in self._objects.keys(): @@ -2163,7 +2166,7 @@ class RegisteredSkinSelectionTemplateItem(BaseTemplateItem): def uninstall(self, context, **kw): portal = context.getPortalObject() - skin_tool = getToolByName(portal, 'portal_skins') + skin_tool = portal.portal_skins object_path = kw.get('object_path') for skin_folder_id in (object_path,) if object_path else self._objects: skin_selection_list = self._objects[skin_folder_id] diff --git a/product/ERP5/ERP5Site.py b/product/ERP5/ERP5Site.py index 0a750092d59a52df0792704eb4561f6e6714af6d..89e7a77ff35929ec9e793f645774f0ab19ab9188 100644 --- a/product/ERP5/ERP5Site.py +++ b/product/ERP5/ERP5Site.py @@ -15,6 +15,8 @@ Portal class """ +from DateTime import DateTime +from six.moves import map import thread, threading from weakref import ref as weakref from OFS.Application import Application, AppInitializer @@ -25,7 +27,7 @@ from Products.SiteErrorLog.SiteErrorLog import manage_addErrorLog from ZPublisher import BeforeTraverse from ZPublisher.BaseRequest import RequestContainer from AccessControl import ClassSecurityInfo -from Products.CMFDefault.Portal import CMFSite +from Products.CMFCore.PortalObject import PortalObjectBase from Products.ERP5Type import Permissions from Products.ERP5Type.Core.Folder import FolderMixIn from Acquisition import aq_base @@ -36,7 +38,8 @@ from Products.ERP5Type.ERP5Type import ERP5TypeInformation from Products.ERP5Type.patches.CMFCoreSkinnable import SKINDATA, skinResolve from Products.CMFActivity.Errors import ActivityPendingError import ERP5Defaults -from Products.ERP5Type.TransactionalVariable import getTransactionalVariable +from Products.ERP5Type.TransactionalVariable import \ + getTransactionalVariable, TransactionalResource from Products.ERP5Type.dynamic.portal_type_class import synchronizeDynamicModules from Products.ERP5Type.mixin.response_header_generator import ResponseHeaderGenerator @@ -227,8 +230,9 @@ class _site(threading.local): getSite, setSite = _site() +_missing_tools_registered = None -class ERP5Site(ResponseHeaderGenerator, FolderMixIn, CMFSite, CacheCookieMixin): +class ERP5Site(ResponseHeaderGenerator, FolderMixIn, PortalObjectBase, CacheCookieMixin): """ The *only* function this class should have is to help in the setup of a new ERP5. It should not assist in the functionality at all. @@ -269,6 +273,15 @@ class ERP5Site(ResponseHeaderGenerator, FolderMixIn, CMFSite, CacheCookieMixin): security = ClassSecurityInfo() security.declareObjectProtected(Permissions.AccessContentsInformation) + def __init__(self, id): + PortalObjectBase.__init__(self, id) + self.creation_date = DateTime() + + security.declarePrivate('reindexObject') + def reindexObject(self, idxs=[]): + """from Products.CMFDefault.Portal""" + pass + security.declarePublic('isSubtreeIndexable') def isSubtreeIndexable(self): """ @@ -328,7 +341,7 @@ class ERP5Site(ResponseHeaderGenerator, FolderMixIn, CMFSite, CacheCookieMixin): from Products.Localizer.MessageCatalog import ( message_catalog_alias_sources ) - sm = self.getSiteManager() + sm = self._components for message_catalog in self.Localizer.objectValues(): sm.registerUtility(message_catalog, provided=ITranslationDomain, @@ -338,21 +351,31 @@ class ERP5Site(ResponseHeaderGenerator, FolderMixIn, CMFSite, CacheCookieMixin): provided=ITranslationDomain, name=alias) - def _doInitialSiteManagerMigration(self): - self._createInitialSiteManager() - # Now that we have a sitemanager, se can do things that require - # one. Including setting up ZTK style utilities and adapters. We - # can even call setSite(self), as long as we roll back that later, - # since we are actually in the middle of a setSite() call. - from zope.site.hooks import getSite, setSite - old_site = getSite() - try: - setSite(self) - # setSite(self) is not really necessary for the migration below, but - # could be needed by other migrations to be added here. - self._doTranslationDomainRegistration() - finally: - setSite(old_site) + _registry_tool_id_list = 'caching_policy_manager', + def _registerMissingTools(self): + tool_id_list = ("portal_skins", "portal_types", "portal_membership", + "portal_url", "portal_workflow") + if (None in map(self.get, tool_id_list) or not + TransactionalResource.registerOnce(__name__, 'site_manager', self.id)): + return + self._registerTools(tool_id_list + self._registry_tool_id_list) + def markRegistered(txn): + global _missing_tools_registered + _missing_tools_registered = self.id + TransactionalResource(tpc_finish=markRegistered) + + def _registerTools(self, tool_id_list): + from Products.CMFCore import interfaces, utils + sm = self._components + for tool_id in tool_id_list: + tool = self.get(tool_id, None) + if tool: + tool_interface = utils._tool_interface_registry.get(tool_id) + if tool_interface is not None: + # Note: already registered tools will be either: + # - updated + # - registered again after being unregistered + sm.registerUtility(aq_base(tool), tool_interface) # backward compatibility auto-migration def getSiteManager(self): @@ -369,14 +392,29 @@ class ERP5Site(ResponseHeaderGenerator, FolderMixIn, CMFSite, CacheCookieMixin): # OFS.ObjectManager.ObjectManager.getSiteManager(), and is exactly # as cheap as it is on the case that self._components is already # set. + + if self.id == _missing_tools_registered: + return self._components # fast path _components = self._components - if _components is not None: - return _components - # This method below can take as (reasonably) long as it pleases - # since it will not be run ever again - self._doInitialSiteManagerMigration() - assert self._components is not None, 'Migration Failed!' - return self._components + if _components is None: + # only create _components + self._createInitialSiteManager() + _components = self._components + # Now that we have a sitemanager, se can do things that require + # one. Including setting up ZTK style utilities and adapters. We + # can even call setSite(self), as long as we roll back that later, + # since we are actually in the middle of a setSite() call. + from zope.site.hooks import getSite, setSite + old_site = getSite() + try: + setSite(self) + self._doTranslationDomainRegistration() + self._registerMissingTools() + finally: + setSite(old_site) + else: + self._registerMissingTools() + return _components security.declareProtected(Permissions.View, 'view') def view(self): @@ -387,7 +425,7 @@ class ERP5Site(ResponseHeaderGenerator, FolderMixIn, CMFSite, CacheCookieMixin): return self.index_html() def __of__(self, parent): - self = CMFSite.__of__(self, parent) + self = PortalObjectBase.__of__(self, parent) # Use a transactional variable for performance reason, # since ERP5Site.__of__ is called quite often. tv = getTransactionalVariable() @@ -471,7 +509,7 @@ class ERP5Site(ResponseHeaderGenerator, FolderMixIn, CMFSite, CacheCookieMixin): path_item_list=path_item_list, new_id=new_id) # Rename the object - return CMFSite.manage_renameObject(self, id=id, new_id=new_id, + return PortalObjectBase.manage_renameObject(self, id=id, new_id=new_id, REQUEST=REQUEST) @@ -589,7 +627,7 @@ class ERP5Site(ResponseHeaderGenerator, FolderMixIn, CMFSite, CacheCookieMixin): # _getProperty is missing, but since there are no protected properties # on an ERP5 Site, we can just use getProperty instead. - _getProperty = CMFSite.getProperty + _getProperty = PortalObjectBase.getProperty security.declareProtected(Permissions.AccessContentsInformation, 'getUid') def getUid(self): @@ -703,7 +741,7 @@ class ERP5Site(ResponseHeaderGenerator, FolderMixIn, CMFSite, CacheCookieMixin): email_from_address, email_from_name, validate_email ): - CMFSite.setupDefaultProperties(self, p, title, description, + PortalObjectBase.setupDefaultProperties(self, p, title, description, email_from_address, email_from_name, validate_email) @@ -1863,7 +1901,7 @@ factory_type_information = () # No original CMF portal_types installed by defaul class PortalGenerator: - klass = CMFSite + klass = PortalObjectBase def setupTools(self, p): """Set up initial tools""" @@ -1872,29 +1910,13 @@ class PortalGenerator: addCMFCoreTool('CMF Actions Tool', None) addCMFCoreTool('CMF Catalog', None) addCMFCoreTool('CMF Member Data Tool', None) + addCMFCoreTool('CMF Membership Tool', None) + addCMFCoreTool('CMF Registration Tool', None) addCMFCoreTool('CMF Skins Tool', None) addCMFCoreTool('CMF Undo Tool', None) addCMFCoreTool('CMF URL Tool', None) addCMFCoreTool('CMF Workflow Tool', None) - addCMFDefaultTool = p.manage_addProduct['CMFDefault'].manage_addTool - addCMFDefaultTool('Default Discussion Tool', None) - addCMFDefaultTool('Default Membership Tool', None) - addCMFDefaultTool('Default Registration Tool', None) - addCMFDefaultTool('Default Properties Tool', None) - addCMFDefaultTool('Default Metadata Tool', None) - addCMFDefaultTool('Default Syndication Tool', None) - - # try to install CMFUid without raising exceptions if not available - try: - addCMFUidTool = p.manage_addProduct['CMFUid'].manage_addTool - except AttributeError: - pass - else: - addCMFUidTool('Unique Id Annotation Tool', None) - addCMFUidTool('Unique Id Generator Tool', None) - addCMFUidTool('Unique Id Handler Tool', None) - def setupMailHost(self, p): p.manage_addProduct['MailHost'].manage_addMailHost( 'MailHost', smtp_host='localhost') @@ -2194,8 +2216,6 @@ class ERP5Generator(PortalGenerator): 'manage_members')) # actions tool removeActionsFromTool(p.portal_actions, ('folderContents',)) - # properties tool - removeActionsFromTool(p.portal_properties, ('configPortal',)) # remove unused action providers for i in ('portal_registration', 'portal_discussion', 'portal_syndication'): p.portal_actions.deleteActionProvider(i) @@ -2206,35 +2226,14 @@ class ERP5Generator(PortalGenerator): """ pass - # this lists only the skin layers of Products.CMFDefault we are actually - # interested in. - CMFDEFAULT_FOLDER_LIST = ['Images'] - def addCMFDefaultDirectoryViews(self, p): - """Semi-manually create DirectoryViews since CMFDefault 2.X no longer - registers the "skins" directory, only its subdirectories, making it - unusable with Products.CMFCore.DirectoryView.addDirectoryViews.""" - from Products.CMFCore.DirectoryView import createDirectoryView, _generateKey - import Products.CMFDefault - - ps = p.portal_skins - # get the layer directories actually present - for cmfdefault_skin_layer in self.CMFDEFAULT_FOLDER_LIST: - reg_key = _generateKey(Products.CMFDefault.__name__, - 'skins/' + cmfdefault_skin_layer) - createDirectoryView(ps, reg_key) - def setupDefaultSkins(self, p): ps = p.portal_skins - self.addCMFDefaultDirectoryViews(p) ps.manage_addProduct['OFSP'].manage_addFolder(id='external_method') ps.manage_addProduct['OFSP'].manage_addFolder(id='custom') # Set the 'custom' layer a high priority, so it remains the first # layer when installing new business templates. ps['custom'].manage_addProperty("business_template_skin_layer_priority", 100.0, "float") - skin_folder_list = [ 'custom' - , 'external_method' - ] + self.CMFDEFAULT_FOLDER_LIST - skin_folders = ', '.join(skin_folder_list) + skin_folders = ', '.join(('custom', 'external_method')) ps.addSkinSelection( 'View' , skin_folders , make_default = 1 diff --git a/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.File.py b/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.File.py index 0b99670ca9917c635d5511f781e74c77175e3d9d..c48c145f5743da3b6406c3f02c696947e115605b 100644 --- a/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.File.py +++ b/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.File.py @@ -34,8 +34,9 @@ from erp5.component.document.Document import Document, VALID_TEXT_FORMAT_LIST from erp5.component.document.Document import VALID_IMAGE_FORMAT_LIST from erp5.component.document.Document import ConversionError from Products.ERP5Type.Base import Base, removeIContentishInterface -from Products.CMFDefault.File import File as CMFFile +from OFS.Image import File as OFS_File from Products.ERP5Type.Utils import deprecated +from cStringIO import StringIO def _unpackData(data): """ @@ -46,7 +47,7 @@ def _unpackData(data): _MARKER = object() -class File(Document, CMFFile): +class File(Document, OFS_File): """ A File can contain raw data which can be uploaded and downloaded. It is the root class of Image, OOoDocument (ERP5OOo product), @@ -110,8 +111,14 @@ class File(Document, CMFFile): filename = kw.get('filename') if filename: self._setFilename(filename) - if self._isNotEmpty(file_object): - self._setFile(file_object, precondition=precondition) + if file_object is not None: + # XXX: Rather than doing nothing if empty, consider changing: + # - _update_image_info to clear metadata + # - interactions to do nothing (or else?) + file_object.seek(0, 2) + if file_object.tell(): + file_object.seek(0) + self._setFile(file_object) Base._edit(self, **kw) security.declareProtected( Permissions.ModifyPortalContent, 'edit' ) @@ -138,11 +145,18 @@ class File(Document, CMFFile): return None def _setFile(self, data, precondition=None): - if data is not None and self.hasData() and \ - str(data.read()) == str(self.getData()): - # Same data as previous, no need to change it's content + if data is None: return - CMFFile._edit(self, precondition=precondition, file=data) + if self.hasData(): + if str(data.read()) == str(self.getData()): + # Same data as previous, no need to change its content + return + else: + data.seek(0, 2) + + if data.tell(): + data.seek(0) + self.manage_upload(data) security.declareProtected(Permissions.ModifyPortalContent,'setFile') def setFile(self, data, precondition=None): @@ -176,11 +190,16 @@ class File(Document, CMFFile): return str(data) # DAV Support - PUT = CMFFile.PUT + security.declareProtected(Permissions.ModifyPortalContent, 'PUT') + def PUT(self, REQUEST, RESPONSE): + """from Products.CMFDefault.File""" + OFS_File.PUT(self, REQUEST, RESPONSE) + self.reindexObject() + security.declareProtected(Permissions.FTPAccess, 'manage_FTPstat', 'manage_FTPlist') - manage_FTPlist = CMFFile.manage_FTPlist - manage_FTPstat = CMFFile.manage_FTPstat + manage_FTPlist = OFS_File.manage_FTPlist + manage_FTPstat = OFS_File.manage_FTPstat security.declareProtected(Permissions.AccessContentsInformation, 'getMimeTypeAndContent') def getMimeTypeAndContent(self): diff --git a/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.WebDAVSupport.py b/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.WebDAVSupport.py index 8bc2d6fd1e215c62fb85b3d937669ee795a04588..1e66ee17a66379f523207b380df0d59ca08dfa7d 100644 --- a/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.WebDAVSupport.py +++ b/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.WebDAVSupport.py @@ -23,6 +23,7 @@ from Products.ERP5Type.Globals import InitializeClass from Products.ERP5Type import Permissions from Products.CMFCore.PortalContent import ResourceLockedError from zExceptions import Forbidden +from cStringIO import StringIO security = ModuleSecurityInfo(__name__) @@ -76,7 +77,7 @@ class TextContent: headers = self.parseHeadersFromText(body) content_type = REQUEST.get_header('Content-Type', '') headers.setdefault('content_type', content_type) - headers['file'] = body + headers['file'] = StringIO(body) self._edit(**headers) except ResourceLockedError: transaction.abort() diff --git a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/ERP5Site_reindexAll.py b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/ERP5Site_reindexAll.py index d66fba1a1581353d71fc33bc0baab3f526a0c483..3cdd44270ab6d93a4a8902afbc3090209c994e7a 100644 --- a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/ERP5Site_reindexAll.py +++ b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/ERP5Site_reindexAll.py @@ -85,7 +85,6 @@ print reindex( 'portal_classes', 'portal_preferences', 'portal_simulation', - 'portal_uidhandler', ) and 'inventory' not in x.id ], diff --git a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/breadcrumbs.py b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/breadcrumbs.py index 198dcef624bc85966a648b718aca393c6be9cc03..9b708accb31c8f2089fc6e6977e3fb80117b8dd9 100644 --- a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/breadcrumbs.py +++ b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/breadcrumbs.py @@ -1,31 +1,29 @@ "Modified version for ERP5 to append the default action (/view) in the URL." -from Products.CMFCore.utils import getToolByName -ptool = getToolByName(script, 'portal_properties') -utool = getToolByName(script, 'portal_url') +portal = context.getPortalObject() + +utool = portal.portal_url portal_url = utool() -result = [] -param = int(context.REQUEST.get('ignore_layout', 0)) and '?ignore_layout:int=1' or '' +param = '?ignore_layout:int=1' if int(portal.REQUEST.get('ignore_layout', 0)) else '' if include_root: - result.append( { 'id' : 'root' - , 'title' : ptool.title() - , 'url' : '%s/view%s' % (portal_url, param) - } - ) - -relative = utool.getRelativeContentPath(context) -portal = utool.getPortalObject() + result = [{ + 'id' : 'root', + 'title' : portal.title, + 'url' : '%s/view%s' % (portal_url, param), + }] +else: + result = [] obj = portal now = [] -for name in relative: +for name in utool.getRelativeContentPath(context): obj = obj.restrictedTraverse(name) now.append(name) title = ( getattr(obj, "getCompactTranslatedTitle", lambda: None)() or obj.getTitle() or obj.getId() ) - if not name == 'talkback': + if name != 'talkback': result.append( { 'id' : name , 'title' : title , 'url' : '%s/%s/view%s' % (portal_url, '/'.join(now), param) diff --git a/product/ERP5OOo/tests/testFormPrintoutAsODG.py b/product/ERP5OOo/tests/testFormPrintoutAsODG.py index cea68a5736992142a24de5a7cd2b88def96786b3..c084fc634059ded0a60f6f8f7c051be7ddf936b3 100644 --- a/product/ERP5OOo/tests/testFormPrintoutAsODG.py +++ b/product/ERP5OOo/tests/testFormPrintoutAsODG.py @@ -69,7 +69,6 @@ class TestFormPrintoutAsODG(TestFormPrintoutMixin): addStyleSheet = custom.manage_addProduct['OFSP'].manage_addFile if custom._getOb('Foo_getODGStyleSheet', None) is None: addStyleSheet(id='Foo_getODGStyleSheet', file=foo_file, title='', - precondition='', content_type='application/vnd.oasis.opendocument.graphics') erp5OOo = custom.manage_addProduct['ERP5OOo'] diff --git a/product/ERP5OOo/tests/testFormPrintoutAsODT.py b/product/ERP5OOo/tests/testFormPrintoutAsODT.py index 63016ef9e6faed83e6da6e39b0bd1d4d28d48f87..083f64e1dd7931cbb62066860bafb0a940133d6f 100644 --- a/product/ERP5OOo/tests/testFormPrintoutAsODT.py +++ b/product/ERP5OOo/tests/testFormPrintoutAsODT.py @@ -84,23 +84,22 @@ class TestFormPrintoutAsODT(TestFormPrintoutMixin): addStyleSheet = custom.manage_addProduct['OFSP'].manage_addFile if custom._getOb('Foo_getODTStyleSheet', None) is None: addStyleSheet(id='Foo_getODTStyleSheet', file=foo_file, title='', - precondition='', content_type = 'application/vnd.oasis.opendocument.text') + content_type = 'application/vnd.oasis.opendocument.text') if custom._getOb('Foo2_getODTStyleSheet', None) is None: addStyleSheet(id='Foo2_getODTStyleSheet', file=foo2_file, title='', - precondition='', content_type = 'application/vnd.oasis.opendocument.text') + content_type = 'application/vnd.oasis.opendocument.text') if custom._getOb('Foo3_getODTStyleSheet', None) is None: addStyleSheet(id='Foo3_getODTStyleSheet', file=foo3_file, title='', - precondition='', content_type = 'application/vnd.oasis.opendocument.text') + content_type = 'application/vnd.oasis.opendocument.text') if custom._getOb('Foo4_getODTStyleSheet', None) is None: addStyleSheet(id='Foo4_getODTStyleSheet', file=foo4_file, title='', - precondition='', content_type = 'application/vnd.oasis.opendocument.text') + content_type = 'application/vnd.oasis.opendocument.text') if custom._getOb('Foo5_getODTStyleSheet', None) is None: addStyleSheet(id='Foo5_getODTStyleSheet', file=foo5_file, title='', - precondition='', content_type = 'application/vnd.oasis.opendocument.text') + content_type = 'application/vnd.oasis.opendocument.text') if custom._getOb('Foo_getVariableODTStyleSheet', None) is None: addStyleSheet(id='Foo_getVariableODTStyleSheet', file=variable_file_object, title='', - precondition='', content_type='application/vnd.oasis.opendocument.text') erp5OOo = custom.manage_addProduct['ERP5OOo'] diff --git a/product/ERP5OOo/tests/testOOoBatchMode.py b/product/ERP5OOo/tests/testOOoBatchMode.py index fb3c41ef6ddb92ceea8a75b8e073e753e327324e..a9bdb97f678760aad491e538d8b3455218e35cf1 100644 --- a/product/ERP5OOo/tests/testOOoBatchMode.py +++ b/product/ERP5OOo/tests/testOOoBatchMode.py @@ -66,7 +66,7 @@ class TestOoodResponse(ERP5TypeTestCase): custom = portal_skins.custom addStyleSheet = custom.manage_addProduct['OFSP'].manage_addFile addStyleSheet(id='Base_getODTStyleSheet', file=import_file, title='', - precondition='', content_type='application/vnd.oasis.opendocument.text') + content_type='application/vnd.oasis.opendocument.text') addOOoTemplate = custom.manage_addProduct['ERP5OOo'].addOOoTemplate addOOoTemplate(id='ERP5Site_viewNothingAsOdt', title='') portal_skins.changeSkin(skinname=None) diff --git a/product/ERP5OOo/tests/testOOoDynamicStyle.py b/product/ERP5OOo/tests/testOOoDynamicStyle.py index 85a5cd1d9967c60e6c32a71f5dcc8e7d7906e025..b3ab7579f9b7b08acc5a780973b99b3ed92004b4 100644 --- a/product/ERP5OOo/tests/testOOoDynamicStyle.py +++ b/product/ERP5OOo/tests/testOOoDynamicStyle.py @@ -76,10 +76,10 @@ class TestOooDynamicStyle(ERP5TypeTestCase): addStyleSheet = self.getPortal().manage_addProduct['OFSP'].manage_addFile if getattr(self.getPortal(), 'Test_getODTStyleSheet_en', None) is None: addStyleSheet(id='Test_getODTStyleSheet_en', file=en_file, title='', - precondition='', content_type=self.content_type_writer) + content_type=self.content_type_writer) if getattr(self.getPortal(), 'Test_getODTStyleSheet_ja', None) is None: addStyleSheet(id='Test_getODTStyleSheet_ja', file=ja_file, title='', - precondition='', content_type=self.content_type_writer) + content_type=self.content_type_writer) if getattr(self.getPortal(), 'Base_getODTStyleSheetByLanguage', None) is None: script_body = """ current_language = context.Localizer.get_selected_language() diff --git a/product/ERP5Type/Base.py b/product/ERP5Type/Base.py index 293bfadd4483befb751e4d7934a3b3629438092c..f137c418586233d387de18ed9edd1a0b9f2b66da 100644 --- a/product/ERP5Type/Base.py +++ b/product/ERP5Type/Base.py @@ -81,7 +81,6 @@ from CopySupport import CopyContainer, CopyError,\ from Errors import DeferredCatalogError, UnsupportedWorkflowMethod from Products.CMFActivity.ActiveObject import ActiveObject from Products.ERP5Type.Accessor.Accessor import Accessor as Method -from Products.ERP5Type.Accessor.TypeDefinition import asDate from Products.ERP5Type.Message import Message from Products.ERP5Type.ConsistencyMessage import ConsistencyMessage from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod, super_user @@ -3229,9 +3228,8 @@ class Base( if history and 'time' in history[0]) except ValueError: pass - if getattr(aq_base(self), 'CreationDate', None) is not None: - return asDate(self.CreationDate()) - return None # JPS-XXX - try to find a way to return a creation date instead of None + if getattr(aq_base(self), 'creation_date', None): + return self.creation_date.toZone(DateTime().timezone()) security.declareProtected(Permissions.AccessContentsInformation, 'getModificationDate') def getModificationDate(self): @@ -3260,7 +3258,13 @@ class Base( # Return a copy of history time, to prevent modification return DateTime(max_date) if self._p_serial: - return DateTime(TimeStamp(self._p_serial).timeTime()) + return DateTime(self._p_mtime) + + security.declareProtected(Permissions.AccessContentsInformation, 'modified') + def modified(self): + warnings.warn('modified is a deprecated alias to getModificationDate.', + DeprecationWarning) + return self.getModificationDate() # Layout management security.declareProtected(Permissions.AccessContentsInformation, 'getApplicableLayout') diff --git a/product/ERP5Type/ZopePatch.py b/product/ERP5Type/ZopePatch.py index d1a1061cb897c5a9d393c4e2134979f0e8137633..02c049c64bcb39dd9ad8e6dd5d868bb05d09275e 100644 --- a/product/ERP5Type/ZopePatch.py +++ b/product/ERP5Type/ZopePatch.py @@ -34,6 +34,7 @@ from Products.ERP5Type.patches import DCWorkflow from Products.ERP5Type.patches import Worklists from Products.ERP5Type.patches import BTreeFolder2 from Products.ERP5Type.patches import WorkflowTool +from Products.ERP5Type.patches import DynamicType from Products.ERP5Type.patches import XMLExportImport from Products.ERP5Type.patches import ppml from Products.ERP5Type.patches import Expression diff --git a/product/ERP5Type/__init__.py b/product/ERP5Type/__init__.py index f5623e4af4a0ebd710ecbcf5fe6575d1a895f7de..00ff7d12070412cdae7ebaf63cd4be9f075e5dc7 100644 --- a/product/ERP5Type/__init__.py +++ b/product/ERP5Type/__init__.py @@ -189,3 +189,18 @@ import zExceptions ModuleSecurityInfo('zExceptions').declarePublic(*filter( lambda x: Exception in getattr(getattr(zExceptions, x), '__mro__', ()), dir(zExceptions))) + +# BBB : allow load of fomer Products.CMFDefault.MembershipTool +# that has been replaced by Products.CMFCore.MembershipTool +try: + from Products.CMFDefault.MembershipTool import MembershipTool +except ImportError: + import sys, imp + m = 'Products.CMFDefault' + sys.modules[m] = imp.new_module(m) + m += ".MembershipTool" + sys.modules[m] = m = imp.new_module(m) + from Products.CMFCore.MembershipTool import MembershipTool + m.MembershipTool = MembershipTool + del m + diff --git a/product/ERP5Type/dynamic/portal_type_class.py b/product/ERP5Type/dynamic/portal_type_class.py index 57bbcc4b9d545a87191da0e9aabc1fe39a833ae3..e0c36e493f9eea25f464e2a2fabca20ccad335fa 100644 --- a/product/ERP5Type/dynamic/portal_type_class.py +++ b/product/ERP5Type/dynamic/portal_type_class.py @@ -468,6 +468,15 @@ def synchronizeDynamicModules(context, force=False): except AttributeError: pass # no Activity Tool yet + for tool_id in ("portal_properties", "portal_uidannotation", + "portal_uidgenerator", "portal_uidhandler"): + if portal.hasObject(tool_id): + portal._delObject(tool_id, suppress_events=True) + migrate = True + if tool_id == 'portal_properties': + portal.portal_skins.erp5_xhtml_style.breadcrumbs.write( + 'return []') + if migrate: portal.migrateToPortalTypeClass() portal.portal_skins.changeSkin(None) diff --git a/product/ERP5Type/patches/CMFCoreSkinnable.py b/product/ERP5Type/patches/CMFCoreSkinnable.py index cdbba4af1ada0d9302ba031fe755b82b11175902..72fd4a74433398c9bdb7d4f70edd5e0cbf5140d8 100644 --- a/product/ERP5Type/patches/CMFCoreSkinnable.py +++ b/product/ERP5Type/patches/CMFCoreSkinnable.py @@ -141,11 +141,9 @@ def CMFCoreSkinnableSkinnableObjectManager_changeSkin(self, skinname, REQUEST=No Patched not to call getSkin. ''' if skinname is None: - sfn = self.getSkinsFolderName() - if sfn is not None: - sf = getattr(self, sfn, None) - if sf is not None: - skinname = sf.getDefaultSkin() + sf = getattr(self, "portal_skins", None) + if sf is not None: + skinname = sf.getDefaultSkin() tid = get_ident() SKINDATA[tid] = ( None, diff --git a/product/ERP5Type/patches/CMFCoreUtils.py b/product/ERP5Type/patches/CMFCoreUtils.py index 6ac86a93d661c481a41a1d376c041852d77b9826..172d6094d4a24d5e00e6bb378910c3bdda1bac96 100644 --- a/product/ERP5Type/patches/CMFCoreUtils.py +++ b/product/ERP5Type/patches/CMFCoreUtils.py @@ -13,24 +13,25 @@ ############################################################################## from Acquisition import aq_parent -from Products.CMFCore.utils import getToolByName, SUBTEMPLATE +from Products.CMFCore.utils import SUBTEMPLATE +from zope.component import queryUtility +from Products.CMFCore.interfaces import ICachingPolicyManager # patch _setCacheHeaders so that existing headers are not overridden def _setCacheHeaders(obj, extra_context): """Set cache headers according to cache policy manager for the obj.""" REQUEST = getattr(obj, 'REQUEST', None) - if REQUEST is not None: call_count = getattr(REQUEST, SUBTEMPLATE, 1) - 1 setattr(REQUEST, SUBTEMPLATE, call_count) if call_count != 0: - return + return # cleanup delattr(REQUEST, SUBTEMPLATE) content = aq_parent(obj) - manager = getToolByName(obj, 'caching_policy_manager', None) + manager = queryUtility(ICachingPolicyManager) if manager is None: return diff --git a/product/ERP5Type/patches/CookieCrumbler.py b/product/ERP5Type/patches/CookieCrumbler.py index df547957805fcc99194c68e5e88ac1b25606fce1..583a374d75e27659e66f21630c2dd364bcf604ce 100644 --- a/product/ERP5Type/patches/CookieCrumbler.py +++ b/product/ERP5Type/patches/CookieCrumbler.py @@ -30,6 +30,7 @@ from App.class_init import InitializeClass from Products.CMFCore.CookieCrumbler import CookieCrumbler from Products.CMFCore.CookieCrumbler import CookieCrumblerDisabled from urllib import quote, unquote +from zExceptions import Redirect from ZPublisher.HTTPRequest import HTTPRequest ATTEMPT_NONE = 0 # No attempt at authentication @@ -47,6 +48,10 @@ class PatchedCookieCrumbler(CookieCrumbler): security = ClassSecurityInfo() +CookieCrumbler.auto_login_page = 'login_form' +CookieCrumbler.unauth_page = '' +CookieCrumbler.logout_page = 'logged_out' + def getLoginURL(self): ''' Redirects to the login page. @@ -176,14 +181,175 @@ def modifyRequest(self, req, resp): CookieCrumbler.modifyRequest = modifyRequest -def credentialsChanged(self, user, name, pw): - ac = standard_b64encode('%s:%s' % (name, pw)) - method = self.getCookieMethod( 'setAuthCookie' - , self.defaultSetAuthCookie ) - resp = self.REQUEST['RESPONSE'] - method( resp, self.auth_cookie, quote( ac ) ) +def credentialsChanged(self, user, name, pw, request=None): + """ + Updates cookie credentials if user details are changed. + """ + if request is None: + request = getRequest() # BBB for Membershiptool + reponse = request['RESPONSE'] + # + # We don't want new lines, so use base64.standard_b64encode instead of + # base64.encodestring + ac = standard_b64encode('%s:%s' % (name, pw)).rstrip() + # + method = self.getCookieMethod('setAuthCookie', + self.defaultSetAuthCookie) + method(reponse, self.auth_cookie, quote(ac)) CookieCrumbler.credentialsChanged = credentialsChanged +# The following patches are to keep the original behaviour of automatic +# redirection to login page. Recent CMF uses a view that is implemented +# in CMFDefault (UnauthorizedView, on zExceptions.Unauthorized). + +class ResponseCleanup: + def __init__(self, resp): + self.resp = resp + + def __del__(self): + # Free the references. + # + # No errors of any sort may propagate, and we don't care *what* + # they are, even to log them. + try: + del self.resp.unauthorized + except Exception: + pass + try: + del self.resp._unauthorized + except Exception: + pass + try: + del self.resp + except Exception: + pass + + +if 1: + def __call__(self, container, req): + '''The __before_publishing_traverse__ hook.''' + resp = req['RESPONSE'] + try: + attempt = self.modifyRequest(req, resp) + except CookieCrumblerDisabled: + return + # + if req.get('disable_cookie_login__', 0): + return + + if (self.unauth_page or + attempt == ATTEMPT_LOGIN or attempt == ATTEMPT_NONE): + # Modify the "unauthorized" response. + req._hold(ResponseCleanup(resp)) + resp.unauthorized = self.unauthorized + resp._unauthorized = self._unauthorized + # + if attempt != ATTEMPT_NONE: + # Trying to log in or resume a session + if self.cache_header_value: + # we don't want caches to cache the resulting page + resp.setHeader('Cache-Control', self.cache_header_value) + # demystify this in the response. + resp.setHeader('X-Cache-Control-Hdr-Modified-By', + 'CookieCrumbler') + phys_path = self.getPhysicalPath() + # + if self.logout_page: + # Cookies are in use. + page = getattr(container, self.logout_page, None) + if page is not None: + # Provide a logout page. + req._logout_path = phys_path + ('logout',) + req._credentials_changed_path = ( + phys_path + ('credentialsChanged',)) + # + + def _cleanupResponse(self): + # XXX: this method violates the rules for tools/utilities: + # it depends on self.REQUEST + resp = self.REQUEST['RESPONSE'] + # No errors of any sort may propagate, and we don't care *what* + # they are, even to log them. + try: del resp.unauthorized + except Exception: pass + try: del resp._unauthorized + except Exception: pass + return resp + + security.declarePrivate('unauthorized') + def unauthorized(self): + resp = self._cleanupResponse() + # If we set the auth cookie before, delete it now. + if resp.cookies.has_key(self.auth_cookie): + del resp.cookies[self.auth_cookie] + # Redirect if desired. + url = self.getUnauthorizedURL() + if url is not None: + raise Redirect, url + # Fall through to the standard unauthorized() call. + resp.unauthorized() + + def _unauthorized(self): + resp = self._cleanupResponse() + # If we set the auth cookie before, delete it now. + if resp.cookies.has_key(self.auth_cookie): + del resp.cookies[self.auth_cookie] + # Redirect if desired. + url = self.getUnauthorizedURL() + if url is not None: + resp.redirect(url, lock=1) + # We don't need to raise an exception. + return + # Fall through to the standard _unauthorized() call. + resp._unauthorized() + + security.declarePublic('getUnauthorizedURL') + def getUnauthorizedURL(self): + ''' + Redirects to the login page. + ''' + # XXX: this method violates the rules for tools/utilities: + # it depends on self.REQUEST + req = self.REQUEST + resp = req['RESPONSE'] + attempt = getattr(req, '_cookie_auth', ATTEMPT_NONE) + if attempt == ATTEMPT_NONE: + # An anonymous user was denied access to something. + page_id = self.auto_login_page + retry = '' + elif attempt == ATTEMPT_LOGIN: + # The login attempt failed. Try again. + page_id = self.auto_login_page + retry = '1' + else: + # An authenticated user was denied access to something. + page_id = self.unauth_page + retry = '' + if page_id: + page = self.restrictedTraverse(page_id, None) + if page is not None: + came_from = req.get('came_from', None) + if came_from is None: + came_from = req.get('ACTUAL_URL') + query = req.get('QUERY_STRING') + if query: + # Include the query string in came_from + if not query.startswith('?'): + query = '?' + query + came_from = came_from + query + url = '%s?came_from=%s&retry=%s&disable_cookie_login__=1' % ( + page.absolute_url(), quote(came_from), retry) + return url + return None + + CookieCrumbler.__call__ = __call__ + CookieCrumbler._cleanupResponse = _cleanupResponse + CookieCrumbler.unauthorized = unauthorized + CookieCrumbler._unauthorized = _unauthorized + CookieCrumbler.getUnauthorizedURL = getUnauthorizedURL + +### + CookieCrumbler.security = security InitializeClass(CookieCrumbler) diff --git a/product/ERP5Type/patches/DynamicType.py b/product/ERP5Type/patches/DynamicType.py new file mode 100644 index 0000000000000000000000000000000000000000..6eb6a880211387f167c229d05de7f9a7bdb57f6f --- /dev/null +++ b/product/ERP5Type/patches/DynamicType.py @@ -0,0 +1,26 @@ +############################################################################## +# +# Copyright (c) 2001 Zope Foundation and Contributors. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## + +from Products.CMFCore.DynamicType import DynamicType + +def getTypeInfo(self): + """ Get the TypeInformation object specified by the portal type. + """ + # + tool = getattr(self.getPortalObject(), "portal_types", None) + # + if tool is None: + return None + return tool.getTypeInfo(self) # Can return None. + +DynamicType.getTypeInfo = getTypeInfo diff --git a/product/ERP5Type/patches/Restricted.py b/product/ERP5Type/patches/Restricted.py index 3e11978ce43f312cf7fc1a2bc8cd51e387f3bd52..9ef0a6f43f5622463cc11479269e11bdad4fa3a5 100644 --- a/product/ERP5Type/patches/Restricted.py +++ b/product/ERP5Type/patches/Restricted.py @@ -265,7 +265,9 @@ allow_module('cStringIO') import cStringIO allow_type(cStringIO.InputType) allow_type(cStringIO.OutputType) - +allow_module('io') +import io +allow_type(io.BytesIO) ModuleSecurityInfo('cgi').declarePublic('escape', 'parse_header') allow_module('datetime') diff --git a/product/ERP5Type/patches/WorkflowTool.py b/product/ERP5Type/patches/WorkflowTool.py index d27e374781530f52fd8d1e343e9deab514257b06..0363b80db17e5ca02f590cfe481622afde1f4bce 100644 --- a/product/ERP5Type/patches/WorkflowTool.py +++ b/product/ERP5Type/patches/WorkflowTool.py @@ -951,4 +951,17 @@ def canDoActionFor(self, ob, action, wf_id=None, guard_kw={}): WorkflowTool.canDoActionFor = canDoActionFor +security.declarePrivate('_listTypeInfo') +def _listTypeInfo(self): + """ List the portal types which are available. + """ + # + ttool = getattr(self.getPortalObject(), "portal_types", None) + # + if ttool is not None: + return ttool.listTypeInfo() + return () + +WorkflowTool._listTypeInfo = _listTypeInfo + InitializeClass(WorkflowTool) diff --git a/product/ERP5Type/tests/ERP5TypeTestCase.py b/product/ERP5Type/tests/ERP5TypeTestCase.py index 61ace0aac7a4aa6ed1a7c24443e9cb6fba44c988..92f9ee387cd0517ea724e50968f26a2437a45590 100644 --- a/product/ERP5Type/tests/ERP5TypeTestCase.py +++ b/product/ERP5Type/tests/ERP5TypeTestCase.py @@ -839,6 +839,10 @@ class ERP5TypeTestCaseMixin(ProcessingNodeTestCase, PortalTestCase): PAS._extractUserIds = orig_extractUserIds # Restore security manager setSecurityManager(sm) + # Restore site removed by closing of request + setSite(self.portal) + + # Make sure that the skin cache does not have objects that were # loaded with the connection used by the requested url.