vendor/CMFonFive/1.2.0

changeset 0:f34bdb56bc9c 1.2.0

Import in tag instead.
author sfermigier
date Fri, 26 Aug 2005 13:12:08 +0000
parents
children d9180cd7b7c5
files CHANGES.txt COPYING.txt Extensions/install.py README.txt __init__.py configure.zcml five_template.pt fiveactionstool.py globalbrowsermenuservice.py interfaces.py meta.zcml tests/__init__.py tests/products/CMFonFiveTest/__init__.py tests/products/CMFonFiveTest/cmfcontent.py tests/products/CMFonFiveTest/configure.zcml tests/products/CMFonFiveTest/interfaces.py tests/products/CMFonFiveTest/tests/__init__.py tests/products/CMFonFiveTest/tests/test_actionstool.py tests/products/CMFonFiveTest/www/cmfContentAdd.zpt tests/products/README.txt tool.gif version.txt www/document_edit.pt www/document_view.pt www/folder_contents.pt
diffstat 25 files changed, 1139 insertions(+), 0 deletions(-) [+]
line diff
     1.1 new file mode 100644
     1.2 --- /dev/null
     1.3 +++ b/CHANGES.txt
     1.4 @@ -0,0 +1,27 @@
     1.5 +CMFonFive Product Changelog
     1.6 +
     1.7 +  CMFonFive 1.2.0 (2005/07/22)
     1.8 +  
     1.9 +    - CMF 1.5.2 moves the interface bridging into CMF itself, so it it
    1.10 +      removed from CMFonFive.
    1.11 +  
    1.12 +  CMFonFive 1.1.0 (2005/06/19)
    1.13 +
    1.14 +    - A new ZCML directive has been added: cmf:menuItem. This works
    1.15 +      as browser:menuItem, except that the filter parameter will be
    1.16 +      evaluated as a Zope2 TALES statement instead of a Zope3 TALES
    1.17 +      statement. In practice, if you need filters on your menuItems,
    1.18 +      you will want to use this. 
    1.19 +      
    1.20 +    - The five_template has some errors giving templates double <html>-tags.
    1.21 +    
    1.22 +  CMFonFive 1.0.1 (2005/06/01)
    1.23 +
    1.24 +    - configure.zcml:  Provided an i18n translation domain, 'CMFonFive'.
    1.25 +
    1.26 +    - permissions.py:  Use newer permission imports from CMF 1.5,
    1.27 +      if available (fall back to 1.4 spellings).
    1.28 +
    1.29 +  CMFonFive 1.0.0 (2005/04/01)
    1.30 +
    1.31 +    - Initial release.
     2.1 new file mode 100644
     2.2 --- /dev/null
     2.3 +++ b/COPYING.txt
     2.4 @@ -0,0 +1,27 @@
     2.5 +Five is distributed under the provisions of the Zope Public License
     2.6 +(ZPL) v2.1.  See doc/ZopePublicLicense.txt for the license text.
     2.7 +
     2.8 +Copyright (C) 2005 Five Contributors. See CREDITS.txt for a list of
     2.9 +Five contributors.
    2.10 +
    2.11 +Five contains source code derived from:
    2.12 +
    2.13 +- Zope 3, copyright (C) 2001-2005 by Zope Corporation.  Code that
    2.14 +  falls under this copyright is prefixed with the following header:
    2.15 +
    2.16 +  Copyright (c) 2001-2004 Zope Corporation and Contributors.
    2.17 +  All Rights Reserved.
    2.18 +
    2.19 +  This software is subject to the provisions of the Zope Public
    2.20 +  License, Version 2.1 (ZPL).  A copy of the ZPL should accompany this
    2.21 +  distribution.  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL
    2.22 +  EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT
    2.23 +  LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY,
    2.24 +  AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE.
    2.25 +
    2.26 +- metaclass.py is derived from PEAK, copyright (C) 1996-2004 by
    2.27 +  Phillip J. Eby and Tyler C. Sarna. PEAK may be used under the same
    2.28 +  terms as Zope.
    2.29 +
    2.30 +- TrustedExecutables. Dieter Mauer kindly allow licensing this under the
    2.31 +  ZPL 2.1.
    2.32 \ No newline at end of file
     3.1 new file mode 100644
     3.2 --- /dev/null
     3.3 +++ b/Extensions/install.py
     3.4 @@ -0,0 +1,110 @@
     3.5 +# (C) Copyright 2005 Nuxeo SARL <http://nuxeo.com>
     3.6 +# Author: Lennart Regebro <regebro@nuxeo.com>
     3.7 +#
     3.8 +# This program is free software; you can redistribute it and/or modify
     3.9 +# it under the terms of the GNU General Public License version 2 as published
    3.10 +# by the Free Software Foundation.
    3.11 +#
    3.12 +# This program is distributed in the hope that it will be useful,
    3.13 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    3.14 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    3.15 +# GNU General Public License for more details.
    3.16 +#
    3.17 +# You should have received a copy of the GNU General Public License
    3.18 +# along with this program; if not, write to the Free Software
    3.19 +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
    3.20 +# 02111-1307, USA.
    3.21 +#
    3.22 +# $Id: install.py 10227 2005-04-01 16:51:45Z regebro $
    3.23 +
    3.24 +from zLOG import LOG, INFO, DEBUG
    3.25 +from Products.CMFCore.utils import getToolByName, _marker
    3.26 +
    3.27 +log_ok_message = '...Already correctly installed'
    3.28 +
    3.29 +class CMFonFiveInstaller:
    3.30 +    """A simplified version of the installers from CPSInstaller"""
    3.31 +
    3.32 +    # Sure, it's overkill, but easier that writing things from scratch. /Lennart
    3.33 +   
    3.34 +    product_name = 'CMFonFive'
    3.35 +
    3.36 +    def __init__(self, context):
    3.37 +        """CMFInstaller initialization
    3.38 +
    3.39 +        product_name should be set as a class attribute when subclassing,
    3.40 +        but must be passed if you are not subclassing the installer.
    3.41 +
    3.42 +        is_main_installer should be se to 0 if this installer is called
    3.43 +        from another installer to prevent multiple reindexing of catalogs
    3.44 +        and similar actions that only needs to be done once.
    3.45 +        """
    3.46 +        self.context = context
    3.47 +        self.messages = []
    3.48 +        self.portal = context.portal_url.getPortalObject()
    3.49 +
    3.50 +    #
    3.51 +    # Logging
    3.52 +    #
    3.53 +    def log(self, message):
    3.54 +        self.messages.append(message)
    3.55 +        LOG(self.product_name, INFO, message)
    3.56 +
    3.57 +    def logOK(self):
    3.58 +        self.messages[-1] = self.messages[-1] + log_ok_message
    3.59 +        #self.log(log_ok_message)
    3.60 +
    3.61 +    def flush(self):
    3.62 +        log = '\n'.join(self.messages)
    3.63 +        self.messages = []
    3.64 +        return log
    3.65 +
    3.66 +    def logResult(self):
    3.67 +        if not self.isMainInstaller():
    3.68 +            return self.flush()
    3.69 +        # Wrap HTML around it if it's the main installer.
    3.70 +        return '''<html><head><title>%s</title></head>
    3.71 +            <body><pre>%s</pre></body></html>''' % (
    3.72 +            self.product_name, self.flush() )
    3.73 +    #
    3.74 +    # Other support methods
    3.75 +    #
    3.76 +    def portalHas(self, id):
    3.77 +        return id in self.portal.objectIds()
    3.78 +
    3.79 +    def getTool(self, id, default=_marker):
    3.80 +        """Gets the tool by id
    3.81 +        
    3.82 +        If No default is given, it will raise an error.
    3.83 +        """
    3.84 +        return getToolByName(self.portal, id, default)
    3.85 +    #
    3.86 +    # Methods to setup and manage actions
    3.87 +    #
    3.88 +    def verifyActionProvider(self, action_provider):
    3.89 +        self.log('Verifying action provider %s' % action_provider)
    3.90 +        atool = self.getTool('portal_actions')
    3.91 +        if action_provider in atool.listActionProviders():
    3.92 +            self.logOK()
    3.93 +        else:    
    3.94 +            atool.addActionProvider(action_provider)
    3.95 +            self.log(' Installed')
    3.96 +        
    3.97 +    def verifyTool(self, toolid, product, meta_type):
    3.98 +        self.log('Verifying tool %s' % toolid)
    3.99 +        if self.portalHas(toolid):
   3.100 +            tool = self.getTool(toolid)
   3.101 +            if tool.meta_type == meta_type:
   3.102 +                self.logOK()
   3.103 +                return
   3.104 +            self.log(' Deleting old %s tool' % tool.meta_type)
   3.105 +            self.portal.manage_delObjects([toolid])
   3.106 +        self.log(' Adding')
   3.107 +        self.portal.manage_addProduct[product].manage_addTool(meta_type)
   3.108 +
   3.109 +
   3.110 +def install(self):
   3.111 +    installer = CMFonFiveInstaller(self)
   3.112 +    installer.verifyTool('portal_fiveactions', 'CMFonFive', 'Five Actions Tool')
   3.113 +    installer.verifyActionProvider('portal_fiveactions')
   3.114 +    return installer.flush()
     4.1 new file mode 100644
     4.2 --- /dev/null
     4.3 +++ b/README.txt
     4.4 @@ -0,0 +1,68 @@
     4.5 +CMFonFive
     4.6 +=========
     4.7 +
     4.8 +CMFonFive is a product that provides integration between CMF and Five, 
     4.9 +making it ossible to write Five products tht run on CMF.
    4.10 +
    4.11 +* http://www.zope.org/Products/CMF/
    4.12 +
    4.13 +* http://codespeak.net/z3/five/
    4.14 +
    4.15 +CMFonFive has two main parts:
    4.16 +
    4.17 +* A set of interface definitions and bridges for many parts of CMF.
    4.18 +  (This part has been moved to CMF itself in CMF 1.5.2).
    4.19 +
    4.20 +* A Five Actions Tool, that enables you to display the Zope 3 menuItems
    4.21 +  as CMF actions. Any menuItem registered will be accessible though the 
    4.22 +  portal_actions tool, where the menu the item was registered for will be
    4.23 +  used as the action category.
    4.24 +
    4.25 +
    4.26 +Requirements
    4.27 +------------
    4.28 +
    4.29 +* Five 1.0.1 or 1.0.2
    4.30 +
    4.31 +* CMF 1.4 or 1.5.
    4.32 +
    4.33 +
    4.34 +Download
    4.35 +--------
    4.36 +Make sure you match your CMF version and your CMFonFive version!
    4.37 +
    4.38 +* CMFonFive 1.2.0 (2005-07-22). This is the version to use for CMF 1.5.2.
    4.39 +
    4.40 +  http://codespeak.net/z3/cmfonfive/release/CMFonFive-1.2.0.tgz
    4.41 +
    4.42 +* CMFonFive 1.1.0 (2005-06-19) This is the version to use for CMF 1.4, 
    4.43 +  1.5.0 and 1.5.1.
    4.44 +
    4.45 +  http://codespeak.net/z3/cmfonfive/release/CMFonFive-1.1.0.tgz
    4.46 +
    4.47 +These earlier versions are depracated, as later version have had bugs fixed.
    4.48 +
    4.49 +* CMFonFive 1.0.1 (2005-06-01) Supports CMF 1.4, 1.5.0 and 1.5.1.
    4.50 +
    4.51 +  http://codespeak.net/z3/cmfonfive/release/CMFonFive-1.0.1.tgz
    4.52 +
    4.53 +* CMFonFive 1.0.0 (2005-04-01) Supports CMF 1.4 only.
    4.54 +
    4.55 +  http://codespeak.net/z3/cmfonfive/release/CMFonFive-1.0.0.tgz
    4.56 +
    4.57 +
    4.58 +Installation
    4.59 +------------
    4.60 +The interface suppert needs no installation. If you want the menuItem 
    4.61 +support, you simply create a Five Actions Tool in your CMF site, and set 
    4.62 +that tool up as an action provider in the portal_actions tool.
    4.63 +
    4.64 +CMFonFive has support for CMFQuickInstaller, so if you have that installed
    4.65 +you can use that to do the above installation.
    4.66 +
    4.67 +
    4.68 +Contributors
    4.69 +------------
    4.70 +* Tres Seaver (tseaver@zope.com)
    4.71 +
    4.72 +* Lennart Regebro (regebro@nuxeo.com)
     5.1 new file mode 100644
     5.2 --- /dev/null
     5.3 +++ b/__init__.py
     5.4 @@ -0,0 +1,22 @@
     5.5 +##############################################################################
     5.6 +#
     5.7 +# Copyright (c) 2005 CMFonFive Contributors. All rights reserved.
     5.8 +#
     5.9 +# This software is distributed under the terms of the Zope Public
    5.10 +# License (ZPL) v2.1. See COPYING.txt for more information.
    5.11 +#
    5.12 +###########################################################################
    5.13 +""" Bridge CMF to Five.
    5.14 +
    5.15 +$Id: __init__.py 10227 2005-04-01 16:51:45Z regebro $
    5.16 +"""
    5.17 +from Products.CMFCore.utils import ToolInit
    5.18 +import fiveactionstool, interfaces
    5.19 +
    5.20 +def initialize(context):
    5.21 +
    5.22 +    ToolInit( 'Five Tools'
    5.23 +            , tools=(fiveactionstool.FiveActionsTool,)
    5.24 +            , product_name='CMFonFive'
    5.25 +            , icon='tool.gif'
    5.26 +            ).initialize( context )
     6.1 new file mode 100644
     6.2 --- /dev/null
     6.3 +++ b/configure.zcml
     6.4 @@ -0,0 +1,33 @@
     6.5 +<configure
     6.6 +  xmlns="http://namespaces.zope.org/zope"
     6.7 +  xmlns:five="http://namespaces.zope.org/five"
     6.8 +  xmlns:browser="http://namespaces.zope.org/browser"
     6.9 +  i18n_domain="CMFonFive"
    6.10 +  >
    6.11 +
    6.12 +  <!-- Set up default menues as action categories. -->
    6.13 +  <browser:menu
    6.14 +    id="object"
    6.15 +    title="Object menu"
    6.16 +    />
    6.17 +
    6.18 +  <browser:layer
    6.19 +    name="cmf"
    6.20 +    />
    6.21 +  <browser:skin
    6.22 +    name="cmf"
    6.23 +    layers="cmf default"
    6.24 +    />
    6.25 +  <browser:defaultSkin
    6.26 +    name="cmf"
    6.27 +    />
    6.28 +
    6.29 +  <browser:page
    6.30 +    for="*"
    6.31 +    template="five_template.pt"
    6.32 +    name="five_template"
    6.33 +    permission="zope.Public"
    6.34 +    layer="cmf"
    6.35 +    />
    6.36 +
    6.37 +</configure>
     7.1 new file mode 100644
     7.2 --- /dev/null
     7.3 +++ b/five_template.pt
     7.4 @@ -0,0 +1,15 @@
     7.5 +<metal:block define-macro="page"><metal:block use-macro="here/main_template/macros/master">
     7.6 + <metal:block fill-slot="base">
     7.7 +   <metal:block define-slot="base" />
     7.8 + </metal:block>
     7.9 + <metal:block fill-slot="header">
    7.10 +   <metal:block define-slot="header" />
    7.11 + </metal:block>
    7.12 + <metal:block fill-slot="css_slot">
    7.13 +   <metal:block define-slot="style_slot" />
    7.14 + </metal:block>
    7.15 + <metal:block fill-slot="main">
    7.16 +   <metal:block define-slot="body" />
    7.17 + </metal:block>
    7.18 +</metal:block>
    7.19 +</metal:block>
     8.1 new file mode 100644
     8.2 --- /dev/null
     8.3 +++ b/fiveactionstool.py
     8.4 @@ -0,0 +1,81 @@
     8.5 +##############################################################################
     8.6 +#
     8.7 +# Copyright (c) 2005 CMFonFive Contributors. All rights reserved.
     8.8 +#
     8.9 +# This software is distributed under the terms of the Zope Public
    8.10 +# License (ZPL) v2.1. See COPYING.txt for more information.
    8.11 +#
    8.12 +###########################################################################
    8.13 +""" Five actions tool.
    8.14 +
    8.15 +$Id: fiveactionstool.py 14923 2005-07-22 15:47:32Z regebro $
    8.16 +"""
    8.17 +
    8.18 +from AccessControl import ClassSecurityInfo
    8.19 +from Acquisition import aq_base
    8.20 +from Globals import InitializeClass
    8.21 +from OFS.SimpleItem import SimpleItem
    8.22 +
    8.23 +from Products.CMFCore.ActionInformation import ActionInformation
    8.24 +from Products.CMFCore.ActionProviderBase import ActionProviderBase
    8.25 +from Products.CMFCore.Expression import Expression
    8.26 +from Products.CMFCore.utils import UniqueObject
    8.27 +
    8.28 +from zope.app.publisher.browser.globalbrowsermenuservice import \
    8.29 +     globalBrowserMenuService
    8.30 +from globalbrowsermenuservice import getMenu
    8.31 +from Products.Five import security
    8.32 +import zope.thread
    8.33 +
    8.34 +class FiveActionsTool( UniqueObject, SimpleItem, ActionProviderBase ):
    8.35 +    """ Links content to discussions.
    8.36 +    """
    8.37 +
    8.38 +    __implements__ = (ActionProviderBase.__implements__)
    8.39 +
    8.40 +    id = 'portal_fiveactions'
    8.41 +    meta_type = 'Five Actions Tool'
    8.42 +
    8.43 +    security = ClassSecurityInfo()
    8.44 +
    8.45 +    def getReqestURL(self):
    8.46 +        return self.REQUEST.URL
    8.47 +
    8.48 +    security.declarePrivate('listActions')
    8.49 +    def listActions(self, info=None, object=None):
    8.50 +        """ List all the actions defined by a provider.
    8.51 +        """       
    8.52 +        # Necessary to make the Request look like a Zope3 request
    8.53 +        # XXX This can be removed when we no longer need Five 1.0 compatibility.
    8.54 +        #self.REQUEST.getURL = self.getReqestURL
    8.55 +        if object is None:
    8.56 +            object = info.content
    8.57 +        actions = []
    8.58 +        for mid in globalBrowserMenuService._registry.keys():
    8.59 +            menu = getMenu(mid, object, self.REQUEST)
    8.60 +            for entry in menu:
    8.61 +                # The action needs a unique name, so I'll build one
    8.62 +                # from the object_id and the action url. That is sure
    8.63 +                # to be unique.
    8.64 +                action = str(entry['action'])
    8.65 +                if object is None:
    8.66 +                    act_id = 'action_%s' % action
    8.67 +                else:
    8.68 +                    act_id = 'action_%s_%s' % (object.getId(), action)
    8.69 +                    
    8.70 +                if entry['filter'] is None:
    8.71 +                    filter = None
    8.72 +                else:
    8.73 +                    filter = Expression(text=str(entry['filter']))
    8.74 +
    8.75 +                act = ActionInformation(id=act_id,
    8.76 +                    title=str(entry['title']),
    8.77 +                    action=Expression(text='string:%s' % action),
    8.78 +                    condition=filter,
    8.79 +                    category=str(mid),
    8.80 +                    visible=1)
    8.81 +                actions.append(act)
    8.82 +        return actions or ()
    8.83 +
    8.84 +
    8.85 +InitializeClass( FiveActionsTool )
     9.1 new file mode 100644
     9.2 --- /dev/null
     9.3 +++ b/globalbrowsermenuservice.py
     9.4 @@ -0,0 +1,158 @@
     9.5 +##############################################################################
     9.6 +#
     9.7 +# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
     9.8 +# All Rights Reserved.
     9.9 +#
    9.10 +# This software is subject to the provisions of the Zope Public License,
    9.11 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
    9.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
    9.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    9.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
    9.15 +# FOR A PARTICULAR PURPOSE.
    9.16 +#
    9.17 +##############################################################################
    9.18 +"""Global Browser Menu Service
    9.19 +
    9.20 +$Id: globalbrowsermenuservice.py 27079 2004-08-12 19:45:55Z srichter $
    9.21 +"""
    9.22 +from zope.security.interfaces import Unauthorized, Forbidden
    9.23 +from zope.interface import implementedBy
    9.24 +from Products.Five.security import checkPermission, CheckerPublic
    9.25 +from zope.app.publication.browser import PublicationTraverser
    9.26 +from zope.app.component.interface import provideInterface
    9.27 +from zope.app.servicenames import BrowserMenu
    9.28 +
    9.29 +from zope.app.publisher.browser.globalbrowsermenuservice import globalBrowserMenuService, MenuItem
    9.30 +
    9.31 +from zope.interface.interfaces import IInterface
    9.32 +import types
    9.33 +
    9.34 +def addMenuItem(menu_id, interface, action, title,
    9.35 +                description='', filter_string=None, permission=None,
    9.36 +                extra=None,
    9.37 +                ):
    9.38 +    registry = globalBrowserMenuService._registry[menu_id].registry
    9.39 +
    9.40 +    if permission:
    9.41 +        if permission == 'zope.Public':
    9.42 +            permission = CheckerPublic
    9.43 +
    9.44 +    if interface is not None and not IInterface.providedBy(interface):
    9.45 +        if isinstance(interface, (type, types.ClassType)):
    9.46 +            interface = implementedBy(interface)
    9.47 +        else:
    9.48 +            raise TypeError(
    9.49 +                "The interface argument must be an interface (or None) "
    9.50 +                "or a class.")
    9.51 +
    9.52 +    data = registry.get(interface) or []
    9.53 +    data.append(
    9.54 +        MenuItem(action, title, description, filter_string, permission, extra)
    9.55 +        )
    9.56 +    registry.register(interface, data)
    9.57 +
    9.58 +
    9.59 +def getMenu(menu_id, object, request, max=999999):
    9.60 +    traverser = PublicationTraverser()
    9.61 +
    9.62 +    result = []
    9.63 +    seen = {}
    9.64 +
    9.65 +    # stuff for figuring out the selected view
    9.66 +    request_url = request.getURL()
    9.67 +
    9.68 +    for item in globalBrowserMenuService.getAllMenuItems(menu_id, object):
    9.69 +
    9.70 +        # Make sure we don't repeat a specification for a given title
    9.71 +        title = item.title
    9.72 +        if title in seen:
    9.73 +            continue
    9.74 +        seen[title] = 1
    9.75 +
    9.76 +        permission = item.permission
    9.77 +        action = item.action
    9.78 +        
    9.79 +        if permission:
    9.80 +            # If we have an explicit permission, check that we
    9.81 +            # can access it.
    9.82 +            if not checkPermission(permission, object):
    9.83 +                continue
    9.84 +
    9.85 +        elif action:
    9.86 +            # Otherwise, test access by attempting access
    9.87 +            path = action
    9.88 +            l = action.find('?')
    9.89 +            if l >= 0:
    9.90 +               path = action[:l]
    9.91 +            try:
    9.92 +                v = traverser.traverseRelativeURL(
    9.93 +                    request, object, path)
    9.94 +                # TODO:
    9.95 +                # tickle the security proxy's checker
    9.96 +                # we're assuming that view pages are callable
    9.97 +                # this is a pretty sound assumption
    9.98 +                v.__call__
    9.99 +            except (Unauthorized, Forbidden):
   9.100 +                continue # Skip unauthorized or forbidden
   9.101 +
   9.102 +        normalized_action = action
   9.103 +        if action.startswith('@@'):
   9.104 +            normalized_action = action[2:]
   9.105 +
   9.106 +        if request_url.endswith('/'+normalized_action):
   9.107 +            selected='selected'
   9.108 +        elif request_url.endswith('/++view++'+normalized_action):
   9.109 +            selected='selected'
   9.110 +        elif request_url.endswith('/@@'+normalized_action):
   9.111 +            selected='selected'
   9.112 +        else:
   9.113 +            selected=''
   9.114 +
   9.115 +        result.append({
   9.116 +            'title': title,
   9.117 +            'description': item.description,
   9.118 +            'action': "%s" % action,
   9.119 +            'filter': item.filter,
   9.120 +            'selected': selected,
   9.121 +            'extra': item.extra,
   9.122 +            })
   9.123 +
   9.124 +        if len(result) >= max:
   9.125 +            return result
   9.126 +
   9.127 +    return result
   9.128 +
   9.129 +
   9.130 +
   9.131 +def menuItemDirective(_context, menu, for_,
   9.132 +                      action, title, description='', filter=None,
   9.133 +                      permission=None, extra=None):
   9.134 +    return menuItemsDirective(_context, menu, for_).menuItem(
   9.135 +        _context, action, title, description, filter, permission, extra)
   9.136 +
   9.137 +
   9.138 +class menuItemsDirective(object):
   9.139 +
   9.140 +    def __init__(self, _context, menu, for_):
   9.141 +        self.interface = for_
   9.142 +        self.menu = menu
   9.143 +
   9.144 +    def menuItem(self, _context, action, title, description='',
   9.145 +                 filter=None, permission=None, extra=None):
   9.146 +        _context.action(
   9.147 +            discriminator = ('browser:menuItem',
   9.148 +                             self.menu, self.interface, title),
   9.149 +            callable = addMenuItem,
   9.150 +            args = (self.menu, self.interface,
   9.151 +                    action, title, description, filter, permission, extra),
   9.152 +            ),
   9.153 +
   9.154 +    def __call__(self, _context):
   9.155 +        _context.action(
   9.156 +            discriminator = None,
   9.157 +            callable = provideInterface,
   9.158 +            args = (self.interface.__module__+'.'+self.interface.getName(),
   9.159 +                    self.interface)
   9.160 +            )
   9.161 +        
   9.162 +
    10.1 new file mode 100644
    10.2 --- /dev/null
    10.3 +++ b/interfaces.py
    10.4 @@ -0,0 +1,23 @@
    10.5 +##############################################################################
    10.6 +#
    10.7 +# Copyright (c) 2005 CMFonFive Contributors. All rights reserved.
    10.8 +#
    10.9 +# This software is distributed under the terms of the Zope Public
   10.10 +# License (ZPL) v2.1. See COPYING.txt for more information.
   10.11 +#
   10.12 +###########################################################################
   10.13 +""" Interfaces for the action menu integration
   10.14 +
   10.15 +$Id: interfaces.py 14935 2005-07-22 17:34:41Z regebro $
   10.16 +"""
   10.17 +
   10.18 +from zope.app.publisher.interfaces.browser import IBrowserMenu
   10.19 +from zope.schema import TextLine
   10.20 +
   10.21 +class IActionMenu(IBrowserMenu):
   10.22 +
   10.23 +    category = TextLine(
   10.24 +        title=u"Category",
   10.25 +        description=u"The action category of this action menu",
   10.26 +        required=True)
   10.27 +
    11.1 new file mode 100644
    11.2 --- /dev/null
    11.3 +++ b/meta.zcml
    11.4 @@ -0,0 +1,34 @@
    11.5 +<configure
    11.6 +    xmlns="http://namespaces.zope.org/zope"
    11.7 +    xmlns:meta="http://namespaces.zope.org/meta">
    11.8 +
    11.9 +  <meta:directives namespace="http://namespaces.zope.org/cmf">
   11.10 +
   11.11 +    <meta:directive
   11.12 +        name="menu"
   11.13 +        schema="zope.app.publisher.browser.metadirectives.IMenuDirective"
   11.14 +        handler="zope.app.publisher.browser.globalbrowsermenuservice.menuDirective"
   11.15 +        />
   11.16 +
   11.17 +    <meta:directive
   11.18 +        name="menuItem"
   11.19 +        schema="zope.app.publisher.browser.metadirectives.IMenuItemDirective"
   11.20 +        handler=".globalbrowsermenuservice.menuItemDirective"
   11.21 +        />
   11.22 +
   11.23 +    <meta:complexDirective
   11.24 +        name="menuItems"
   11.25 +        schema="zope.app.publisher.browser.metadirectives.IMenuItemsDirective"
   11.26 +        handler=".globalbrowsermenuservice.menuItemsDirective"
   11.27 +        >
   11.28 +
   11.29 +      <meta:subdirective
   11.30 +          name="menuItem"
   11.31 +          schema="zope.app.publisher.browser.metadirectives.IMenuItemSubdirective"
   11.32 +          />
   11.33 +
   11.34 +    </meta:complexDirective>
   11.35 +
   11.36 +  </meta:directives>
   11.37 +
   11.38 +</configure>
    12.1 new file mode 100644
    12.2 --- /dev/null
    12.3 +++ b/tests/__init__.py
    12.4 @@ -0,0 +1,4 @@
    12.5 +""" Unit tests for CMFonFive.
    12.6 +
    12.7 +$Id: __init__.py 10227 2005-04-01 16:51:45Z regebro $
    12.8 +"""
    13.1 new file mode 100644
    13.2 --- /dev/null
    13.3 +++ b/tests/products/CMFonFiveTest/__init__.py
    13.4 @@ -0,0 +1,11 @@
    13.5 +import cmfcontent
    13.6 +
    13.7 +def initialize(context):
    13.8 +
    13.9 +    context.registerClass(
   13.10 +        cmfcontent.CMFContent,
   13.11 +        constructors = (cmfcontent.manage_addCMFContentForm,
   13.12 +                        cmfcontent.manage_addCMFContent),
   13.13 +        )
   13.14 +
   13.15 +
    14.1 new file mode 100644
    14.2 --- /dev/null
    14.3 +++ b/tests/products/CMFonFiveTest/cmfcontent.py
    14.4 @@ -0,0 +1,43 @@
    14.5 +from OFS.SimpleItem import SimpleItem
    14.6 +from Globals import InitializeClass
    14.7 +from AccessControl import ClassSecurityInfo
    14.8 +from Products.PageTemplates.PageTemplateFile import PageTemplateFile
    14.9 +from zope.interface import implements
   14.10 +from interfaces import ICMFContent
   14.11 +
   14.12 +class CMFContent(SimpleItem):
   14.13 +    implements(ICMFContent)
   14.14 +
   14.15 +    meta_type = 'CMFonFive Content'
   14.16 +    security = ClassSecurityInfo()
   14.17 +
   14.18 +    def __init__(self, id, title):
   14.19 +        self.id = id
   14.20 +        self.title = title
   14.21 +
   14.22 +    security.declarePublic('mymethod')
   14.23 +    def mymethod(self):
   14.24 +        return "Hello world"
   14.25 +
   14.26 +InitializeClass(CMFContent)
   14.27 +
   14.28 +
   14.29 +manage_addCMFContentForm = PageTemplateFile(
   14.30 +    "www/cmfContentAdd", globals(),
   14.31 +    __name__ = 'manage_addCmfContentForm')
   14.32 +
   14.33 +def manage_addCMFContent(self, id, title, REQUEST=None):
   14.34 +    """Add the simple content."""
   14.35 +    id = self._setObject(id, CMFContent(id, title))
   14.36 +
   14.37 +    if REQUEST is None:
   14.38 +        return
   14.39 +    try:
   14.40 +        u = self.DestinationURL()
   14.41 +    except:
   14.42 +        u = REQUEST['URL1']
   14.43 +    if REQUEST.has_key('submit_edit'):
   14.44 +        u = "%s/%s" % (u, urllib.quote(id))
   14.45 +    REQUEST.RESPONSE.redirect(u+'/manage_main')
   14.46 +
   14.47 +    return ''
    15.1 new file mode 100644
    15.2 --- /dev/null
    15.3 +++ b/tests/products/CMFonFiveTest/configure.zcml
    15.4 @@ -0,0 +1,30 @@
    15.5 +<configure xmlns="http://namespaces.zope.org/zope"
    15.6 +           xmlns:browser="http://namespaces.zope.org/browser"
    15.7 +           xmlns:five="http://namespaces.zope.org/five">
    15.8 +
    15.9 +  <five:traversable class=".cmfcontent.CMFContent" />
   15.10 +
   15.11 +  <!-- browser menu support -->
   15.12 +  <browser:menu
   15.13 +      id="folder"
   15.14 +      title="CMF menu" />
   15.15 +
   15.16 +  <browser:menuItem
   15.17 +      for=".interfaces.ICMFContent"
   15.18 +      menu="folder"
   15.19 +      title="Public Test Menu Item"
   15.20 +      action="public.html"
   15.21 +      description="This is a public test menu item"
   15.22 +      permission="zope.Public"
   15.23 +      />
   15.24 +
   15.25 +  <browser:menuItem
   15.26 +      for=".interfaces.ICMFContent"
   15.27 +      menu="folder"
   15.28 +      title="Protected Test Menu Item"
   15.29 +      action="protected.html"
   15.30 +      description="This is a protected test menu item"
   15.31 +      permission="zope2.ManageUsers"
   15.32 +      />
   15.33 +
   15.34 +</configure>
    16.1 new file mode 100644
    16.2 --- /dev/null
    16.3 +++ b/tests/products/CMFonFiveTest/interfaces.py
    16.4 @@ -0,0 +1,5 @@
    16.5 +from zope.interface import Interface
    16.6 +
    16.7 +class ICMFContent(Interface):
    16.8 +    pass
    16.9 +
    17.1 new file mode 100644
    17.2 --- /dev/null
    17.3 +++ b/tests/products/CMFonFiveTest/tests/__init__.py
    17.4 @@ -0,0 +1,4 @@
    17.5 +""" Unit tests for CMFonFive.
    17.6 +
    17.7 +$Id$
    17.8 +"""
    18.1 new file mode 100644
    18.2 --- /dev/null
    18.3 +++ b/tests/products/CMFonFiveTest/tests/test_actionstool.py
    18.4 @@ -0,0 +1,52 @@
    18.5 +""" Unit tests for mapped interfaces.
    18.6 +
    18.7 +$Id$
    18.8 +"""
    18.9 +
   18.10 +import unittest
   18.11 +from Testing import ZopeTestCase
   18.12 +from Testing.ZopeTestCase.functional import Functional
   18.13 +from Products.CMFCore.utils import getToolByName
   18.14 +from Products.Five.traversable import newInteraction
   18.15 +
   18.16 +# we need to install test products *before* Five as Five
   18.17 +# looks up zcml files in the products it can find.
   18.18 +ZopeTestCase.installProduct('CMFonFiveTest')
   18.19 +ZopeTestCase.installProduct('CMFonFive')
   18.20 +ZopeTestCase.installProduct('Five')
   18.21 +
   18.22 +class ActionsToolTests(ZopeTestCase.ZopeTestCase):
   18.23 +
   18.24 +    def afterSetUp(self):
   18.25 +        self.folder.manage_addProduct['CMFonFive'].manage_addTool(
   18.26 +            'Five Actions Tool')
   18.27 +        self._login()
   18.28 +
   18.29 +    def test_ActionsToolDocument(self):
   18.30 +        tool = getToolByName(self.folder, 'portal_fiveactions')
   18.31 +        # The action created for the content should appear in the list
   18.32 +        # of actions for such content
   18.33 +        self.folder.manage_addProduct['CMFonFiveTest'].manage_addCMFContent(
   18.34 +            'content', 'Content Title')
   18.35 +        newInteraction()
   18.36 +        actions = tool.listActions(object=self.folder.content)
   18.37 +        action_names = [action.id for action in actions]
   18.38 +        self.failUnless('action_content_public.html' in action_names,
   18.39 +            'Expected menu item was not found in action list')
   18.40 +        # But not the protected action:
   18.41 +        self.failIf('action_content_protected.html' in action_names,
   18.42 +            'Protected menu item was found in action list')
   18.43 +        # And there should be no actions anywhere else:
   18.44 +        self.failUnlessEqual(list(tool.listActions(object=self.folder)), [])
   18.45 +
   18.46 +
   18.47 +def test_suite():
   18.48 +    suite = unittest.TestSuite()
   18.49 +    suite.addTest(unittest.makeSuite(ActionsToolTests))
   18.50 +    return suite
   18.51 +
   18.52 +
   18.53 +
   18.54 +
   18.55 +if __name__ == '__main__':
   18.56 +    unittest.main()
    19.1 new file mode 100644
    19.2 --- /dev/null
    19.3 +++ b/tests/products/CMFonFiveTest/www/cmfContentAdd.zpt
    19.4 @@ -0,0 +1,45 @@
    19.5 +<h1 tal:replace="structure here/manage_page_header">Header</h1>
    19.6 +
    19.7 +<h2 tal:define="form_title string:Add CMF Content"
    19.8 +    tal:replace="structure here/manage_form_title">Form Title</h2>
    19.9 +
   19.10 +<p class="form-help">
   19.11 +Add CMF Content
   19.12 +</p>
   19.13 +
   19.14 +<form action="manage_addCMFContent" method="post">
   19.15 +<table cellspacing="0" cellpadding="2" border="0">
   19.16 +  <tr>
   19.17 +    <td align="left" valign="top">
   19.18 +    <div class="form-label">
   19.19 +    Id
   19.20 +    </div>
   19.21 +    </td>
   19.22 +    <td align="left" valign="top">
   19.23 +    <input type="text" name="id" size="40" />
   19.24 +    </td>
   19.25 +  </tr>
   19.26 +  <tr>
   19.27 +    <td align="left" valign="top">
   19.28 +    <div class="form-label">
   19.29 +    Title
   19.30 +    </div>
   19.31 +    </td>
   19.32 +    <td align="left" valign="top">
   19.33 +    <input type="text" name="title" size="40" />
   19.34 +    </td>
   19.35 +  </tr>
   19.36 +  <tr>
   19.37 +    <td align="left" valign="top">
   19.38 +    </td>
   19.39 +    <td align="left" valign="top">
   19.40 +    <div class="form-element">
   19.41 +    <input class="form-element" type="submit" name="submit_add"
   19.42 +     value=" Add " />
   19.43 +    </div>
   19.44 +    </td>
   19.45 +  </tr>
   19.46 +</table>
   19.47 +</form>
   19.48 +
   19.49 +<h1 tal:replace="structure here/manage_page_footer">Footer</h1>
    20.1 new file mode 100644
    20.2 --- /dev/null
    20.3 +++ b/tests/products/README.txt
    20.4 @@ -0,0 +1,17 @@
    20.5 +This directory contains a product CMFonFiveTest, which is used to
    20.6 +do testing of CMFonFive. Mainly, it tests that the zcml statements
    20.7 +work, by having a small product that uses the zcml statements.
    20.8 +
    20.9 +Most other things can be tested in the main unit tests of CMFonFive.
   20.10 +
   20.11 +To use these tests, move or link) the CMFonFiveTest directory to
   20.12 +your Products directory:
   20.13 +
   20.14 +  cd <zope_dir>/Products
   20.15 +  ln -s CMFonFive/tests/products/CMFonFiveTest .
   20.16 +
   20.17 +Then run the unit tests as normal
   20.18 +
   20.19 +  cd <zope_dir>
   20.20 +  bin/zopectl test --dir Products/CMFonFiveTest
   20.21 +
    21.1 new file mode 100644
    21.2 index 0000000000000000000000000000000000000000..8aa90b53b65ee410b8a9be3819fcba223c936c4e
    21.3 GIT binary patch
    21.4 literal 166
    21.5 zc${<hbhEHb6krfwc+9}CY0HL=j*jye&gSIiPn<a6=+R>pRTa;lKc6*c#>$l|!@|OE
    21.6 z+_;gLl<4i_^&bor|MB@IrlcyAXO?6rxO@5rFev_HVdP@qXV75)0+6W;ERG*edamB8
    21.7 z5Tng@Gc-|#-=T@ALFE+FH0H}Z`3En&SC;VkC&SXpufZbA$LjKNlL8A@=P~XD7D8Nh
    21.8 Qa~U7Jeo%8GoQ1&}0ANHx<p2Nx
    21.9 
    22.1 new file mode 100644
    22.2 --- /dev/null
    22.3 +++ b/version.txt
    22.4 @@ -0,0 +1,1 @@
    22.5 +CMFonFive-1.2.0
    23.1 new file mode 100644
    23.2 --- /dev/null
    23.3 +++ b/www/document_edit.pt
    23.4 @@ -0,0 +1,77 @@
    23.5 +<tal:x tal:define="here nocall: here/context;">
    23.6 +<html xmlns:tal="http://xml.zope.org/namespaces/tal"
    23.7 +      xmlns:metal="http://xml.zope.org/namespaces/metal"
    23.8 +      metal:use-macro="here/main_template/macros/master"
    23.9 +>
   23.10 +<body>
   23.11 +<div metal:fill-slot="main" i18n:domain="cmf_default">
   23.12 +<div class="Desktop">
   23.13 +
   23.14 +<h2 i18n:translate="">Edit <span
   23.15 +    tal:replace="here/getId" i18n:name="objectid">My ID</span></h2>
   23.16 +
   23.17 +<form action="document_edit" method="post" enctype="multipart/form-data"
   23.18 +      tal:attributes="action string:${here/absolute_url}/document_edit"
   23.19 +>
   23.20 +
   23.21 + <input type="hidden" name="SafetyBelt" value=""
   23.22 +        tal:attributes="value here/SafetyBelt" />
   23.23 +
   23.24 +<table class="FormLayout">
   23.25 + <tr>
   23.26 +  <th i18n:translate="">Title</th>
   23.27 +  <td>
   23.28 +   <span tal:replace="here/Title">Title</span>
   23.29 +  </td>
   23.30 + </tr>
   23.31 + <tr>
   23.32 +  <th i18n:translate="">Description</th>
   23.33 +  <td>
   23.34 +   <span tal:replace="here/Description">Description</span>
   23.35 +  </td>
   23.36 + </tr>
   23.37 + <tr>
   23.38 +  <th i18n:translate="">Format</th>
   23.39 +  <td>
   23.40 +   <input type="radio" name="text_format" value="structured-text" id="cb_stx"
   23.41 +          tal:attributes="
   23.42 +                checked python:path('here/text_format')=='structured-text'" />
   23.43 +   <label for="cb_stx" i18n:translate="">structured-text</label>
   23.44 +   <input type="radio" name="text_format" value="plain" id="cb_plain"
   23.45 +          tal:attributes="checked python:path('here/text_format')=='plain'" />
   23.46 +   <label for="cb_plain" i18n:translate="">plain text</label>
   23.47 +   <input type="radio" name="text_format" value="html" id="cb_html"
   23.48 +          tal:attributes="checked python:path('here/text_format')=='html'" />
   23.49 +   <label for="cb_html" i18n:translate="">html</label>
   23.50 +  </td>
   23.51 + </tr>
   23.52 + <tr>
   23.53 +  <th i18n:translate="">Upload</th>
   23.54 +  <td>
   23.55 +   <input type="file" name="file" size="25" />
   23.56 +  </td>
   23.57 + </tr>
   23.58 + <tr>
   23.59 +  <th class="TextField" i18n:translate="">Edit</th>
   23.60 +  <td class="TextField">
   23.61 +   <textarea name="text:text" rows="20" cols="80" wrap="soft"
   23.62 +             tal:content="here/EditableBody"></textarea>
   23.63 +  </td>
   23.64 + </tr>
   23.65 + <tr>
   23.66 +  <td> <br /> </td>
   23.67 +  <td>
   23.68 +   <input type="submit" name="change" value="Change"
   23.69 +          i18n:attributes="value" />
   23.70 +   <input type="submit" name="change_and_view" value="Change and View"
   23.71 +          i18n:attributes="value" />
   23.72 +  </td>
   23.73 + </tr>
   23.74 +</table>
   23.75 +</form>
   23.76 +
   23.77 +</div>
   23.78 +</div>
   23.79 +</body>
   23.80 +</html>
   23.81 +</tal:x>
    24.1 new file mode 100644
    24.2 --- /dev/null
    24.3 +++ b/www/document_view.pt
    24.4 @@ -0,0 +1,37 @@
    24.5 +<tal:x tal:define="here nocall: here/context;">
    24.6 +<html xmlns:tal="http://xml.zope.org/namespaces/tal"
    24.7 +      xmlns:metal="http://xml.zope.org/namespaces/metal"
    24.8 +      metal:use-macro="here/main_template/macros/master">
    24.9 +  <metal:block fill-slot="base">
   24.10 +      <base href=""
   24.11 +            tal:attributes="href python: here.absolute_url() + '/'">
   24.12 +  </metal:block>
   24.13 +<body>
   24.14 +
   24.15 +<div metal:fill-slot="header">
   24.16 +
   24.17 +    <h1 id="DesktopTitle"
   24.18 +        tal:content="here/Title">Document Title</h1>
   24.19 +
   24.20 +    <div id="DesktopDescription" tal:content="here/Description">
   24.21 +        Document Description goes here.
   24.22 +    </div>
   24.23 +
   24.24 +    <div metal:use-macro="here/content_byline/macros/byline">By Me</div>
   24.25 +
   24.26 +</div>
   24.27 +
   24.28 +<div metal:fill-slot="main">
   24.29 +
   24.30 +<div tal:replace="structure here/CookedBody">Cooked Body</div>
   24.31 +
   24.32 +<div class="Discussion">
   24.33 +   <span tal:replace="structure here/viewThreadsAtBottom"
   24.34 +         tal:condition="here/viewThreadsAtBottom|nothing"></span>
   24.35 +</div>
   24.36 +
   24.37 +</div>
   24.38 +
   24.39 +</body>
   24.40 +</html>
   24.41 +</tal:x>
    25.1 new file mode 100644
    25.2 --- /dev/null
    25.3 +++ b/www/folder_contents.pt
    25.4 @@ -0,0 +1,215 @@
    25.5 +<tal:x tal:define="global here here/context;" />
    25.6 +<html xmlns:tal="http://xml.zope.org/namespaces/tal"
    25.7 +      xmlns:metal="http://xml.zope.org/namespaces/metal"
    25.8 +      metal:use-macro="here/context/main_template/macros/master"
    25.9 +>
   25.10 +<body>
   25.11 +<div metal:fill-slot="main"
   25.12 +     tal:define="m_tool here/portal_membership;
   25.13 +                 checkPerm nocall: m_tool/checkPermission;
   25.14 +                 list_here python:checkPerm('List folder contents', here);
   25.15 +                 list_parent python:checkPerm( 'List folder contents', here
   25.16 +                                             , 'aq_parent' );
   25.17 +                "
   25.18 +     i18n:domain="cmf_default"
   25.19 +>
   25.20 +<div tal:condition="not: list_here"
   25.21 +      tal:define="response request/RESPONSE;
   25.22 +                  url here/absolute_url;
   25.23 +                 "
   25.24 + >
   25.25 +  <span tal:define="redirect python: response.redirect( url )"></span>
   25.26 +</div>
   25.27 +
   25.28 +<!-- This is the desktop area -->
   25.29 +<div class="Desktop">
   25.30 +
   25.31 +<h1 i18n:translate="">Desktop</h1>
   25.32 +
   25.33 +<form action="" method="post"
   25.34 +      tal:attributes="action here/absolute_url">
   25.35 + <table class="FormLayout">
   25.36 +  <tr>
   25.37 +   <td valign="top">
   25.38 +    <table class="ContentsList">
   25.39 +     <!-- Navigate to parent -->
   25.40 +     <tbody tal:condition="list_parent">
   25.41 +      <tr valign="top"
   25.42 +          tal:define="upNav python: hasattr(here.aq_parent, 'portal_url');">
   25.43 +        <td colspan="3" align="left">
   25.44 +         <span tal:condition="upNav | nothing">
   25.45 +          <a href="../folder_contents"
   25.46 +          ><img src="" alt="[Link]" border="0"
   25.47 +                tal:attributes="src string:${here/portal_url}/UpFolder_icon.gif"
   25.48 +          /></a>
   25.49 +         </span>&nbsp;&nbsp;
   25.50 +         <span tal:condition="upNav">
   25.51 +          <span tal:omit-tag="" i18n:translate="">Up to</span>
   25.52 +          <a href="../folder_contents">Up</a>
   25.53 +         </span>
   25.54 +         <span tal:condition="python: not(upNav)">
   25.55 +           <span class="mild" i18n:translate="">Root</span>
   25.56 +         </span>
   25.57 +        </td>
   25.58 +      </tr>
   25.59 +     </tbody>
   25.60 +     <!-- This row holds the "two column list" -->
   25.61 +     <tr tal:define="global b_start string:0;
   25.62 +                     b_start request/b_start | b_start;
   25.63 +                     filterString python: request.get('folderfilter', '');
   25.64 +                     filter python: here.decodeFolderFilter(filterString);
   25.65 +                     items python: here.listFolderContents(
   25.66 +                                                contentFilter=filter);
   25.67 +                     Batch nocall: modules/ZTUtils/Batch;
   25.68 +                     global batch1 python:Batch( items, 20, int(b_start)
   25.69 +                                              , orphan=0);
   25.70 +                     global batch2 batch1/next;
   25.71 +                    "
   25.72 +     >
   25.73 +      <!-- First column, first half batch in 'batch1'. -->
   25.74 +      <td colspan="1" align="left" width="49%">
   25.75 +       <table>
   25.76 +        <tr valign="top" tal:repeat="item batch1">
   25.77 +         <td align="left" width="5" nowrap
   25.78 +             tal:define="folderish item/isPrincipiaFolderish;
   25.79 +                         portalish item/isPortalContent | nothing;
   25.80 +                         global methodID python:( folderish
   25.81 +                                              and 'folder_contents'
   25.82 +                                               or (portalish and 'view' or '')
   25.83 +                                                );
   25.84 +                         global icon item/getIcon | item/icon | nothing
   25.85 +                        ">
   25.86 +            <input type="checkbox" name="ids:list" value="" id=""
   25.87 +                   tal:attributes="value item/getId;
   25.88 +                                   id python: 'cb_' + item.getId()" />
   25.89 +         </td>
   25.90 +         <td>
   25.91 +          <span tal:condition="icon">
   25.92 +           <a href=""
   25.93 +              tal:attributes="href string:${item/absolute_url}/${methodID};
   25.94 +                             "
   25.95 +           ><img src="" alt="" border="0"
   25.96 +                 tal:attributes="src string:${here/portal_url}/${icon};
   25.97 +                                 alt item/Type|nothing;"
   25.98 +                 i18n:attributes="alt" /></a>
   25.99 +          </span>
  25.100 +         </td>
  25.101 +         <td>
  25.102 +          <a href=""
  25.103 +             tal:attributes="href string:${item/absolute_url}/${methodID};
  25.104 +                            "
  25.105 +          ><span tal:replace="item/getId">ID</span>
  25.106 +           <span tal:condition="item/Title"
  25.107 +                 tal:replace="string:(${item/Title})">(Title)</span></a>
  25.108 +         </td>
  25.109 +        </tr>
  25.110 +       </table>
  25.111 +      </td>
  25.112 +      <!-- Spacer column. -->
  25.113 +      <td width="2%">&nbsp;</td>
  25.114 +      <!-- Second column, second half batch in 'batch2'. -->
  25.115 +      <td colspan="1" width="49%">
  25.116 +       <table>
  25.117 +        <tr valign="top" tal:repeat="item batch2">
  25.118 +         <td align="left" width="5" nowrap
  25.119 +             tal:define="folderish item/isPrincipiaFolderish;
  25.120 +                         portalish item/isPortalContent | nothing;
  25.121 +                         global methodID python:( folderish
  25.122 +                                              and 'folder_contents'
  25.123 +                                               or (portalish and 'view' or '')
  25.124 +                                                );
  25.125 +                         global icon item/getIcon | item/icon | nothing
  25.126 +                        ">
  25.127 +            <input type="checkbox" name="ids:list" value="" id=""
  25.128 +                   tal:attributes="value item/getId;
  25.129 +                                   id python: 'cb_' + item.getId()" />
  25.130 +         </td>
  25.131 +         <td>
  25.132 +          <span tal:condition="icon">
  25.133 +           <a href=""
  25.134 +              tal:attributes="href string:${item/absolute_url}/${methodID};
  25.135 +                             "
  25.136 +           ><img src="" alt="" border="0"
  25.137 +                 tal:attributes="src string:${here/portal_url}/${icon};
  25.138 +                                 alt item/Type|nothing;"
  25.139 +                 i18n:attributes="alt" /></a>
  25.140 +          </span>
  25.141 +         </td>
  25.142 +         <td>
  25.143 +          <a href=""
  25.144 +             tal:attributes="href string:${item/absolute_url}/${methodID};
  25.145 +                            "
  25.146 +          ><span tal:replace="item/getId">ID</span>
  25.147 +           <span tal:condition="item/Title"
  25.148 +                 tal:replace="string:(${item/Title})">(Title)</span></a>
  25.149 +         </td>
  25.150 +        </tr>
  25.151 +       </table>
  25.152 +      </td>
  25.153 +
  25.154 +     </tr>
  25.155 +
  25.156 +     <tr>
  25.157 +      <td align="left">
  25.158 +       <span tal:define="p batch1/previous" tal:condition="p">
  25.159 +        <a href=""
  25.160 +           tal:attributes="
  25.161 +                href string:folder_contents?b_start=${p/previous/first}"
  25.162 +           i18n:translate=""
  25.163 +        >Previous Items</a>
  25.164 +       </span>
  25.165 +      </td>
  25.166 +      <td>&nbsp;</td>
  25.167 +      <td align="right">
  25.168 +       <span tal:define="n batch2/next | nothing" tal:condition="n">
  25.169 +        <a href=""
  25.170 +           tal:attributes="
  25.171 +                href string:folder_contents?b_start=${batch2/end}"
  25.172 +           i18n:translate=""
  25.173 +        >Next Items</a>
  25.174 +       </span>
  25.175 +      </td>
  25.176 +     </tr>
  25.177 +    <!-- end contentList -->
  25.178 +    </table>
  25.179 +
  25.180 +    <table border="0" cellspacing="0" cellpadding="2">
  25.181 +     <tr>
  25.182 +      <td align="left" valign="top" width="16"></td>
  25.183 +      <td align="left" valign="top">
  25.184 +      <span tal:condition="python: checkPerm('Add portal content', here)
  25.185 +                                   and here.allowedContentTypes()">
  25.186 +        <input type="submit" name="folder_factories:method" value="New..."
  25.187 +               i18n:attributes="value" />
  25.188 +      </span>
  25.189 +      <span tal:condition="python: checkPerm('View management screens', here)">
  25.190 +        <input type="submit" name="folder_rename_form:method" value="Rename"
  25.191 +               i18n:attributes="value" />
  25.192 +        <input type="submit" name="folder_cut:method" value="Cut"
  25.193 +               i18n:attributes="value" />
  25.194 +        <input type="submit" name="folder_copy:method" value="Copy"
  25.195 +               i18n:attributes="value" />
  25.196 +        <span tal:condition="here/cb_dataValid">
  25.197 +        <input type="submit" name="folder_paste:method" value="Paste"
  25.198 +               i18n:attributes="value" />
  25.199 +        </span>
  25.200 +      </span>
  25.201 +      <span tal:condition="python: checkPerm('Delete objects', here)">
  25.202 +        <input type="submit" name="folder_delete:method" value="Delete"
  25.203 +               i18n:attributes="value" />
  25.204 +      </span>
  25.205 +      </td>
  25.206 +     </tr>
  25.207 +    </table>
  25.208 +
  25.209 +   </td>
  25.210 +  </tr>
  25.211 + </table>
  25.212 +</form>
  25.213 +
  25.214 +<div tal:replace="structure here/folder_filter_form">Filter Form Here</div>
  25.215 +
  25.216 +</div>
  25.217 +</div>
  25.218 +</body>
  25.219 +</html>