Back to index

plone3  3.1.7
BaseRegistry.py
Go to the documentation of this file.
00001 import random
00002 
00003 # we *have* to use StringIO here, because we can't add attributes to cStringIO
00004 # instances (needed in BaseRegistryTool.__getitem__).
00005 from StringIO import StringIO
00006 from urllib import quote_plus
00007 
00008 from App.Common import rfc1123_date
00009 from DateTime import DateTime
00010 from Globals import InitializeClass, Persistent, PersistentMapping
00011 from AccessControl import ClassSecurityInfo, Unauthorized
00012 
00013 from zope.interface import implements
00014 
00015 from Acquisition import aq_base, aq_parent, aq_inner, ExplicitAcquisitionWrapper
00016 
00017 from OFS.Image import File
00018 from OFS.SimpleItem import SimpleItem
00019 from OFS.PropertyManager import PropertyManager
00020 from OFS.Cache import Cacheable
00021 
00022 from Products.CMFCore.Expression import Expression
00023 from Products.CMFCore.Expression import createExprContext
00024 from Products.CMFCore.utils import UniqueObject, getToolByName
00025 
00026 from Products.ResourceRegistries import permissions
00027 from Products.ResourceRegistries.interfaces import IResourceRegistry
00028 
00029 import Acquisition
00030 
00031 
00032 # version agnostic import of z3_Resource
00033 try:
00034     import Products.Five
00035 except ImportError:
00036     __five__ = False
00037     from zope.app.publisher.browser.resource import Resource as z3_Resource
00038 else:
00039     __five__ = True
00040     try:
00041         # Zope 2.8 / Five 1.0.2
00042         from Products.Five.resource import Resource as z3_Resource
00043         __five_pre_1_3_ = True
00044     except ImportError:
00045         # Zope 2.9 / Five 1.3
00046         from Products.Five.browser.resource import Resource as z3_Resource
00047         __five_pre_1_3__ = False
00048 
00049 
00050 
00051 def getDummyFileForContent(name, ctype):
00052     # make output file like and add an headers dict, so the contenttype
00053     # is properly set in the headers
00054     output = StringIO()
00055     output.headers = {'content-type': ctype}
00056     return File(name, name, output)
00057 
00058 def getCharsetFromContentType(contenttype, default='utf-8'):
00059     contenttype = contenttype.lower()
00060     if 'charset=' in contenttype:
00061         i = contenttype.index('charset=')
00062         charset = contenttype[i+8:]
00063         charset = charset.split(';')[0]
00064         return charset
00065     else:
00066         return default
00067 
00068 class Resource(Persistent):
00069     security = ClassSecurityInfo()
00070 
00071     def __init__(self, id, **kwargs):
00072         self._data = PersistentMapping()
00073         self._data['id'] = id
00074         self._data['expression'] = kwargs.get('expression', '')
00075         self._data['enabled'] = kwargs.get('enabled', True)
00076         self._data['cookable'] = kwargs.get('cookable', True)
00077         self._data['cacheable'] = kwargs.get('cacheable', True)
00078 
00079     def copy(self):
00080         result = self.__class__(self.getId())
00081         for key, value in self._data.items():
00082             if key != 'id':
00083                 result._data[key] = value
00084         return result
00085 
00086     security.declarePublic('getId')
00087     def getId(self):
00088         return self._data['id']
00089 
00090     def getQuotedId(self):
00091         return quote_plus(self._data['id'])
00092 
00093     def _setId(self, id):
00094         self._data['id'] = id
00095 
00096     security.declarePublic('getExpression')
00097     def getExpression(self):
00098         return self._data['expression']
00099 
00100     security.declareProtected(permissions.ManagePortal, 'setExpression')
00101     def setExpression(self, expression):
00102         self._data['expression'] = expression
00103 
00104     security.declarePublic('getEnabled')
00105     def getEnabled(self):
00106         return bool(self._data['enabled'])
00107 
00108     security.declareProtected(permissions.ManagePortal, 'setEnabled')
00109     def setEnabled(self, enabled):
00110         self._data['enabled'] = enabled
00111 
00112     security.declarePublic('getCookable')
00113     def getCookable(self):
00114         return self._data['cookable']
00115 
00116     security.declareProtected(permissions.ManagePortal, 'setCookable')
00117     def setCookable(self, cookable):
00118         self._data['cookable'] = cookable
00119 
00120     security.declarePublic('getCacheable')
00121     def getCacheable(self):
00122         # as this is a new property, old instance might not have that value, so
00123         # return True as default
00124         return self._data.get('cacheable', True)
00125 
00126     security.declareProtected(permissions.ManagePortal, 'setCacheable')
00127     def setCacheable(self, cacheable):
00128         self._data['cacheable'] = cacheable
00129 
00130 InitializeClass(Resource)
00131 
00132 
00133 class Skin(Acquisition.Implicit):
00134     security = ClassSecurityInfo()
00135 
00136     def __init__(self, skin):
00137         self._skin = skin
00138 
00139     def __before_publishing_traverse__(self, object, REQUEST):
00140         """ Pre-traversal hook. Specify the skin. 
00141         """
00142         self.changeSkin(self._skin, REQUEST)
00143 
00144     def __bobo_traverse__(self, REQUEST, name):
00145         """Traversal hook."""
00146         if REQUEST is not None and \
00147            self.concatenatedresources.get(name, None) is not None:
00148             parent = aq_parent(self)
00149             # see BaseTool.__bobo_traverse__
00150             deferred = getDummyFileForContent(name, self.getContentType())
00151             post_traverse = getattr(aq_base(REQUEST), 'post_traverse', None)
00152             if post_traverse is not None:
00153                 post_traverse(parent.deferredGetContent, (deferred, name, self._skin))
00154             else:
00155                 parent.deferredGetContent(deferred, name, self._skin)
00156             return deferred.__of__(parent)
00157         obj = getattr(self, name, None)
00158         if obj is not None:
00159             return obj
00160         raise AttributeError('%s' % (name,))
00161 
00162 InitializeClass(Skin)
00163 
00164 
00165 class BaseRegistryTool(UniqueObject, SimpleItem, PropertyManager, Cacheable):
00166     """Base class for a Plone registry managing resource files."""
00167 
00168     security = ClassSecurityInfo()
00169     implements(IResourceRegistry)
00170     __implements__ = SimpleItem.__implements__
00171     manage_options = SimpleItem.manage_options
00172 
00173     attributes_to_compare = ('getExpression', 'getCookable', 'getCacheable')
00174     filename_base = 'ploneResources'
00175     filename_appendix = '.res'
00176     merged_output_prefix = u''
00177     cache_duration = 3600
00178     resource_class = Resource
00179 
00180     debugmode = False
00181     autogroupingmode = False
00182 
00183     #
00184     # Private Methods
00185     #
00186 
00187     def __init__(self):
00188         """Add the storages."""
00189         self.resources = ()
00190         self.cookedresources = ()
00191         self.concatenatedresources = {}
00192         self.debugmode = False
00193         self.autogroupingmode = False
00194 
00195     def __getitem__(self, item):
00196         """Return a resource from the registry."""
00197         original = self.REQUEST.get('original', False)
00198         output = self.getResourceContent(item, self, original)
00199         contenttype = self.getContentType()
00200         return (output, contenttype)
00201 
00202     def deferredGetContent(self, deferred, name, skin=None):
00203         """ uploads data of a resource to deferred """
00204         # "deferred" was previosly created by a getDummyFileForContent
00205         # call in the __bobo_traverse__ method. As the name suggests,
00206         # the file is merely a traversable dummy with appropriate
00207         # headers and name. Now as soon as REQUEST.traverse
00208         # finishes and gets to the part where it calls the tuples
00209         # register using post_traverse (that's actually happening
00210         # right now) we can be sure, that all necessary security
00211         # stuff has taken place (e.g. authentication).
00212         kw = {'skin':skin,'name':name}
00213         data = None
00214         duration = self.cache_duration  # duration in seconds
00215         if not self.getDebugMode() and self.isCacheable(name):
00216             if self.ZCacheable_isCachingEnabled():
00217                 data = self.ZCacheable_get(keywords=kw)
00218             if data is None:
00219                 # This is the part were we would fail if
00220                 # we would just return the ressource
00221                 # without using the post_traverse hook:
00222                 # self.__getitem__ leads (indirectly) to
00223                 # a restrictedTraverse call which performs
00224                 # security checks. So if a tool (or its ressource)
00225                 # is not "View"able by anonymous - we'd
00226                 # get an Unauthorized exception.
00227                 data = self.__getitem__(name)
00228                 self.ZCacheable_set(data, keywords=kw)
00229         else:
00230             data = self.__getitem__(name)
00231             duration = 0
00232 
00233         output, contenttype = data
00234 
00235         seconds = float(duration)*24.0*3600.0
00236         response = self.REQUEST.RESPONSE
00237         response.setHeader('Expires',rfc1123_date((DateTime() + duration).timeTime()))
00238         response.setHeader('Cache-Control', 'max-age=%d' % int(seconds))
00239 
00240         if isinstance(output, unicode):
00241             portal_props = getToolByName(self, 'portal_properties')
00242             site_props = portal_props.site_properties
00243             charset = site_props.getProperty('default_charset', 'utf-8')
00244             output = output.encode(charset)
00245             if 'charset=' not in contenttype:
00246                 contenttype += ';charset=' + charset
00247         
00248         out = StringIO(output)
00249         out.headers = {'content-type': contenttype}
00250         # At this point we are ready to provide some content
00251         # for our dummy and since it's just a File instance,
00252         # we can "upload" (a quite delusive method name) the
00253         # data and that's it.
00254         deferred.manage_upload(out)
00255     
00256     def __bobo_traverse__(self, REQUEST, name):
00257         """Traversal hook."""
00258         # First see if it is a skin
00259         skintool = getToolByName(self, 'portal_skins')
00260         skins = skintool.getSkinSelections()
00261         if name in skins:
00262             return Skin(name).__of__(self)
00263 
00264         
00265         if REQUEST is not None and \
00266            self.concatenatedresources.get(name, None) is not None:
00267             # __bobo_traverse__ is called before the authentication has
00268             # taken place, so if some operations require an authenticated
00269             # user (like restrictedTraverse in __getitem__) it will fail.
00270             # Now we can circumvent that by using the post_traverse()
00271             # method from BaseRequest. It temporarely stores a callable
00272             # along with its arguments in a REQUEST instance and calls
00273             # them at the end of BaseRequest.traverse()
00274             deferred = getDummyFileForContent(name, self.getContentType())
00275             # __bobo_traverse__ might be called from within
00276             # OFS.Traversable.Traversable.unrestrictedTraverse()
00277             # which passes a simple dict to the method, instead
00278             # of a "real" REQUEST object
00279             post_traverse = getattr(aq_base(REQUEST), 'post_traverse', None)
00280             if post_traverse is not None:
00281                 post_traverse(self.deferredGetContent, (deferred, name, None))
00282             else:
00283                 self.deferredGetContent(deferred, name, None)
00284             return deferred.__of__(self)
00285         obj = getattr(self, name, None)
00286         if obj is not None:
00287             return obj
00288         raise AttributeError('%s' % (name,))
00289 
00290     security.declarePublic('isCacheable')
00291     def isCacheable(self, name):
00292         """Return a boolean whether the resource is cacheable or not."""
00293         resource_id = self.concatenatedresources.get(name, [None])[0]
00294         if resource_id is None:
00295             return False
00296         resources = self.getResourcesDict()
00297         resource = resources.get(resource_id, None)
00298         result = resource.getCacheable()
00299         return result
00300 
00301     security.declarePrivate('validateId')
00302     def validateId(self, id, existing):
00303         """Safeguard against duplicate ids."""
00304         for sheet in existing:
00305             if sheet.getId() == id:
00306                 raise ValueError, 'Duplicate id %s' %(id)
00307 
00308     security.declarePrivate('storeResource')
00309     def storeResource(self, resource, skipCooking=False):
00310         """Store a resource."""
00311         self.validateId(resource.getId(), self.getResources())
00312         resources = list(self.resources)
00313         resources.append(resource)
00314         self.resources = tuple(resources)
00315         if not skipCooking:
00316             self.cookResources()
00317 
00318     security.declarePrivate('clearResources')
00319     def clearResources(self):
00320         """Clears all resource data.
00321 
00322         Convenience funtion for Plone migrations and tests.
00323         """
00324         self.resources = ()
00325         self.cookedresources = ()
00326         self.concatenatedresources = {}
00327 
00328     security.declarePrivate('getResourcesDict')
00329     def getResourcesDict(self):
00330         """Get the resources as a dictionary instead of an ordered list.
00331 
00332         Good for lookups. Internal.
00333         """
00334         resources = self.getResources()
00335         d = {}
00336         for s in resources:
00337             d[s.getId()] = s
00338         return d
00339 
00340     security.declarePrivate('compareResources')
00341     def compareResources(self, s1, s2):
00342         """Check if two resources are compatible."""
00343         for attr in self.attributes_to_compare:
00344             if getattr(s1, attr)() != getattr(s2, attr)():
00345                 return False
00346         return True
00347 
00348     security.declarePrivate('sortResources')
00349     def sortResourceKey(self, resource):
00350         """Returns a sort key for the resource."""
00351         return [getattr(resource, attr)() for attr in
00352                 self.attributes_to_compare]
00353 
00354     security.declarePrivate('generateId')
00355     def generateId(self, res_id, prev_id=None):
00356         """Generate a random id."""
00357         id_parts = res_id.split('.')
00358         if (len(id_parts) > 1):
00359             base = '.'.join(id_parts[:-1])
00360             appendix = ".%s" % id_parts[-1]
00361         else:
00362             base = id_parts[0]
00363             appendix = self.filename_appendix
00364         base = base.replace('++', '').replace('/', '')
00365         return '%s-cachekey%04d%s' % (base, random.randint(0, 9999), appendix)
00366 
00367     security.declarePrivate('finalizeResourceMerging')
00368     def finalizeResourceMerging(self, resource, previtem):
00369         """Finalize the resource merging with the previous item.
00370 
00371         Might be overwritten in subclasses.
00372         """
00373         pass
00374 
00375     security.declarePrivate('finalizeContent')
00376     def finalizeContent(self, resource, content):
00377         """Finalize the resource content.
00378 
00379         Might be overwritten in subclasses.
00380         """
00381         return content
00382 
00383     security.declareProtected(permissions.ManagePortal, 'cookResources')
00384     def cookResources(self):
00385         """Cook the stored resources."""
00386         if self.ZCacheable_isCachingEnabled():
00387             self.ZCacheable_invalidate()
00388         resources = [r.copy() for r in self.getResources() if r.getEnabled()]
00389         self.concatenatedresources = {}
00390         self.cookedresources = ()
00391         if self.getDebugMode():
00392             results = [x for x in resources]
00393         else:
00394             results = []
00395             if self.getAutoGroupingMode():
00396                 # Sort resources according to their sortkey first, so resources
00397                 # with compatible keys will be merged.
00398                 def _sort_position(r):
00399                     key = self.sortResourceKey(r[0])
00400                     key.append(r[1])
00401                     return key
00402                 # We need to respect the resource position inside the sort groups
00403                 positioned_resources = [(r, resources.index(r)) for r in resources]
00404                 positioned_resources.sort(key=_sort_position)
00405                 resources = [r[0] for r in positioned_resources]
00406             for resource in resources:
00407                 if results:
00408                     previtem = results[-1]
00409                     if resource.getCookable() and previtem.getCookable() \
00410                            and self.compareResources(resource, previtem):
00411                         res_id = resource.getId()
00412                         prev_id = previtem.getId()
00413                         self.finalizeResourceMerging(resource, previtem)
00414                         if self.concatenatedresources.has_key(prev_id):
00415                             self.concatenatedresources[prev_id].append(res_id)
00416                         else:
00417                             magic_id = self.generateId(res_id, prev_id)
00418                             self.concatenatedresources[magic_id] = [prev_id, res_id]
00419                             previtem._setId(magic_id)
00420                     else:
00421                         if resource.getCookable() or resource.getCacheable():
00422                             magic_id = self.generateId(resource.getId())
00423                             self.concatenatedresources[magic_id] = [resource.getId()]
00424                             resource._setId(magic_id)
00425                         results.append(resource)
00426                 else:
00427                     if resource.getCookable() or resource.getCacheable():
00428                         magic_id = self.generateId(resource.getId())
00429                         self.concatenatedresources[magic_id] = [resource.getId()]
00430                         resource._setId(magic_id)
00431                     results.append(resource)
00432 
00433         resources = self.getResources()
00434         for resource in resources:
00435             self.concatenatedresources[resource.getId()] = [resource.getId()]
00436         self.cookedresources = tuple(results)
00437 
00438     security.declarePrivate('evaluateExpression')
00439     def evaluateExpression(self, expression, context):
00440         """Evaluate an object's TALES condition to see if it should be
00441         displayed.
00442         """
00443         try:
00444             if expression and context is not None:
00445                 portal = getToolByName(context, 'portal_url').getPortalObject()
00446 
00447                 # Find folder (code courtesy of CMFCore.ActionsTool)
00448                 if context is None or not hasattr(context, 'aq_base'):
00449                     folder = portal
00450                 else:
00451                     folder = context
00452                     # Search up the containment hierarchy until we find an
00453                     # object that claims it's PrincipiaFolderish.
00454                     while folder is not None:
00455                         if getattr(aq_base(folder), 'isPrincipiaFolderish', 0):
00456                             # found it.
00457                             break
00458                         else:
00459                             folder = aq_parent(aq_inner(folder))
00460 
00461                 __traceback_info__ = (folder, portal, context, expression)
00462                 ec = createExprContext(folder, portal, context)
00463                 return Expression(expression)(ec)
00464             else:
00465                 return 1
00466         except AttributeError:
00467             return 1
00468 
00469     security.declareProtected(permissions.ManagePortal, 'getResource')
00470     def getResource(self, id):
00471         """Get resource object by id.
00472         
00473         If any property of the resource is changed, then cookResources of the
00474         registry must be called."""
00475         resources = self.getResourcesDict()
00476         resource = resources.get(id, None)
00477         if resource is not None:
00478             return ExplicitAcquisitionWrapper(resource, self)
00479         return None
00480 
00481     security.declarePrivate('getResourceContent')
00482     def getResourceContent(self, item, context, original=False):
00483         """Fetch resource content for delivery."""
00484         ids = self.concatenatedresources.get(item, None)
00485         resources = self.getResourcesDict()
00486         if ids is not None:
00487             ids = ids[:]
00488         output = u""
00489         if len(ids) > 1:
00490             output = output + self.merged_output_prefix
00491 
00492         portal = getToolByName(context, 'portal_url').getPortalObject()
00493 
00494         if context == self and portal is not None:
00495             context = portal
00496 
00497         portal_props = getToolByName(self, 'portal_properties')
00498         site_props = portal_props.site_properties
00499         default_charset = site_props.getProperty('default_charset', 'utf-8')
00500 
00501         for id in ids:
00502             try:
00503                 if portal is not None:
00504                     obj = context.restrictedTraverse(id)
00505                 else:
00506                     #Can't do anything other than attempt a getattr
00507                     obj = getattr(context, id)
00508             except (AttributeError, KeyError):
00509                 output += u"\n/* XXX ERROR -- could not find '%s'*/\n" % id
00510                 content = u''
00511                 obj = None
00512             except Unauthorized:
00513                 #If we're just returning a single resource, raise an Unauthorized,
00514                 #otherwise we're merging resources in which case just log an error
00515                 if len(ids) > 1:
00516                     #Object probably isn't published yet
00517                     output += u"\n/* XXX ERROR -- access to '%s' not authorized */\n" % id
00518                     content = u''
00519                     obj = None
00520                 else:
00521                     raise
00522 
00523             if obj is not None:
00524                 if isinstance(obj, z3_Resource):
00525                     # z3 resources
00526                     # XXX this is a temporary solution, we wrap the five resources
00527                     # into our mechanism, where it should be the other way around.
00528                     #
00529                     # First thing we must be aware of: resources give a complete
00530                     # response so first we must save the headers.
00531                     # Especially, we must delete the If-Modified-Since, because
00532                     # otherwise we might get a 30x response status in some cases.
00533                     response_headers = self.REQUEST.RESPONSE.headers.copy()
00534                     if_modif = self.REQUEST.get_header('If-Modified-Since', None)
00535                     try:
00536                         del self.REQUEST.environ['IF_MODIFIED_SINCE']
00537                     except KeyError:
00538                         pass
00539                     try:
00540                         del self.REQUEST.environ['HTTP_IF_MODIFIED_SINCE']
00541                     except KeyError:
00542                         pass
00543                     # Now, get the content.
00544                     method = obj.__browser_default__(self.REQUEST)[1][0]
00545                     method = method == 'HEAD' and 'GET' or method
00546                     content = getattr(obj, method)()
00547                     if not isinstance(content, unicode): 
00548                         contenttype = self.REQUEST.RESPONSE.headers.get('content-type', '')
00549                         contenttype = getCharsetFromContentType(contenttype, default_charset)
00550                         content = unicode(content, contenttype)
00551                     # Now restore the headers and for safety, check that we
00552                     # have a 20x response. If not, we have a problem and
00553                     # some browser would hang indefinitely at this point.
00554                     assert int(self.REQUEST.RESPONSE.getStatus()) / 100 == 2
00555                     self.REQUEST.environ['HTTP_IF_MODIFIED_SINCE'] = if_modif
00556                     self.REQUEST.RESPONSE.headers = response_headers
00557                 elif hasattr(aq_base(obj),'meta_type') and  obj.meta_type in ['DTML Method', 'Filesystem DTML Method']:
00558                     content = obj(client=self.aq_parent, REQUEST=self.REQUEST,
00559                                   RESPONSE=self.REQUEST.RESPONSE)
00560                     contenttype = self.REQUEST.RESPONSE.headers.get('content-type', '')
00561                     contenttype = getCharsetFromContentType(contenttype, default_charset)
00562                     content = unicode(content, contenttype)
00563                 elif hasattr(aq_base(obj),'meta_type') and obj.meta_type == 'Filesystem File':
00564                     obj._updateFromFS()
00565                     content = obj._readFile(0)
00566                     contenttype = getCharsetFromContentType(obj.content_type, default_charset)
00567                     content = unicode(content, contenttype)
00568                 elif hasattr(aq_base(obj),'meta_type') and obj.meta_type == 'ATFile':
00569                     f = obj.getFile()
00570                     contenttype = getCharsetFromContentType(f.getContentType(), default_charset)
00571                     content = unicode(str(f), contenttype)
00572                 # We should add more explicit type-matching checks
00573                 elif hasattr(aq_base(obj), 'index_html') and callable(obj.index_html):
00574                     content = obj.index_html(self.REQUEST,
00575                                              self.REQUEST.RESPONSE)
00576                     if not isinstance(content, unicode):
00577                         content = unicode(content, default_charset)
00578                 elif callable(obj):
00579                     content = obj(self.REQUEST, self.REQUEST.RESPONSE)
00580                     if not isinstance(content, unicode):
00581                         content = unicode(content, default_charset)
00582                 else:
00583                     content = str(obj)
00584                     content = unicode(content, default_charset)
00585 
00586             # Add start/end notes to the resource for better
00587             # understanding and debugging
00588             if content:
00589                 output += u'\n/* - %s - */\n' % (id,)
00590                 if original:
00591                     output += content
00592                 else:
00593                     output += self.finalizeContent(resources[id], content)
00594                 output += u'\n'
00595         return output
00596 
00597     #
00598     # ZMI Methods
00599     #
00600 
00601     security.declareProtected(permissions.ManagePortal, 'moveResourceUp')
00602     def moveResourceUp(self, id, steps=1, REQUEST=None):
00603         """Move the resource up 'steps' number of steps."""
00604         index = self.getResourcePosition(id)
00605         self.moveResource(id, index - steps)
00606         if REQUEST:
00607             REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER'])
00608 
00609     security.declareProtected(permissions.ManagePortal, 'moveResourceDown')
00610     def moveResourceDown(self, id, steps=1, REQUEST=None):
00611         """Move the resource down 'steps' number of steps."""
00612         index = self.getResourcePosition(id)
00613         self.moveResource(id, index + steps)
00614         if REQUEST:
00615             REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER'])
00616 
00617     security.declareProtected(permissions.ManagePortal, 'moveResourceToTop')
00618     def moveResourceToTop(self, id, REQUEST=None):
00619         """Move the resource to the first position."""
00620         self.moveResource(id, 0)
00621         if REQUEST:
00622             REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER'])
00623 
00624     security.declareProtected(permissions.ManagePortal, 'moveResourceToBottom')
00625     def moveResourceToBottom(self, id, REQUEST=None):
00626         """Move the resource to the last position."""
00627         self.moveResource(id, len(self.resources))
00628         if REQUEST:
00629             REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER'])
00630 
00631     security.declareProtected(permissions.ManagePortal, 'moveResourceBefore')
00632     def moveResourceBefore(self, id, dest_id, REQUEST=None):
00633         """Move the resource before the resource with dest_id."""
00634         index = self.getResourcePosition(id)
00635         dest_index = self.getResourcePosition(dest_id)
00636         if index < dest_index:
00637             self.moveResource(id, dest_index - 1)
00638         else:
00639             self.moveResource(id, dest_index)
00640         if REQUEST:
00641             REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER'])
00642 
00643     security.declareProtected(permissions.ManagePortal, 'moveResourceAfter')
00644     def moveResourceAfter(self, id, dest_id, REQUEST=None):
00645         """Move the resource after the resource with dest_id."""
00646         index = self.getResourcePosition(id)
00647         dest_index = self.getResourcePosition(dest_id)
00648         if index < dest_index:
00649             self.moveResource(id, dest_index)
00650         else:
00651             self.moveResource(id, dest_index + 1)
00652         if REQUEST:
00653             REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER'])
00654 
00655     #
00656     # Protected Methods
00657     #
00658 
00659     security.declareProtected(permissions.ManagePortal, 'registerResource')
00660     def registerResource(self, id, expression='', enabled=True,
00661                          cookable=True, cacheable=True):
00662         """Register a resource."""
00663         resource = Resource(id,
00664                             expression=expression,
00665                             enabled=enabled,
00666                             cookable=cookable,
00667                             cacheable=cacheable)
00668         self.storeResource(resource)
00669 
00670     security.declareProtected(permissions.ManagePortal, 'unregisterResource')
00671     def unregisterResource(self, id):
00672         """Unregister a registered resource."""
00673         resources = [item for item in self.getResources()
00674                      if item.getId() != id]
00675         self.resources = tuple(resources)
00676         self.cookResources()
00677 
00678     security.declareProtected(permissions.ManagePortal, 'renameResource')
00679     def renameResource(self, old_id, new_id):
00680         """Change the id of a registered resource."""
00681         self.validateId(new_id, self.getResources())
00682         resources = list(self.resources)
00683         for resource in resources:
00684             if resource.getId() == old_id:
00685                 resource._setId(new_id)
00686                 break
00687         self.resources = tuple(resources)
00688         self.cookResources()
00689 
00690     security.declareProtected(permissions.ManagePortal, 'getResourceIds')
00691     def getResourceIds(self):
00692         """Return the ids of all resources."""
00693         return tuple([x.getId() for x in self.getResources()])
00694 
00695     security.declareProtected(permissions.ManagePortal, 'getResources')
00696     def getResources(self):
00697         """Get all the registered resource data, uncooked.
00698 
00699         For management screens.
00700         """
00701         result = []
00702         for item in self.resources:
00703             if isinstance(item, dict):
00704                 # BBB we used dicts before
00705                 item = item.copy()
00706                 item_id = item['id']
00707                 del item['id']
00708                 obj = self.resource_class(item_id, **item)
00709                 result.append(obj)
00710             else:
00711                 result.append(item)
00712         return tuple(result)
00713 
00714     security.declareProtected(permissions.ManagePortal, 'getCookedResources')
00715     def getCookedResources(self):
00716         """Get the cooked resource data."""
00717         result = []
00718         for item in self.cookedresources:
00719             if isinstance(item, dict):
00720                 # BBB we used dicts before
00721                 item = item.copy()
00722                 item_id = item['id']
00723                 del item['id']
00724                 obj = self.resource_class(item_id, **item)
00725                 result.append(obj)
00726             else:
00727                 result.append(item)
00728         return tuple(result)
00729 
00730     security.declareProtected(permissions.ManagePortal, 'moveResource')
00731     def moveResource(self, id, position):
00732         """Move a registered resource to the given position."""
00733         index = self.getResourcePosition(id)
00734         if index == position:
00735             return
00736         elif position < 0:
00737             position = 0
00738         resources = list(self.getResources())
00739         resource = resources.pop(index)
00740         resources.insert(position, resource)
00741         self.resources = tuple(resources)
00742         self.cookResources()
00743 
00744     security.declareProtected(permissions.ManagePortal, 'getResourcePosition')
00745     def getResourcePosition(self, id):
00746         """Get the position (order) of an resource given its id."""
00747         resource_ids = list(self.getResourceIds())
00748         return resource_ids.index(id)
00749 
00750     security.declareProtected(permissions.ManagePortal, 'getDebugMode')
00751     def getDebugMode(self):
00752         """Is resource merging disabled?"""
00753         return self.debugmode
00754 
00755     security.declareProtected(permissions.ManagePortal, 'setDebugMode')
00756     def setDebugMode(self, value):
00757         """Set whether resource merging should be disabled."""
00758         self.debugmode = value
00759         self.cookResources()
00760 
00761     security.declareProtected(permissions.ManagePortal, 'getAutoGroupingMode')
00762     def getAutoGroupingMode(self):
00763         """Is resource merging disabled?"""
00764         return self.autogroupingmode
00765 
00766     security.declareProtected(permissions.ManagePortal, 'setAutoGroupingMode')
00767     def setAutoGroupingMode(self, value):
00768         """Set whether resource merging should be disabled."""
00769         self.autogroupingmode = bool(value)
00770         self.cookResources()
00771 
00772     security.declareProtected(permissions.View, 'getEvaluatedResources')
00773     def getEvaluatedResources(self, context):
00774         """Return the filtered evaluated resources."""
00775         results = self.getCookedResources()
00776 
00777         # filter results by expression
00778         results = [item for item in results
00779                    if self.evaluateExpression(item.getExpression(), context)]
00780 
00781         return results
00782 
00783     security.declareProtected(permissions.View, 'getInlineResource')
00784     def getInlineResource(self, item, context):
00785         """Return a resource as inline code, not as a file object.
00786 
00787         Needs to take care not to mess up http headers.
00788         """
00789         headers = self.REQUEST.RESPONSE.headers.copy()
00790         # Save the RESPONSE headers
00791         output = self.getResourceContent(item, context)
00792         # File objects and other might manipulate the headers,
00793         # something we don't want. we set the saved headers back
00794         self.REQUEST.RESPONSE.headers = headers
00795         return output
00796 
00797     security.declareProtected(permissions.View, 'getContentType')
00798     def getContentType(self):
00799         """Return the registry content type.
00800 
00801         Should be overwritten by subclasses.
00802         """
00803         return 'text/plain'
00804