WebSection.py 15.4 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
##############################################################################
#
# Copyright (c) 2002-2006 Nexedi SARL and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

from AccessControl import ClassSecurityInfo
29
from AccessControl.SecurityManagement import getSecurityManager, newSecurityManager, setSecurityManager
Jean-Paul Smets's avatar
Jean-Paul Smets committed
30 31 32 33
from Products.CMFCore.utils import getToolByName
from Products.ERP5Type import Permissions, PropertySheet,\
                              Constraint, Interface, Cache
from Products.ERP5.Document.Domain import Domain
34
#from Products.ERP5.Document.WebSite import WebSite
Jean-Paul Smets's avatar
Jean-Paul Smets committed
35 36 37 38
from Acquisition import ImplicitAcquisitionWrapper, aq_base, aq_inner
from Products.ERP5Type.Base import TempBase
from Globals import get_request

39
from zLOG import LOG, WARNING
Jean-Paul Smets's avatar
Jean-Paul Smets committed
40

41
from Products.ERP5Type.Cache import getReadOnlyTransactionCache
42

43 44 45 46 47 48 49 50 51 52 53 54
# Global keys used for URL generation
WEBSECTION_KEY = 'web_section_value'
WEBSITE_USER = 'web_site_user'

Domain_getattr = Domain.inheritedAttribute('__getattr__')

# We use a request key (CACHE_KEY) to store access attributes and prevent infinite recursion
# We define a couple of reserved names for which we are not
# going to try to do acquisition
CACHE_KEY = 'web_site_aq_cache'
DOCUMENT_NAME_KEY = 'web_section_document_name'
reserved_name_dict = { 'getApplicableLayout' : 1,
Jean-Paul Smets's avatar
Jean-Paul Smets committed
55
                       'getWebmaster' : 1,
Jean-Paul Smets's avatar
Jean-Paul Smets committed
56
                       'getContainerLayout': 1,
Jean-Paul Smets's avatar
Jean-Paul Smets committed
57
                       'isWebMode' : 1,
58 59 60 61 62 63 64 65
                       'getLayout' : 1,
                       'Localizer' : 1,
                       'field_render' : 1,
                       'getListItemUrl' : 1,
                       'getLocalPropertyManager' : 1,
                       'getOrderedGlobalActionList' : 1,
                       'allow_discussion' : 1,
                       'im_func' : 1,
66 67
                       'im_self' : 1,
                       'ListBox_asHTML' : 1,
68 69 70
                       'id' : 1,
                       'method_id' : 1,
                       'role_map' : 1,
71 72
                       'func_defaults': 1,
                       '_v_section_webmaster': 1,
Jean-Paul Smets's avatar
Jean-Paul Smets committed
73 74 75
                       'priority': 1,
                       'to_processing_date': 1,
                       'categories': 1,
76
                     }
77
reserved_name_dict_init = 0
78

Jean-Paul Smets's avatar
Jean-Paul Smets committed
79 80 81
class WebSection(Domain):
    """
      A Web Section is a Domain with an extended API intended to
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
      support the creation of Web front ends to
      server ERP5 contents through a pretty and configurable
      user interface.

      WebSection uses the following scripts for customisation:

      - WebSection_getBreadcrumbItemList

      - WebSection_getDocumentValueList

      - WebSection_getPermanentURL

      - WebSection_getDocumentValue

      - WebSection_getDefaultDocumentValue

      - WebSection_getSectionValue

      - WebSection_getWebSiteValue

      It defines the following REQUEST global variables:

      - current_web_section

      - current_web_document

      - is_web_section_default_document
Jean-Paul Smets's avatar
Jean-Paul Smets committed
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
    """
    # CMF Type Definition
    meta_type = 'ERP5 Web Section'
    portal_type = 'Web Section'
    isPortalContent = 1
    isRADContent = 1

    # Declarative security
    security = ClassSecurityInfo()
    security.declareObjectProtected(Permissions.AccessContentsInformation)

    # Default Properties
    property_sheets = ( PropertySheet.Base
                      , PropertySheet.XMLObject
                      , PropertySheet.CategoryCore
                      , PropertySheet.DublinCore
125
                      , PropertySheet.WebSection
Jean-Paul Smets's avatar
Jean-Paul Smets committed
126
                      , PropertySheet.SortIndex
127
                      , PropertySheet.Predicate
Jean-Paul Smets's avatar
Jean-Paul Smets committed
128 129
                      )

130
    web_section_key = WEBSECTION_KEY
Jean-Paul Smets's avatar
Jean-Paul Smets committed
131

132 133 134 135
    def _aq_dynamic(self, name):
      """
        Try to find a suitable document based on the
        web site local naming policies as defined by
136
        the getDocumentValue method
137 138 139 140 141
      """
      global reserved_name_dict_init
      global reserved_name_dict
      request = self.REQUEST
      # Register current web site physical path for later URL generation
142 143
      if not request.has_key(self.web_section_key):
        request[self.web_section_key] = self.getPhysicalPath()
144 145 146 147 148 149 150 151 152 153 154 155 156 157
        # Normalize web parameter in the request
        # Fix common user mistake and transform '1' string to boolean
        for web_param in ['ignore_layout', 'editable_mode']:
          if hasattr(request, web_param):
            if getattr(request, web_param, None) in ('1', 1, True):
              request.set(web_param, True)
            else:
              request.set(web_param, False)
      # First let us call the super method
      dynamic = Domain._aq_dynamic(self, name)
      if dynamic is not None:
        return dynamic
      # Do some optimisation here for names which can not be names of documents
      if  reserved_name_dict.has_key(name) \
158
          or name.endswith('_getDocumentValue') \
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
          or name.startswith('_') or name.startswith('portal_')\
          or name.startswith('aq_') or name.startswith('selection_') \
          or name.startswith('sort-') or name.startswith('WebSite_') \
          or name.startswith('WebSection_') or name.startswith('Base_'):
        return None
      if not reserved_name_dict_init:
        # Feed reserved_name_dict_init with skin names
        portal = self.getPortalObject()
        for skin_folder in portal.portal_skins.objectValues():
          for id in skin_folder.objectIds():
            reserved_name_dict[id] = 1
        for id in portal.objectIds():
          reserved_name_dict[id] = 1
        reserved_name_dict_init = 1
      #LOG('aq_dynamic name',0, name)
      if not request.has_key(CACHE_KEY):
        request[CACHE_KEY] = {}
      elif request[CACHE_KEY].has_key(name):
        return request[CACHE_KEY][name]
      try:
        portal = self.getPortalObject()
        # Use the webmaster identity to find documents
        if request[CACHE_KEY].has_key(WEBSITE_USER):
          user = request[CACHE_KEY][WEBSITE_USER] # Retrieve user from request cache
        else:
184 185 186 187
          # Cache webmaster for faster lookup
          if not hasattr(aq_base(self), '_v_section_webmaster'):
            self._v_section_webmaster = self.getWebmaster()
          user = portal.acl_users.getUserById(self._v_section_webmaster)
188 189 190 191
          request[CACHE_KEY][WEBSITE_USER] = user # Cache user per request
        if user is not None:
          old_manager = getSecurityManager()
          newSecurityManager(get_request(), user)
192 193 194 195
        else:
          LOG('WebSection _aq_dynamic', WARNING, 'No user defined for %s.'
          'This will prevent accessing object through their permanent URL' % self.getWebmaster())
        #LOG('Lookup', 0, str(name))
196
        document = self.getDocumentValue(name=name, portal=portal)
197 198 199 200 201 202 203 204
        request[CACHE_KEY][name] = document
        if user is not None:
          setSecurityManager(old_manager)
      except:
        # Cleanup non recursion dict in case of exception
        if request[CACHE_KEY].has_key(name):
          del request[CACHE_KEY][name]
        raise
205 206 207 208 209 210
      if document is not None:
        request[DOCUMENT_NAME_KEY] = name
        document = aq_base(document.asContext(id=name, # Hide some properties to permit location the original
                                              original_container=document.getParentValue(),
                                              original_id=document.getId(),
                                              editable_absolute_url=document.absolute_url()))
211
      return document
212

213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
    security.declareProtected(Permissions.AccessContentsInformation, 'getWebSectionValue')
    def getWebSectionValue(self):
      """
        Returns the current web section (ie. self) though containment acquisition.

        To understand the misteries of acquisition and how the rule
        containment vs. acquisition works, please look at
        XXXX (Zope web site)
      """
      return self

    # Default view display
    security.declareProtected(Permissions.View, '__call__')
    def __call__(self):
      """
        If a Web Section has a default document, we render
        the default document instead of rendering the Web Section
        itself.

        The implementation is based on the presence of specific
        variables in the REQUEST (besides editable_mode and
        ignore_layout).

        current_web_section -- defines the Web Section which is
        used to display the current document.

        current_web_document -- defines the Document (ex. Web Page)
        which is being displayed within current_web_section.

        is_web_section_default_document -- a boolean which is
        set each time we display a default document as a section.

        We use REQUEST parameters so that they are reset for every
        Web transaction and can be accessed from widgets. 
      """
      self.REQUEST.set('current_web_section', self)
      if not self.REQUEST.get('editable_mode') and not self.REQUEST.get('ignore_layout'):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
250 251 252 253 254
        # Try to use a custom renderer if any
        custom_render_method_id = self.getCustomRenderMethodId()
        if custom_render_method_id is not None:
          return getattr(self, custom_render_method_id)()
        # The following could be moved to a typed based method for more flexibility
255 256
        document = self.getDefaultDocumentValue()
        if document is not None:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
257
          self.REQUEST.set('current_web_document', document.__of__(self)) # Used to be document
258 259 260 261 262 263 264 265 266 267 268 269 270 271
          self.REQUEST.set('is_web_section_default_document', 1)
          return document.__of__(self)()
      return Domain.__call__(self)

    # Layout Selection API
    security.declareProtected(Permissions.AccessContentsInformation, 'getApplicableLayout')
    def getApplicableLayout(self):
      """
        The applicable layout on a section is the container layout.
      """
      return self.getContainerLayout()

    # WebSection API
    security.declareProtected(Permissions.View, 'getDocumentValue')
272
    def getDocumentValue(self, name=None, portal=None, **kw):
273 274 275 276 277 278 279 280 281 282
      """
        Return the default document with the given
        name. The name parameter may represent anything
        such as a document reference, an identifier,
        etc.

        If name is not provided, the method defaults
        to returning the default document by calling
        getDefaultDocumentValue.

283 284 285
        kw parameters can be useful to filter content
        (ex. force a given validation state)

286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
        This method must be implemented through a
        portal type dependent script:
          WebSection_getDocumentValue
      """
      if name is None:
        return self.getDefaultDocumentValue()

      cache = getReadOnlyTransactionCache(self)
      method = None
      if cache is not None:
        key = ('getDocumentValue', self)
        try:
          method = cache[key]
        except KeyError:
          pass

      if method is None: method = self._getTypeBasedMethod('getDocumentValue',
                                        fallback_script_id='WebSection_getDocumentValue')

      if cache is not None:
        if not cache.has_key(key): cache[key] = method

308
      return method(name, portal=portal, **kw)
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376

    security.declareProtected(Permissions.View, 'getDefaultDocumentValue')
    def getDefaultDocumentValue(self):
      """
        Return the default document of the current
        section.

        This method must be implemented through a
        portal type dependent script:
          WebSection_getDefaultDocumentValue
      """
      cache = getReadOnlyTransactionCache(self)
      if cache is not None:
        key = ('getDefaultDocumentValue', self)
        try:
          return cache[key]
        except KeyError:
          pass

      result = self._getTypeBasedMethod('getDefaultDocumentValue',
                     fallback_script_id='WebSection_getDefaultDocumentValue')()

      if cache is not None:
        cache[key] = result

      return result

    security.declareProtected(Permissions.View, 'getDocumentValueList')
    def getDocumentValueList(self, **kw):
      """
        Return the list of documents which belong to the
        current section. The API is designed to
        support additional parameters so that it is possible
        to group documents by reference, version, language, etc.
        or to implement filtering of documents.

        This method must be implemented through a
        portal type dependent script:
          WebSection_getDocumentValueList
      """
      cache = getReadOnlyTransactionCache(self)
      if cache is not None:
        key = ('getDocumentValueList', self) + tuple(kw.items())
        try:
          return cache[key]
        except KeyError:
          pass

      result = self._getTypeBasedMethod('getDocumentValueList',
                     fallback_script_id='WebSection_getDocumentValueList')(**kw)

      if cache is not None:
        cache[key] = result

      return result

    security.declareProtected(Permissions.View, 'getPermanentURL')
    def getPermanentURL(self, document):
      """
        Return a permanent URL of document in the context
        of the current section.

        This method must be implemented through a
        portal type dependent script:
          WebSection_getPermanentURL
      """
      cache = getReadOnlyTransactionCache(self)
      if cache is not None:
377
        key = ('getPermanentURL', self, document.getPath())
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402
        try:
          return cache[key]
        except KeyError:
          pass

      result = self._getTypeBasedMethod('getPermanentURL',
                     fallback_script_id='WebSection_getPermanentURL')(document)

      if cache is not None:
        cache[key] = result

      return result

    security.declareProtected(Permissions.View, 'getBreadcrumbItemList')
    def getBreadcrumbItemList(self, document):
      """
        Return a section dependent breadcrumb in the form
        of a list of (title, document) tuples.

        This method must be implemented through a
        portal type dependent script:
          WebSection_getBreadcrumbItemList
      """
      cache = getReadOnlyTransactionCache(self)
      if cache is not None:
403
        key = ('getBreadcrumbItemList', self, document.getPath())
404 405 406 407 408 409 410 411 412 413 414 415
        try:
          return cache[key]
        except KeyError:
          pass

      result = self._getTypeBasedMethod('getBreadcrumbItemList',
                     fallback_script_id='WebSection_getBreadcrumbItemList')(document)

      if cache is not None:
        cache[key] = result

      return result