Back to index

plone3  3.1.7
utils.py
Go to the documentation of this file.
00001 ##############################################################################
00002 #
00003 # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
00004 #
00005 # This software is subject to the provisions of the Zope Public License,
00006 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
00007 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
00008 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00009 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
00010 # FOR A PARTICULAR PURPOSE.
00011 #
00012 ##############################################################################
00013 """ Utility functions.
00014 
00015 $Id: utils.py 78657 2007-08-07 09:27:12Z wichert $
00016 """
00017 
00018 import re
00019 from copy import deepcopy
00020 from os import path as os_path
00021 from os.path import abspath
00022 from warnings import warn
00023 import sys
00024 
00025 from AccessControl import ClassSecurityInfo
00026 from AccessControl import getSecurityManager
00027 from AccessControl import ModuleSecurityInfo
00028 from AccessControl.Permission import Permission
00029 from AccessControl.PermissionRole import rolesForPermissionOn
00030 from AccessControl.Role import gather_permissions
00031 from Acquisition.interfaces import IAcquirer
00032 from Acquisition import aq_get
00033 from Acquisition import aq_inner
00034 from Acquisition import aq_parent
00035 from Acquisition import Implicit
00036 from DateTime import DateTime
00037 from ExtensionClass import Base
00038 from Globals import HTMLFile
00039 from Globals import ImageFile
00040 from Globals import InitializeClass
00041 from Globals import MessageDialog
00042 from Globals import package_home
00043 from Globals import UNIQUE
00044 from OFS.misc_ import misc_ as misc_images
00045 from OFS.misc_ import Misc_ as MiscImage
00046 from OFS.PropertyManager import PropertyManager
00047 from OFS.PropertySheets import PropertySheets
00048 from OFS.SimpleItem import SimpleItem
00049 from thread import allocate_lock
00050 from webdav.common import rfc1123_date
00051 from zope.component import getUtility
00052 from zope.component.interfaces import ComponentLookupError
00053 from zope.dottedname.resolve import resolve as resolve_dotted_name
00054 from zope.i18nmessageid import MessageFactory
00055 
00056 from exceptions import AccessControl_Unauthorized
00057 from exceptions import NotFound
00058 
00059 SUBTEMPLATE = '__SUBTEMPLATE__'
00060 
00061 security = ModuleSecurityInfo( 'Products.CMFCore.utils' )
00062 
00063 _globals = globals()
00064 _dtmldir = os_path.join( package_home( globals() ), 'dtml' )
00065 _wwwdir = os_path.join( package_home( globals() ), 'www' )
00066 
00067 #
00068 #   Simple utility functions, callable from restricted code.
00069 #
00070 _marker = []  # Create a new marker object.
00071 
00072 _tool_interface_registry = {}
00073 
00074 security.declarePrivate('registerToolInterface')
00075 def registerToolInterface(tool_id, tool_interface):
00076     """ Register a tool ID for an interface
00077     """
00078     global  _tool_interface_registry
00079     _tool_interface_registry[tool_id] = tool_interface
00080 
00081 security.declarePrivate('getToolInterface')
00082 def getToolInterface(tool_id):
00083     """ Get the interface registered for a tool ID
00084     """
00085     global  _tool_interface_registry
00086     return _tool_interface_registry.get(tool_id, None)
00087 
00088 security.declarePublic('getToolByName')
00089 def getToolByName(obj, name, default=_marker):
00090 
00091     """ Get the tool, 'toolname', by acquiring it.
00092 
00093     o Application code should use this method, rather than simply
00094       acquiring the tool by name, to ease forward migration (e.g.,
00095       to Zope3).
00096     """
00097     tool_interface = _tool_interface_registry.get(name)
00098 
00099     if tool_interface is not None:
00100         try:
00101             utility = getUtility(tool_interface)
00102             # Site managers, except for five.localsitemanager, return unwrapped
00103             # utilities. If the result is something which is acquisition-unaware
00104             # but unwrapped we wrap it on the context.
00105             if IAcquirer.providedBy(obj) and \
00106                     aq_parent(utility) is None and \
00107                     IAcquirer.providedBy(utility):
00108                 utilty = utility.__of__(obj)
00109             return utility
00110         except ComponentLookupError:
00111             # behave in backwards-compatible way
00112             # fall through to old implementation
00113             pass
00114     
00115     try:
00116         tool = aq_get(obj, name, default, 1)
00117     except AttributeError:
00118         if default is _marker:
00119             raise
00120         return default
00121     else:
00122         if tool is _marker:
00123             raise AttributeError, name
00124         return tool
00125 
00126 security.declarePublic('getToolByInterfaceName')
00127 def getToolByInterfaceName(dotted_name, default=_marker):
00128     warn('getToolByInterfaceName() is deprecated and will be removed in CMF '
00129          '2.3. Use getUtilityByInterfaceName instead.',
00130          DeprecationWarning, stacklevel=2)
00131 
00132     return getUtilityByInterfaceName(dotted_name, default)
00133 
00134 
00135 security.declarePublic('getUtilityByInterfaceName')
00136 def getUtilityByInterfaceName(dotted_name, default=_marker):
00137     """ Get a tool by its fully-qualified dotted interface path
00138 
00139     This method replaces getToolByName for use in untrusted code.
00140     Trusted code should use zope.component.getUtility instead.
00141     """
00142     try:
00143         iface = resolve_dotted_name(dotted_name)
00144     except ImportError:
00145         if default is _marker:
00146             raise ComponentLookupError, dotted_name
00147         return default
00148 
00149     try:
00150         return getUtility(iface)
00151     except ComponentLookupError:
00152         if default is _marker:
00153             raise
00154         return default
00155 
00156 security.declarePublic('cookString')
00157 def cookString(text):
00158 
00159     """ Make a Zope-friendly ID from 'text'.
00160 
00161     o Remove any spaces
00162 
00163     o Lowercase the ID.
00164     """
00165     rgx = re.compile(r'(^_|[^a-zA-Z0-9-_~\,\.])')
00166     cooked = re.sub(rgx, "",text).lower()
00167     return cooked
00168 
00169 security.declarePublic('tuplize')
00170 def tuplize( valueName, value ):
00171 
00172     """ Make a tuple from 'value'.
00173 
00174     o Use 'valueName' to generate appropriate error messages.
00175     """
00176     if isinstance(value, tuple):
00177         return value
00178     if isinstance(value, list):
00179         return tuple( value )
00180     if isinstance(value, basestring):
00181         return tuple( value.split() )
00182     raise ValueError, "%s of unsupported type" % valueName
00183 
00184 #
00185 #   Security utilities, callable only from unrestricted code.
00186 #
00187 security.declarePrivate('_getAuthenticatedUser')
00188 def _getAuthenticatedUser(self):
00189     return getSecurityManager().getUser()
00190 
00191 security.declarePrivate('_checkPermission')
00192 def _checkPermission(permission, obj):
00193     return getSecurityManager().checkPermission(permission, obj)
00194 
00195 # If Zope ever provides a call to getRolesInContext() through
00196 # the SecurityManager API, the method below needs to be updated.
00197 security.declarePrivate('_limitGrantedRoles')
00198 def _limitGrantedRoles(roles, context, special_roles=()):
00199     # Only allow a user to grant roles already possessed by that user,
00200     # with the exception that all special_roles can also be granted.
00201     user = _getAuthenticatedUser(context)
00202     if user is None:
00203         user_roles = ()
00204     else:
00205         user_roles = user.getRolesInContext(context)
00206     if 'Manager' in user_roles:
00207         # Assume all other roles are allowed.
00208         return
00209     for role in roles:
00210         if role not in special_roles and role not in user_roles:
00211             raise AccessControl_Unauthorized('Too many roles specified.')
00212 
00213 security.declarePrivate('_mergedLocalRoles')
00214 def _mergedLocalRoles(object):
00215     """Returns a merging of object and its ancestors'
00216     __ac_local_roles__."""
00217     # Modified from AccessControl.User.getRolesInContext().
00218     merged = {}
00219     object = getattr(object, 'aq_inner', object)
00220     while 1:
00221         if hasattr(object, '__ac_local_roles__'):
00222             dict = object.__ac_local_roles__ or {}
00223             if callable(dict): dict = dict()
00224             for k, v in dict.items():
00225                 if merged.has_key(k):
00226                     merged[k] = merged[k] + v
00227                 else:
00228                     merged[k] = v
00229         if hasattr(object, 'aq_parent'):
00230             object=object.aq_parent
00231             object=getattr(object, 'aq_inner', object)
00232             continue
00233         if hasattr(object, 'im_self'):
00234             object=object.im_self
00235             object=getattr(object, 'aq_inner', object)
00236             continue
00237         break
00238 
00239     return deepcopy(merged)
00240 
00241 security.declarePrivate('_ac_inherited_permissions')
00242 def _ac_inherited_permissions(ob, all=0):
00243     # Get all permissions not defined in ourself that are inherited
00244     # This will be a sequence of tuples with a name as the first item and
00245     # an empty tuple as the second.
00246     d = {}
00247     perms = getattr(ob, '__ac_permissions__', ())
00248     for p in perms: d[p[0]] = None
00249     r = gather_permissions(ob.__class__, [], d)
00250     if all:
00251        if hasattr(ob, '_subobject_permissions'):
00252            for p in ob._subobject_permissions():
00253                pname=p[0]
00254                if not d.has_key(pname):
00255                    d[pname]=1
00256                    r.append(p)
00257        r = list(perms) + r
00258     return r
00259 
00260 security.declarePrivate('_modifyPermissionMappings')
00261 def _modifyPermissionMappings(ob, map):
00262     """
00263     Modifies multiple role to permission mappings.
00264     """
00265     # This mimics what AccessControl/Role.py does.
00266     # Needless to say, it's crude. :-(
00267     something_changed = 0
00268     perm_info = _ac_inherited_permissions(ob, 1)
00269     for name, settings in map.items():
00270         cur_roles = rolesForPermissionOn(name, ob)
00271         if isinstance(cur_roles, basestring):
00272             cur_roles = [cur_roles]
00273         else:
00274             cur_roles = list(cur_roles)
00275         changed = 0
00276         for (role, allow) in settings.items():
00277             if not allow:
00278                 if role in cur_roles:
00279                     changed = 1
00280                     cur_roles.remove(role)
00281             else:
00282                 if role not in cur_roles:
00283                     changed = 1
00284                     cur_roles.append(role)
00285         if changed:
00286             data = ()  # The list of methods using this permission.
00287             for perm in perm_info:
00288                 n, d = perm[:2]
00289                 if n == name:
00290                     data = d
00291                     break
00292             p = Permission(name, data, ob)
00293             p.setRoles(tuple(cur_roles))
00294             something_changed = 1
00295     return something_changed
00296 
00297 
00298 # Parse a string of etags from an If-None-Match header
00299 # Code follows ZPublisher.HTTPRequest.parse_cookie
00300 parse_etags_lock=allocate_lock()
00301 def parse_etags( text
00302                , result=None
00303                 # quoted etags (assumed separated by whitespace + a comma)
00304                , etagre_quote = re.compile('(\s*\"([^\"]*)\"\s*,{0,1})')
00305                 # non-quoted etags (assumed separated by whitespace + a comma)
00306                , etagre_noquote = re.compile('(\s*([^,]*)\s*,{0,1})')
00307                , acquire=parse_etags_lock.acquire
00308                , release=parse_etags_lock.release
00309                ):
00310 
00311     if result is None: result=[]
00312     if not len(text):
00313         return result
00314 
00315     acquire()
00316     try:
00317         m = etagre_quote.match(text)
00318         if m:
00319             # Match quoted etag (spec-observing client)
00320             l     = len(m.group(1))
00321             value = m.group(2)
00322         else:
00323             # Match non-quoted etag (lazy client)
00324             m = etagre_noquote.match(text)
00325             if m:
00326                 l     = len(m.group(1))
00327                 value = m.group(2)
00328             else:
00329                 return result
00330     finally: release()
00331 
00332     if value:
00333         result.append(value)
00334     return apply(parse_etags,(text[l:],result))
00335 
00336 def _checkConditionalGET(obj, extra_context):
00337     """A conditional GET is done using one or both of the request
00338        headers:
00339 
00340        If-Modified-Since: Date
00341        If-None-Match: list ETags (comma delimited, sometimes quoted)
00342 
00343        If both conditions are present, both must be satisfied.
00344 
00345        This method checks the caching policy manager to see if
00346        a content object's Last-modified date and ETag satisfy
00347        the conditional GET headers.
00348 
00349        Returns the tuple (last_modified, etag) if the conditional
00350        GET requirements are met and None if not.
00351 
00352        It is possible for one of the tuple elements to be None.
00353        For example, if there is no If-None-Match header and
00354        the caching policy does not specify an ETag, we will
00355        just return (last_modified, None).
00356        """
00357 
00358     REQUEST = getattr(obj, 'REQUEST', None)
00359     if REQUEST is None:
00360         return False
00361 
00362     # check whether we need to suppress subtemplates
00363     call_count = getattr(REQUEST, SUBTEMPLATE, 0)
00364     setattr(REQUEST, SUBTEMPLATE, call_count+1)
00365     if call_count != 0:
00366         return False
00367 
00368     if_modified_since = REQUEST.get_header('If-Modified-Since', None)
00369     if_none_match = REQUEST.get_header('If-None-Match', None)
00370 
00371     if if_modified_since is None and if_none_match is None:
00372         # not a conditional GET
00373         return False
00374 
00375     manager = getToolByName(obj, 'caching_policy_manager', None)
00376     if manager is None:
00377         return False
00378 
00379     ret = manager.getModTimeAndETag(aq_parent(obj), obj.getId(), extra_context)
00380     if ret is None:
00381         # no appropriate policy or 304s not enabled
00382         return False
00383 
00384     (content_mod_time, content_etag, set_last_modified_header) = ret
00385     if content_mod_time:
00386         mod_time_secs = long(content_mod_time.timeTime())
00387     else:
00388         mod_time_secs = None
00389 
00390     if if_modified_since:
00391         # from CMFCore/FSFile.py:
00392         if_modified_since = if_modified_since.split(';')[0]
00393         # Some proxies seem to send invalid date strings for this
00394         # header. If the date string is not valid, we ignore it
00395         # rather than raise an error to be generally consistent
00396         # with common servers such as Apache (which can usually
00397         # understand the screwy date string as a lucky side effect
00398         # of the way they parse it).
00399         try:
00400             if_modified_since=long(DateTime(if_modified_since).timeTime())
00401         except:
00402             if_mod_since=None
00403 
00404     client_etags = None
00405     if if_none_match:
00406         client_etags = parse_etags(if_none_match)
00407 
00408     if not if_modified_since and not client_etags:
00409         # not a conditional GET, or headers are messed up
00410         return False
00411 
00412     if if_modified_since:
00413         if ( not content_mod_time or 
00414              mod_time_secs < 0 or 
00415              mod_time_secs > if_modified_since ):
00416             return False
00417 
00418     if client_etags:
00419         if ( not content_etag or 
00420              (content_etag not in client_etags and '*' not in client_etags) ):
00421             return False
00422     else:
00423         # If we generate an ETag, don't validate the conditional GET unless 
00424         # the client supplies an ETag
00425         # This may be more conservative than the spec requires, but we are 
00426         # already _way_ more conservative.
00427         if content_etag:
00428             return False
00429 
00430     response = REQUEST.RESPONSE
00431     if content_mod_time and set_last_modified_header:
00432         response.setHeader('Last-modified', str(content_mod_time))
00433     if content_etag:
00434         response.setHeader('ETag', content_etag, literal=1)
00435     response.setStatus(304)
00436     delattr(REQUEST, SUBTEMPLATE)
00437 
00438     return True
00439 
00440 security.declarePrivate('_setCacheHeaders')
00441 def _setCacheHeaders(obj, extra_context):
00442     """Set cache headers according to cache policy manager for the obj."""
00443     REQUEST = getattr(obj, 'REQUEST', None)
00444 
00445     if REQUEST is not None:
00446         call_count = getattr(REQUEST, SUBTEMPLATE, 1) - 1
00447         setattr(REQUEST, SUBTEMPLATE, call_count)
00448         if call_count != 0:
00449            return
00450 
00451         # cleanup
00452         delattr(REQUEST, SUBTEMPLATE)
00453 
00454         content = aq_parent(obj)
00455         manager = getToolByName(obj, 'caching_policy_manager', None)
00456         if manager is None:
00457             return
00458 
00459         view_name = obj.getId()
00460         headers = manager.getHTTPCachingHeaders(
00461                           content, view_name, extra_context
00462                           )
00463         RESPONSE = REQUEST['RESPONSE']
00464         for key, value in headers:
00465             if key == 'ETag':
00466                 RESPONSE.setHeader(key, value, literal=1)
00467             else:
00468                 RESPONSE.setHeader(key, value)
00469         if headers:
00470             RESPONSE.setHeader('X-Cache-Headers-Set-By',
00471                                'CachingPolicyManager: %s' %
00472                                '/'.join(manager.getPhysicalPath()))
00473 
00474 class _ViewEmulator(Implicit):
00475     """Auxiliary class used to adapt FSFile and FSImage
00476     for caching_policy_manager
00477     """
00478     def __init__(self, view_name=''):
00479         self._view_name = view_name
00480 
00481     def getId(self):
00482         return self._view_name
00483 
00484 
00485 #
00486 #   Base classes for tools
00487 #
00488 class ImmutableId(Base):
00489 
00490     """ Base class for objects which cannot be renamed.
00491     """
00492     def _setId(self, id):
00493 
00494         """ Never allow renaming!
00495         """
00496         if id != self.getId():
00497             raise MessageDialog(
00498                 title='Invalid Id',
00499                 message='Cannot change the id of this object',
00500                 action ='./manage_main',)
00501 
00502 class UniqueObject (ImmutableId):
00503 
00504     """ Base class for objects which cannot be "overridden" / shadowed.
00505     """
00506     __replaceable__ = UNIQUE
00507 
00508 
00509 class SimpleItemWithProperties (PropertyManager, SimpleItem):
00510     """
00511     A common base class for objects with configurable
00512     properties in a fixed schema.
00513     """
00514     manage_options = (
00515         PropertyManager.manage_options
00516         + SimpleItem.manage_options)
00517 
00518 
00519     security = ClassSecurityInfo()
00520     security.declarePrivate('manage_addProperty')
00521     security.declarePrivate('manage_delProperties')
00522     security.declarePrivate('manage_changePropertyTypes')
00523 
00524     def manage_propertiesForm(self, REQUEST, *args, **kw):
00525         """ An override that makes the schema fixed.
00526         """
00527         my_kw = kw.copy()
00528         my_kw['property_extensible_schema__'] = 0
00529         form = PropertyManager.manage_propertiesForm.__of__(self)
00530         return form(self, REQUEST, *args, **my_kw)
00531 
00532 InitializeClass( SimpleItemWithProperties )
00533 
00534 
00535 #
00536 #   "Omnibus" factory framework for tools.
00537 #
00538 class ToolInit:
00539 
00540     """ Utility class for generating the factories for several tools.
00541     """
00542     __name__ = 'toolinit'
00543 
00544     security = ClassSecurityInfo()
00545     security.declareObjectPrivate()     # equivalent of __roles__ = ()
00546 
00547     def __init__(self, meta_type, tools, product_name=None, icon=None):
00548         self.meta_type = meta_type
00549         self.tools = tools
00550         if product_name is not None:
00551             warn("The product_name parameter of ToolInit is now ignored",
00552                  DeprecationWarning, stacklevel=2)
00553         self.icon = icon
00554 
00555     def initialize(self, context):
00556         # Add only one meta type to the folder add list.
00557         productObject = context._ProductContext__prod
00558         self.product_name = productObject.id
00559         context.registerClass(
00560             meta_type = self.meta_type,
00561             # This is a little sneaky: we add self to the
00562             # FactoryDispatcher under the name "toolinit".
00563             # manage_addTool() can then grab it.
00564             constructors = (manage_addToolForm,
00565                             manage_addTool,
00566                             self,),
00567             icon = self.icon
00568             )
00569 
00570         if self.icon:
00571             icon = os_path.split(self.icon)[1]
00572         else:
00573             icon = None
00574         for tool in self.tools:
00575             tool.__factory_meta_type__ = self.meta_type
00576             tool.icon = 'misc_/%s/%s' % (self.product_name, icon)
00577 
00578 InitializeClass( ToolInit )
00579 
00580 addInstanceForm = HTMLFile('dtml/addInstance', globals())
00581 
00582 def manage_addToolForm(self, REQUEST):
00583 
00584     """ Show the add tool form.
00585     """
00586     # self is a FactoryDispatcher.
00587     toolinit = self.toolinit
00588     tl = []
00589     for tool in toolinit.tools:
00590         tl.append(tool.meta_type)
00591     return addInstanceForm(addInstanceForm, self, REQUEST,
00592                            factory_action='manage_addTool',
00593                            factory_meta_type=toolinit.meta_type,
00594                            factory_product_name=toolinit.product_name,
00595                            factory_icon=toolinit.icon,
00596                            factory_types_list=tl,
00597                            factory_need_id=0)
00598 
00599 def manage_addTool(self, type, REQUEST=None):
00600 
00601     """ Add the tool specified by name.
00602     """
00603     # self is a FactoryDispatcher.
00604     toolinit = self.toolinit
00605     obj = None
00606     for tool in toolinit.tools:
00607         if tool.meta_type == type:
00608             obj = tool()
00609             break
00610     if obj is None:
00611         raise NotFound(type)
00612     self._setObject(obj.getId(), obj)
00613     if REQUEST is not None:
00614         return self.manage_main(self, REQUEST)
00615 
00616 
00617 #
00618 #   Now, do the same for creating content factories.
00619 #
00620 class ContentInit:
00621 
00622     """ Utility class for generating factories for several content types.
00623     """
00624     __name__ = 'contentinit'
00625 
00626     security = ClassSecurityInfo()
00627     security.declareObjectPrivate()
00628 
00629     def __init__( self
00630                 , meta_type
00631                 , content_types
00632                 , permission=None
00633                 , extra_constructors=()
00634                 , fti=()
00635                 ):
00636         # BBB: fti argument is ignored
00637         self.meta_type = meta_type
00638         self.content_types = content_types
00639         self.permission = permission
00640         self.extra_constructors = extra_constructors
00641 
00642     def initialize(self, context):
00643         # Add only one meta type to the folder add list.
00644         context.registerClass(
00645             meta_type = self.meta_type
00646             # This is a little sneaky: we add self to the
00647             # FactoryDispatcher under the name "contentinit".
00648             # manage_addContentType() can then grab it.
00649             , constructors = ( manage_addContentForm
00650                                , manage_addContent
00651                                , self ) + self.extra_constructors
00652             , permission = self.permission
00653             )
00654 
00655         for ct in self.content_types:
00656             ct.__factory_meta_type__ = self.meta_type
00657 
00658 InitializeClass( ContentInit )
00659 
00660 def manage_addContentForm(self, REQUEST):
00661 
00662     """ Show the add content type form.
00663     """
00664     # self is a FactoryDispatcher.
00665     ci = self.contentinit
00666     tl = []
00667     for t in ci.content_types:
00668         tl.append(t.meta_type)
00669     return addInstanceForm(addInstanceForm, self, REQUEST,
00670                            factory_action='manage_addContent',
00671                            factory_meta_type=ci.meta_type,
00672                            factory_icon=None,
00673                            factory_types_list=tl,
00674                            factory_need_id=1)
00675 
00676 def manage_addContent( self, id, type, REQUEST=None ):
00677 
00678     """ Add the content type specified by name.
00679     """
00680     # self is a FactoryDispatcher.
00681     contentinit = self.contentinit
00682     obj = None
00683     for content_type in contentinit.content_types:
00684         if content_type.meta_type == type:
00685             obj = content_type( id )
00686             break
00687     if obj is None:
00688         raise NotFound(type)
00689     self._setObject( id, obj )
00690     if REQUEST is not None:
00691         return self.manage_main(self, REQUEST)
00692 
00693 
00694 def initializeBasesPhase1(base_classes, module):
00695     """ Execute the first part of initialization of ZClass base classes.
00696 
00697     Stuffs a _ZClass_for_x class in the module for each base.
00698     """
00699     warn('initializeBasesPhase1() is deprecated and will be removed in CMF '
00700          '2.3. There is no replacement because ZClasses are also deprecated.',
00701          DeprecationWarning, stacklevel=2)
00702 
00703     rval = []
00704     for base_class in base_classes:
00705         d={}
00706         zclass_name = '_ZClass_for_%s' % base_class.__name__
00707         exec 'class %s: pass' % zclass_name in d
00708         Z = d[ zclass_name ]
00709         Z.propertysheets = PropertySheets()
00710         Z._zclass_ = base_class
00711         Z.manage_options = ()
00712         Z.__module__ = module.__name__
00713         setattr( module, zclass_name, Z )
00714         rval.append(Z)
00715     return rval
00716 
00717 def initializeBasesPhase2(zclasses, context):
00718     """ Finishes ZClass base initialization.
00719 
00720     o 'zclasses' is the list returned by initializeBasesPhase1().
00721 
00722     o 'context' is a ProductContext object.
00723     """
00724     warn('initializeBasesPhase2() is deprecated and will be removed in CMF '
00725          '2.3. There is no replacement because ZClasses are also deprecated.',
00726          DeprecationWarning, stacklevel=2)
00727 
00728     for zclass in zclasses:
00729         context.registerZClass(zclass)
00730 
00731 def registerIcon(klass, iconspec, _prefix=None):
00732 
00733     """ Make an icon available for a given class.
00734 
00735     o 'klass' is the class being decorated.
00736 
00737     o 'iconspec' is the path within the product where the icon lives.
00738     """
00739     modname = klass.__module__
00740     pid = modname.split('.')[1]
00741     name = os_path.split(iconspec)[1]
00742     klass.icon = 'misc_/%s/%s' % (pid, name)
00743     icon = ImageFile(iconspec, _prefix)
00744     icon.__roles__=None
00745     if not hasattr(misc_images, pid):
00746         setattr(misc_images, pid, MiscImage(pid, {}))
00747     getattr(misc_images, pid)[name]=icon
00748 
00749 #
00750 #   Metadata Keyword splitter utilities
00751 #
00752 KEYSPLITRE = re.compile(r'[,;]')
00753 
00754 security.declarePublic('keywordsplitter')
00755 def keywordsplitter( headers
00756                    , names=('Subject', 'Keywords',)
00757                    , splitter=KEYSPLITRE.split
00758                    ):
00759     """ Split keywords out of headers, keyed on names.  Returns list.
00760     """
00761     out = []
00762     for head in names:
00763         keylist = splitter(headers.get(head, ''))
00764         keylist = map(lambda x: x.strip(), keylist)
00765         out.extend( [key for key in keylist if key] )
00766     return out
00767 
00768 #
00769 #   Metadata Contributors splitter utilities
00770 #
00771 CONTRIBSPLITRE = re.compile(r';')
00772 
00773 security.declarePublic('contributorsplitter')
00774 def contributorsplitter( headers
00775                        , names=('Contributors',)
00776                        , splitter=CONTRIBSPLITRE.split
00777                        ):
00778     """ Split contributors out of headers, keyed on names.  Returns list.
00779     """
00780     return keywordsplitter( headers, names, splitter )
00781 
00782 #
00783 #   Directory-handling utilities
00784 #
00785 security.declarePublic('normalize')
00786 def normalize(p):
00787     # the first .replace is needed to help normpath when dealing with Windows
00788     # paths under *nix, the second to normalize to '/'
00789     return os_path.normpath(p.replace('\\','/')).replace('\\','/')
00790 
00791 import Products
00792 ProductsPath = [ abspath(ppath) for ppath in Products.__path__ ]
00793 
00794 security.declarePublic('expandpath')
00795 def expandpath(p):
00796     """ Convert minimal filepath to (expanded) filepath.
00797 
00798     The (expanded) filepath is the valid absolute path on the current platform
00799     and setup.
00800     """
00801     warn('expandpath() is deprecated and will be removed in CMF 2.3.',
00802          DeprecationWarning, stacklevel=2)
00803 
00804     p = os_path.normpath(p)
00805     if os_path.isabs(p):
00806         return p
00807 
00808     for ppath in ProductsPath:
00809         abs = os_path.join(ppath, p)
00810         if os_path.exists(abs):
00811             return abs
00812 
00813     # return the last one, errors will happen else where as as result
00814     # and be caught
00815     return abs
00816 
00817 security.declarePublic('minimalpath')
00818 def minimalpath(p):
00819     """ Convert (expanded) filepath to minimal filepath.
00820 
00821     The minimal filepath is the cross-platform / cross-setup path stored in
00822     persistent objects and used as key in the directory registry.
00823 
00824     Returns a slash-separated path relative to the Products path. If it can't
00825     be found, a normalized path is returned.
00826     """
00827     warn('minimalpath() is deprecated and will be removed in CMF 2.3.',
00828          DeprecationWarning, stacklevel=2)
00829 
00830     p = abspath(p)
00831     for ppath in ProductsPath:
00832         if p.startswith(ppath):
00833             p = p[len(ppath)+1:]
00834             break
00835     return p.replace('\\','/')
00836 
00837 security.declarePrivate('getContainingPackage')
00838 def getContainingPackage(module):
00839     parts = module.split('.')
00840     while parts:
00841         name = '.'.join(parts)
00842         mod = sys.modules[name]
00843         if '__init__' in mod.__file__:
00844             return name
00845         parts = parts[:-1]
00846 
00847     raise ValueError('Unable to find package for module %s' % module)
00848 
00849 security.declarePrivate('getPackageLocation')
00850 def getPackageLocation(module):
00851     """ Return the filesystem location of a module.
00852 
00853     This is a simple wrapper around the global package_home method which
00854     tricks it into working with just a module name.
00855     """
00856     package = getContainingPackage(module)
00857     return package_home({'__name__' : package})
00858 
00859 security.declarePrivate('getPackageName')
00860 def getPackageName(globals_):
00861     module = globals_['__name__']
00862     return getContainingPackage(module)
00863 
00864 def _OldCacheHeaders(obj):
00865     # Old-style checking of modified headers
00866 
00867     REQUEST = getattr(obj, 'REQUEST', None)
00868     if REQUEST is None:
00869         return False
00870 
00871     RESPONSE = REQUEST.RESPONSE
00872     header = REQUEST.get_header('If-Modified-Since', None)
00873     last_mod = long(obj.modified().timeTime())
00874 
00875     if header is not None:
00876         header = header.split(';')[0]
00877         # Some proxies seem to send invalid date strings for this
00878         # header. If the date string is not valid, we ignore it
00879         # rather than raise an error to be generally consistent
00880         # with common servers such as Apache (which can usually
00881         # understand the screwy date string as a lucky side effect
00882         # of the way they parse it).
00883         try:
00884             mod_since=DateTime(header)
00885             mod_since=long(mod_since.timeTime())
00886         except TypeError:
00887             mod_since=None
00888 
00889         if mod_since is not None:
00890             if last_mod > 0 and last_mod <= mod_since:
00891                 RESPONSE.setStatus(304)
00892                 return True
00893 
00894     #Last-Modified will get stomped on by a cache policy if there is
00895     #one set....
00896     RESPONSE.setHeader('Last-Modified', rfc1123_date(last_mod))
00897 
00898 def _FSCacheHeaders(obj):
00899     # Old-style setting of modified headers for FS-based objects
00900 
00901     REQUEST = getattr(obj, 'REQUEST', None)
00902     if REQUEST is None:
00903         return False
00904 
00905     RESPONSE = REQUEST.RESPONSE
00906     header = REQUEST.get_header('If-Modified-Since', None)
00907     last_mod = obj._file_mod_time
00908 
00909     if header is not None:
00910         header = header.split(';')[0]
00911         # Some proxies seem to send invalid date strings for this
00912         # header. If the date string is not valid, we ignore it
00913         # rather than raise an error to be generally consistent
00914         # with common servers such as Apache (which can usually
00915         # understand the screwy date string as a lucky side effect
00916         # of the way they parse it).
00917         try:
00918             mod_since=DateTime(header)
00919             mod_since=long(mod_since.timeTime())
00920         except TypeError:
00921             mod_since=None
00922 
00923         if mod_since is not None:
00924             if last_mod > 0 and last_mod <= mod_since:
00925                 RESPONSE.setStatus(304)
00926                 return True
00927 
00928     #Last-Modified will get stomped on by a cache policy if there is
00929     #one set....
00930     RESPONSE.setHeader('Last-Modified', rfc1123_date(last_mod))
00931 
00932 
00933 class SimpleRecord:
00934     """ record-like class """
00935 
00936     def __init__(self, **kw):
00937         self.__dict__.update(kw)
00938 
00939 
00940 security.declarePublic('Message')
00941 Message = MessageFactory('cmf_default')