############################################################################## # # Zope Public License (ZPL) Version 1.0 # ------------------------------------- # # Copyright (c) Digital Creations. All rights reserved. # # This license has been certified as Open Source(tm). # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # 1. Redistributions in source code must retain the above copyright # notice, this list of conditions, and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions, and the following disclaimer in # the documentation and/or other materials provided with the # distribution. # # 3. Digital Creations requests that attribution be given to Zope # in any manner possible. Zope includes a "Powered by Zope" # button that is installed by default. While it is not a license # violation to remove this button, it is requested that the # attribution remain. A significant investment has been put # into Zope, and this effort will continue if the Zope community # continues to grow. This is one way to assure that growth. # # 4. All advertising materials and documentation mentioning # features derived from or use of this software must display # the following acknowledgement: # # "This product includes software developed by Digital Creations # for use in the Z Object Publishing Environment # (http://www.zope.org/)." # # In the event that the product being advertised includes an # intact Zope distribution (with copyright and license included) # then this clause is waived. # # 5. Names associated with Zope or Digital Creations must not be used to # endorse or promote products derived from this software without # prior written permission from Digital Creations. # # 6. Modified redistributions of any form whatsoever must retain # the following acknowledgment: # # "This product includes software developed by Digital Creations # for use in the Z Object Publishing Environment # (http://www.zope.org/)." # # Intact (re-)distributions of any official Zope release do not # require an external acknowledgement. # # 7. Modifications are encouraged but must be packaged separately as # patches to official Zope releases. Distributions that do not # clearly separate the patches from the original work must be clearly # labeled as unofficial distributions. Modifications which do not # carry the name Zope may be packaged in any form, as long as they # conform to all of the clauses above. # # # Disclaimer # # THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY # EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT # OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # # # This software consists of contributions made by Digital Creations and # many individuals on behalf of Digital Creations. Specific # attributions are listed in the accompanying credits file. # ############################################################################## __doc__="""Object Manager $Id: ObjectManager.py,v 1.139 2001/07/05 13:19:02 andreas Exp $""" __version__='$Revision: 1.139 $'[11:-2] import App.Management, Acquisition, Globals, CopySupport, Products import os, App.FactoryDispatcher, re, Products from OFS.Traversable import Traversable from Globals import DTMLFile, Persistent from Globals import MessageDialog, default__class_init__ from webdav.NullResource import NullResource from webdav.Collection import Collection from Acquisition import aq_base from urllib import quote from cStringIO import StringIO import marshal import App.Common from AccessControl import getSecurityManager from zLOG import LOG, ERROR import sys,string,fnmatch,copy import XMLExportImport customImporters={ XMLExportImport.magic: XMLExportImport.importXML, } bad_id=re.compile(r'[^a-zA-Z0-9-_~,.$ ]').search #TS # Global constants: __replaceable__ flags: NOT_REPLACEABLE = 0 REPLACEABLE = 1 UNIQUE = 2 BadRequestException = 'Bad Request' def checkValidId(self, id, allow_dup=0): # If allow_dup is false, an error will be raised if an object # with the given id already exists. If allow_dup is true, # only check that the id string contains no illegal chars; # check_valid_id() will be called again later with allow_dup # set to false before the object is added. if not id or (type(id) != type('')): raise BadRequestException, 'Empty or invalid id specified.' if bad_id(id) is not None: raise BadRequestException, ( 'The id "%s" contains characters illegal in URLs.' % id) if id[0]=='_': raise BadRequestException, ( 'The id "%s" is invalid - it begins with an underscore.' % id) if id[:3]=='aq_': raise BadRequestException, ( 'The id "%s" is invalid - it begins with "aq_".' % id) if id[-2:]=='__': raise BadRequestException, ( 'The id "%s" is invalid - it ends with two underscores.' % id) if not allow_dup: obj = getattr(self, id, None) if obj is not None: # An object by the given id exists either in this # ObjectManager or in the acquisition path. flags = getattr(obj, '__replaceable__', NOT_REPLACEABLE) if hasattr(aq_base(self), id): # The object is located in this ObjectManager. if not flags & REPLACEABLE: raise BadRequestException, ('The id "%s" is invalid--' 'it is already in use.' % id) # else the object is replaceable even if the UNIQUE # flag is set. elif flags & UNIQUE: raise BadRequestException, ('The id "%s" is reserved.' % id) if id == 'REQUEST': raise BadRequestException, 'REQUEST is a reserved name.' if '/' in id: raise BadRequestException, ( 'The id "%s" contains characters illegal in URLs.' % id ) class BeforeDeleteException( Exception ): pass # raise to veto deletion class BreakoutException ( Exception ): pass # raised to break out of loops _marker=[] class ObjectManager( CopySupport.CopyContainer, App.Management.Navigation, App.Management.Tabs, Acquisition.Implicit, Persistent, Collection, Traversable, ): """Generic object manager This class provides core behavior for collections of heterogeneous objects. """ __ac_permissions__=( ('View management screens', ('manage_main','manage_menu')), ('Access contents information', ('objectIds', 'objectValues', 'objectItems',''), ('Anonymous', 'Manager'), ), ('Delete objects', ('manage_delObjects',)), ('FTP access', ('manage_FTPstat','manage_FTPlist')), ('Import/Export objects', ('manage_importObject','manage_importExportForm', 'manage_exportObject') ), ) meta_type ='Object Manager' meta_types=() # Sub-object types that are specific to this object _objects =() manage_main=DTMLFile('dtml/main', globals()) manage_index_main=DTMLFile('dtml/index_main', globals()) manage_options=( {'label':'Contents', 'action':'manage_main', 'help':('OFSP','ObjectManager_Contents.stx')}, # {'label':'Import/Export', 'action':'manage_importExportForm', # 'help':('OFSP','ObjectManager_Import-Export.stx')}, ) isAnObjectManager=1 isPrincipiaFolderish=1 def __class_init__(self): try: mt=list(self.meta_types) except: mt=[] for b in self.__bases__: try: for t in b.meta_types: if t not in mt: mt.append(t) except: pass mt.sort() self.meta_types=tuple(mt) default__class_init__(self) def all_meta_types(self, interfaces=None): pmt=() if hasattr(self, '_product_meta_types'): pmt=self._product_meta_types elif hasattr(self, 'aq_acquire'): try: pmt=self.aq_acquire('_product_meta_types') except: pass gmt = [] for entry in Products.meta_types: if interfaces is None: if entry.get("visibility", None) == "Global": gmt.append(entry) else: try: eil = entry.get("interfaces", None) if eil is not None: for ei in eil: for i in interfaces: if ei is i or ei.extends(i): gmt.append(entry) raise BreakoutException # only append 1ce except BreakoutException: pass return list(self.meta_types)+gmt+list(pmt) def _subobject_permissions(self): return (Products.__ac_permissions__+ self.aq_acquire('_getProductRegistryData')('ac_permissions') ) def filtered_meta_types(self, user=None): # Return a list of the types for which the user has # adequate permission to add that type of object. user=getSecurityManager().getUser() meta_types=[] if callable(self.all_meta_types): all=self.all_meta_types() else: all=self.all_meta_types for meta_type in all: if meta_type.has_key('permission'): if user.has_permission(meta_type['permission'],self): meta_types.append(meta_type) else: meta_types.append(meta_type) return meta_types _checkId = checkValidId def _setOb(self, id, object): setattr(self, id, object) def _delOb(self, id): delattr(self, id) def _getOb(self, id, default=_marker): # FIXME: what we really need to do here is ensure that only # sub-items are returned. That could have a measurable hit # on performance as things are currently implemented, so for # the moment we just make sure not to expose private attrs. if id[:1] != '_' and hasattr(aq_base(self), id): return getattr(self, id) if default is _marker: raise AttributeError, id return default def _setObject(self,id,object,roles=None,user=None, set_owner=1): v=self._checkId(id) if v is not None: id=v try: t=object.meta_type except: t=None # If an object by the given id already exists, remove it. for object_info in self._objects: if object_info['id'] == id: self._delObject(id) break self._objects=self._objects+({'id':id,'meta_type':t},) # Prepare the _p_jar attribute immediately. _getCopy() may need it. if hasattr(object, '_p_jar') and object._p_jar is None: object._p_jar = self._p_jar self._setOb(id,object) object=self._getOb(id) if set_owner: object.manage_fixupOwnershipAfterAdd() # Try to give user the local role "Owner", but only if # no local roles have been set on the object yet. if hasattr(object, '__ac_local_roles__'): if object.__ac_local_roles__ is None: user=getSecurityManager().getUser() name=user.getUserName() if name != 'Anonymous User': object.manage_setLocalRoles(name, ['Owner']) object.manage_afterAdd(object, self) return id def manage_afterAdd(self, item, container): for object in self.objectValues(): try: s=object._p_changed except: s=0 if hasattr(aq_base(object), 'manage_afterAdd'): object.manage_afterAdd(item, container) if s is None: object._p_deactivate() def manage_afterClone(self, item): for object in self.objectValues(): try: s=object._p_changed except: s=0 if hasattr(aq_base(object), 'manage_afterClone'): object.manage_afterClone(item) if s is None: object._p_deactivate() def manage_beforeDelete(self, item, container): for object in self.objectValues(): try: s=object._p_changed except: s=0 try: if hasattr(aq_base(object), 'manage_beforeDelete'): object.manage_beforeDelete(item, container) except BeforeDeleteException, ob: raise except: LOG('Zope',ERROR,'manage_beforeDelete() threw', error=sys.exc_info()) pass if s is None: object._p_deactivate() def _delObject(self, id, dp=1): object=self._getOb(id) try: object.manage_beforeDelete(object, self) except BeforeDeleteException, ob: raise except: LOG('Zope',ERROR,'manage_beforeDelete() threw', error=sys.exc_info()) pass self._objects=tuple(filter(lambda i,n=id: i['id']!=n, self._objects)) self._delOb(id) # Indicate to the object that it has been deleted. This is # necessary for object DB mount points. Note that we have to # tolerate failure here because the object being deleted could # be a Broken object, and it is not possible to set attributes # on Broken objects. try: object._v__object_deleted__ = 1 except: pass def objectIds(self, spec=None): # Returns a list of subobject ids of the current object. # If 'spec' is specified, returns objects whose meta_type # matches 'spec'. if spec is not None: if type(spec)==type('s'): spec=[spec] set=[] for ob in self._objects: if ob['meta_type'] in spec: set.append(ob['id']) return set return map(lambda i: i['id'], self._objects) def objectValues(self, spec=None): # Returns a list of actual subobjects of the current object. # If 'spec' is specified, returns only objects whose meta_type # match 'spec'. return map(self._getOb, self.objectIds(spec)) def objectItems(self, spec=None): # Returns a list of (id, subobject) tuples of the current object. # If 'spec' is specified, returns only objects whose meta_type match # 'spec' r=[] a=r.append g=self._getOb for id in self.objectIds(spec): a((id, g(id))) return r def objectMap(self): # Return a tuple of mappings containing subobject meta-data return tuple(map(lambda dict: dict.copy(), self._objects)) def objectIds_d(self,t=None): if hasattr(self, '_reserved_names'): n=self._reserved_names else: n=() if not n: return self.objectIds(t) r=[] a=r.append for id in self.objectIds(t): if id not in n: a(id) return r def objectValues_d(self,t=None): return map(self._getOb, self.objectIds_d(t)) def objectItems_d(self,t=None): r=[] a=r.append g=self._getOb for id in self.objectIds_d(t): a((id, g(id))) return r def objectMap_d(self,t=None): if hasattr(self, '_reserved_names'): n=self._reserved_names else: n=() if not n: return self._objects r=[] a=r.append for d in self._objects: if d['id'] not in n: a(d.copy()) return r def superValues(self,t): # Return all of the objects of a given type located in # this object and containing objects. if type(t)==type('s'): t=(t,) obj=self seen={} vals=[] have=seen.has_key x=0 while x < 100: if not hasattr(obj,'_getOb'): break get=obj._getOb if hasattr(obj,'_objects'): for i in obj._objects: try: id=i['id'] if (not have(id)) and (i['meta_type'] in t): vals.append(get(id)) seen[id]=1 except: pass if hasattr(obj,'aq_parent'): obj=obj.aq_parent else: return vals x=x+1 return vals manage_addProduct=App.FactoryDispatcher.ProductDispatcher() def manage_delObjects(self, ids=[], REQUEST=None): """Delete a subordinate object The objects specified in 'ids' get deleted. """ if type(ids) is type(''): ids=[ids] if not ids: return MessageDialog(title='No items specified', message='No items were specified!', action ='./manage_main',) try: p=self._reserved_names except: p=() for n in ids: if n in p: return MessageDialog(title='Not Deletable', message='<EM>%s</EM> cannot be deleted.' % n, action ='./manage_main',) while ids: id=ids[-1] v=self._getOb(id, self) if v is self: raise 'BadRequest', '%s does not exist' % ids[-1] self._delObject(id) del ids[-1] if REQUEST is not None: return self.manage_main(self, REQUEST, update_menu=1) def tpValues(self): # Return a list of subobjects, used by tree tag. r=[] if hasattr(aq_base(self), 'tree_ids'): tree_ids=self.tree_ids try: tree_ids=list(tree_ids) except TypeError: pass if hasattr(tree_ids, 'sort'): tree_ids.sort() for id in tree_ids: if hasattr(self, id): r.append(self._getOb(id)) else: obj_ids=self.objectIds() obj_ids.sort() for id in obj_ids: o=self._getOb(id) if hasattr(o, 'isPrincipiaFolderish') and \ o.isPrincipiaFolderish: r.append(o) return r def manage_exportObject(self, id='', download=None, toxml=None, RESPONSE=None): """Exports an object to a file and returns that file.""" if not id: # can't use getId() here (breaks on "old" exported objects) id=self.id if hasattr(id, 'im_func'): id=id() ob=self else: ob=self._getOb(id) suffix=toxml and 'xml' or 'zexp' if download: f=StringIO() if toxml: XMLExportImport.exportXML(ob._p_jar, ob._p_oid, f) else: ob._p_jar.exportFile(ob._p_oid, f) if RESPONSE is not None: RESPONSE.setHeader('Content-type','application/data') RESPONSE.setHeader('Content-Disposition', 'inline;filename=%s.%s' % (id, suffix)) return f.getvalue() f = os.path.join(CLIENT_HOME, '%s.%s' % (id, suffix)) if toxml: XMLExportImport.exportXML(ob._p_jar, ob._p_oid, f) else: ob._p_jar.exportFile(ob._p_oid, f) if RESPONSE is not None: return MessageDialog( title="Object exported", message="<EM>%s</EM> sucessfully\ exported to <pre>%s</pre>." % (id, f), action="manage_main") manage_importExportForm=DTMLFile('dtml/importExport',globals()) def manage_importObject(self, file, REQUEST=None, set_owner=1): """Import an object from a file""" dirname, file=os.path.split(file) if dirname: raise BadRequestException, 'Invalid file name %s' % file instance_home = INSTANCE_HOME software_home = os.path.join(SOFTWARE_HOME, '..%s..' % os.sep) software_home = os.path.normpath(software_home) for impath in (instance_home, software_home): filepath = os.path.join(impath, 'import', file) if os.path.exists(filepath): break else: raise BadRequestException, 'File does not exist: %s' % file # locate a valid connection connection=self._p_jar obj=self while connection is None: obj=obj.aq_parent connection=obj._p_jar ob=connection.importFile( filepath, customImporters=customImporters) if REQUEST: self._verifyObjectPaste(ob, validate_src=0) #id=ob.getId() #can't use above getId() call, it fails on 'old' exported objects id=ob.id if hasattr(id, 'im_func'): id=id() self._setObject(id, ob, set_owner=set_owner) # try to make ownership implicit if possible in the context # that the object was imported into. ob=self._getOb(id) ob.manage_changeOwnershipType(explicit=0) if REQUEST is not None: return self.manage_main(self, REQUEST, manage_tabs_message='<em>%s</em> sucessfully imported' % id, title = 'Object imported', update_menu=1) # FTP support methods def manage_FTPlist(self, REQUEST): "Directory listing for FTP" out=() # check to see if we are being acquiring or not ob=self while 1: if App.Common.is_acquired(ob): raise ValueError('FTP List not supported on acquired objects') if not hasattr(ob,'aq_parent'): break ob=ob.aq_parent files=self.objectItems() # recursive ride through all subfolders (ls -R) (ajung) if REQUEST.environ.get('FTP_RECURSIVE',0) == 1: all_files = copy.copy(files) for f in files: if f[1].meta_type == "Folder": all_files.extend(findChilds(f[1])) else: all_files.append(f) files = all_files try: files.sort() except AttributeError: files=list(files) files.sort() # Perform globbing on list of files (ajung) globbing = REQUEST.environ.get('GLOBBING','') if globbing : files = filter(lambda x,g=globbing: fnmatch.fnmatch(x[0],g) , files) try: files.sort() except AttributeError: files=list(files) files.sort() if not (hasattr(self,'isTopLevelPrincipiaApplicationObject') and self.isTopLevelPrincipiaApplicationObject): files.insert(0,('..',self.aq_parent)) for k,v in files: # Note that we have to tolerate failure here, because # Broken objects won't stat correctly. If an object fails # to be able to stat itself, we will ignore it. try: stat=marshal.loads(v.manage_FTPstat(REQUEST)) except: stat=None if stat is not None: out=out+((k,stat),) return marshal.dumps(out) def manage_FTPstat(self,REQUEST): "Psuedo stat used for FTP listings" mode=0040000 from AccessControl.User import nobody # check to see if we are acquiring our objectValues or not if not (len(REQUEST.PARENTS) > 1 and self.objectValues() == REQUEST.PARENTS[1].objectValues()): try: if getSecurityManager().validateValue(self.manage_FTPlist): mode=mode | 0770 except: pass if nobody.allowed( self.manage_FTPlist, self.manage_FTPlist.__roles__): mode=mode | 0007 mtime=self.bobobase_modification_time().timeTime() # get owner and group owner=group='Zope' for user, roles in self.get_local_roles(): if 'Owner' in roles: owner=user break return marshal.dumps((mode,0,0,1,owner,group,0,mtime,mtime,mtime)) def __getitem__(self, key): v=self._getOb(key, None) if v is not None: return v if hasattr(self, 'REQUEST'): request=self.REQUEST method=request.get('REQUEST_METHOD', 'GET') if request.maybe_webdav_client and not method in ('GET', 'POST'): return NullResource(self, key, request).__of__(self) raise KeyError, key def findChilds(obj,dirname=''): """ recursive walk through the object hierarchy to find all childs of an object (ajung) """ lst =[] for name,child in obj.objectItems(): if child.meta_type=="Folder": lst.extend(findChilds(child,dirname+ obj.id + '/')) else: lst.append( (dirname + obj.id + "/" + name,child) ) return lst class IFAwareObjectManager: def all_meta_types(self, interfaces=None): if interfaces is None: if hasattr(self, '_product_interfaces'): interfaces=self._product_interfaces elif hasattr(self, 'aq_acquire'): try: interfaces=self.aq_acquire('_product_interfaces') except: pass # Bleah generic pass is bad return ObjectManager.all_meta_types(self, interfaces) Globals.default__class_init__(ObjectManager)