diff --git a/product/ERP5Type/Core/CacheFactory.py b/product/ERP5Type/Core/CacheFactory.py new file mode 100644 index 0000000000000000000000000000000000000000..78f6d44e2ea0b760a8d32582fcddf14931150ce9 --- /dev/null +++ b/product/ERP5Type/Core/CacheFactory.py @@ -0,0 +1,88 @@ +############################################################################## +# +# Copyright (c) 2005 Nexedi SARL 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 AccessControl import ClassSecurityInfo +from Products.CMFCore import CMFCorePermissions +from Products.ERP5Type import Permissions +from Products.ERP5Type import PropertySheet +from Products.ERP5Type.PropertySheet.CacheFactory import CacheFactory +from Products.ERP5Type.XMLObject import XMLObject +from Products.ERP5Type.Cache import CachingMethod + +class CacheFactory(XMLObject): + """ + CacheFactory is a collection of cache plugins. CacheFactory is an object which lives in ZODB. + """ + + meta_type = 'ERP5 Cache Factory' + portal_type = 'Cache Factory' + isPortalContent = 1 + isRADContent = 1 + + allowed_types = ('ERP5 Ram Cache', + 'ERP5 Distributed Ram Cache', + 'ERP5 SQL Cache', + ) + + security = ClassSecurityInfo() + security.declareProtected(CMFCorePermissions.ManagePortal, + 'manage_editProperties', + 'manage_changeProperties', + 'manage_propertiesForm', + ) + + property_sheets = ( PropertySheet.Base + , PropertySheet.SimpleItem + , PropertySheet.Folder + , CacheFactory + ) + + + def getCachePluginList(self): + """ get ordered list of installed cache plugins in ZODB """ + cache_plugins = self.objectValues(self.allowed_types) + cache_plugins = map(None, cache_plugins) + cache_plugins.sort(lambda x,y: cmp(x.int_index, y.int_index)) + return cache_plugins + + security.declareProtected(Permissions.AccessContentsInformation, 'getRamCacheFactory') + def getRamCacheFactory(self): + """ Return RAM based cache factory """ + erp5_site_id = self.getPortalObject().getId() + return CachingMethod.factories[erp5_site_id][self.cache_scope] + + security.declareProtected(Permissions.AccessContentsInformation, 'getRamCacheFactoryPluginList') + def getRamCacheFactoryPluginList(self): + """ Return RAM based list of cache plugins for this factory """ + return self.getRamCacheFactory().getCachePluginList() + + def clearCache(self): + """ clear cache for this cache factory """ + for cp in self.getRamCacheFactory().getCachePluginList(): + cp.clearCache() diff --git a/product/ERP5Type/Core/DistributedRamCache.py b/product/ERP5Type/Core/DistributedRamCache.py new file mode 100644 index 0000000000000000000000000000000000000000..62be7d028d88ba5b32535fd1928253b8ef951053 --- /dev/null +++ b/product/ERP5Type/Core/DistributedRamCache.py @@ -0,0 +1,63 @@ +############################################################################## +# +# Copyright (c) 2005 Nexedi SARL 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 AccessControl import ClassSecurityInfo +from Products.CMFCore import CMFCorePermissions +from Products.ERP5Type.XMLObject import XMLObject +from Products.ERP5Type import PropertySheet +from Products.ERP5.PropertySheet.SortIndex import SortIndex +from Products.ERP5Type.PropertySheet.BaseCache import BaseCache +from Products.ERP5Type.PropertySheet.DistributedRamCache import DistributedRamCache + +class DistributedRamCache(XMLObject): + """ + DistributedRamCache is a Zope (persistent) representation of + the Distributed RAM Cache real cache plugin object. + """ + + meta_type='ERP5 Distributed Ram Cache' + portal_type='Distributed Ram Cache' + isPortalContent = 1 + isRADContent = 1 + + allowed_types = () + + security = ClassSecurityInfo() + security.declareProtected(CMFCorePermissions.ManagePortal, + 'manage_editProperties', + 'manage_changeProperties', + 'manage_propertiesForm', + ) + + property_sheets = ( PropertySheet.Base + , PropertySheet.SimpleItem + , PropertySheet.Folder + , BaseCache + , SortIndex + , DistributedRamCache + ) diff --git a/product/ERP5Type/Core/Folder.py b/product/ERP5Type/Core/Folder.py new file mode 100644 index 0000000000000000000000000000000000000000..c7d1cfd6522bb60aa4f5e87a2b897fb9f6c4b9cd --- /dev/null +++ b/product/ERP5Type/Core/Folder.py @@ -0,0 +1,875 @@ +############################################################################## +# +# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved. +# Jean-Paul Smets-Solanes <jp@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 AccessControl import ClassSecurityInfo +from Acquisition import aq_base, aq_self +from OFS.History import Historical +import ExtensionClass + +from Products.CMFCore.utils import _getAuthenticatedUser +from Products.CMFCore.CMFCatalogAware import CMFCatalogAware + +from Products.ERP5Type.Base import Base +from Products.ERP5Type.CopySupport import CopyContainer +from Products.ERP5Type import PropertySheet, Permissions +from Products.ERP5Type.XMLExportImport import Folder_asXML +from Products.ERP5Type.Cache import CachingMethod +from Products.ERP5Type.Utils import sortValueList + +try: + from Products.CMFCore.CMFBTreeFolder import CMFBTreeFolder +except ImportError: + from Products.BTreeFolder2.CMFBTreeFolder import CMFBTreeFolder +from AccessControl import getSecurityManager +from Products.ERP5Type import Permissions +from random import randint + +import os + +from zLOG import LOG, PROBLEM + +# Dummy Functions for update / upgrade +def dummyFilter(object,REQUEST=None): + return 1 + +def dummyTestAfter(object,REQUEST=None): + return [] + +class FolderMixIn(ExtensionClass.Base): + """A mixin class for folder operations, add content, delete content etc. + """ + + # Declarative security + security = ClassSecurityInfo() + security.declareObjectProtected(Permissions.AccessContentsInformation) + + security.declarePublic('newContent') + def newContent(self, id=None, portal_type=None, id_group=None, + default=None, method=None, immediate_reindex=0, + container=None, created_by_builder=0, activate_kw=None, + is_indexable=None, temp_object=0, **kw): + """Creates a new content. + This method is public, since TypeInformation.constructInstance will perform + the security check. + """ + if container is None: + container = self + if id is None: + new_id = str(container.generateNewId(id_group = id_group, + default=default, method=method)) + else: + new_id = str(id) + if portal_type is None: + # XXX This feature is very confusing + # And made the code more difficult to update + portal_type = container.allowedContentTypes()[0].id + + if temp_object: + from Products.ERP5Type import Document + # we get an object from factory only for first temp container object + # otherwise we get an id so we can use the classic way + if not hasattr(container, 'isTempObject') or \ + (hasattr(container, 'isTempObject') and not container.isTempObject()): + factory_name = 'newTemp%s' %(portal_type.replace(' ', '')) + m = getattr(Document, factory_name) + return m(container, new_id) + + self.portal_types.constructContent(type_name=portal_type, + container=container, + id=new_id, + created_by_builder=created_by_builder, + activate_kw=activate_kw, + is_indexable=is_indexable + ) # **kw) removed due to CMF bug + # TODO :the **kw makes it impossible to create content not based on + # ERP5TypeInformation, because factory method often do not support + # keywords arguments. + + new_instance = container[new_id] + if kw != {} : new_instance._edit(force_update=1, **kw) + if immediate_reindex: new_instance.immediateReindexObject() + return new_instance + + security.declareProtected( + Permissions.DeletePortalContent, 'deleteContent') + def deleteContent(self, id): + """ delete items in this folder. + `id` can be a list or a string. + """ + if isinstance(id, str): + self._delObject(id) + elif isinstance(id, list) or isinstance(id, tuple): + for my_id in id: + self._delObject(my_id) + else: + raise TypeError, 'deleteContent only accepts string or list, '\ + 'not %s' % type(id) + + def _generateRandomId(self): + """ + Generate a random Id. + 10000 factor makes the odd to generate an already existing Id of 1 out + of 10000, not depending on the number of objects present in this folder. + len(self)+1 to make sure generation works on an empty Folder. + """ + return '%X' % (randint(1, 10000 * (len(self) + 1)), ) + + def _generateNextId(self): + """ + Get the last generated Id, increment it until no object with generated + Id exist, then save the Id. + """ + try: + my_id = int(self.getLastId()) + except TypeError: + my_id = 1 + while self.hasContent(str(my_id)): + my_id = my_id + 1 + my_id = str(my_id) + self._setLastId(my_id) # Make sure no reindexing happens + return my_id + + # Automatic ID Generation method + security.declareProtected(Permissions.View, 'generateNewId') + def generateNewId(self,id_group=None,default=None,method=None): + """ + Generate a new Id which has not been taken yet in this folder. + Eventually increment the id number until an available id + can be found + + Permission is view because we may want to add content to a folder + without changing the folder content itself. + XXX + """ + my_id = None + if id_group is None: + id_group = self.getIdGroup() + if id_group in (None, 'None'): + id_generator = self.getIdGenerator() + if not isinstance(id_generator, str): + LOG('Folder.generateNewId', 0, '%s.id_generator is not a string. Falling back on default behaviour.' % (self.absolute_url(), )) + id_generator = '' + if id_generator != '': # Custom aq_dynamic function (like the one defined on WebSite objects) can find an object which has no name. So we must recognise the default value of id_generator and force safe fallback in this case. + idGenerator = getattr(self, id_generator, None) + if idGenerator is None: + idGenerator = self._generateNextId + else: + idGenerator = self._generateNextId + my_id = idGenerator() + while self.hasContent(my_id): + my_id = _generateNextId() + else: + my_id = str(self.portal_ids.generateNewId(id_group=id_group,default=default,method=method)) + return my_id + + security.declareProtected(Permissions.View, 'hasContent') + def hasContent(self,id): + return self.hasObject(id) + + # Get the content + security.declareProtected(Permissions.View, 'searchFolder') + def searchFolder(self, **kw): + """ + Search the content of a folder by calling + the portal_catalog. + """ + if not kw.has_key('parent_uid'): #WHY ???? + kw['parent_uid'] = self.getUid() + + # Make sure that if we use parent base category + # We do not have conflicting parent uid values + delete_parent_uid = 0 + if kw.has_key('selection_domain'): + if kw['selection_domain'].asDomainDict().has_key('parent'): + delete_parent_uid = 1 + if kw.has_key('selection_report'): + if kw['selection_report'].asDomainDict().has_key('parent'): + delete_parent_uid = 1 + if delete_parent_uid: + del kw['parent_uid'] + + kw2 = {} + # Remove useless matter before calling the + # catalog. In particular, consider empty + # strings as None values + for cname in kw.keys(): + if kw[cname] != '' and kw[cname] != None: + kw2[cname] = kw[cname] + # The method to call to search the folder + # content has to be called z_search_folder + method = self.portal_catalog.portal_catalog + return method(**kw2) + + security.declareProtected(Permissions.View, 'countFolder') + def countFolder(self, **kw): + """ + Search the content of a folder by calling + the portal_catalog. + """ + if not kw.has_key('parent_uid'): #WHY ???? + kw['parent_uid'] = self.getUid() + + # Make sure that if we use parent base category + # We do not have conflicting parent uid values + delete_parent_uid = 0 + if kw.has_key('selection_domain'): + if kw['selection_domain'].asDomainDict().has_key('parent'): + delete_parent_uid = 1 + if kw.has_key('selection_report'): + if kw['selection_report'].asDomainDict().has_key('parent'): + delete_parent_uid = 1 + if delete_parent_uid: + del kw['parent_uid'] + + kw2 = {} + # Remove useless matter before calling the + # catalog. In particular, consider empty + # strings as None values + for cname in kw.keys(): + if kw[cname] != '' and kw[cname]!=None: + kw2[cname] = kw[cname] + # The method to call to search the folder + # content has to be called z_search_folder + method = self.portal_catalog.countResults + return method(**kw2) + + # Count objects in the folder + security.declarePrivate('_count') + def _count(self, **kw): + """ + Returns the number of items in the folder. + """ + return self.countFolder(**kw)[0][0] + +class Folder( CopyContainer, CMFBTreeFolder, Base, FolderMixIn): + """ + A Folder is a subclass of Base but not of XMLObject. + Folders are not considered as documents and are therefore + not synchronisable. + + ERP5 folders are implemented as CMFBTreeFolder objects + and can store up to a million documents on a standard + computer. + ERP5 folders will eventually use in the near future the + AdaptableStorage implementation in order to reach performances + of 10 or 100 millions of documents in a single folder. + + ERP5 folders include an automatic id generation feature + which allows user not to define an id when they create + a new document in a folder. + + ERP5 folders use the ZSQLCatalog to search for objects + or display content. This requires a method called + *z_search_folder* to be put inside the ZSQLCatalog object + of the ERP5 portal. + + An ERP5 Binder document class will eventually be defined + in order to implement a binder of documents which can itself + be categorized. + """ + + meta_type = 'ERP5 Folder' + portal_type = 'Folder' + add_permission = Permissions.AddPortalContent + isPortalContent = 1 + isRADContent = 1 + + # Declarative security + security = ClassSecurityInfo() + security.declareObjectProtected(Permissions.AccessContentsInformation) + + manage_options = ( CMFBTreeFolder.manage_options + + Historical.manage_options + + CMFCatalogAware.manage_options + ) + # Declarative properties + property_sheets = ( PropertySheet.Base + , PropertySheet.XMLObject + , PropertySheet.SimpleItem + , PropertySheet.Folder + ) + + # Class inheritance fixes + security.declareProtected( Permissions.ModifyPortalContent, 'edit' ) + edit = Base.edit + security.declareProtected( Permissions.ModifyPortalContent, '_edit' ) + _edit = Base._edit + _setPropValue = Base._setPropValue + _propertyMap = Base._propertyMap # are there any others XXX ? + + # Override Zope default by folder id generation + def _get_id(self, id): + if self._getOb(id, None) is None : + return id + return self.generateNewId() + + #security.declareProtected( Permissions.DeletePortalContent, 'manage_delObjects' ) + #manage_delObjects = CopyContainer.manage_delObjects + + # Implementation +# security.declarePrivate('_setObject') +# def _setObject(self, id, object, roles=None, user=None, set_owner=1): +# """ +# This method is here in order to dynamically update old +# folders into the new BTree folder type. +# This method is destructive in the sens that objects +# of the old folder will be lost during the update +# """ +# # First make sur the folder has been initialized +# if not hasattr(self, '_tree'): +# CMFBTreeFolder.__init__(self, self.id) +# if not self._tree: +# CMFBTreeFolder.__init__(self, self.id) +# # Then insert the object +# CMFBTreeFolder._setObject(self, id, object, roles=roles, user=user, set_owner=set_owner) +# This method destroys the title when we create new object in empty folder + + security.declareProtected(Permissions.View, 'hasContent') + def hasContent(self,id): + return self.hasObject(id) + + security.declareProtected( Permissions.ModifyPortalContent, 'exportAll' ) + def exportAll(self,dir=None): + """ + Allows to export all object inside a particular folder, one by one + """ + folder_id = self.getId() + if dir != None: + for id in self.objectIds(): + f = os.path.join(dir, '%s___%s.zexp' % (folder_id,id)) + ob = self._getOb(id) + ob._p_jar.exportFile(ob._p_oid,f) + get_transaction().commit() + + security.declareProtected( Permissions.ModifyPortalContent, 'recursiveApply') + def recursiveApply(self, filter=dummyFilter, method=None, + test_after=dummyTestAfter, include=1, REQUEST=None, **kw): + """ + Apply a method to self and to all children + + filter -- only instances which return 1 when applied filter + are considered + + method -- the method to apply to acceptable instances + + test_after -- test to apply after calling method in order to search + for inconsistencies + + include -- if set to 1 (default), apply method to self + + + REQUEST -- the http REQUEST (if needed) + + **kw -- optional parameters passed to method + """ + update_list = [] + #LOG('Folder, recursiveApply ',0,"first one self.path: %s" % self.getPath()) + + # Only apply method to self if filter is to 1 and filter returns 1 + if include==1 and filter(object=self.getObject(),REQUEST=REQUEST): + method_message = method(object=self.getObject(),REQUEST=REQUEST, **kw) + if type(method_message) is type([]): + update_list += method_message + update_list += test_after(object=self.getObject(),REQUEST=REQUEST) + + for o in self.objectValues(): # contentValues sometimes fail in BTreeFolder + # Test on each sub object if method should be applied + if filter(object=o,REQUEST=REQUEST): + method_message = method(object=o,REQUEST=REQUEST, **kw) + if type(method_message) is type([]): + update_list += method_message + update_list += test_after(o,REQUEST=REQUEST) + # And commit subtransaction + #get_transaction().commit(1) + get_transaction().commit() # we may use commit(1) some day XXX + # Recursively call recursiveApply if o has a recursiveApply method (not acquired) + obase = aq_base(o) + if hasattr(obase, 'recursiveApply'): + #LOG('Found recursiveApply', 0, o.absolute_url()) + update_list += o.recursiveApply(filter=filter, \ + method=method, test_after=test_after,REQUEST=REQUEST,include=0,**kw) + + return update_list + + security.declareProtected( Permissions.ModifyPortalContent, 'updateAll' ) + def updateAll(self, filter=None, method=None, test_after=None, request=None, include=1,**kw): + """ + update all objects inside this particular folder wich + returns not None to the test. + + filter have to be a method with one parameter (the object) + wich returns None if we must not update the object + + test_after have to be a method with one parameter (the object) + wich returns a string + + method is the update method with also one parameter + + """ + update_list = [] + #LOG('Folder, updateAll ',0,"first one self.path: %s" % self.getPath()) + + if include==1 and filter(object=self.getObject(),request=request): + method_message = method(object=self.getObject(),request=request) + if type(method_message) is type([]): + update_list += method_message + update_list += test_after(object=self.getObject(),request=request) + + for o in self.objectValues(): + # Test if we must apply the upgrade + if filter(object=o,request=request): + method_message = method(object=o,request=request) + if type(method_message) is type([]): + update_list += method_message + update_list += test_after(object=o,request=request) + #for object in o.objectValues(): + #LOG('Folder, updateAll ',0,"object.id: %s" % object.id) + obase = aq_base(o) + get_transaction().commit() + if hasattr(obase, 'updateAll'): + update_list += o.updateAll(filter=filter, \ + method=method, test_after=test_after,request=request,include=0,**kw) + + return update_list + + security.declareProtected( Permissions.ModifyPortalContent, 'upgradeObjectClass' ) + def upgradeObjectClass(self, test_before=None, from_class=None,\ + to_class=None, test_after=None): + """ + upgrade the class of all objects inside this + particular folder + test have to be a method with one parameter + migrations is a dictionnary of class, { from_class : to_class } + """ + #LOG("upradeObjectClass: folder ",0,self.id) + test_list = [] + folder = self.getObject() + for o in self.listFolderContents(): + # Make sure this sub object is not the same as object + if o.getPhysicalPath() != self.getPhysicalPath(): + id = o.getId() + obase = aq_base(o) + # Check if the subobject have to also be upgraded + if hasattr(obase,'upgradeObjectClass'): + test_list += o.upgradeObjectClass(test_before=test_before, \ + from_class=from_class, to_class=to_class, + test_after=test_after) + # Test if we must apply the upgrade + if test_before(o) is not None: + LOG("upradeObjectClass: id ",0,id) + klass = obase.__class__ + LOG("upradeObjectClass: klass ",0,str(klass)) + LOG("upradeObjectClass: from_class ",0,str(from_class)) + if klass == from_class: + try: + newob = to_class(obase.id) + newob.id = obase.id # This line activates obase. + except AttributeError: + newob = to_class(id) + newob.id = id + keys = obase.__dict__.keys() + for k in keys: + if k not in ('id', 'meta_type', '__class__'): + setattr(newob,k,obase.__dict__[k]) + + self.manage_delObjects(id) + LOG("upradeObjectClass: ",0,"add new object: %s" % str(newob.id)) + get_transaction().commit() # XXX this commit should be after _setObject + LOG("upradeObjectClass: ",0,"newob.__class__: %s" % str(newob.__class__)) + self._setObject(id, newob) + object_to_test = self._getOb(id) + test_list += test_after(object_to_test) + + return test_list + + + # Catalog related + security.declarePublic( 'reindexObject' ) + def reindexObject(self, *args, **kw): + """ + Fixes the hierarchy structure (use of Base class) + XXXXXXXXXXXXXXXXXXXXXXXX + BUG here : when creating a new base category + """ + return Base.reindexObject(self, *args, **kw) + + security.declareProtected(Permissions.ModifyPortalContent, 'reindexObjectSecurity') + def reindexObjectSecurity(self): + """ + Reindex security-related indexes on the object + (and its descendants). + """ + # In ERP5, simply reindex all objects. + self.recursiveReindexObject() + + security.declarePublic( 'recursiveReindexObject' ) + def recursiveReindexObject(self, *args, **kw): + """ + Fixes the hierarchy structure (use of Base class) + XXXXXXXXXXXXXXXXXXXXXXXX + BUG here : when creating a new base category + """ + if self.isIndexable: + self.activate(group_method_id='portal_catalog/catalogObjectList', expand_method_id='getIndexableChildValueList', alternate_method_id='alternateReindexObject', **kw).recursiveImmediateReindexObject(*args, **kw) + + security.declareProtected( Permissions.AccessContentsInformation, 'getIndexableChildValueList' ) + def getIndexableChildValueList(self): + """ + Get indexable childen recursively. + """ + value_list = [] + if self.isIndexable: + value_list.append(self) + for c in self.objectValues(): + if hasattr(aq_base(c), 'getIndexableChildValueList'): + value_list.extend(c.getIndexableChildValueList()) + return value_list + + security.declarePublic( 'recursiveImmediateReindexObject' ) + def recursiveImmediateReindexObject(self, *args, **kw): + """ + Applies immediateReindexObject recursively + """ + # Reindex self + root_indexable = int(getattr(self.getPortalObject(),'isIndexable',1)) + if self.isIndexable and root_indexable: + self.flushActivity(invoke = 0, method_id='immediateReindexObject') # This might create a recursive lock + self.flushActivity(invoke = 0, method_id='recursiveImmediateReindexObject') # This might create a recursive lock + self.immediateReindexObject(*args, **kw) + # Reindex contents + #LOG('recursiveImmediateReindexObject', 0, 'self = %r, self.objectValues = %r' % (self, self.objectValues())) + for c in self.objectValues(): + if hasattr(aq_base(c), 'recursiveImmediateReindexObject'): + c.recursiveImmediateReindexObject(*args, **kw) + + security.declareProtected( Permissions.ModifyPortalContent, 'recursiveMoveObject' ) + def recursiveMoveObject(self): + """ + Called when the base of a hierarchy is renamed + """ + # Reindex self + if self.isIndexable: + self.moveObject() + # Reindex contents + for c in self.objectValues(): + if hasattr(aq_base(c), 'recursiveMoveObject'): + c.recursiveMoveObject() + + # Special Relation keyword : 'content' and 'container' + security.declareProtected( Permissions.AccessContentsInformation, '_getCategoryMembershipList' ) + def _getCategoryMembershipList(self, category, + spec=(), filter=None, portal_type=(), base=0 ): + if category == 'content': + content_list = self.searchFolder(portal_type=spec) + return map(lambda x: x.relative_url, content_list) + else: + return Base.getCategoryMembershipList(self, category, + spec=spec, filter=filter, portal_type=portal_type, base=base) + + # Alias - class inheritance resolution + security.declareProtected( Permissions.View, 'Title' ) + Title = Base.Title + + security.declareProtected(Permissions.AccessContentsInformation, + 'checkConsistency') + def checkConsistency(self, fixit=0): + """ + Check the consistency of this object, then + check recursively the consistency of every sub object. + """ + error_list = [] + # Fix BTree + if fixit: + btree_ok = self._cleanup() + if not btree_ok: + # We must commit if we want to keep on recursing + get_transaction().commit(1) + error_list += [(self.getRelativeUrl(), 'BTree Inconsistency', + 199, '(fixed)')] + # Call superclass + error_list += Base.checkConsistency(self, fixit=fixit) + # We must commit before listing folder contents + # in case we erased some data + if fixit: + get_transaction().commit(1) + # Then check the consistency on all sub objects + for obj in self.contentValues(): + if fixit: + extra_errors = obj.fixConsistency() + else: + extra_errors = obj.checkConsistency() + if len(extra_errors) > 0: + error_list += extra_errors + # We should also return an error if any + return error_list + + security.declareProtected( Permissions.AccessContentsInformation, 'asXML' ) + def asXML(self, ident=0): + """ + Generate an xml text corresponding to the content of this object + """ + return Folder_asXML(self,ident=ident) + + # Optimized Menu System + security.declarePublic('getVisibleAllowedContentTypeList') + def getVisibleAllowedContentTypeList(self): + """ + List portal_types' names wich can be added in this folder / object. + Cache results. + + This function is *much* similar to allowedContentTypes, except it does + not returns portal types but their ids and filter out those listed as + hidden content types. It allows to be much faster when only the type id + is needed. + """ + if not getSecurityManager().checkPermission( + Permissions.AddPortalContent, self): + return [] + + portal = self.getPortalObject() + + def _getVisibleAllowedContentTypeList(): + hidden_type_list = portal.portal_types.getTypeInfo(self).getHiddenContentTypeList() + return [type.id for type in CMFBTreeFolder.allowedContentTypes(self) if type.id not in hidden_type_list] + + user = str(_getAuthenticatedUser(self)) + portal_type = self.getPortalType() + portal_path = portal.getPhysicalPath() + + _getVisibleAllowedContentTypeList = CachingMethod(_getVisibleAllowedContentTypeList, + id=("_getAllowedContentTypeTitleList", + user, portal_path, portal_type), + cache_duration=None) + return _getVisibleAllowedContentTypeList() + + security.declarePublic('allowedContentTypes') + def allowedContentTypes( self ): + """ List portal_types which can be added in this folder / object. + Cache results. + Only paths are cached, because we must not cache objects. + This makes the result, even if based on cache, O(n) so it becomes quite + costly with many allowed content types. + Example: + on Person (12 allowed content types): 1000 calls take 3s. + on Person Module (1 allowed content type): 1000 calls take 0.3s. + """ + # if we don't have add portal content permission, return directly. + # this prevents returning cached allowed types when the user no longer have + # the permission to any content type. (security definitions in workflows + # usually remove some permission once an object is "Valid") + # This also prevents filling the cache with an empty list, when the user + # does not have the permission to add any content yet. + + # XXX this works just fine, unless some objects can be added with another + # permission that "Add portal content". For now, this is only the case for + # Role Definition objects, but this shows that generally speaking, this is + # not the right approach. + if not getSecurityManager().checkPermission( + Permissions.AddPortalContent, self): + return [] + + def _allowedContentTypes( portal_type=None, user=None, portal_path=None ): + # Sort the list for convenience -yo + # XXX This is not the best solution, because this does not take + # account i18n into consideration. + # XXX So sorting should be done in skins, after translation is performed. + def compareTypes(a, b): return cmp(a.title or a.id, b.title or b.id) + type_list = CMFBTreeFolder.allowedContentTypes(self) + type_list.sort(compareTypes) + return ['/'.join(x.getPhysicalPath()) for x in type_list] + + _allowedContentTypes = CachingMethod( _allowedContentTypes, + id = 'allowedContentTypes', + cache_duration = None) + user = str(_getAuthenticatedUser(self)) + portal_type = self.getPortalType() + portal = self.getPortalObject() + portal_path = portal.getPhysicalPath() + return [portal.restrictedTraverse(path) for path in + _allowedContentTypes( portal_type = portal_type, + user = user, + portal_path = portal_path )] + + # Multiple Inheritance Priority Resolution + _setProperty = Base._setProperty + setProperty = Base.setProperty + getProperty = Base.getProperty + hasProperty = Base.hasProperty + view = Base.view + + # Aliases + getObjectIds = CMFBTreeFolder.objectIds + + # Overloading + security.declareProtected( Permissions.AccessContentsInformation, 'getParentSqlExpression' ) + def getParentSqlExpression(self, table = 'catalog', strict_membership = 0): + """ + Builds an SQL expression to search children and subclidren + """ + if strict_membership: + return Base.getParentSqlExpression(self, table=table, strict_membership=strict_membership) + result = "%s.parent_uid = %s" % (table, self.getUid()) + for o in self.objectValues(): + if hasattr(aq_base(o), 'objectValues'): + # Do not consider non folder objects + result = "%s OR %s" % (result, o.getParentSqlExpression(table=table, strict_membership=strict_membership)) + return "( %s )" % result + + + def mergeContent(self,from_object=None,to_object=None, delete=1,**kw): + """ + This method will merge two objects. + + When we have to different objects wich represent the same content, we + may want to merge them. In this case, we want to be sure to report + + """ + if from_object is None or to_object is None: + return + + from_object_related_object_list = self.portal_categories.getRelatedValueList(from_object) + to_object_url = to_object.getRelativeUrl() + from_object_url = from_object.getRelativeUrl() + corrected_list = [] + for object in from_object_related_object_list: + #LOG('Folder.mergeContent, working on object:',0,object) + object_url = object.getRelativeUrl() + new_category_list = [] + found = 0 + for category in object.getCategoryList(): # so ('destination/person/1',...) + #LOG('Folder.mergeContent, working on category:',0,category) + linked_object_url = '/'.join(category.split('/')[1:]) + if linked_object_url == from_object_url: + base_category = category.split('/')[0] + found = 1 + new_category_list.append(base_category + '/' + to_object_url) + else: + new_category_list.append(category) + if found: + corrected_list.append(object) + object.setCategoryList(new_category_list) + object.immediateReindexObject() + if delete: + if len(from_object.portal_categories.getRelatedValueList(from_object))==0: + parent = from_object.getParentValue() + parent.manage_delObjects(from_object.getId()) + return corrected_list + + security.declareProtected( Permissions.AccessContentsInformation, + 'objectValues' ) + def objectValues(self, spec=None, meta_type=None, portal_type=None, + sort_on=None, sort_order=None, **kw): + """ + Returns a list containing object contained in this folder. + """ + if meta_type is not None: + spec = meta_type + # when an object inherits from Folder after it was instanciated, it lacks + # its BTreeFolder properties. + if getattr(self, '_tree', None) is None: + try: + self._initBTrees() + except AttributeError: + from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2Base + BTreeFolder2Base.__init__(self, self.getId()) + object_list = CMFBTreeFolder.objectValues(self, spec=spec) + if portal_type is not None: + if type(portal_type) == type(''): + portal_type = (portal_type,) + object_list = filter(lambda x: x.getPortalType() in portal_type, object_list) + object_list = sortValueList(object_list, sort_on, sort_order, **kw) + return object_list + + security.declareProtected( Permissions.AccessContentsInformation, + 'contentValues' ) + def contentValues(self, spec=None, meta_type=None, portal_type=None, + sort_on=None, sort_order=None, **kw): + """ + Returns a list containing object contained in this folder. + Filter objects with appropriate permissions (as in contentValues) + """ + if meta_type is not None: + spec = meta_type + if portal_type is not None: + kw['portal_type'] = portal_type + filter = kw.pop('filter', {}) or {} + kw.update(filter) + object_list = CMFBTreeFolder.contentValues(self, spec=spec, filter=kw) + object_list = sortValueList(object_list, sort_on, sort_order, **kw) + return object_list + + # Override security declaration of CMFCore/PortalFolder (used by CMFBTreeFolder) + security.declareProtected(Permissions.ModifyPortalContent,'setDescription') + security.declareProtected( Permissions.ModifyPortalContent, 'setTitle' ) + + security.declareProtected( Permissions.AccessContentsInformation, 'manage_copyObjects' ) # XXX Why this one doesn't work in CopySupport ? + security.declareProtected( Permissions.AddPortalContent, 'manage_pasteObjects' ) # XXX Why this one doesn't work in CopySupport ? + + # Template Management + security.declareProtected(Permissions.View, 'getDocumentTemplateList') + def getDocumentTemplateList(self) : + """ + Returns the list of allowed templates for this folder + by calling the preference tool + """ + return self.getPortalObject().portal_preferences.getDocumentTemplateList(self) + + security.declareProtected(Permissions.ModifyPortalContent,'makeTemplate') + def makeTemplate(self): + """ + Make document behave as a template. + A template is no longer indexable + + TODO: + - prevent from changing templates or invoking workflows + """ + Base.makeTemplate(self) + for o in self.objectValues(): + if hasattr(aq_base(o), 'makeTemplate'): o.makeTemplate() + + security.declareProtected(Permissions.ModifyPortalContent,'makeTemplateInstance') + def makeTemplateInstance(self): + """ + Make document behave as standard document (indexable) + """ + Base.makeTemplateInstance(self) + for o in self.objectValues(): + if hasattr(aq_base(o), 'makeTemplateInstance'): o.makeTemplateInstance() + + def _delObject(self, id, dp=1): + """ + _delObject is redefined here in order to make sure + we do not do silent except while we remove objects + from catalog + """ + object = self._getOb(id) + object.manage_beforeDelete(object, self) + self._delOb(id) + +# Overwrite Zope setTitle() +Folder.setTitle = Base.setTitle diff --git a/product/ERP5Type/Core/RamCache.py b/product/ERP5Type/Core/RamCache.py new file mode 100644 index 0000000000000000000000000000000000000000..44c9d0c478d62bddae82a2a020a559f04ae87163 --- /dev/null +++ b/product/ERP5Type/Core/RamCache.py @@ -0,0 +1,60 @@ +############################################################################## +# +# Copyright (c) 2005 Nexedi SARL 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 AccessControl import ClassSecurityInfo +from Products.CMFCore import CMFCorePermissions +from Products.ERP5Type.XMLObject import XMLObject +from Products.ERP5Type import PropertySheet +from Products.ERP5.PropertySheet.SortIndex import SortIndex +from Products.ERP5Type.PropertySheet.BaseCache import BaseCache + +class RamCache(XMLObject): + """ + RamCache is a Zope (persistent) representation of + the RAM based real cache plugin object. + """ + meta_type = 'ERP5 Ram Cache' + portal_type = 'Ram Cache' + isPortalContent = 1 + isRADContent = 1 + allowed_types = () + + security = ClassSecurityInfo() + security.declareProtected(CMFCorePermissions.ManagePortal, + 'manage_editProperties', + 'manage_changeProperties', + 'manage_propertiesForm', + ) + + property_sheets = ( PropertySheet.Base + , PropertySheet.SimpleItem + , PropertySheet.Folder + , SortIndex + , BaseCache + ) diff --git a/product/ERP5Type/Core/SQLCache.py b/product/ERP5Type/Core/SQLCache.py new file mode 100644 index 0000000000000000000000000000000000000000..8ac49beb10733e0b81c15ad93e6677e10a7e372e --- /dev/null +++ b/product/ERP5Type/Core/SQLCache.py @@ -0,0 +1,64 @@ +############################################################################## +# +# Copyright (c) 2005 Nexedi SARL 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 AccessControl import ClassSecurityInfo +from Products.CMFCore import CMFCorePermissions +from Products.ERP5Type.Base import Base +from Products.ERP5Type.XMLObject import XMLObject +from Products.ERP5Type import PropertySheet +from Products.ERP5.PropertySheet.SortIndex import SortIndex +from Products.ERP5Type.PropertySheet.BaseCache import BaseCache +from Products.ERP5Type.PropertySheet.SQLCache import SQLCache + +class SQLCache(XMLObject): + """ + SQLCache is a Zope (persistent) representation of + the RAM based real SQL cache plugin object. + """ + + meta_type = 'ERP5 SQL Cache' + portal_type = 'SQL Cache' + isPortalContent = 1 + isRADContent = 1 + + allowed_types = () + + security = ClassSecurityInfo() + security.declareProtected(CMFCorePermissions.ManagePortal, + 'manage_editProperties', + 'manage_changeProperties', + 'manage_propertiesForm', + ) + + property_sheets = ( PropertySheet.Base + , PropertySheet.SimpleItem + , PropertySheet.Folder + , BaseCache + , SortIndex + , SQLCache + ) diff --git a/product/ERP5Type/Core/__init__.py b/product/ERP5Type/Core/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391