Commit dfb15874 authored by Julien Muchembled's avatar Julien Muchembled

Add support for CMF 2.3

See merge request nexedi/erp5!957
parents 989ad1fd 9f248dfc
......@@ -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
......
......@@ -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"""
......
......@@ -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):
"""
......
......@@ -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)
......
......@@ -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")
......
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
......@@ -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
......@@ -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
......
......@@ -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())
......
......@@ -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]
......
......@@ -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
......
......@@ -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):
......
......@@ -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()
......
......@@ -85,7 +85,6 @@ print reindex(
'portal_classes',
'portal_preferences',
'portal_simulation',
'portal_uidhandler',
) and
'inventory' not in x.id
],
......
"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)
......
......@@ -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']
......
......@@ -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']
......
......@@ -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)
......
......@@ -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()
......
......@@ -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')
......
......@@ -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
......
......@@ -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
......@@ -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)
......
......@@ -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,
......
......@@ -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
......
......@@ -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']
# <patch>
# We don't want new lines, so use base64.standard_b64encode instead of
# base64.encodestring
ac = standard_b64encode('%s:%s' % (name, pw)).rstrip()
# </patch>
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
# <patch>
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
# </patch>
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()
# <patch>
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',))
# </patch>
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)
##############################################################################
#
# 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.
"""
# <patch>
tool = getattr(self.getPortalObject(), "portal_types", None)
# </patch>
if tool is None:
return None
return tool.getTypeInfo(self) # Can return None.
DynamicType.getTypeInfo = getTypeInfo
......@@ -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')
......
......@@ -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.
"""
# <patch>
ttool = getattr(self.getPortalObject(), "portal_types", None)
# </patch>
if ttool is not None:
return ttool.listTypeInfo()
return ()
WorkflowTool._listTypeInfo = _listTypeInfo
InitializeClass(WorkflowTool)
......@@ -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.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment