diff --git a/product/ERP5/Document/Document.py b/product/ERP5/Document/Document.py index de9eb7281017dbf82e3eeeeda9d15fddfa9a2c2c..7aa5be9dff90f67b8db4a716a3d5f5bf1cad4cf3 100644 --- a/product/ERP5/Document/Document.py +++ b/product/ERP5/Document/Document.py @@ -59,6 +59,7 @@ from Products.ERP5.mixin.cached_convertable import CachedConvertableMixin from Products.ERP5.mixin.text_convertable import TextConvertableMixin from Products.ERP5.mixin.downloadable import DownloadableMixin from Products.ERP5.mixin.document import DocumentMixin +from Products.ERP5.mixin.extensible_traversable import DocumentExtensibleTraversableMixIn _MARKER = [] VALID_ORDER_KEY_LIST = ('user_login', 'content', 'file_name', 'input') @@ -128,129 +129,6 @@ class DocumentProxyError(Exception):pass class NotConvertedError(Exception):pass allow_class(NotConvertedError) -class PermanentURLMixIn(ExtensibleTraversableMixIn): - """ - Provides access to documents through their permanent URL. - This class must be inherited by all document classes so - that documents displayed outside a Web Site / Web Section - can also use the permanent URL principle. - """ - - # Declarative security - security = ClassSecurityInfo() - - def _forceIdentification(self, request): - # force identification (usable for extensible content) - cache = getReadOnlyTransactionCache(self) - if cache is not None: - key = ('__bobo_traverse__', self, 'user') - try: - user = cache[key] - except KeyError: - user = _MARKER - else: - user = _MARKER - old_user = getSecurityManager().getUser() - if user is _MARKER: - user = None # By default, do nothing - if old_user is None or old_user.getUserName() == 'Anonymous User': - user_folder = getattr(self.getPortalObject(), 'acl_users', None) - if user_folder is not None: - try: - if request.get('PUBLISHED', _MARKER) is _MARKER: - # request['PUBLISHED'] is required by validate - request['PUBLISHED'] = self - has_published = False - else: - has_published = True - try: - user = user_folder.validate(request) - except AttributeError: - # This kind of error happens with unrestrictedTraverse, - # because the request object is a fake, and it is just - # a dict object. - user = None - if not has_published: - try: - del request.other['PUBLISHED'] - except AttributeError: - # The same here as above. unrestrictedTraverse provides - # just a plain dict, so request.other does not exist. - del request['PUBLISHED'] - except: - LOG("ERP5 WARNING",0, - "Failed to retrieve user in __bobo_traverse__ of WebSection %s" % self.getPath(), - error=sys.exc_info()) - user = None - if user is not None and user.getUserName() == 'Anonymous User': - user = None # If the user which is connected is anonymous, - # do not try to change SecurityManager - if cache is not None: - cache[key] = user - - old_manager = None - if user is not None: - # We need to perform identification - old_manager = getSecurityManager() - newSecurityManager(get_request(), user) - - return old_manager, user - - ### Extensible content - def _getExtensibleContent(self, request, name): - # Permanent URL traversal - old_manager, user = self._forceIdentification(request) - # Next get the document per name - portal = self.getPortalObject() - document = self.getDocumentValue(name=name, portal=portal) - # restore original security context if there's a logged in user - if user is not None: - setSecurityManager(old_manager) - if document is not None: - document = aq_base(document.asContext(id=name, # Hide some properties to permit locating the original - original_container=document.getParentValue(), - original_id=document.getId(), - editable_absolute_url=document.absolute_url())) - return document.__of__(self) - - # no document found for current user, still such document may exists - # in some cases user (like Anonymous) can not view document according to portal catalog - # but we may ask him to login if such a document exists - isAuthorizationForced = getattr(self, 'isAuthorizationForced', None) - if isAuthorizationForced is not None and isAuthorizationForced(): - if unrestricted_apply(self.getDocumentValue, (name, portal)) is not None: - # force user to login as specified in Web Section - raise Unauthorized - - security.declareProtected(Permissions.View, 'getDocumentValue') - def getDocumentValue(self, name=None, portal=None, **kw): - """ - Return the default document with the given - name. The name parameter may represent anything - such as a document reference, an identifier, - etc. - - If name is not provided, the method defaults - to returning the default document by calling - getDefaultDocumentValue. - - kw parameters can be useful to filter content - (ex. force a given validation state) - - This method must be implemented through a - portal type dependent script: - WebSection_getDocumentValue - """ - if name is None: - return self.getDefaultDocumentValue() - - method = self._getTypeBasedMethod('getDocumentValue', - fallback_script_id='WebSection_getDocumentValue') - - document = method(name, portal=portal, **kw) - if document is not None: - return document.__of__(self) - class DocumentProxyMixin: """ Provides access to documents referenced by the follow_up field @@ -329,7 +207,7 @@ class UpdateMixIn: return method() -class Document(PermanentURLMixIn, XMLObject, UrlMixIn, CachedConvertableMixin, +class Document(DocumentExtensibleTraversableMixIn, XMLObject, UrlMixIn, CachedConvertableMixin, SnapshotMixin, UpdateMixIn, TextConvertableMixin, DownloadableMixin, DocumentMixin): """Document is an abstract class with all methods related to document diff --git a/product/ERP5/Document/WebSection.py b/product/ERP5/Document/WebSection.py index c1083cba5e8075dc46a843c24fd9042ac3601b7a..9501cbd9a7b1f1c2f2c5942a45c8bc5e25c7eaec 100644 --- a/product/ERP5/Document/WebSection.py +++ b/product/ERP5/Document/WebSection.py @@ -30,8 +30,8 @@ from AccessControl import ClassSecurityInfo from Products.ERP5Type import Permissions, PropertySheet from Products.ERP5.Document.Domain import Domain -from Products.ERP5.Document.Document import PermanentURLMixIn -from Acquisition import aq_base, aq_inner +from Products.ERP5.mixin.extensible_traversable import DocumentExtensibleTraversableMixIn as PermanentURLMixIn +from Acquisition import aq_base, aq_inner from Products.ERP5Type.UnrestrictedMethod import unrestricted_apply from AccessControl import Unauthorized from OFS.Traversable import NotFound diff --git a/product/ERP5/interfaces/extensible_traversable.py b/product/ERP5/interfaces/extensible_traversable.py new file mode 100644 index 0000000000000000000000000000000000000000..2afc8ac970ff2248e597920106a50330f4037ebf --- /dev/null +++ b/product/ERP5/interfaces/extensible_traversable.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved. +# Ivan Tyagov <ivan@nexedi.com> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +from zope.interface import Interface + +class ILegacyExtensibleTraversable(Interface): + """ + Extensible Traversable legacy interface specification + """ + + def _getExtensibleContent(request, name): + """ + Return extensible subcontent of context document during traversal. + """ + +class IExtensibleTraversable(ILegacyExtensibleTraversable): + """ + Extensible Traversable interface specification + + IExtensibleTraversable provides methods so a document may become a container for extensible content + during traversal. + """ + + def getExtensibleContent(request, name): + """ + Return extensible subcontent of context document during traversal. + """ \ No newline at end of file diff --git a/product/ERP5/mixin/extensible_traversable.py b/product/ERP5/mixin/extensible_traversable.py new file mode 100644 index 0000000000000000000000000000000000000000..02879dcec214fb4c47a952721f662f5257ca16ed --- /dev/null +++ b/product/ERP5/mixin/extensible_traversable.py @@ -0,0 +1,208 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved. +# Ivan Tyagov <ivan@nexedi.com> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +from zLOG import LOG +from Acquisition import aq_base +from Products.ERP5Type.Globals import get_request +from AccessControl import Unauthorized +from Products.ERP5Type.ExtensibleTraversable import ExtensibleTraversableMixIn +from Products.ERP5Type.Cache import getReadOnlyTransactionCache +from AccessControl import ClassSecurityInfo, getSecurityManager +from AccessControl.SecurityManagement import newSecurityManager, setSecurityManager +from Products.ERP5Type import Permissions +from Products.CMFCore.utils import getToolByName, _setCacheHeaders, _ViewEmulator +from OFS.Image import File as OFSFile +from warnings import warn + + +# XXX: these duplicate ones in ERP5.Document +_MARKER = [] +EMBEDDED_FORMAT = '_embedded' +class ConversionError(Exception):pass +class DocumentProxyError(Exception):pass +class NotConvertedError(Exception):pass + +class BaseExtensibleTraversableMixIn(ExtensibleTraversableMixIn): + """ + This class provides a generic base mixin implementation of IExtensibleTraversable. + + Provides access to documents through their permanent URL. + This class shoulf be used as a base mixin class using which can be used create + "extensible" mixin classes. + """ + + def _getExtensibleContent(self, request, name): + """ + Legacy API + """ + warn("_getExtensibleContent() function is deprecated. Use getExtensibleContent() instead.", \ + DeprecationWarning, stacklevel=2) + return self.getExtensibleContent(request, name) + + # Declarative security + security = ClassSecurityInfo() + + def _forceIdentification(self, request): + # force identification (usable for extensible content) + cache = getReadOnlyTransactionCache(self) + if cache is not None: + key = ('__bobo_traverse__', self, 'user') + try: + user = cache[key] + except KeyError: + user = _MARKER + else: + user = _MARKER + old_user = getSecurityManager().getUser() + if user is _MARKER: + user = None # By default, do nothing + if old_user is None or old_user.getUserName() == 'Anonymous User': + user_folder = getattr(self.getPortalObject(), 'acl_users', None) + if user_folder is not None: + try: + if request.get('PUBLISHED', _MARKER) is _MARKER: + # request['PUBLISHED'] is required by validate + request['PUBLISHED'] = self + has_published = False + else: + has_published = True + try: + user = user_folder.validate(request) + except AttributeError: + # This kind of error happens with unrestrictedTraverse, + # because the request object is a fake, and it is just + # a dict object. + user = None + if not has_published: + try: + del request.other['PUBLISHED'] + except AttributeError: + # The same here as above. unrestrictedTraverse provides + # just a plain dict, so request.other does not exist. + del request['PUBLISHED'] + except: + LOG("ERP5 WARNING",0, + "Failed to retrieve user in __bobo_traverse__ of WebSection %s" % self.getPath(), + error=sys.exc_info()) + user = None + if user is not None and user.getUserName() == 'Anonymous User': + user = None # If the user which is connected is anonymous, + # do not try to change SecurityManager + if cache is not None: + cache[key] = user + + old_manager = None + if user is not None: + # We need to perform identification + old_manager = getSecurityManager() + newSecurityManager(get_request(), user) + + return old_manager, user + + security.declareProtected(Permissions.View, 'getDocumentValue') + def getDocumentValue(self, name=None, portal=None, **kw): + """ + Return the default document with the given + name. The name parameter may represent anything + such as a document reference, an identifier, + etc. + + If name is not provided, the method defaults + to returning the default document by calling + getDefaultDocumentValue. + + kw parameters can be useful to filter content + (ex. force a given validation state) + + This method must be implemented through a + portal type dependent script: + WebSection_getDocumentValue + """ + if name is None: + return self.getDefaultDocumentValue() + + method = self._getTypeBasedMethod('getDocumentValue', + fallback_script_id='WebSection_getDocumentValue') + + document = method(name, portal=portal, **kw) + if document is not None: + return document.__of__(self) + +class DocumentExtensibleTraversableMixIn(BaseExtensibleTraversableMixIn): + """ + This class provides a implementation of IExtensibleTraversable for Document classed based documents. + """ + + def getExtensibleContent(self, request, name): + old_manager, user = self._forceIdentification(request) + # Next get the document per name + portal = self.getPortalObject() + document = self.getDocumentValue(name=name, portal=portal) + # restore original security context if there's a logged in user + if user is not None: + setSecurityManager(old_manager) + if document is not None: + document = aq_base(document.asContext(id=name, # Hide some properties to permit locating the original + original_container=document.getParentValue(), + original_id=document.getId(), + editable_absolute_url=document.absolute_url())) + return document.__of__(self) + + # no document found for current user, still such document may exists + # in some cases user (like Anonymous) can not view document according to portal catalog + # but we may ask him to login if such a document exists + isAuthorizationForced = getattr(self, 'isAuthorizationForced', None) + if isAuthorizationForced is not None and isAuthorizationForced(): + if unrestricted_apply(self.getDocumentValue, (name, portal)) is not None: + # force user to login as specified in Web Section + raise Unauthorized + +class OOoDocumentExtensibleTraversableMixIn(BaseExtensibleTraversableMixIn): + """ + This class provides a implementation of IExtensibleTraversable for OOoDocument classed based documents. + """ + + def getExtensibleContent(self, request, name): + # Be sure that html conversion is done, + # as it is required to extract extensible content + old_manager, user = self._forceIdentification(request) + web_cache_kw = {'name': name, + 'format': EMBEDDED_FORMAT} + try: + self._convert(format='html') + _setCacheHeaders(_ViewEmulator().__of__(self), web_cache_kw) + mime, data = self.getConversion(format=EMBEDDED_FORMAT, file_name=name) + document = OFSFile(name, name, data, content_type=mime).__of__(self.aq_parent) + except (NotConvertedError, ConversionError, KeyError): + document = DocumentExtensibleTraversableMixIn._getExtensibleContent(self, request, name) + # restore original security context if there's a logged in user + if user is not None: + setSecurityManager(old_manager) + return document + \ No newline at end of file