CMF-1.3/0040755000076500007650000000000007524010071011700 5ustar tseavertseaverCMF-1.3/CMFCalendar/0040755000076500007650000000000007524010061013736 5ustar tseavertseaverCMF-1.3/CMFCalendar/Extensions/0040755000076500007650000000000007524010060016074 5ustar tseavertseaverCMF-1.3/CMFCalendar/Extensions/Install.py0100644000076500007650000001275007466765453020112 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """\ This file is an installation script for CMFCalendar (Events). It's meant to be used as an External Method. To use, add an external method to the root of the CMF Site that you want CMF Event registered in with the configuration: id: install_events title: Install Events *optional* module name: CMFCalendar.Install function name: install Then go to the management screen for the newly added external method and click the 'Try it' tab. The install function will execute and give information about the steps it took to register and install the CMF Events into the CMF Site instance. """ from Products.CMFCore.TypesTool import ContentFactoryMetadata from Products.CMFCore.DirectoryView import addDirectoryViews from Products.CMFCore.utils import getToolByName, ToolInit from Products.CMFCalendar import Event, event_globals from Acquisition import aq_base from cStringIO import StringIO import string def install(self): " Register the CMF Event with portal_types and friends " out = StringIO() typestool = getToolByName(self, 'portal_types') skinstool = getToolByName(self, 'portal_skins') metadatatool = getToolByName(self, 'portal_metadata') catalog = getToolByName(self, 'portal_catalog') portal_url = getToolByName(self, 'portal_url') # Due to differences in the API's for adding indexes between # Zope 2.3 and 2.4, we have to catch them here before we can add # our new ones. base = aq_base(catalog) if hasattr(base, 'addIndex'): # Zope 2.4 addIndex = catalog.addIndex else: # Zope 2.3 and below addIndex = catalog._catalog.addIndex if hasattr(base, 'addColumn'): # Zope 2.4 addColumn = catalog.addColumn else: # Zope 2.3 and below addColumn = catalog._catalog.addColumn try: addIndex('start', 'FieldIndex') except: pass try: addIndex('end', 'FieldIndex') except: pass try: addColumn('start') except: pass try: addColumn('end') except: pass out.write('Added "start" and "end" field indexes and columns to '\ 'the portal_catalog\n') # Borrowed from CMFDefault.Portal.PortalGenerator.setupTypes() # We loop through anything defined in the factory type information # and configure it in the types tool if it doesn't already exist for t in Event.factory_type_information: if t['id'] not in typestool.objectIds(): cfm = apply(ContentFactoryMetadata, (), t) typestool._setObject(t['id'], cfm) out.write('Registered with the types tool\n') else: out.write('Object "%s" already existed in the types tool\n' % ( t['id'])) # Setup a MetadataTool Element Policy for Events try: metadatatool.addElementPolicy( element='Subject', content_type='Event', is_required=0, supply_default=0, default_value='', enforce_vocabulary=0, allowed_vocabulary=('Appointment', 'Convention', 'Meeting', 'Social Event', 'Work'), REQUEST=None, ) except: pass out.write('Event added to Metadata element Policies\n') # Add the CMFCalendar tool to the site's root p = portal_url.getPortalObject() x = p.manage_addProduct['CMFCalendar'].manage_addTool(type="CMF Calendar Tool") # Setup the skins # This is borrowed from CMFDefault/scripts/addImagesToSkinPaths.pys if 'calendar' not in skinstool.objectIds(): # We need to add Filesystem Directory Views for any directories # in our skins/ directory. These directories should already be # configured. addDirectoryViews(skinstool, 'skins', event_globals) out.write("Added 'calendar' directory view to portal_skins\n") # Now we need to go through the skin configurations and insert # 'calendar' into the configurations. Preferably, this should be # right before where 'content' is placed. Otherwise, we append # it to the end. skins = skinstool.getSkinSelections() for skin in skins: path = skinstool.getSkinPath(skin) path = map(string.strip, string.split(path,',')) if 'calendar' not in path: try: path.insert(path.index('content'), 'calendar') except ValueError: path.append('calendar') try: path.insert(path.index('zpt_content'), 'zpt_calendar') except ValueError: pass path = string.join(path, ', ') # addSkinSelection will replace exissting skins as well. skinstool.addSkinSelection(skin, path) out.write("Added 'calendar' to %s skin\n" % skin) else: out.write("Skipping %s skin, 'calendar' is already set up\n" % ( skin)) return out.getvalue() CMF-1.3/CMFCalendar/Extensions/__init__.py0100644000076500007650000000000007503473141020202 0ustar tseavertseaverCMF-1.3/CMFCalendar/CREDITS.txt0100644000076500007650000000055507464242164015614 0ustar tseavertseaver-- CREDITS.TXT -- Orignal Event meta_type and CMFCalendar by Zope Corporation. The implementation of the calendar and the calendar_tool was completed by Andy Dawkins (New Information Paradigms Ltd) Andy originally implemented the Calendar design in plone (www.plone.org) with help from the plone development team (Alexander Limi, Vidar Andersen and Alan Runyan) CMF-1.3/CMFCalendar/CalendarTool.py0100644000076500007650000002767007522537642016711 0ustar tseavertseaver######################################################################## # # Calendar Tool by Andy Dawkins (New Information Paradigms Ltd) # # Converted to a CMF Tool by Alan Runyan # # Additional Modification for the CMFCalendar by Andy Dawkins 29/04/2025 # ######################################################################## import calendar calendar.setfirstweekday(6) #start day Mon(0)-Sun(6) from DateTime import DateTime from Products.CMFCore.utils import UniqueObject from Products.CMFCore.utils import _checkPermission, _getAuthenticatedUser from Products.CMFCore.utils import getToolByName, _dtmldir from OFS.SimpleItem import SimpleItem from Globals import InitializeClass from AccessControl import ClassSecurityInfo from Products.CMFCore import CMFCorePermissions from Products.PageTemplates.PageTemplateFile import PageTemplateFile class CalendarTool (UniqueObject, SimpleItem): """ a calendar tool for encapsualting how calendars work and are displayed """ id = 'portal_calendar' meta_type= 'CMF Calendar Tool' security = ClassSecurityInfo() plone_tool = 1 manage_options = ( ({ 'label' : 'Overview', 'action' : 'manage_overview' } , { 'label' : 'Configure', 'action' : 'manage_configure' } , ) + SimpleItem.manage_options ) # # ZMI methods # security.declareProtected( CMFCorePermissions.ManagePortal , 'manage_overview' ) manage_overview = PageTemplateFile('www/explainCalendarTool', globals(), __name__='manage_overview') security.declareProtected( CMFCorePermissions.ManagePortal , 'manage_configure' ) manage_configure = PageTemplateFile('www/configureCalendarTool', globals(), __name__='manage_configure') def __init__(self): self.calendar_types = ['Event'] self.use_session = "" security.declareProtected( CMFCorePermissions.ManagePortal , 'edit_configuration' ) def edit_configuration(self, show_types, use_session): """ Change the configuration of the calendar tool """ self.calendar_types = show_types self.use_session = use_session if hasattr(self.REQUEST, 'RESPONSE'): self.REQUEST.RESPONSE.redirect('manage_configure') security.declarePublic('getCalendarTypes') def getCalendarTypes(self): """ Returns a list of type that will show in the calendar """ return self.calendar_types security.declarePublic('getUseSession') def getUseSession(self): """ Returns the Use_Session option """ return self.use_session security.declarePublic('getDays') def getDays(self): """ Returns a list of days with the correct start day first """ import string return string.split(calendar.weekheader(2),' ') security.declarePublic('getWeeksList') def getWeeksList(self, month='1', year='2002'): """Creates a series of weeks, each of which contains an integer day number. A day number of 0 means that day is in the previous or next month. """ # daysByWeek is a list of days inside a list of weeks, like so: # [[0, 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, 29, 30, 31, 0, 0, 0]] daysByWeek=calendar.monthcalendar(year, month) return daysByWeek security.declarePublic('getEventsForCalendar') def getEventsForCalendar(self, month='1', year='2002'): """ recreates a sequence of weeks, by days each day is a mapping. {'day': #, 'url': None} """ year=int(year) month=int(month) # daysByWeek is a list of days inside a list of weeks, like so: # [[0, 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, 29, 30, 31, 0, 0, 0]] daysByWeek=calendar.monthcalendar(year, month) weeks=[] events=self.catalog_getevents(year, month) for week in daysByWeek: days=[] for day in week: if events.has_key(day): days.append(events[day]) else: days.append({'day': day, 'event': 0, 'eventslist':[]}) weeks.append(days) return weeks security.declarePublic('catalog_getevents') def catalog_getevents(self, year, month): """ given a year and month return a list of days that have events """ first_date=DateTime(str(month)+'/1/'+str(year)) last_day=calendar.monthrange(year, month)[1] last_date=DateTime(str(month)+'/'+str(last_day)+'/'+str(year)) query=self.portal_catalog(Type=self.calendar_types, review_state='published', start=(first_date, last_date), start_usage='range:min:max', sort_on='start') # I don't like doing two searches # What i want to do is # start date => 1/1/2025 and start date <= 31/1/2025 # or # end date => 1/1/2025 and end date <= 31/1/2025 # but I don't know how to do that in one search query :( - AD # if you look at calendar_slot you can see how to do this in 1 query - runyaga query+=self.portal_catalog(Type=self.calendar_types, review_state='published', end=(first_date, last_date), end_usage='range:min:max', sort_on='end') # compile a list of the days that have events eventDays={} for daynumber in range(1, 32): # 1 to 31 eventDays[daynumber] = {'eventslist':[], 'event':0, 'day':daynumber} includedevents = [] for result in query: if result.getRID() in includedevents: break else: includedevents.append(result.getRID()) event={} # we need to deal with events that end next month if result.end.month() != month: # doesn't work for events that last ~12 months - fix it if it's a problem, otherwise ignore eventEndDay = last_day event['end'] = None else: eventEndDay = result.end.day() event['end'] = result.end.Time() # and events that started last month if result.start.month() != month: # same as above re: 12 month thing eventStartDay = 1 event['start'] = None else: eventStartDay = result.start.day() event['start'] = result.start.Time() event['title'] = result.Title or result.id if eventStartDay != eventEndDay: allEventDays = range(eventStartDay, eventEndDay+1) eventDays[eventStartDay]['eventslist'].append({'end':None, 'start':result.start.Time(), 'title':result.Title}) eventDays[eventStartDay]['event'] = 1 for eventday in allEventDays[1:-1]: eventDays[eventday]['eventslist'].append({'end':None, 'start':None, 'title':result.Title}) eventDays[eventday]['event'] = 1 eventDays[eventEndDay]['eventslist'].append({'end':result.end.Time(), 'start':None, 'title':result.Title}) eventDays[eventEndDay]['event'] = 1 else: eventDays[eventStartDay]['eventslist'].append(event) eventDays[eventStartDay]['event'] = 1 # This list is not uniqued and isn't sorted # uniquing and sorting only wastes time # and in this example we don't need to because # later we are going to do an 'if 2 in eventDays' # so the order is not important. # example: [23, 28, 29, 30, 31, 23] return eventDays security.declarePublic('getEventsForThisDay') def getEventsForThisDay(self, thisDay): """ given an exact day return ALL events that: A) Start on this day OR B) End on this day OR C) Start before this day AND end after this day""" catalog = self.portal_catalog first_date, last_date = self.getBeginAndEndTimes(thisDay.day(), thisDay.month(), thisDay.year()) #first_date=DateTime(thisDay.Date()+" 00:00:00") #last_date=DateTime(thisDay.Date()+" 23:59:59") # Get all events that Start on this day query=self.portal_catalog(Type=self.calendar_types, review_state='published', start=(first_date,last_date), start_usage='range:min:max') # Get all events that End on this day query+=self.portal_catalog(Type=self.calendar_types, review_state='published', end=(first_date,last_date), end_usage='range:min:max') # Get all events that Start before this day AND End after this day query+=self.portal_catalog(Type=self.calendar_types, review_state='published', start=first_date, start_usage='range:max', end=last_date, end_usage='range:min') # Unique the results results = [] rids = [] for item in query: rid = item.getRID() if not rid in rids: results.append(item) rids.append(rid) def sort_function(x,y): z = cmp(x.start,y.start) if not z: return cmp(x.end,y.end) return z # Sort by start date results.sort(sort_function) return results security.declarePublic('getPreviousMonth') def getPreviousMonth(self, month, year): # given any particular year and month, this method will return a datetime object # for one month prior try: month=int(month) except: raise "Calendar Type Error", month try: year=int(year) except: raise "Calendar Type Error", year if month==0 or month==1: month, year = 12, year - 1 else: month-=1 return DateTime(str(month) + '/1/' + str(year)) security.declarePublic('getNextMonth') def getNextMonth(self, month, year): # given any particular year and month, this method will return a datetime object # for one month after try: month=int(month) except: raise "Calendar Type Error", month try: year=int(year) except: raise "Calendar Type Error", year if month==12: month, year = 1, year + 1 else: month+=1 return DateTime(str(month) + '/1/' + str(year)) security.declarePublic('getBeginAndEndTimes') def getBeginAndEndTimes(self, day, month, year): # Given any day, month and year this method returns 2 DateTime objects # That represent the exact start and the exact end of that particular day. day=str(day) month=str(month) year=str(year) begin=DateTime(month+'/'+day+'/'+year+' 12:00:00AM') end=DateTime(month+'/'+day+'/'+year+' 11:59:59PM') return (begin, end) InitializeClass(CalendarTool) CMF-1.3/CMFCalendar/DEPENDENCIES.txt0100644000076500007650000000000007332047577016333 0ustar tseavertseaverCMF-1.3/CMFCalendar/Event.py0100644000076500007650000002254707522303412015403 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Event: A CMF-enabled Event object. $Id: Event.py,v 1.7.28.1 2025/08/01 19:07:54 tseaver Exp $ """ import os, urllib from DateTime import DateTime from Globals import InitializeClass from AccessControl import ClassSecurityInfo from Products.CMFDefault.DublinCore import DefaultDublinCoreImpl from Products.CMFCore.PortalContent import PortalContent from Products.CMFCore.WorkflowCore import WorkflowAction # Import permission names from Products.CMFCore import CMFCorePermissions import EventPermissions # Factory type information -- makes Events objects play nicely # with the Types Tool (portal_types) factory_type_information = ( {'id': 'Event', 'content_icon': 'event_icon.gif', 'meta_type': 'CMF Event', 'description': ('Events are objects for use in Calendar topical ' 'queries on the catalog.'), 'product': 'CMFCalendar', 'factory': 'addEvent', 'immediate_view': 'event_edit_form', 'actions': ({'id': 'view', 'name': 'View', 'action': 'event_view', 'permissions': (CMFCorePermissions.View,)}, {'id': 'edit', 'name': 'Edit', 'action': 'event_edit_form', 'permissions': (EventPermissions.ChangeEvents,)}, ), # End Actions }, ) def addEvent(self , id , title='' , description='' , effective_date = None , expiration_date = None , start_date = DateTime() , end_date = DateTime() , location='' , contact_name='' , contact_email='' , contact_phone='' , event_url='' , REQUEST=None): """ Create an empty event. """ event = Event(id , title , description , effective_date , expiration_date , start_date , end_date , location , contact_name , contact_email , contact_phone , event_url ) self._setObject(id, event) def _dateStrings( when ): strings = {} if when is not None: strings[ 'year' ] = str( when.year() ) strings[ 'month' ] = str( when.month() ) strings[ 'day' ] = str( when.day() ) else: strings[ 'year' ] = '' strings[ 'month' ] = '' strings[ 'day' ] = '' return strings class Event(PortalContent, DefaultDublinCoreImpl): """ Events are objects for the Calendar topical query. """ meta_type='CMF Event' # Declarative security security = ClassSecurityInfo() security.declareObjectProtected(CMFCorePermissions.View) def __init__(self , id , title='' , description='' , effective_date = None , expiration_date = None , start_date = DateTime() , end_date = DateTime() , location='' , contact_name='' , contact_email='' , contact_phone='' , event_url='' ): DefaultDublinCoreImpl.__init__(self) self.id=id self.setTitle(title) self.setDescription(description) self.effective_date = effective_date self.expiration_date = expiration_date self.setStartDate(start_date) if end_date < start_date: end_date = start_date self.setEndDate(end_date) self.location=location self.contact_name=contact_name self.contact_email=contact_email self.contact_phone=contact_phone self.event_url=event_url security.declarePrivate( '_datify' ) def _datify( self, attrib ): if attrib == 'None': attrib = None elif not isinstance( attrib, DateTime ): if attrib is not None: attrib = DateTime( attrib ) return attrib security.declarePublic('getEndStrings') def getEndStrings(self): """ """ return _dateStrings(self.end()) security.declarePublic('getStartStrings') def getStartStrings(self): """ """ return _dateStrings(self.start()) security.declareProtected(EventPermissions.ChangeEvents, 'edit') def edit(self , title=None , description=None , eventType=None , effectiveDay=None , effectiveMo=None , effectiveYear=None , expirationDay=None , expirationMo=None , expirationYear=None , start_time=None , startAMPM=None , stop_time=None , stopAMPM=None , location=None , contact_name=None , contact_email=None , contact_phone=None , event_url=None ): """\ """ if title is not None: self.setTitle(title) if description is not None: self.setDescription(description) if eventType is not None: self.setSubject(eventType) efdate = '%s/%s/%s %s %s' % (effectiveDay , effectiveMo , effectiveYear , start_time , startAMPM ) start_date = DateTime( efdate ) exdate = '%s/%s/%s %s %s' % (expirationDay , expirationMo , expirationYear , stop_time , stopAMPM ) end_date = DateTime( exdate ) if end_date < start_date: end_date = start_date self.setStartDate( start_date ) self.setEndDate( end_date ) if location is not None: self.location = location if contact_name is not None: self.contact_name = contact_name if contact_email is not None: self.contact_email = contact_email if contact_phone is not None: self.contact_phone = contact_phone if event_url is not None: self.event_url = event_url self.reindexObject() edit = WorkflowAction(edit) security.declarePublic('buildTimes') def buildTimes(self): result = [] for hour in range (1, 13): for min in (00, 30): result.append('%02d:%02d' % (hour, min)) return result security.declarePublic('buildDays') def buildDays(self): result = [] for day in range (1, 32): result.append(str('%d' % (day))) return result security.declarePublic('buildMonths') def buildMonths(self): result = [] for month in range (1, 13): result.append(str('%d' % (month))) return result security.declarePublic('buildYears') def buildYears(self): result = [] start = (DateTime().year() - 2) end = (DateTime().year() + 5) for year in range (start, end): result.append(str(year)) return result security.declareProtected(EventPermissions.ChangeEvents, 'setStartDate') def setStartDate(self, start): """ Setting the event start date, when the event is scheduled to begin. """ self.start_date = self._datify(start) security.declareProtected(EventPermissions.ChangeEvents, 'setEndDate') def setEndDate(self, end): """ Setting the event end date, when the event ends. """ self.end_date = self._datify(end) security.declarePublic('start') def start(self): """ Return our start time as a string. """ date = getattr( self, 'start_date', None ) return date is None and self.created() or date security.declarePublic('end') def end(self): """ Return our stop time as a string. """ date = getattr( self, 'end_date', None ) return date is None and self.start() or date security.declarePublic('getStartTimeString') def getStartTimeString( self ): """ Return our start time as a string. """ return self.start().AMPMMinutes() security.declarePublic('getStopTimeString') def getStopTimeString( self ): """ Return our stop time as a string. """ return self.end().AMPMMinutes() # Intialize the Event class, setting up security. InitializeClass(Event) CMF-1.3/CMFCalendar/EventPermissions.py0100644000076500007650000000213107522303412017622 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ EventPermissions: Permissions used in the CMF Events class $Id: EventPermissions.py,v 1.3.28.1 2025/08/01 19:07:54 tseaver Exp $ """ from Products.CMFCore.CMFCorePermissions import setDefaultRoles # Gathering Event Related Permissions into one place AddEvents = 'Add portal events' ChangeEvents = 'Change portal events' # Set up default roles for permissions setDefaultRoles(AddEvents, ('Manager', 'Owner', 'Member')) setDefaultRoles(ChangeEvents, ('Manager', 'Owner',)) CMF-1.3/CMFCalendar/INSTALL.txt0100644000076500007650000000171307332047577015627 0ustar tseavertseaverInstalling CMFCalendar To install CMFCalendar, uncompress the CMFCalendar product into your zope/Products directory or link it there, e.g.:: ln -s /path/to/installation /path/to/zope/Products In the root of your CMFSite installation (within the ZMI): 1. Add an external method to the root of the CMF Site. 2. Use the following configuration values for the external method: o id: install_events o title: Install Events *optional* o module name: CMFCalendar.Install o function name: install 3. Go to the management screen for the newly added external method and click the 'Try it' tab. The install function will execute and give information about the steps it took to register and install the CMF Events into the CMF Site instance. If you wish to use the zpt_calendar skins, you will need to have PageTemplates installed, and the CMFDecor product installed. CMF-1.3/CMFCalendar/README.txt0100644000076500007650000000207307466765453015466 0ustar tseavertseaverCMFCalendar README The CMFCalendar product is an example of creating a CMF Product. The CMFCalendar product is also expected to be a generally useful out of the 'box' and skinnable to accomodate customization within your existing CMF instance. To see how to go about building a CMF product, this hopefully allows a developer to follow through the process of registering their product, skins, and help with the CMF by example. It shows how an object is created and registered with the types_tool, necessary skins added to the skins_tool, with the Calendar skins directory added to the skin paths, and providing portal_metadatool with an Element policy for the content_type of the object. The event portal_type is complete, and can be stand alone, but now accompanyied by the CMFCalendar which is fully functional and tested. After installing the CMFCalendar you should notice a calendar appear in your CMF. This is fully customisable to your portals needs. See the INSTALL.txt file for how to get the product installed within your CMF. CMF-1.3/CMFCalendar/TODO.txt0100644000076500007650000000025407466765453015275 0ustar tseavertseaverTo-do's o Add the calendar product itself, at rev 0.1 there is only an event object. -- Done (Andy Dawkins - 1st May 2002) o other todo items as I think of them.... CMF-1.3/CMFCalendar/__init__.py0100644000076500007650000000377607466765453016114 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from Products.CMFDefault import Portal import Event import Products.CMFCore from Products.CMFCore import utils, CMFCorePermissions from Products.CMFCore.DirectoryView import registerDirectory import EventPermissions import CalendarTool import sys this_module = sys.modules[ __name__ ] contentConstructors = (Event.addEvent,) contentClasses = (Event.Event,) tools = ( CalendarTool.CalendarTool, ) z_bases = utils.initializeBasesPhase1( contentClasses, this_module ) # This is used by a script (external method) that can be run # to set up Events in an existing CMF Site instance. event_globals=globals() # Make the skins available as DirectoryViews registerDirectory('skins', globals()) registerDirectory('skins/calendar', globals()) def initialize( context ): utils.ToolInit('CMFCalendar Tool', tools=tools, product_name='CMFCalendar', icon='tool.gif', ).initialize( context ) utils.initializeBasesPhase2( z_bases, context ) context.registerHelpTitle('CMF Calendar Help') context.registerHelp(directory='help') utils.ContentInit( 'CMF Event' , content_types = contentClasses , permission = CMFCorePermissions.AddPortalContent , extra_constructors = contentConstructors , fti = Event.factory_type_information ).initialize( context ) CMF-1.3/CMFCalendar/tool.gif0100644000076500007650000000024607464242164015417 0ustar tseavertseaverGIF89almoxzx皜VVWabaKLJ!Made with GIMP! ,AI \+Ua@$ o#L(D  8 >|W;CMF-1.3/CMFCalendar/version.txt0100644000076500007650000000000407523373536016175 0ustar tseavertseaver1.3 CMF-1.3/CMFCalendar/help/0040755000076500007650000000000007524010060014665 5ustar tseavertseaverCMF-1.3/CMFCalendar/help/placeholder.txt0100644000076500007650000000005207310431715017711 0ustar tseavertseaverSo that 'cvs up -d' doesn't blow us away. CMF-1.3/CMFCalendar/image_sources/0040755000076500007650000000000007524010060016562 5ustar tseavertseaverCMF-1.3/CMFCalendar/image_sources/event_info_tab.psd0100644000076500007650000007626107322420446022276 0ustar tseavertseaver8BPSZ8BIM Print InfoxHH(FG(HH(d'`8BIM ResolutionHH8BIM FX Global Lighting AngleZ8BIMFX Global Altitude8BIM Print Flags 8BIM Copyright Flag8BIM'Japanese Print Flags 8BIMColor Halftone SettingsH/fflff/ff2Z5-8BIMColor Transfer Settingsp8BIM Layer State8BIM Layer Groups8BIMGuides@@8BIM URL overrides8BIMSlicesiZTestZ8BIMICC Untagged Flag8BIMLayer ID Generator Base 8BIM New Windows Thumbnail/ZPJFIFHHAdobed            Z"?   3!1AQa"q2B#$Rb34rC%Scs5&DTdE£t6UeuF'Vfv7GWgw5!1AQaq"2B#R3$brCScs4%&5DTdEU6teuFVfv'7GWgw ?:dC?8~{r=XsI#].I"T}sQ׫mXY ľʩuCG6c{wnnJbfculQ: vzt|=J!˜sRQkΣknkيj]N~ne6]K;3ziUHF/]SǤ,ϰsZ 'УdxwYY;}{vA=8ncQC+~ۓVCnlkw3c3)a4VnvL:~ӗ]n[k{ۏ[sX5W.7_KqߴT ~,~;Uc=Duhxm#Y{vk??"$7;ütF/)I>>͸Ou/漹$n7xy&hWSwwSe6lyy^XJ}Y%)$8BIM!Version compatibility infoUAdobe PhotoshopAdobe Photoshop 6.08BIMPlug-in resource blockmoptd8BIMPlug-in resource blockmsetnull HTMLSettingsObjcnull AttributeCaselongQuoteAllAttributesboolSpacersVerticallongSpacersHorizontallong StylesFormatlongIncludeCommentsboolGoLiveCompatibleboolSpacersEmptyCellslong TDWidthHeightlong ImageMapTypelongImageMapLocationlongFileSavingSettingsObjcnullIncludeCopyrightboolDuplicateFileNameBehaviorlongCopyBackgroundboolUseImageSubfolderboolImageSubfolderNameTEXTimagesSliceFileNameComponentsVlLslonglonglong longlonglongSavingFileNameComponentsVlLs longlonglonglonglonglonglonglonglongNameCompatibilityObjcnull NameCompatMacboolNameCompatWindowsboolNameCompatUNIXboolTagCaselong LineEndingslongIndentlongUseCSSboolHTMLBackgroundSettingsObjcnullUseImageAsBackgroundboolBackgroundColorStatelongBackgroundColorGreenlongBackgroundColorRedlongBackgroundColorBluelongBackgroundImagePathTEXTVersionlong[p[\ZVVVV8BIMnorm((Layer 68BIMluniLayer 68BIMlnsrlayr8BIMlyid 8BIMclbl8BIMinfx8BIMknko8BIMlspf8BIMlclr8BIMfxrp,Z8BIMnorm((Layer 58BIMlfx2\nullScl UntF#Prc@YmasterFXSwitchboolebblObjcebblenabboolhglMenumBlnMScrnhglCObjcRGBCRd doub@oGrn doub@oBl doub@ohglOUntF#Prc@RsdwMenumBlnMMltpsdwCObjcRGBCRd doubGrn doubBl doubsdwOUntF#Prc@RbvlTenumbvlTSfBLbvlSenumBESlInrBuglgboollaglUntF#Ang@^LaldUntF#Ang@>srgRUntF#Prc@?blurUntF#Pxl@bvlDenumBESsIn TrnSObjcShpCNm TEXTLinearCrv VlLsObjcCrPtHrzndoubVrtcdoubObjcCrPtHrzndoub@oVrtcdoub@oantialiasGlossboolSftnUntF#PxluseShapeboolMpgSObjcShpCNm TEXTLinearCrv VlLsObjcCrPtHrzndoubVrtcdoubObjcCrPtHrzndoub@oVrtcdoub@oAntAboolInprUntF#Prc@I useTexturebool8BIMlrFX8BIMcmnS8BIMdsdw3x8BIMmul 8BIMisdw3x8BIMmul 8BIMoglw*8BIMscrn8BIMiglw+8BIMscrn8BIMbevlNx(8BIMscrn8BIMmul 8BIMsofi"8BIMnorm8BIMluniLayer 58BIMlnsrlayr8BIMlyid8BIMclbl8BIMinfx8BIMknko8BIMlspf8BIMlclr8BIMfxrpZ8BIMnorm((Layer 78BIMluniLayer 78BIMlnsrlayr8BIMlyid 8BIMclbl8BIMinfx8BIMknko8BIMlspf8BIMlclr8BIMfxrp,M8BIMnorm(G( Event Info8BIMlfx2tnullScl UntF#Prc@YmasterFXSwitchboolDrShObjcDrSh enabboolMd enumBlnMMltpClr ObjcRGBCRd doubGrn doubBl doubOpctUntF#Prc@YuglgboollaglUntF#Ang@^DstnUntF#PxlCkmtUntF#Pxl@(blurUntF#Pxl@NoseUntF#PrcAntAboolTrnSObjcShpCNm TEXTLinearCrv VlLsObjcCrPtHrzndoubVrtcdoubObjcCrPtHrzndoub@oVrtcdoub@o layerConcealsbool8BIMlrFX8BIMcmnS8BIMdsdw3x8BIMmul 8BIMisdw3x8BIMmul 8BIMoglw*8BIMscrn8BIMiglw+8BIMscrn8BIMbevlNx8BIMscrn8BIMmul 8BIMsofi"8BIMnorm8BIMTyShB??@/@/mm2TxLrTxt TEXT Event InfoTxtCObjcnullHrzndoubVrtcdoub textGriddingenum textGriddingNoneOrntenumOrntHrznAntAenumAnntAnSm EngineDatatdta@ << /EngineDict << /Editor << /Text (Event Info ) >> /ParagraphRun << /DefaultRunData << /ParagraphSheet << /Properties << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 8 /PreHyphen 3 /PostHyphen 3 /ConsecutiveHyphens 2 /Zone 36.0 /HyphenateCapitalized true /WordSpacing [ 0.80000 1.0 1.33000 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.20000 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> /Adjustments << /Axis [ 1.0 0.0 1.0 ] /XY [ 0.0 0.0 ] >> >> /RunArray [ << /ParagraphSheet << /Properties << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 8 /PreHyphen 3 /PostHyphen 3 /ConsecutiveHyphens 2 /Zone 36.0 /HyphenateCapitalized true /WordSpacing [ 0.80000 1.0 1.33000 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.20000 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> /Adjustments << /Axis [ 1.0 0.0 1.0 ] /XY [ 0.0 0.0 ] >> >> ] /RunLengthArray [ 11 ] /IsJoinable 1 >> /StyleRun << /DefaultRunData << /StyleSheet << /StyleSheetData << /AutoKerning true /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /Tracking 0 /HorizontalScale 1.0 /VerticalScale 1.0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /YUnderline 1 /Strikethrough false /Ligatures true /OldStyleFigures false /ProportionalNumbers true /BaselineDirection 1 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /WariChuLineCount 1 /WariChuScale 1.0 /WariChuWidowPercentage 25 /WariChuOrphanPercentage 25 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 1.0 1.0 1.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst false /Font 0 >> >> >> /RunArray [ << /StyleSheet << /StyleSheetData << /AutoKerning true /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /Tracking 0 /HorizontalScale 1.0 /VerticalScale 1.0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /YUnderline 1 /Strikethrough false /Ligatures true /OldStyleFigures false /ProportionalNumbers true /BaselineDirection 1 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /WariChuLineCount 1 /WariChuScale 1.0 /WariChuWidowPercentage 25 /WariChuOrphanPercentage 25 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 1.0 1.0 1.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst false /Font 0 >> >> >> << /StyleSheet << /StyleSheetData << /AutoKerning true /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /Tracking 0 /HorizontalScale 1.0 /VerticalScale 1.0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /YUnderline 1 /Strikethrough false /Ligatures true /OldStyleFigures false /ProportionalNumbers true /BaselineDirection 1 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /WariChuLineCount 1 /WariChuScale 1.0 /WariChuWidowPercentage 25 /WariChuOrphanPercentage 25 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 1.0 1.0 1.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst false /Font 0 /Kerning 0 >> >> >> << /StyleSheet << /StyleSheetData << /AutoKerning true /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /Tracking 0 /HorizontalScale 1.0 /VerticalScale 1.0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /YUnderline 1 /Strikethrough false /Ligatures true /OldStyleFigures false /ProportionalNumbers true /BaselineDirection 1 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /WariChuLineCount 1 /WariChuScale 1.0 /WariChuWidowPercentage 25 /WariChuOrphanPercentage 25 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 1.0 1.0 1.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst false /Font 0 >> >> >> ] /RunLengthArray [ 7 2 2 ] /IsJoinable 2 >> /GridInfo << /GridIsOn false /ShowGrid false /GridSize 0.0 /GridLeading 0.0 /GridColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /GridLeadingFillColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /AlignLineHeightToGridFlags false >> /AntiAlias 3 /UseFractionalGlyphWidths true /Rendered << /Version 1 /Shapes << /WritingDirection 0 /Children [ << /ShapeType 0 /Procession 0 /Lines << /WritingDirection 0 /Children [ << /Procession 0 /LineTop -12.16406 /Leading 14.40000 /WordStarts [ 0 6 11 ] /CharacterCount 11 /Segments << /WritingDirection 0 /Children [ << /Range [ 0.0 30000.0 ] /Words << /WritingDirection 0 /Children [ << /Type 0 /Base << /CharacterCount 6 /Advance 37.00781 /TrailingAdvance 2.98828 /Leading 14.40000 /TrailingCharacterCount 1 /StyleRunAlignment 2 /Language 0 /IsBrokenWord 0 /BreakType 0 /StartHang 0.0 /EndHang 0.0 /MojiKumiFirst 0.0 /MojiKumiMiddle 0.0 /MojiKumiEnd 0.0 /Strikes << /WritingDirection 0 /Children [ << /Font 0 /Direction 0 /GlyphDirection 0 /Scale [ 12.0 12.0 ] /FontSize 12.0 /Tracking 0.0 /AntiAlias 3 /Kerning 0.0 /Origin [ 0.0 0.0 ] /GlyphMaps [ 40 89 72 81 87 3 ] /LigatureMaps [ ] /Flags 4 /FillColor << /Type 1 /Values [ 1.0 1.0 1.0 1.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /OriginEnd [ 37.00781 0.0 ] >> ] >> >> >> << /Type 0 /Base << /CharacterCount 5 /Advance 26.92969 /TrailingAdvance 2.98828 /Leading 14.40000 /TrailingCharacterCount 1 /StyleRunAlignment 2 /Language 0 /IsBrokenWord 0 /BreakType 0 /StartHang 0.0 /EndHang 0.0 /MojiKumiFirst 0.0 /MojiKumiMiddle 0.0 /MojiKumiEnd 0.0 /Strikes << /WritingDirection 0 /Children [ << /Font 0 /Direction 0 /GlyphDirection 0 /Scale [ 12.0 12.0 ] /FontSize 12.0 /Tracking 0.0 /AntiAlias 3 /Kerning 0.0 /Origin [ 37.00781 0.0 ] /GlyphMaps [ 44 ] /LigatureMaps [ ] /Flags 4 /FillColor << /Type 1 /Values [ 1.0 1.0 1.0 1.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /OriginEnd [ 41.40234 0.0 ] >> << /Font 0 /Direction 0 /GlyphDirection 0 /Scale [ 12.0 12.0 ] /FontSize 12.0 /Tracking 0.0 /AntiAlias 3 /Kerning 0.0 /Origin [ 41.40234 0.0 ] /GlyphMaps [ 81 73 ] /LigatureMaps [ ] /Flags 4 /FillColor << /Type 1 /Values [ 1.0 1.0 1.0 1.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /OriginEnd [ 52.98047 0.0 ] >> << /Font 0 /Direction 0 /GlyphDirection 0 /Scale [ 12.0 12.0 ] /FontSize 12.0 /Tracking 0.0 /AntiAlias 3 /Kerning 0.0 /Origin [ 52.98047 0.0 ] /GlyphMaps [ 82 2 ] /LigatureMaps [ ] /Flags 68 /FillColor << /Type 1 /Values [ 1.0 1.0 1.0 1.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /OriginEnd [ 63.93750 0.0 ] >> ] >> >> >> ] >> >> ] >> >> ] >> /Cookie << /Photoshop << /ShapeType 0 /PointBase [ 0.0 0.0 ] /Base << /ShapeType 0 /TransformPoint0 [ 1.0 0.0 ] /TransformPoint1 [ 0.0 1.0 ] /TransformPoint2 [ 0.0 0.0 ] >> >> >> >> ] >> >> >> /ResourceDict << /KinsokuSet [ << /Name (PhotoshopKinsokuHard) /NoStart (!\),.:;?]}    0!! 0000 0 0 0000A0C0E0G0I0c000000000000000000000000 =]) /NoEnd (\([{  00 0 0000 ;[) /Keep (  %) /Hanging (,.00) >> << /Name (PhotoshopKinsokuSoft) /NoStart (  0000 0 0 00000000 =]) /NoEnd (  00 0 000;[) /Keep (  %) /Hanging (,.00) >> ] /MojiKumiSet [ << /InternalName (Photoshop6MojiKumiSet1) >> << /InternalName (Photoshop6MojiKumiSet2) >> << /InternalName (Photoshop6MojiKumiSet3) >> << /InternalName (Photoshop6MojiKumiSet4) >> ] /TheNormalStyleSheet 0 /TheNormalParagraphSheet 0 /ParagraphSheetSet [ << /Name (Default) /Properties << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 8 /PreHyphen 3 /PostHyphen 3 /ConsecutiveHyphens 2 /Zone 36.0 /HyphenateCapitalized true /WordSpacing [ 0.80000 1.0 1.33000 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.20000 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> ] /StyleSheetSet [ << /Name (Normal) /StyleSheetData << /FontSize 18.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 27.0 /Tracking 0 /HorizontalScale 1.0 /VerticalScale 1.0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /YUnderline 1 /Strikethrough false /Ligatures true /OldStyleFigures false /ProportionalNumbers true /BaselineDirection 1 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /WariChuLineCount 1 /WariChuScale 1.0 /WariChuWidowPercentage 25 /WariChuOrphanPercentage 25 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst true /Font 1 >> >> << /StyleSheetData << >> >> ] /FontSet [ << /Name (Charcoal) /Script 0 /FontType 1 /Synthetic 0 /FontMetrics << /FontSize 1.0 /SpaceGlyphWidth 0.24902 /Ascent 1.01367 /Descent 0.39209 /CapHeight 0.74699 /DistanceToBaseline 1.01367 /UnderlinePosition 0.14999 /UnderlineThickness 0.04999 /HCJKProporitional false /VProporitional false >> >> << /Name (Helvetica) /Script 0 /FontType 1 /Synthetic 0 /FontMetrics << /FontSize 1.0 /SpaceGlyphWidth 0.27783 /Ascent 1.02539 /Descent 0.44629 /CapHeight 0.73700 /DistanceToBaseline 1.02539 /UnderlinePosition 0.07568 /UnderlineThickness 0.04932 /HCJKProporitional false /VProporitional false >> >> ] /SuperscriptSize 0.58300 /SuperscriptPosition 0.33300 /SubscriptSize 0.58300 /SubscriptPosition 0.33300 /SmallCapSize 0.70000 >> >> Padh8aTlp|tKHHT`>(Ah8b?T:|c.|iN \~88H`|`t,@HH8~\Ki,@$\x8L@A(4 DDE(H OЀQ|SĀpXpPH\0]xHapL8cTeh g@ i08l@l(nx(o|D0r8v@{4|܀(~x3N>>(5TNeyٳh-;CMF-1.3/CMFCalendar/skins/calendar/event_edit.py0100644000076500007650000000262207305267545021357 0ustar tseavertseaver## Script (Python) "event_edit" ##bind container=container ##bind context=context ##bind namespace= ##bind script=script ##bind subpath=traverse_subpath ##parameters=REQUEST, RESPONSE, title=None, description=None, event_type=None, effectiveDay=None, effectiveMo=None, effectiveYear=None, expirationDay=None, expirationMo=None, expirationYear=None, start_time=None, startAMPM=None, stop_time=None, stopAMPM=None, location=None, contact_name=None, contact_email=None, contact_phone=None, event_url=None ##title= ## try: context.edit(title=title , description=description , eventType=event_type , effectiveDay=effectiveYear , effectiveMo=effectiveMo , effectiveYear=effectiveDay , expirationDay=expirationYear , expirationMo=expirationMo , expirationYear=expirationDay , start_time=start_time , startAMPM=startAMPM , stopAMPM=stopAMPM , stop_time=stop_time , location=location , contact_name=contact_name , contact_email=contact_email , contact_phone=contact_phone , event_url=event_url ) except: RESPONSE.redirect('%s/event_edit_form' % context.absolute_url() + '?portal_status_message=Oops!' ) else: RESPONSE.redirect('%s/event_view' % context.absolute_url()) CMF-1.3/CMFCalendar/skins/calendar/event_edit_form.dtml0100644000076500007650000002122207332045456022702 0ustar tseavertseaver
 
   
Event Name Contact Name
Location Contact Email
Event type Contact Phone
Event URL

Start Date Stop Date
Start Time am pm Stop Time am pm

Description
CMF-1.3/CMFCalendar/skins/calendar/event_icon.gif0100644000076500007650000000163607305267545021503 0ustar tseavertseaverGIF89af3̙f3f3ffffff3f3333f333f3f3̙f3̙̙̙̙f̙3̙ffffff3f3333f333f3̙f3̙̙f3̙f3ff̙ffff3f33̙33f333̙f3ffffff3ffff̙fff3fffffff3ffffffffffff3fff3f3f3f3ff33f3ffffff3f3333f333333̙3f3333333f3333f3f3f3ff3f33f33333333f333333333f333f3̙f3f3ffffff3f3333f333f3FdVm€!,@{ Hnܶ)\Cl -b(q l CQ E(3f,y*7d S&6mڰqȰgO. ʭѣHi\̘3'>x3N>>(5TNeyٳh-;CMF-1.3/CMFCalendar/skins/calendar/event_info_tab.gif0100644000076500007650000000163207305267545022330 0ustar tseavertseaverGIF89aZfghVWXz|}GHHrstwyz}uvwopqacckmn]^_@AAPQR!,Z` Ң(Klp,߹Hc.~pH, G6P`JZجv˽F(znb󀁂xz{uhM uv w   Ҳ   ¨ ~x } ⳝxˆl2X!  DXFЙ=Q8x0*04AYC%s3YU .ܴY^@;7#_>s8B @Xc{,TJ,<"+k.V!;CMF-1.3/CMFCalendar/skins/calendar/event_view.dtml0100644000076500007650000001056007332045456021707 0ustar tseavertseaver
 
 
Event Name &dtml-Title; Contact Name &dtml-contact_name;
Location &dtml-location; Contact Email &dtml-contact_email;
Event type Contact Phone &dtml-contact_phone;
Event URL &dtml-event_url;

Start Date Stop Date
Start Time Stop Time

Description &dtml-Description;
 
CMF-1.3/CMFCalendar/skins/calendar/space.gif0100644000076500007650000000005307305267545020435 0ustar tseavertseaverGIF89a!,D;CMF-1.3/CMFCalendar/skins/zpt_calendar/0040755000076500007650000000000007524010060017532 5ustar tseavertseaverCMF-1.3/CMFCalendar/skins/zpt_calendar/CalendarStyle.css0100644000076500007650000000366407466721370023026 0ustar tseavertseaver .CalendarArrow { font-weight: bold; text-decoration: none; color: #000000; } .CalendarTitle { font-weight: bold; text-decoration: none; text-align: center; color: #000000; } div.CalendarBox { background-color: white; border: None; } table.calendar { border: 1px solid Black; text-align: right; } table.calendar th { background-color: #DDDDDD; font-weight: bold; text-align: center; } table.calendar td { white-space: nowrap background-color: white; width: 1.5em; } table.calendar a { text-decoration: none; color: Blue; } table.calendar a:hover { text-decoration: none; } table.calendar td.weekdays { background-color: #AAAAAA; border: Black; border-style: solid none; text-align: center; } table.calendar td.event { background-color: #DDDDDD; font-weight: bold; } table.calendar td.todayevent { background-color: #DDDDDD; border: 2px solid Orange; font-weight: bold; } table.calendar td.todaynoevent { border-collapse: collapse; border: 2px solid Orange; } div.day { background-color: #FFFFBB; border: 1px solid Black; visibility: hidden; width: 12em; z-index: 2; } div.dayViewBox { background-color: white; border: None; } table.dayView { border: 1px solid Black; text-align: right; } table.dayView th { background-color: #DDDDDD; font-weight: bold; font-size: 75%; text-align: center; } table.dayView td { white-space: nowrap background-color: white; font-size: 75%; width: 1.5em; padding: 0.2em; text-align: center; } table.dayView td.startDate { text-align: right; } table.dayView td.endDate { text-align: left; } table.dayView td.heading { background-color: #AAAAAA; border: 1px solid Black; text-align: center; } CMF-1.3/CMFCalendar/skins/zpt_calendar/calendarBox.pt0100644000076500007650000000642207466721600022340 0ustar tseavertseaver
 
CMF Calendar
« »
Su
   
CMF-1.3/CMFCalendar/skins/zpt_calendar/calendar_day_view.pt0100644000076500007650000000315007512743672023556 0ustar tseavertseaver
 
CMF-1.3/CMFCalendar/skins/zpt_calendar/event_edit_form.pt0100644000076500007650000003005207332047577023271 0ustar tseavertseaver
« Date Heading »
event title
Event Start -- Event End
 
   
Event Name Contact Name
Location Contact Email
Event type Contact Phone
Event URL

Start Date     Stop Date    
Start Time   am pm Stop Time   am pm

Description
CMF-1.3/CMFCalendar/skins/zpt_calendar/event_view.pt0100644000076500007650000001416307361614176022275 0ustar tseavertseaver
 
 
Event Name Title Contact Name contact_name
Location location Contact Email contact_email
Event type Contact Phone contact_phone
Event URL event_url

Start Date start Stop Date end
Start Time Stop Time

Description Description
 
CMF-1.3/CMFCalendar/skins/zpt_calendar/getDaysClass.py0100644000076500007650000000076107464242165022513 0ustar tseavertseaver##parameters=day, month, year, event=None # Is the date given today? # If so we want to return a class of 'todayevent' so that the event gets the # today border around it. # If not we want to return just 'event' so the event gets the proper shading. import DateTime current = DateTime.DateTime() if current.year()==year and current.month()==month and current.day()==int(day): if event: return "todayevent" else: return "todaynoevent" if event: return "event" else: return ""CMF-1.3/CMFCalendar/skins/zpt_calendar/getEndAsString.py0100644000076500007650000000040607464242165023002 0ustar tseavertseaver##parameters=thisDay, event # Returns a string to represent the event from DateTime import DateTime text = "" last_date=DateTime(thisDay.Date()+" 23:59:59") if event.end > last_date: return event.end.aCommon()[:12] else: return event.end.TimeMinutes() CMF-1.3/CMFCalendar/skins/zpt_calendar/getMonthAndYear.py0100644000076500007650000000161207522537642023154 0ustar tseavertseaver # Get the year and month that the calendar should display. import DateTime current = DateTime.DateTime() year = None month = None use_session = container.portal_calendar.getUseSession() # First priority goes to the data in the request year = context.REQUEST.get('year', None) month = context.REQUEST.get('month', None) session = None # Next get the data from the SESSION if use_session == "True": session = context.REQUEST.get('SESSION', None) if session: if not year: year = session.get('calendar_year', None) if not month: month = session.get('calendar_month', None) # Last resort to Today if not year: year = current.year() if not month: month = current.month() # Then store the results in the session for next time if session: session.set('calendar_year', year) session.set('calendar_month', month) # Finally return the results return (year, month) CMF-1.3/CMFCalendar/skins/zpt_calendar/getNextDayLink.py0100644000076500007650000000033607464242165023015 0ustar tseavertseaver##parameters=base_url, thisDay # Takes a base url and returns a link to the previous day thisDay += 1 x = '%s?date=%s' % ( base_url, thisDay.Date() ) return xCMF-1.3/CMFCalendar/skins/zpt_calendar/getNextMonthLink.py0100644000076500007650000000064007464242165023363 0ustar tseavertseaver##parameters=base_url, month, year nextMonthTime = container.portal_calendar.getNextMonth(month, year) # Takes a base url and returns a link to the previous month x = '%s?month:int=%d&year:int=%d' % ( base_url, nextMonthTime.month(), nextMonthTime.year() ) return xCMF-1.3/CMFCalendar/skins/zpt_calendar/getPreviousDayLink.py0100644000076500007650000000033207464242165023707 0ustar tseavertseaver##parameters=base_url, thisDay # Takes a base url and returns a link to the next day thisDay -= 1 x = '%s?date=%s' % ( base_url, thisDay.Date() ) return xCMF-1.3/CMFCalendar/skins/zpt_calendar/getPreviousMonthLink.py0100644000076500007650000000064407464242165024265 0ustar tseavertseaver##parameters=base_url, month, year prevMonthTime = container.portal_calendar.getPreviousMonth(month, year) # Takes a base url and returns a link to the previous month x = '%s?month:int=%d&year:int=%d' % ( base_url, prevMonthTime.month(), prevMonthTime.year() ) return xCMF-1.3/CMFCalendar/skins/zpt_calendar/getStartAsString.py0100644000076500007650000000041707464242165023373 0ustar tseavertseaver##parameters=thisDay, event # Returns a string to represent the start of the event from DateTime import DateTime first_date=DateTime(thisDay.Date()+" 00:00:00") if event.start < first_date: return event.start.aCommon()[:12] else: return event.start.TimeMinutes() CMF-1.3/CMFCalendar/tests/0040755000076500007650000000000007524010061015100 5ustar tseavertseaverCMF-1.3/CMFCalendar/tests/__init__.py0100644000076500007650000000023507306064550017220 0ustar tseavertseaver"""\ Unit test package for CMFCalendar. As test suites are added, they should be added to the mega-test-suite in Products.CMFCalendar.tests.test_all.py """ CMF-1.3/CMFCalendar/tests/test_Calendar.py0100644000076500007650000004703007516642543020244 0ustar tseavertseaverimport unittest import Zope from Testing.makerequest import makerequest from Products.CMFCalendar import CalendarTool from DateTime import DateTime from AccessControl.SecurityManagement import newSecurityManager from AccessControl.User import UnrestrictedUser from Products.ExternalMethod.ExternalMethod import manage_addExternalMethod class TestCalendar(unittest.TestCase): def setUp(self): get_transaction().begin() self.app = makerequest(Zope.app()) # Log in as a god :-) newSecurityManager(None, UnrestrictedUser('god', 'god', [], '')) app = self.app app.REQUEST.set('URL1','http://foo/sorcerertest/test') try: app._delObject('CalendarTest') except AttributeError: pass app.manage_addProduct['CMFDefault'].manage_addCMFSite('CalendarTest') self.Site = app.CalendarTest manage_addExternalMethod(app.CalendarTest, id='install_events', title="Install Events", module="CMFCalendar.Install", function="install") ExMethod = app.restrictedTraverse('/CalendarTest/install_events') ExMethod() self.Tool = app.restrictedTraverse('/CalendarTest/portal_calendar') # sessioning bodge until we find out how to do this properly self.have_session = hasattr( app, 'session_data_manager' ) if self.have_session: app.REQUEST.set_lazy( 'SESSION' , app.session_data_manager.getSessionData ) # bodge us a URL1 def _testURL(self,url,params=None): Site = self.Site obj = Site.restrictedTraverse(url) if params is None: params=(obj, Site.REQUEST) apply(obj,params) def tearDown(self): get_transaction().abort() self.app._p_jar.close() def test_new(self): tool = CalendarTool.CalendarTool() self.assertEqual(tool.getId(),'portal_calendar') def test_types(self): self.assertEqual(self.Tool.getCalendarTypes(),['Event']) self.Tool.edit_configuration(show_types=['Event','Party'] , use_session="") self.assertEqual(self.Tool.getCalendarTypes(),['Event', 'Party']) def test_Days(self): assert self.Tool.getDays() == ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'] def XXX_test_sessions(self): if not self.have_session: return self.Tool.edit_configuration(show_types=['Event'], use_session="True") self._testURL('/CalendarTest/calendarBox', ()) self.failUnless(self.app.REQUEST.SESSION.get('calendar_year',None)) def XXX_test_noSessions(self): self.Tool.edit_configuration(show_types=['Event'], use_session="") self._testURL('/CalendarTest/calendarBox', ()) if self.have_session: self.failIf(self.app.REQUEST.SESSION.get('calendar_year',None)) def test_simpleCalendarRendering(self): data = [ [ {'day': 0, 'event': 0, 'eventslist':[]}, {'day': 0, 'event': 0, 'eventslist':[]}, {'day': 1, 'event': 0, 'eventslist':[]}, {'day': 2, 'event': 0, 'eventslist':[]}, {'day': 3, 'event': 0, 'eventslist':[]}, {'day': 4, 'event': 0, 'eventslist':[]}, {'day': 5, 'event': 0, 'eventslist':[]} ], [ {'day': 6, 'event': 0, 'eventslist':[]}, {'day': 7, 'event': 0, 'eventslist':[]}, {'day': 8, 'event': 0, 'eventslist':[]}, {'day': 9, 'event': 0, 'eventslist':[]}, {'day':10, 'event': 0, 'eventslist':[]}, {'day':11, 'event': 0, 'eventslist':[]}, {'day':12, 'event': 0, 'eventslist':[]} ], [ {'day':13, 'event': 0, 'eventslist':[]}, {'day':14, 'event': 0, 'eventslist':[]}, {'day':15, 'event': 0, 'eventslist':[]}, {'day':16, 'event': 0, 'eventslist':[]}, {'day':17, 'event': 0, 'eventslist':[]}, {'day':18, 'event': 0, 'eventslist':[]}, {'day':19, 'event': 0, 'eventslist':[]} ], [ {'day':20, 'event': 0, 'eventslist':[]}, {'day':21, 'event': 0, 'eventslist':[]}, {'day':22, 'event': 0, 'eventslist':[]}, {'day':23, 'event': 0, 'eventslist':[]}, {'day':24, 'event': 0, 'eventslist':[]}, {'day':25, 'event': 0, 'eventslist':[]}, {'day':26, 'event': 0, 'eventslist':[]} ], [ {'day':27, 'event': 0, 'eventslist':[]}, {'day':28, 'event': 0, 'eventslist':[]}, {'day':29, 'event': 0, 'eventslist':[]}, {'day':30, 'event': 0, 'eventslist':[]}, {'day':31, 'event': 0, 'eventslist':[]}, {'day': 0, 'event': 0, 'eventslist':[]}, {'day': 0, 'event': 0, 'eventslist':[]} ] ] assert self.Tool.getEventsForCalendar(month='1', year='2002') == data, self.Tool.getEventsForCalendar(month='1', year='2002') def test_singleEventCalendarRendering(self): self.Site.Members.folder_factories.invokeFactory(type_name="Event",id='Event1') event = self.app.restrictedTraverse('/CalendarTest/Members/Event1') event.edit( title='title' , description='description' , eventType=( 'eventType', ) , effectiveDay=1 , effectiveMo=1 , effectiveYear=2002 , expirationDay=1 , expirationMo=1 , expirationYear=2002 , start_time="00:00" , startAMPM="AM" , stop_time="11:59" , stopAMPM="PM" ) self.Site.portal_workflow.doActionFor( event, 'publish', comment='testing') data = [ [ {'day': 0, 'event': 0, 'eventslist':[]}, {'day': 0, 'event': 0, 'eventslist':[]}, {'day': 1, 'event': 1, 'eventslist':[{'title': 'title', 'end': '23:59:00', 'start': '00:00:00'}]}, {'day': 2, 'event': 0, 'eventslist':[]}, {'day': 3, 'event': 0, 'eventslist':[]}, {'day': 4, 'event': 0, 'eventslist':[]}, {'day': 5, 'event': 0, 'eventslist':[]} ], [ {'day': 6, 'event': 0, 'eventslist':[]}, {'day': 7, 'event': 0, 'eventslist':[]}, {'day': 8, 'event': 0, 'eventslist':[]}, {'day': 9, 'event': 0, 'eventslist':[]}, {'day':10, 'event': 0, 'eventslist':[]}, {'day':11, 'event': 0, 'eventslist':[]}, {'day':12, 'event': 0, 'eventslist':[]} ], [ {'day':13, 'event': 0, 'eventslist':[]}, {'day':14, 'event': 0, 'eventslist':[]}, {'day':15, 'event': 0, 'eventslist':[]}, {'day':16, 'event': 0, 'eventslist':[]}, {'day':17, 'event': 0, 'eventslist':[]}, {'day':18, 'event': 0, 'eventslist':[]}, {'day':19, 'event': 0, 'eventslist':[]} ], [ {'day':20, 'event': 0, 'eventslist':[]}, {'day':21, 'event': 0, 'eventslist':[]}, {'day':22, 'event': 0, 'eventslist':[]}, {'day':23, 'event': 0, 'eventslist':[]}, {'day':24, 'event': 0, 'eventslist':[]}, {'day':25, 'event': 0, 'eventslist':[]}, {'day':26, 'event': 0, 'eventslist':[]} ], [ {'day':27, 'event': 0, 'eventslist':[]}, {'day':28, 'event': 0, 'eventslist':[]}, {'day':29, 'event': 0, 'eventslist':[]}, {'day':30, 'event': 0, 'eventslist':[]}, {'day':31, 'event': 0, 'eventslist':[]}, {'day': 0, 'event': 0, 'eventslist':[]}, {'day': 0, 'event': 0, 'eventslist':[]} ] ] assert self.Tool.getEventsForCalendar(month='1', year='2002') == data, self.Tool.getEventsForCalendar(month='1', year='2002') def test_spanningEventCalendarRendering(self): self.Site.Members.folder_factories.invokeFactory(type_name="Event",id='Event1') event = self.app.restrictedTraverse('/CalendarTest/Members/Event1') event.edit( title='title' , description='description' , eventType=( 'eventType', ) , effectiveDay=1 , effectiveMo=1 , effectiveYear=2002 , expirationDay=31 , expirationMo=1 , expirationYear=2002 , start_time="00:00" , startAMPM="AM" , stop_time="11:59" , stopAMPM="PM" ) self.Site.portal_workflow.doActionFor( event, 'publish', comment='testing') data = [ [ {'day': 0, 'event': 0, 'eventslist':[]}, {'day': 0, 'event': 0, 'eventslist':[]}, {'day': 1, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': '00:00:00'}]}, {'day': 2, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day': 3, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day': 4, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day': 5, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]} ], [ {'day': 6, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day': 7, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day': 8, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day': 9, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day':10, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day':11, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day':12, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]} ], [ {'day':13, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day':14, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day':15, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day':16, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day':17, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day':18, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day':19, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]} ], [ {'day':20, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day':21, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day':22, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day':23, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day':24, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day':25, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day':26, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]} ], [ {'day':27, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day':28, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day':29, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day':30, 'event': 1, 'eventslist':[{'title': 'title', 'end': None, 'start': None}]}, {'day':31, 'event': 1, 'eventslist':[{'title': 'title', 'end': '23:59:00', 'start': None}]}, {'day': 0, 'event': 0, 'eventslist':[]}, {'day': 0, 'event': 0, 'eventslist':[]} ] ] assert self.Tool.getEventsForCalendar(month='1', year='2002') == data, self.Tool.getEventsForCalendar(month='1', year='2002') def test_getPreviousMonth(self): assert self.Tool.getPreviousMonth(2,2002) == DateTime('1/1/2025') assert self.Tool.getPreviousMonth(1,2002) == DateTime('12/1/2025') def test_getNextMonth(self): assert self.Tool.getNextMonth(12,2001) == DateTime('1/1/2025') assert self.Tool.getNextMonth(1,2002) == DateTime('2/1/2025') def test_getBeginAndEndTimes(self): assert self.Tool.getBeginAndEndTimes(1,12,2001) == (DateTime('12/1/2025 12:00:00AM'),DateTime('12/1/2025 11:59:59PM')) def test_singleDayRendering(self): self.Site.Members.folder_factories.invokeFactory(type_name="Event",id='Event1') event = self.app.restrictedTraverse('/CalendarTest/Members/Event1') event.edit( title='title' , description='description' , eventType=( 'eventType', ) , effectiveDay=1 , effectiveMo=1 , effectiveYear=2002 , expirationDay=31 , expirationMo=1 , expirationYear=2002 , start_time="00:00" , startAMPM="AM" , stop_time="11:59" , stopAMPM="PM" ) self.Site.portal_workflow.doActionFor( event, 'publish', comment='testing') assert len(self.Site.portal_calendar.getEventsForThisDay(thisDay=DateTime('1/1/2025'))) == 1 self.Site.Members.folder_factories.invokeFactory(type_name="Event",id='Event2') event = self.app.restrictedTraverse('/CalendarTest/Members/Event2') event.edit( title='title' , description='description' , eventType=( 'eventType', ) , effectiveDay=1 , effectiveMo=1 , effectiveYear=2002 , expirationDay=1 , expirationMo=1 , expirationYear=2002 , start_time="00:00" , startAMPM="AM" , stop_time="11:59" , stopAMPM="PM" ) self.Site.portal_workflow.doActionFor( event, 'publish', comment='testing') assert len(self.Site.portal_calendar.getEventsForThisDay(thisDay=DateTime('1/1/2025'))) == 2 self.Site.Members.folder_factories.invokeFactory(type_name="Event",id='Event3') event = self.app.restrictedTraverse('/CalendarTest/Members/Event3') event.edit( title='title' , description='description' , eventType=( 'eventType', ) , effectiveDay=12 , effectiveMo=12 , effectiveYear=2001 , expirationDay=1 , expirationMo=1 , expirationYear=2002 , start_time="00:00" , startAMPM="AM" , stop_time="11:59" , stopAMPM="PM" ) self.Site.portal_workflow.doActionFor( event, 'publish', comment='testing') assert len(self.Site.portal_calendar.getEventsForThisDay(thisDay=DateTime('1/1/2025'))) == 3 self.Site.Members.folder_factories.invokeFactory(type_name="Event",id='Event4') event = self.app.restrictedTraverse('/CalendarTest/Members/Event4') event.edit( title='title' , description='description' , eventType=( 'eventType', ) , effectiveDay=12 , effectiveMo=12 , effectiveYear=2001 , expirationDay=31 , expirationMo=1 , expirationYear=2002 , start_time="00:00" , startAMPM="AM" , stop_time="11:59" , stopAMPM="PM" ) self.Site.portal_workflow.doActionFor( event, 'publish', comment='testing') assert len(self.Site.portal_calendar.getEventsForThisDay(thisDay=DateTime('1/1/2025'))) == 4 self.Site.Members.folder_factories.invokeFactory(type_name="Event",id='Event5') event = self.app.restrictedTraverse('/CalendarTest/Members/Event5') event.edit( title='title' , description='description' , eventType=( 'eventType', ) , effectiveDay=31 , effectiveMo=1 , effectiveYear=2002 , expirationDay=31 , expirationMo=1 , expirationYear=2002 , start_time="00:00" , startAMPM="AM" , stop_time="11:59" , stopAMPM="PM" ) self.Site.portal_workflow.doActionFor( event, 'publish', comment='testing') assert len(self.Site.portal_calendar.getEventsForThisDay(thisDay=DateTime('1/1/2025'))) == 4 def test_suite(): return unittest.TestSuite(( unittest.makeSuite( TestCalendar ), )) if __name__ == '__main__': unittest.main(defaultTest='test_suite') CMF-1.3/CMFCalendar/tests/test_Event.py0100644000076500007650000000343507512313572017606 0ustar tseavertseaverimport unittest import Zope from Products.CMFCalendar.Event import Event from DateTime import DateTime class TestEvent(unittest.TestCase): def test_new(self): event = Event('test') assert event.getId() == 'test' assert not event.Title() def test_edit(self): event = Event('editing') event.edit( title='title' , description='description' , eventType=( 'eventType', ) , effectiveDay=1 , effectiveMo=1 , effectiveYear=1999 , expirationDay=12 , expirationMo=31 , expirationYear=1999 , start_time="00:00" , startAMPM="AM" , stop_time="11:59" , stopAMPM="PM" ) assert event.Title() == 'title' assert event.Description() == 'description' assert event.Subject() == ( 'eventType', ), event.Subject() assert event.effective_date == None assert event.expiration_date == None assert event.end() == DateTime('2024/12/31 23:59') assert event.start() == DateTime('2025/01/01 00:00') assert not event.contact_name def test_puke(self): event = Event( 'shouldPuke' ) self.assertRaises( DateTime.DateError , event.edit , effectiveDay=31 , effectiveMo=2 , effectiveYear=1999 , start_time="00:00" , startAMPM="AM" ) def test_suite(): return unittest.TestSuite(( unittest.makeSuite( TestEvent ), )) if __name__ == '__main__': unittest.main(defaultTest='test_suite') CMF-1.3/CMFCalendar/tests/test_all.py0100644000076500007650000000051207512313572017266 0ustar tseavertseaverimport unittest import Zope from Products.CMFCore.tests.base.utils import build_test_suite def test_suite(): return build_test_suite('Products.CMFCalendar.tests', ['test_Event', 'test_Calendar']) if __name__ == '__main__': unittest.main(defaultTest='test_suite') CMF-1.3/CMFCalendar/www/0040755000076500007650000000000007524010061014562 5ustar tseavertseaverCMF-1.3/CMFCalendar/www/configureCalendarTool.zpt0100644000076500007650000000344507466765454021631 0ustar tseavertseaverheader tabs

Configure portal_calendar Tool

Portal Types to show in the calendar

All types that are to show in the calendar must have an attribute 'start' and an attribute 'end' which return DateTime objects to the Catalog.

Use sessions to remember the calendars state
Don't use sessions to remember the calendars state
Use sessions to remember the calendars state
Don't use sessions to remember the calendars state

footer CMF-1.3/CMFCalendar/www/explainCalendarTool.zpt0100644000076500007650000000060507466765454021303 0ustar tseavertseaverheader tabs

portal_calendar Tool

This tool provides a common interface for providing calendar rendering and manipulation functions.

footer CMF-1.3/CHANGES.txt0100644000076500007650000004650507524010040013514 0ustar tseavertseaverCMF 1.3 New Features - FSPageTemplates now autodetect XML if a document has the standard '<?xml version="xx"?>' at the top, setting the content_type to "text/xml" so that it is parsed and served as XML. - Added a 'Workflows' tab in the ZMI that displays the workflow state of the object. - Wired 'scrubHTML' check into default versions of 'document_edit' and 'newsitem_edit' skins (site managers who prefer to allow JavaScript in content can customize to remove the check). - Added utility methods for checking / scrubbing HTML content of unwanted tags: CMFDefault.utils: scrubHTML, isHTMLSafe Known bad tags ('script', 'applet', 'embed', 'object'), attributes (starting with 'on'), and values (starting with 'javascript:') cause 'scrubHTML' to raise an exception; unknown tags are silently dropped. 'isHTMLSafe' returns true if 'scrubHTML' would not raise an exception on the same text. - Exposed useful utility methods to scripting / skinning. CMFCore.utils: getToolByName, cookString, tuplize, format_stx, keywordsplitter, normalize, expandpath, minimalpath CMFDefault.utils: formatRFC822Headers, parseHeadersBody, semi_split, comma_split, seq_strip, tuplize, bodyfinder, html_headcheck. - Allowed Portal Folders to be discussable. - Improved the ActionsTool so that permission checking for actions is done in the context of the object for all categories that start with 'object' or 'workflow', in the context of the folder for those that start with 'folder', and in the context of the portal otherwise. This is useful for admin-defined additional categories. Bug Fixes - Fixed Image and File so that they reindex on PUT. - Fixed WorkflowTool so that it doesn't pretend to workflow non-CMF objects (Collector #19). - Removed redundant block of code from CMFDefault/Link.py (Collector #18). - Fixed discussion tree display not to embed entire HTML document (Collector #17, thanks to Jeffrey Shell for the inspiration). - Fixed CookieCrumbler to emit "Basic" and not "basic" auth as per HTTP spec (Collector #14, thanks to Simon Eisenmann for the report). This fixes some WebDAV locking problems with (rightfully) picky clients, like ExternalEditor. - Made old ListCriterion instances forward-compatible across earlier addition of 'operator' field. - Defaulted CMFCore.MembershipTool's createMemberArea flag to true, to preserve existing behavior: member area should be created by default on first login (Collector #5). - Hardened DefaultDublinCoreImpl's EffectiveDate() and ExpirationDate() methods to deal better with older / uninitialzed content. - Refactored content construction in the Types tool to make cataloguing and workflow notification more uniform. - Made Undo work again by removing any LF in transaction ids. - Made WorkflowTool.updateRoleMappings update security-related indexes. - Append '/' to links in skin templates to the root of the site, to pacify clients which won't send Basic Auth tokens because the path doesn't match. - Removed the binding of Folder and Topic types to the default workflow for newly-created CMFSites (Collector #4). Note that existing sites will still need to remove these bindings manually, as indicated in the "Upgrading" section of INSTALL.txt. - Updated initial action reported by CMFDefault's DefaultWorkflow: was "joined", now "created". - Fix 'index_html' template to filter using 'View' permission (folders which were not viewable were triggering authentication, instead of being skipped). - Repair free-form subject entry on 'full_metadata_edit_form' (was merging all keywords onto the same line, plus soft-wrapping!). Likewise the "Contributors" textarea. - Ensured that Link, File, and Image content objects reindex themselves after edit (wrapping 'edit' as a WorkflowMethod no longer guarantees that). - Fixed CMFCalendar's "day view" to accomodate the removal of 'title' metadata from catalog in favor of 'Title' (thanks to Dieter Maurer for pointing this out). - Corrected a skins performance optimization (the version as released in the beta did not take effect). CMF 1.3 beta2 (2025/07/07) New Features - Changed the modification date so that it is updated at reindex time and does not rely anymore on bobobase_modification_time. This makes export+import of objects not lose the modification date. Before exporting objects created with an older CMF version, reindex the "modified" index in the catalog (you just have to do it once, and only if you need to export objects). - Made the workflow tool compute chains using type names, as well as instances (Tracker #441). - Made the DCWorkflow worklists accept a list of formatted values for cataloged variable matches. The separator is a semicolon (Tracker #496). - Made the security-related indexes of the portal catalog be updated for all impacted objects whenever local roles are changed (Tracker #494). This feature makes use of the 'path' index. - Made 'path' (PathIndex) a standard index inside CMF. When upgrading from earlier versions this index will have to be created by hand. - Enabled "within day" queries for FriendlyDateCriteria E.g., field="modified", value="Now", operation="within_day", daterange="old" -> content which was modified "today". (Tracker #474). - Made the id of the current user available to old action providers (DCWorkflow being a prime example) as 'user_id' (Tracker #495, thanks to Luca Olivetti for the patch). - Modified the 'standard_error_message' page template to show the 'error_log_url', if passed in (Zope 2.6 will pass this argument if an error log is installed). - Extended the metadata tool to permit passing the type name directly (e.g., when building a new object, one might need to know the allowed subjects before construction). - Added a working calendar implementation to the CMFCalendar product, which had previously provided only an Event content type. Thanks to Andy Dawkins for the work. Note that we are disabling the use of sessions to track calendar selections by default; you can re-enable them by visiting the 'portal_calendar' tool in the ZMI. - Updated FS-based skin methods to read optional '.security files, whcih control the role-permission mappings for each skin method. See CMFCore/tests/fake_skins/fake_skin/test4.py.security for an example. Bugs Fixed - Fixed PortalFolder's filtering so that it correctly restricts itself to the portal types allowed by the TypesTool (Tracker #483). - Fixed the TypesTool so that TypeInformation's Title is decorrelated from its Id (the old TypeInformation.Type() should not be used anymore). The Id is what gets put in the "portal_type" attribute of a content object, which one gets at using somecontent.getPortalTypeName(). The Title is returned by somecontent.Type(). "portal_type" is now indexed, and in most cases should be used when doing catalog queries instead of "Type". - Provided all default skins (content_hide_form, content_show_form) for the DCWorkflow default workflow [rev 2]. Note that, when using this workflow, the descriptions in the retract and reject forms are slightly incorrect as they mention the "private" state but in fact go to the "visible" state. - Fixed verification of portal types in the TypesTool to work in the presence of portal types with a non-empty title (Tracker #497). - Removed unintentional sharing of ActionInformation data between class defaults and persistent instances derived from ActionProviderBase. Likewise for TypeInformation instances. - Enabled creation of "My Stuff" folder for users (e.g., the manager) authenticated "above" the normal user folder (Tracker #485, thanks to Dieter Maurer for the patch). - Fixed handling of discussion items so that they are correctly indexed, unindexed and reinserted into the workflows when copied or moved. - Correctly reindex the just-reset workflow variables of an object in a DCWorkflow after a paste. - Correctly insert into the workflows the objects created by a Scriptable Type. - Fixed Friendly Date Criterion edit form to correctly display the duration in its menu (Tracker #475, thanks to Axel Joester for the patch). - Fixed relative URL in metadata_edit_form that broke if the main_template had a base tag (Tracker #506). - Made discussion replies work correctly when hitting Enter in the title field (Tracker #515), and corrected discussion Preview followed by an Edit that lost body information (Tracker #516). - Fixed the deletion of replies (Tracker #513, thanks to Stefan H. Holek for the patch). - Made the Link objects deal correctly with empty remote urls (Tracker #507) and not strip trailing slashes (Tracker #451). - Made content_type_registry deal correctly with TypeInfos that have a title (Tracker #465, thanks to Juan Antonio Valio Garca for the patch). - Changed CMFDefault.MembershipTool not to create a member folder at member creation time when the memberareaCreationFlag is false (Tracker #519). Note that any code that depended on the member folder being created after addMember will fail, that code should call createMemberarea directly if needed. - Fixed CMFDefault.RegistrationTool to correctly check the lack of 'email' property when creating a new member or when checking member's properties validity (Tracker #508). - Fixed addtoFavorites to correctly add a favorite of someone else's document (Tracker #501). - Fixed CatalogTool to create a meta_type index; this is needed now that ZCatalog doesn't create any default indexes when instantiated. - Fixed recent_news to use 'Title' instead of 'title' to display the title of each news ('title' isn't indexed anymore). - Fixed listFolderContents to take into account its optional 'spec' argument. - Fixed index_html to correctly filter out unauthorized subfolders (Tracker #503). - Fixed exception handling to not use a string exception in PortalFolder (Tracker #512). - Changed the permission protecting the "Join" action provided by the default registration tool from "View" to "Add portal member" (Tracker #509). - Removed redundant "Syndication" action from SkinnedFolder; the SyndicationTool provides this aciton. (Tracker #481) - Updated INSTALL.txt to note the dependency of CMF on the TAL and ZTUtils packages and on the PageTemplates product for Zopes < 2.5. (Tracker #489). - Updated CatalogTool to create a Vocabulary if none present (as won't be for catalogs created under Zope 2.6). - Adapted interface checks to work with new spellings in Zope 2.6 (older Zopes should still work). - Removed '##bind' headers from FSPythonScripts where they only repeat the defaults. - Corrected patterns used for "HTML body stripping" to avoid HTML embedded within structured text. - Fixed computed action of form in 'folder_rename_form' (Tracker #511; thanks to "yuppie" for the patch). - Improved cacheability of skin images by using absolute URLs. - Suppressed repeated load of FSImage content from filesystem (should only happen when in debug mode). - Repaired skin methods' read of '.properties' files, which hold additional metadata about the skin method beyond what can be expressed in the body. - Updated caching policy manager tool to use correct date format (RFC 1123 instead of RFC 822). CMF 1.3 beta 1 (2025/04/03) New Features - CMFDefault Documents, News Items and Discussion Items now support a 'plain' text format, which simply HTML-quote's the text before displaying it. - Added CachingPolicyManager tool, which manages caching policies for skin methods, and updated FSPageTemplate to use a CPM if found. - Added functionality such that only users who have the view permission on the relevent Type object can create content of that type. - Added the ability to limit what types of object an object of a given Portal Type can be created in. If 'Implicity Addable' is set (the default), then objects of that Type can be added anywhere. If it is not set, then objects of that Type can only be added to objects whose Type's allowed_content_types contains the Type. - Enabled querying actions from workflow tool in absence of actions tool (Tracker #401). - Added 'operator' attribute to CMFTopic.ListCriterion, to permit specifying an operator ('and', for the most part) for indexes which support it, e.g., KeywordIndex (Tracker #442). - Added ZMI interface for editing Link URL (Tracker #364). Bugs Fixed - Make ZMI editing of NewsItems safe (Tracker #472). - Made generated home pages for new members participate fully in workflow (Tracker #467). - Added 'text_format' widget to NewsItem's edit forms (Tracker #460). - Fix sharing bug pointed out by Dieter Maurer (Tracker #484) in ActionProviderBase. Thanks for the patch! - Added forwared-compatibility for "old" content (pre CMF 1.0!; Tracker #454, thanks to Lucas Hofman for the patch). - Made 'PortalFolder.invokeFactory' enforce the 'filter_content_types' property of the folder's type information object. - Added stripping of leading / trailing whitespace from Subject keywords (Tracker #479, thanks to Lucas Hofman for the patch!) - Gave URL tool explicit __roles__, to enable use in 'nocall:' expressions. - Turned off auto-expansion of customized FSPageTemplates (Tracker #477). - Fixed ActionProvidorBase for the case when no permission is specified for an action that is added TTW. - Fixed ActionsTool so that duplicate actions are stripped. - Caused CMFDefault Portal constructor to strip passed-in id before creating a portal object with that id. - Allowed the ActionsTool to gracefully handle objects which return ActionInformation objects. Thanks to Andy Dawkins for the analysis. (Tracker #457) - Made workflow Expressions use the correct ModuleImporter so that they operate correctly in their restricted environment. Thanks to Dieter Maurer for the patch. (Tracker 463) - Fixed incorrent permissions in "pending" state of default DCWorkflows. Thanks for Lynn Walton for the report and Florent Guillaume for the patch. (Tracker #464) - Fixed missing comma that affected manager permission to modify in the published state. Thanks to Florent Guillaume for the patch (Tracker #459) - html_quote'd errors raised by FSPageTemplates. Thanks to Dieter Maurer for the patch. (Tracker #462) - Fixed typo in zpt_stylesheet.css. Thanks to Florent Guillaume for the patch. (Tracker #461) - Fixed long standing bug in FSPythonScript where get_size returned the incorrect length. This broke editing using EMACS via FTP or WebDAV. Thanks to John Glavin at South River Technologies for help finding the bug. - Reworked functionality added in Tracker #409 which broke the Types Tool. (Tracker #458) - Fixed bug whereby DirectoryView instances were not noticing some of the changes they should when Zope was running in debug mode on Windows (Tracker #305) - Fixed a bug where the workflow notifyCreated method was called during manage_afterAdd in PortalContent, making it possible for the notification to occur on the wrong workflow. The notification has moved to the contstructInstance method on the TypesTool after the _setPortalTypeName method has been called on the object. - Extended TypesTool to permit registration of new TypeInformation implementations (Tracker #409, thanks to Jeffrey Shell for the work!) - Fixed a bug in Favorites.getObject to use restrictedTraverse on the portal object. - Made all tool-generated actions configurable through-the-web, via an "Actions" tab on each tool; made the list of ActionProviders configurable TTW as well. - Fixed setting the Link.format to URL_FORMAT so the initially returned metadata headers would return 'text/url' properly. Added unittests. - Enabled querying actions from workflow tool in absence of actions tool (Tracker #401). - Fixed CMFDefault.utils.parseHeadersBody to properly handle the headers generated on a windows app (i.e. Dreamweaver) with /r/n; added the compiled regular expression object to the method signature. - Added full webdav sipport code to Link.py. Changed _writeFromPUT to call _editMetadata instead of editMetadata. - Made links emitted by 'topic_view' play nice with virtual hosting (Tracker #433). - Cleaned up emission of RFC822-style headers (Tracker #407), terminating headers must be terminated with CRLF, and padding continuation lines (for values with embedded newlines) with leading whitespace). - Ensure that package initialization files are non-empty, to prevent suspicion that they were corrupted in download (Tracker #426). - Added external method update_catalogIndexes.py to run as part of a upgrade to CMFs migrating to Zope2.4+ from from CMF sites which were built using Zope2.3 catalog - Use ID to label Favorite when target has an empty Title (Tracker #440). - Allowed sub-folders to have different syndication properties than parents (Tracker #421). - Added 'CMFDefault.Upgrade.upgrade_decor_skins' external method to convert existing sites which had installed skin directories from the now-deprecated 'CMFDecor' product (Tracker #434). Added note explaining the issue, and the workaround, to 'ISSUES.txt'. - Ensure that Favorites display the correct, absolute URL to their target, without needing to have 'base' tag set (Tracker #419). - Worked around Opera's strange insistence on selecting an option, even for multi-select lists (Tracker #332). - Hardened CMFCore to initialize correctly in the absence of the PageTemplates product (Tracker #430). - Restored slot in 'head' of ZPT main template into which content can insert the 'base' tag (Tracker #418). - Fixed 'CMFTopic.SimpleIntegerCriterion.edit' to require a pair of values when 'direction' is 'min:max'; updated skins to use new 'getValueString', which renders such values properly (Tracker #439). - Ensured that Documents created with initial STX get cooked (Tracker #435). - Made links emitted by 'topic_view' play nice with virtual hosting (Tracker #433). - Made 'CMFCore/interfaces/__init__.py' non-empty, to remove suspicion that the file was corrupted in the download (Tracker #426). CMF-1.3/HISTORY.txt0100644000076500007650000010541507523240432013612 0ustar tseavertseaver 1.2 beta (2025/12/07) New features - Added docs from the crack ZC docs guys; these docs live in the top-level 'docs' directory. - Merged CMFDecor product's artifacts into CMFCore / CMFDefault; theses aretifacts allow use of filesystem-based Zope Page Templates as skins. Note that the CMFDecor skin is the one which will be receiving all our development focus: we will fix bugs in the DTML skins, but are not likely to invest significant effort in upgrading it. - Hooked 'manage_addFolder' to allow creation of PortalFolders from both WebDAV, FTP, and ZMI. - Improved tracebacks from broken FSDTMLMethods, which no longer indicate that every problem is in RestrictedDTML. - Made it possible to add CookieCrumblers in nested folders. You can just drop in a cookie crumbler anywhere to change the login form for that area of the site. In fact, now you don't have to create a user folder just to change the login process. - Made Link objects editable via FTP / WebDAV. - Merged Chris Withers' FSSQLMethod into CMFCore. - Added documentation for installing from CVS. - Moved permission checking inside personalize_form to make sure Anonymous cannot access it without logging in (CMF Tracker Issue 349, thanks go to Bill Anderson). - Added initial CMF use cases as FSSTXMethods in CMFDefault/help. - Made validation methods of 'portal_metadata' available to scripts. - Made skinned 'index_html' reflect generic view on folder content, rather than simple title/description of the portal. - Added "Change and View" submit button to content editing forms; added check for this button to POST handlers in CMFDefault, and indirected redirect targets in those methods through 'getActionByID'. - Added knob for skin cookie persistence to SkinsTool's "properties" tab. The default policy (unchanged) is that skin cookies expire at the end of the browser session. if Skin Cookie Persistence is checked the cookie will last a full yesr. - Added an API to the 'portal_actions' interface for querying, adding, and removing action providers. - Added a "multi-review" form, enabling a reviewer to publish or reject multiple items at once, using a common comment. - Added ZMI tab to DirectoryView to allow re-basing the filesystem path. - Added "breadcrumbs" to CMFDecor skins. - Added initial support for WebDAV locaking to PortalContent. - Added SortCriterion to list of criterion types for Topics, to permit sorting of results. - Added "Local Roles" action to folders to ease collaboration. - Add scarecrow assertions for the CMF-centric interfaces, and made the actual interfaces compatible with the standard Zope Interface package. - Made FSSTXMethod display skinnable, and added ZPT version. - Added 'visible' attribute to TypeInformation actions, to permit indirection (via 'getActionById') without exposing the action in the CMF UI. - Extended MetadataTool to allow adding / removing element specs (i.e., it can now manage policies for "custom" schemas, as well as Dublin Core). Bug fixes - Refactored content and metadata editing methods of DefaultDublinCoreImpl, Document, and NewsItem to disentangle the excessive coupling. Each "path" for editing now has a "presentation"-level method, which directs traffic and reindexes the object; the underlying methods are much simplified. - Fixed inner / named links in Document / News Item (thanks to Kenichi Sato for the patch!) - Ensured that editing methods handle WebDAV locks correctly, using a new 'failIfLocked' assertion. - Added cookString method to CMFCore.utils for taking a string and making it id friendly, it also does a string.lower on the resultant regex. Changed TypesTool to utilize cookString to ensure that action ids are properly formated if the name is being used as the id. - Added 'getReply' to CMFDefault.DiscussionItem.DiscussionItemContainer, to permit access to an individual reply without needing to do traversal. - Corrected pass-through of 'file' and 'seatbelt' arguments in new 'CMFDefault.Document.edit' method; also sync'ed ZMI edit method for documents with standard protocol (Tracker #417). - Added cookString method to CMFCore.utils for taking a string and making it id friendly, it also does a string.lower on the resultant regex. - Made examples in INSTALL.txt less terse, and added notes on Windows-specific issues (thanks to Johan Mukhsein for the suggestions). - Made error message generated by FSPropertiesObject capture the offending line and line #; also, added logic to allow blank lines and comment lines beginning with '#' (tracker #338). - Added fixup to Link objects for user-entered URLs which don't supply scheme: for example, fix up 'www.zope.com' to 'http://www.zope.com'. (tracker #361) - Updated CMFCore.CatalogTool to allow new, optional 'idxs' argument to 'catalog_object' (tracker #405). - Added a workaround for the problem where the CookieCrumber set cookies even though the user entered an incorrect password. RESPONSE.unauthorized() now cancels the cookie response header. The new login_form and logged_in form both try to invoke unauthorized(), so make sure you install the new forms. - Implement the notional 'search results item' interface for SkinnedFolder. - Corrected solecism in Topic (use of 'criteria' for singular); removed the need to know about the funky generated IDs for criterion objects. Fixed derived bug in skins (tracker #408). - Modified error-logging code to avoid potential leaks of traceback frame. - Made Document's 'manage_FTPget' use 'EditableBody', rather than accessing 'text' attribute directly (improves reusability). Likewise 'Document.SearchableText'. - Merged Seb Bacon's refactoring of 'getDefaultView' into 'CMFCore.utils._getView'; clients can now specify a view by name, as well. - Made the default content type for Image 'image/png', instead of the unintuitive 'text/html' inherited from DefaultDublinCoreImpl (tracker #384). - Corrected typo in ActionsTool which broke actions for the root portal object (tracker #379). - Updated the MemberDataTool to use an OOBTree, instead of the old-style BTree, to store member data wrappers (CMF Tracker 375). - Corrected 'personalize_form' to use 'getProperty' where feasible, rather than relying on direct attribute access (tracker #372). - Removed an incompatibility with LoginManager in CMFCore.MembershipTool (tracker #365). - Removed an infinite loop condition that arises when MembershipTool.createMemberArea gets called inside wrapUser (this could only happen if "Create Member Area" was checked in the Membership tool.) - Added new 'TitleOrId' skin method, and updated skins to depend on it rather than SimpleItem.title_or_id. - Made unit tests runnable without reliance on 'zctl test'. - Corrected initial column set in catalog to include "ModificationDate" instead of "ModifiedDate". - Ensured that object is recatalogued (e.g., after setting 'portal_type'; thanks to Florent Guillaume). - Removed silly dependency of 'CatalogTool.searchResults' on REQUEST (catalog already does the Right Thing (tm) when no REQUEST is available). Note that this requires updates to the 'search' skins, as they didn't pass it in. - Changed redirect target after rejecting an item to the search page for pending content items; this resolves the problem that the non-Manager reviewr who rejects an item no longer has View permission on it, and therefore gets an Unauthorized when redirecting to the object's view action. - Moved generation of the "Add to Favorites" and "My Favorites" links from the CMFCore/ActionsTool into the CMFDefault/MembershipTool, which is a more logical location for it because it relies on information from the membership tool. - Made Topic directly publishable (like PortalContent), using its 'view' action (or the first action found, if view is not present). - Set title for newly-created member folders; fixes breadcrumbs that expect a title on the object. - Allow URLs with query strings in StructuredText links. - Update 'news_box' to search based on 'Type', rather than 'meta_type'. - Fix 'SkinnedFolder.Creator()' to call 'getOwner()' with info argument. - Made CookieCrumbler check for 'WEBDAV_SOURCE_PORT' environment variable (supplied by Zope 2.4.1+), and bail on intercepting authentication if found. - Included 'Owner' in list of significant roles returned by 'MembershipTool.getPortalRoles' (e.g., so that the "Local Roles" interface can allow assignment of it). - Allow users with local role of "Reviewer" to see the "pending review" action. - Made TypesTool, rather than individual type objects, responsible for generating "immediate view" URL; type objects now return the newly-created object, which makes scripting them much simpler. - Remove fossilized reference to 'getSecurityManager' from 'PortalContent._verifyActionPermissions'. - Modified the redirect after "Add to Favorites" to us the view action, rather than 'view'. - Fixed 'Document.guessFormat()' to use 'utils.html_headcheck()' instead of 'bodyfinder' regex to detect structured text versus html; avoids recursion limit for large HTML files. - Removed spurious 'afterCreate' protocol for content objects. - Added mapping of 'css' file extension to FSDTMLMethods. - Modifed PortalFolder.listFolderContents to handle permission-based filtering; duplicates what skip_unauthorized is doing in DocumentTemplate/DT_IN.py (but works for ZPT as well). - Modified CMFDefault.RegistrationTool.addMember to avoid flunking validation if properties are not passed (Tracker #335). - Applied patch from Chris Withers to 'register' skin method; the patch which avoids quoting problems for the error message if a problem occurs (Tracker #339). - Added 'DiscussionItem.replyCount' (Tracker #328). 'DiscussionItem.hasReplies' now returns only a boolean value. Standard skins don't call 'replyCount' due to performance concerns. - Factored content filtering logic into a Python Script. - Improved handling of multiple rename targets (thanks to Chris Withers for the patch.) - Completed conversion of form targets from DTML Methods to Python Scripts. - Improved compatibility with Zope 2.4: o support for new "restricted execution" mode; o support for new catalog initialization API. o updated 'test_all' unit test drivers to use standard 'unittest' module from Python 2.1 (it no longer has 'JUnitTestTextRunner' class). 1.1 final (2025/06/20) New features - Landed "skinned folder" module, which permits creation of "folderish" types with customized replacements for 'index_html'. - Added 'run_all_tests' script, to simplify running all unit tests for CMF-related products. - Factored out index and column lists, to ease customization (Tracker #289) - Modified DefaultDublinCoreImpl#setContributors() to use new tuplize functionality (the ability to pass in a different splitter function). If a string is passed in to setContributors(), it gets split on semicolons. This allows setting of contributor strings to be like "Doe, John E (john@doe.a.deer); The Mertz Family Foundation; PBS" If one so deeply and dearly desired. (The above would turn into a three element tuple). - Made it possible to map "normal" Zope objects as content (Tracker #283): o Skin and tool code which used to query the object for its Type in order to then ask the types tool for a TypeInfo object now just asks the types tool for type TypeInfo object directly. o Modified the TypesTool interface to signal that passing an object to 'getTypeInfo' is acceptable, with the semantic that the tool will attempt to find a TypeInfo object based on the tool's type or metatype. - Expanded signature of Document.CookedBody to change the header levels Structured Text starts rendering at, and whether to preserve those changes, following the following logic: If the format is html, and 'stx_level' is not passed in or is the same as the object's current settings, return the cached cooked text. Otherwise, recook. If we recook and 'setlevel' is true, then set the recooked text and stx_level on the object. Bug fixes - Hardened DirectoryView against objects which raise exceptions during initial reads (typically due to permission problems); these objects now capture and log the exception, and create a BadFile object which allows browsing the traceback via the ZMI (Tracker #317). - Stopped polluting the browser with persistent skin cookie; clear it as well on logout (Tracker #304). - Cleaned up folder content filtering (Tracker #298, thanks to Chris Withers for the patch). - Implement new "List folder contents" permission (Tracker #320), to prevent non-privileged users from being able to browse 'folder_contents' (permission is by default mapped to 'Owner' and 'Manager'). Note that this is really only a UI change: 'PortalFolder.contentIds', 'PortalFolder.contentValues', and 'PortalFolder.contentItems' are still public, to allow for reasonable "site map" views on folders. - Use saner type ID listing in 'search_form' (Tracker #324). - Rewired "POST target" skin methods, replacing filesystem-based DTML Methods with filesystem-based Python Scripts (from Tracker #301 -- thanks to Chris Withers). - Implemented *LARGE* patch to CMFDefault.DiscussionItem and CMFDefault.DiscussionTool, plus removal of CMFDefault.Discussions: changed threading strategy to remove dependency on the path of the "host" content object. Includes an ExternalMethod, CMFDefault/Extensions/update_discussion.py, which must be run to convert existing content which contains discussion. - Overrode DublinCore's isEffective in CMFCalendar.Event to always return true; this change prevents blocking view of the event by standard_html_header. - Updated INSTALL.txt to note that: o Use with Zope 2.4.0a1 is *not* recommended. o CMFDecor requires Zope Page Templates (Tracker #291). and to describe upgrade process from CMF 1.0. - Fixed typo in 'Unauthorized' exception raised by 'PortalContent._getDefaultView'. - Made all content types findable by catalog (Tracker #286). - Ensured that ContentTypeRegistry's "Test" tab doesn't puke if no MIMEtype is entered, or if one is entered which doesn't have a '/' in it (Tracker #292). - Suppressed overwrite of Subject when no 'Subject' or 'Keywords' headers were present (Tracker #294). - Ensured that 'Type' is indexed in newly-created 'portal_catalog'. - Fixed acquisition stripping in PortalContent.objectItems(). - Modified CMFDefault.DublinCore.getMetadataHeaders() to join Subject by commas, instead of spaces. Since we parse/split Subject on commas or semicolons now when a document is saved by FTP or WebDAV, we need to feed it out in the same format. - Fleshed out security declarations for 'portal_metadata' tool. - Corrected pathname of 'MemberDataTool.memberdataContents' to account for use of '_dtmldir' (thanks to Ricardo Newbery for the patch!). - Fixed buglet resulting from uninitialized local in 'PortalContent.objectItems' (thanks to Seb Bacon for the patch). - Modified stock 'search_form' to query 'Type' rather than 'meta_type'. - Removed debugging / "paranoia" print statements (thanks to Chris Withers for the patches). 1.1 beta (2025/06/01) New features - Added CMFCore.utils.keywordsplitter to construct 'Subject' dublin core header from both 'Keywords' and 'Subject' headers passed in (meta tags/rfc822). - CMF Document now uses StructuredTextNG for structured text handling. Nicely enough, it uses the "with images" features, and also enables named and inner links. - CMF Document exposes two new accessor methods for getting at the "cooked" and editable body, 'CookedBody()', and 'EditableBody()'. Using the 'cooked_text' and 'text' instance attributes should be considered deprecated. - Added new "exemplar" content product, CMFCalendar, which (for now) adds a simple content object, Event. This product serves a similar didactic purpose as CMFTopic (which is now "in the core"): it shows how to build new content types as filesystem products. - Implemented improved "safety belt" for concurrent Document editing. See the dogbowl proposal, http://cmf.zope.org/rqmts/proposals/ContentSafetyBelt - Added new filesystem-based skin method type, FSSTXMethod, for creating "methods" which render STX. - Exposed "controlled vocabulary" for Subject in 'metadata_edit_form'. - Gave 'portal_skins' tool its own PUT_factory (Tracker #238). Allows site builder to create skin methods via WebDAV/FTP, including ZPT, PythonScripts, etc. - Implemented "outbound" segment of the "Syndication tool" proposal, q.v., http://cmf.zope.org/CMF/Members/andrew/folderRSS - Made 'PUT_factory' use new 'content_type_registry' tool. - Made 'PUT_factory' use 'invokeFactory', so that objects created via PUT have their 'portal_type' set properly. - Added ContentTypeRegistry class and related predicates for mapping PUT requests (name,content_type,body) to type object names. - Allowed Reviewers to "retract" published content owned by others. - Added "Overview" ZMI view to tools. - Added "Dublin Core" ZMI view to PortalContent; added an editing view for Document and derivatives. - Added "drop-in" workflow objects to the WorkflowTool, including the ability to map workflows onto content types. - Changed CMFDefault.Portal to install CMF Topic by default when building a new CMF Site. - Added a new criteria type, *FriendlyDateCriterion*, to CMFTopic for building queries like 'When effective_date is less than five days old'. - Implemented 'portal_metadata' tool. See the dogbowl proposal, http://cmf.zope.org/rqmts/proposals/completed/metadata_tool. - Extended DirectoryView objects to reload when the directory they represent has changed, if Zope is running in debug mode. - Added hooks to CookieCrumbler to permit site managers to customize seting/expiring of the authentication cookie by adding PythonScripts; two default filesystem implementations are in CMFDefault's "control" skin. - Extended TypesTool to deal appropriately with DTML factory methods (e.g., for ZClasses). - Made FSDTMLMethods searchable from the ZMI (Issue PTK(251)[]). Bug Fixes - Added 'manage_beforeDelete' to DiscussionItemContainer class, and propagated to it from content deletion (Tracker #269). - Only display the "join" action if the user has the "Add portal member" permission. - Check for appropriate REQUEST variables before invoking CopySupport machinery, return a nice 'portal_status_message' instead of allowing the traceback to kick in (Tracker #247). - Initialize default DublinCore metadata for the site, so that *some* value is available everywhere (Tracker #285). - Add module-level globals aliases, to permit other products to use elements of skins (Tracker #273). - Replaced 'metadata_edit' DTMLMethod with PythonScript. - Updated 'metadata_edit' to avoid clearing values not passed in REQUEST (Tracker #268). - Removed spurious whitespace being added to Subject. - Fixed FSPythonScript to open file in text mode, so that Windows verstions with CR don't cause the parser to choke (Tracker #284). - Simplified login-disabling logic in CookieCrumbler. - Added '' tag to headers of Document, NewsItem, and DiscussionItem so that relative links work properly. - Fixed the ActionsTool to suppress adding a trailing '/' to "empty" action URL's. - Fixed Document's content type sniffing to deal with odd case in which "entire" HTML document was embedded as an STX example. - Replaced literal string permissins with "named permissions", imported from modules in most places. (Please report any which escaped!) - Moved 'factory_type_information' registry entries back out to the relevant modules (instead of lumping them in CMFDefault.Portal). - Simplified NewsItem to "Document with a twist" (NewsItems now inherit lots of useful behavior from Document. - Aliased DublinCore's Format() as 'content_type', so that Zope's WebDAV support will do the Right Thing (TM) when handling a PROPFIND. - Suppressed "eager redirect" behavior in CookieCrumbler for FTP or WebDAV (note that WebDAV fix only works for "extra" HTTP methods; GET/PUT/POST still redirect). - Made sure to update the catalog when a content object's parent is deleted / moved (Tracker #261). - Fixed lossage to File objects when no upload supplied (Tracker #271). - Fixed "Add to Favorites" when applied to Folders. - Suppressed "Members" link in topbar (pointed to 'roster' method) for users who would not be able to see it. - Fixed irritating bugs in setting of skin-selection cookie. - Removed ugly '_mimetype_registry' hack for 'PUT_factory'. - Fixed a bug in CMFDefault.Document with PUT that only affected FTP: when a StructuredText document that contained a full HTML example (complete with , , and tags) in its body, it would pass the "does it smell like HTML" test and throw everything away that wasn't inside the first matching pair of tags it found. Funnily enough, this bug was exposed when trying to save an article about how to use FTP with Zope and the CMF that contained such an example. - Fixed glitch caused by '' in CMFDefault/skins/control/reconfig.dtml. Thanks to Hans de Wit for reporting this! - Allowed 'portal_type' to be a method, as well as an attribute (eases ZClass-as-content). - Fixed search form to pass 'created' instead of 'date' (probably need 'modified', too); also, added selection list for 'Subject', and fixed the "Last Month" option value. - Fixed non-import in CMFCore/WorkflowCore.py (Tracker #239, thanks to 'snej' for the patch). - Updated CatalogTool to handle searchable vs. displayable DublinCore dates properly. - Replaced usage of .id with .getId() all through the CMF, to conform with the new SimpleItem API. Thanks to Jens Quade for pointing this out. (Issue PTK(241)[]). - Extended 'index_html' to render portal description using STX (Tracker #246). - Updated CMFDefault skins to use Contentish.getIcon, including the portal catalog. This greatly simplifies using ZClasses as content. In CMFDefault/scripts you will find 'convertCatalogGetIconColumn.pys), a Python Script that will convert your existing Catalog to use 'getIcon' instead of 'icon' (Issue PTK(244)[]). Also, synched interface definition to show the 'relative_to_portal' argument. - Extended 'folder_factories' to filter type objects using 'isConstructionAllowed' in 'PortalFolder.allowedContentTypes' (Tracker #249). - Fixed display of replies on discussion items. - Made appropriate use of 'portal_url', instead of BASEPATH ad hockery, in skin methods (Tracker #259). - Synched interface definition of 'Contentish.getIcon' with reality (documented 'relative' parameter). - Fixed "Ever" entry in 'search_form' to use a value safe in timezones east of GMT (Tracker #236). - Deprecated the CMFDefault.File.download method; it is still available, but is no longer called by the current CMF skin, nor is index_html aliased to it. The method was necessary when the "View' method was the default action when calling the URL of the File object, this is no longer the case. The setting of the Content-Disposition header is not needed when using the absolute URL of the object, and causes certain browsers to misbehave. 1.0 final (2025/03/29) - Removed embedded spaces in 'portal_status_message' strings (thanks to Dieter Maurer for pointing this out) - Added 'index_html' (copy/paste from CMFCore.PortalContent?!?) so that topics become directly publishable (thanks to Dieter Maurer for pointing this out). - Removed references to 'Images/' in skin DTML, adding an extra item to the default skins' lookup path. In CMFDefault/scripts you will find 'addImagesToSkinPaths.pys', a Python Script that adds the extra name to all your skin paths. (Tracker issue #205). - Added FSPythonScripts, allowing filesystem-based skins to expose customizable PythonScripts. - Fixed CMF Tracker item (207), "HTML test when editing a Portal Document is ambiguous". Now, the same regular expression used to scoop out the contents of the tags, which is case insensitive, is also used to detect whether the document may be HTML. - Converted physical path to string (Tracker #224). - Fixed 'Creator' metadata for DiscussionItems (tracker issue #206, thanks to Jeff Sasmor for the patch!). - Added simple search field for 'Subject' (Tracker #213). - Fixed CMF Tracker issue 211, "Topics have Folder icon" by finally moving the Topic icon into the skins, and writing a custom icon() method that uses getIcon (Subclassing from PortalFolder made this behavior strange). - Changed CMFDefault.Document__init__ to call self.edit at the end, instead of self._parse, which means that HTML can finally be passed into the constructor. Unit tests verify that initializing with just the 'id' gives the proper results. - Added installation script (external method) for CMFTopic. - Ensured that content metatypes show up in the types list of the portal_catalog tool (Tracker issue #209, thanks to Jeff Sasmor). - Fixed unwanted multiple font reductions on 'recent_news' (Tracker issue #215). - Changed visited link color to enhance readability. - Fixed PNG transparency of logo (for NS 4.7). - Allowed Contributors to publish content directly (Tracker issue #216). - Gave more real estate to skin path fields (Tracker issue #226). - Added simple listing of subtopics to the default view (Tracker issue #214). - Regularized DiscussionItem.absolute_url (further work on Tracker issues #203 & #206). - Made 'addToFavorites' work as skinned method of target, and work for DiscussionItems (Tracker issue #227). - Moved actual mail sending into CMFDefault.RegistrationTool, to avoid having to give proxy roles to the skin methods involved (Tracker issue #160). - Add member count to roster display. - Made 'review_state' workflow info publically visible. - Made DiscussionItem.getReplies() and DiscussionItemContainter.getReplies() show only 'published' replies (allows retraction, for now). - Fixed WorkflowAction.__call__ to use the correct method name for exception notification (Tracker issue #232). - Ensure that reply's Creator propagates through preview (thanks to Jeff Sasmor for reporting that our fix to Tracker issue #206 missed the "Preview" case). - Removed crufty calls to registerPortalContent. - Trimmed over-long item IDs in actions box (Tracker issue # 234; thanks to Jeff Sasmor and Steve Alexandar for the patch). 1.0 beta (2025/03/05) * Migrated from old PTK repository on cvs.zope.org. * Changed package and module names: 'PTKBase' has become (mostly) 'CMFCore'; 'PTKDemo', 'CMFDefault'. * Added package 'CMFTopic', which provides a new content type, 'Topic'; topics are "logical" folders, which aggregate content based on catalog searches against metadata. * Revised discussion mechanism to store discussion with its underlying content (rather than in members' "Correspondence" folders). * Mad the membership system agnostic to the kind of user folder, including whether it is found in the portal or not. * "Skinned" the portal UI and all the content objects, enabling portal managers to replace it "piecemeal", safely. * Implemented local type registration, allowing portal managers to configure and extend the types addable in the portal. * Add 'PortalFolder.allowedContentTypes' and supporting machinery in type info objects, to permit configuration of the content types addable to a given folder type. 0.10alpha (2025/02/02) * Fixed breakage of "reply" feature introduced by new constructor regime (PTK Tracker #159). * Make creation of an 'acl_users' in the portal itself optional. * Add mapping of external roles (i.e., belonging to a user retrieved from an acquired user folder) onto "portal-specific" roles. For example, users from the root user folder may have the "Employee" role; the 'portal_membership' tool can map this role onto the "Member" role needed by the portal. * Corrected HTML quoting of Document text (PTK Tracker #154). * If content_url is not None and the user has their own member folder (as returned by 'portal_membership.getHomeUrl') then two new actions will show up in the user-related action box: - "Add to Favorites", adds an item to the user's "Favorites" folder, which will get created underneath the user's home folder if not present. - "My Favorites", linked to the Favorites folder (if the member has one). * Add 'Favorites' content type (derived from 'Link', optimized for objects within the same site). * Add 'PTKBase.interfaces.Contentish', an interface describing non-metadata methods for all content objects. * Added cool new portal icon -- thanks to Michael Bernstein! * Add 'simple_metadata' and 'metadata_help' methods, for easing "constructorish" capture of metadata. Also demos "skinning" object-specific DTML. * Rearranged base classes to deal with the fact that SimpleItem now has a _setId() method. * Added a "quick start" script for creating a new INSTANCE_HOME sandbox (e.g., to allow easy experimentation with new-style portal). * Remove "metadata" editing from most "normal" edit forms. * Tweak stylesheets to make Netscrape 4.x more happy (links on secondary accent areas wouldn't show). * Enforced standard construction interface (only 'id' is required) on all "stock" content types. * Added default factory registration in 'portal_types' for all "stock" content types. * Add 'portal_types' tool, allowing registry of type / constructor information about portal content which may be created in a given portal instance (replaces 'Wizards' folder as type registry). * Added the description property to those attributes shown on the "view" screen for a file object (PTK Tracker #153). * Added credentialsChanged hook which can now work with CookieCrumblers. * Added Shane Hathaway's CookieCrumbler product, which spoofs HTTP Basic Auth for "don't know from cookies" user folders. * Add object for mapping property lists as filesystem object (to support parameterized stylesheet). These objects are customized as normal Folders, which allows a great deal of flexibility. * Corrected 'PTKDemo.File.edit' to avoid replacing file contents with empty string when no file is uploaded (PTK Tracker #152). * Remove distribution-tab verisons of TTW products. * Remove derived UML model files (UML-HTML). * Added the PortalGenerator class, which is a subclassable portal instance creator, and created a new addable meta type. * Removed ZClass dependency from PropertiesTool. * Corrected glitch that 'PTKDemo.PortalObject.manage_options' was inherited from ObjectManager rather than PortalFolder. * Add 'PUT_factory' to PTKBase.PortalFolder (make it replaceable, too!) * Register PTKDemo content class metatypes with PTKBase.PortalFolder's MIME-type registry. * Unscrew PTKDemo.Document's dependence on vanished NullPortalResource. * Clean up CSS in DemoPortal (PTK Tracker #124). * Remove dependency on PythonMethod in DemoPortal (PTK Tracker #151). * Integrated user preference for "skin" with 'portal_skins' options. * Added capability to filter * 'PortalFolder.content{Ids,ValuesItems}' based on metadata queries. * Migrated skins from 'DemoPortal/Interfaces' folders into filesystem skins directories. * Improved integration of FSDTMLMethod and FSImage with output from the FSDump product. * New tab for the member data tool: 'contents' displays the number of members stored in the member data tool and also the number of "orphaned" members, meaning those that do not appear in the underlying acl_users anymore. a button is provided to "prune" those orphaned members. * Added methods to examine the contents of the meber data tool and to prune all those members who have been deleted out of the underlying 'acl_users'. * Fixed bug when adding the mailhost during Portal initialization. * Added the portal_memberdata tool, which can wrap any user object with member properties. Yeehaw! the portal no longer needs a special user folder! * Add 'portal_skins' tool, enabling safe TTW customization of the default portal UI, which will be delivered as filesystem-based skin folders. Huge kudos to Shane Hathaway, who figured out a sane way to do this! * Extend fix for PTK Tracker #149 to 'generic' and 'sweng' interfaces. * Append '/view' to search results in order to allow navigation to objects which don't use standard header/footer in their 'index_html', e.g., Images. (PTK Tracker Issue #149) * Add meta_type to DiscussionItem (PTK Tracker Issue #150). * Integrate Dan Pierson's patch to show discussion threads inline (PTK Tracker issue #93). * Fix PTK Tracker #145 (PTKDemo/Portal.py or MailHost broken): the 2025/12/08 hotfix removed the "legacy" name, 'manage_addMailHost'. * Fix PTK Tracker #148 ('Table rows need vertical align') * Fix PTK Tracker #147 ("PTKDemo's Metadata HTML is ugly"). * Fix PTK Tracker issue #136 ('CSS selection broken'). * Fix Tracker issue #141 ('Wizard product has broken subobjects list'). * Remove redundant 'description' arg from constructors (PTK Tracker issue #142; thanks to jon@totient.demon.co.uk for reporting this). * Enable FTP upload/creation of NewsItems * Add the ability to query and set DublinCore metadata via FTP: - Updated PTKDemo.DemoDublinCore.DemoDublinCore to add a method, 'getMetadataHeaders()', which builds a list of DublinCore headers, suitable for inclusion in tags or in "Structured Text Headers". - Updated PTKDemo.Document.Document to export/import the metadata when accessed via FTP. * Fix PortalFolder.all_meta_types() to allow non-content. * Fix PortalFolder.listActions() to return a tuple, not a list CMF-1.3/INSTALL.txt0100644000076500007650000000777707523377073013607 0ustar tseavertseaverInstalling CMF 1.3 Requirements - Zope v. 2.4.3 or later, v. 2.5 and later (*not* 2.4.2 or 2.4.1) o Note: For Zope 2.4.x, you will also need to install the most recent versions of the following packages: * PageTemplates product * TAL package * ZTUtils package These packages are available at: http://www.zope.org/Members/4am/ZPT/ Assumptions - New installation - Zope configured using INSTANCE_HOME, /var/zope, and SOFTWARE_HOME, /usr/local/zope/Zope-2.5.1 Procedure 1. Unpack the CMF-1.3.tar.gz tarball into a working directory. For instance:: $ cd /usr/local/zope $ tar xzf /tmp/CMF-1.3.tar.gz Note for Windows users: if you are using WinZip to unpack the file, be *sure* to disable the "TAR file smart CR/LF expansion" option (thanks to Frank McGeough for tracking down this problem!) 2. Link (or copy/move) the CMF packages into $INSTANCE_HOME/Products (or into $SOFTWARE_HOME/lib/python/Products). For instance:: $ cd /var/zope/Products # /var/zope is INSTANCE_HOME $ ln -s /usr/local/zope/CMF-1.3/CMFCore . $ ln -s /usr/local/zope/CMF-1.3/CMFDefault . $ ln -s /usr/local/zope/CMF-1.3/CMFTopic . or, as a shortcut:: $ ln -s /usr/local/zope/CMF-1.3/CMF* . Since linking doesn't apply on Windows, you will need to cut or copy the files from the place where you unpacked them to the 'Products' directory of your Zope. 3. Restart Zope; verify that the CMF products loaded property, by examining them in Control_Panel/Product. 4. Create a CMF Site object. join, and begin adding content. Enjoy! Upgrading from Earlier Versions Install the New Software. 0. "Download":CMF-1.3.tar.gz the tarball. 1. Copy your "working" products off to one side (in case you need / choose to revert). 2. Unpack the tarball into a separate location; copy or link the subdirectories into the 'Products' directory of your INSTANCE_HOME. 3. Restart Zope. Follow additionnal version-specific information below. Upgrading from versions earlier than CMF 1.3-beta2 Add New Metadata: "portal_type" From the ZMI of your CMFSite, go to the 'portal_catalog' tool, and select the "Metadata" tab. Add a Metadata for "portal_type". Add New Indexes: "path", "portal_type" In the Indexes tab of the 'portal_catalog' tool, add a "PathIndex" using the Add list, and give it an Id of "path". Then add a "FieldIndex" and give it an Id of "portal_type". Reindex the New Indexes In the Indexes tab of the 'portal_catalog' tool, select the two new indexes ("path" and "portal_type") and click "Reindex". Remove Unwanted Workflow Bindings In the Workflows tabl of the 'portal_workflow' tool, clear the "(Default)" value from the input fields for the "Folder" and "Topic" types and click "Change". Upgrading from CMF 1.0 Create and Configure New tools 'portal_metadata' tool 1. From the ZMI of your CMFSite, select "CMFDefault Tool" from the Add list. Select "Default Metadata Tool" and click "Add". 2. Configure the "Elements" tab, particularly the "Subject" element (give it "suggested" subjects, perhaps by looking at the values on the search page). 'portal_syndication' 1. From the ZMI of your CMFSite, select "CMFDefault Tool" from the Add list. Select "Default Syndication Tool" and click "Add". 2. Use the "Properties" tab to enable syndication for the site (if desired), and then to configure the "sitewide" policies for outbound syndication. Once enabled, you will be able to toggle syndication and policies for individual folders as well. Add New Content Type: Event 1. Build and run the installation ExternalMethod as described in CMFCalendar/INSTALL.txt. CMF-1.3/INSTALL_CVS.txt0100644000076500007650000000561007512157715014277 0ustar tseavertseaverOverview This document describes the process of getting the CMF from the CVS repository, and installing it into your Zope. It assumes the following: * You want to install the "latest-and-greatest" version of the CMF from a CVS checkout. - See "Installing CMF":INSTALL.txt to install from a tarball. - See the note below [1] if you want to export or check out the sources for a specific version from CVS. *Please note that current versions of the CMF (1.2 and later) require Zope 2.4.3 or later* * For help with CVS in general, see the "CVS Online Manual", http://www.loria.fr/~molli/cvs-index.html * For direcitons on using the Zope CVS server, see "Public CVS Access", http://dev.zope.org/CVS/ReadOnlyAccess; substitute 'CMF' for 'Zope'. Installation 1. Fetch the CMF package from the Zope CVS repository:: $ cd /tmp $ cvs -d :pserver:anonymous@cvs.zope.org:/cvs-repository login $ cvs -d :pserver:anonymous@cvs.zope.org:/cvs-repository \ checkout -d CMF-head CMF This checkout creats a directory, 'CMF-head', which contain subdirectories: - CMFCore - CMFDefault - CMFTopic - CMFCalendar and several others. 2. Copy or link each of these subdirectories into the 'Products' directory of your Zope server (either SOFTWARE_HOME or INSTANCE_HOME) e.g.:: $ cd /var/zope/Products $ ln -s /tmp/CMF-head/CMFDefault . $ ln -s /tmp/CMF-head/CMFDefault . $ ln -s /tmp/CMF-head/CMFTopic . 3. Verify filesystem products: Start or restart your Zope server. Check to see that the following products are present in the Control_Panel / Products list; each should show a "normal" (non-broken) icon, and should have a version number matching the release version of the snapshot: - CMFCore - CMFDefault - CMFTopic 4. Create a CMF Site: From the management interface, somewhere in the "main" section of your site (*not* the Control_Panel!), select "Portal (New)" from the add list. Fill out the constructor form, and click the "Add" button. Refreshing your Checkout Note that to refresh an existing CVS sandbox, you should use 'cvs up -d -P' (assuming you want to remove obsolete directories and retrieve any newly-added ones). Fetching a Previous Version .. [1] A normal CVS checkout retrieves what is called the "head" (the latest revision for each file) from the "main trunk" (no unmerged branches). Retrieving Sources for a Release Sources for prior releases are tagged, using the release version identifier, but with the periods replaced with underscores. For instance, version 1.1 of the CMF was tagged as 'CMF-1_1-src'. To fetch such a release from CVS:: $ cvs -d :pserver:anonymous@cvs.zope.org:/cvs-repository \ checkout -r CMF-1_1-src CMF CMF-1.3/LICENSE.txt0100644000076500007650000000447607401232657013545 0ustar tseavertseaverZope Public License (ZPL) Version 2.0 ----------------------------------------------- This software is Copyright (c) Zope Corporation (tm) and Contributors. All rights reserved. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the above copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name Zope Corporation (tm) must not be used to endorse or promote products derived from this software without prior written permission from Zope Corporation. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of Zope Corporation. Use of them is covered in a separate agreement (see http://www.zope.com/Marks). 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY ZOPE CORPORATION ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ZOPE CORPORATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This software consists of contributions made by Zope Corporation and many individuals on behalf of Zope Corporation. Specific attributions are listed in the accompanying credits file. CMF-1.3/README.txt0100644000076500007650000000215507523400343013402 0ustar tseavertseaverZope Content Management Framework (CMF) README What is the CMF? The Zope Content Management Framework provides a set of services and content objects useful for building highly dynamic, content-oriented portal sites. As packaged, the CMF generates a site much like the Zope.org site. The CMF is intended to be easily customizable, in terms of both the types of content used and the policies and services it provides. Resources * The CMF "dogbowl" site, http://cmf.zope.org. * The mailing list, zope-cmf@zope.org. List information and online signup are available at: http://lists.zope.org/mailman/listinfo/zope-cmf. Archives of the list are at: http://lists.zope.org/pipermail/zope-cmf. Known Issues * Please search the "CMF Collector", http://collector.zope.org/CMF for issues which are open against the CMF. You can also report issues there (please look for similar ones first!) Installation Please see "Installing CMF":INSTALL.txt. "Installing CMF from CVS":INSTALL_CVS.txt covers installing the CMF from a CVS checkout. CMF-1.3/TODO.txt0100644000076500007650000000753207511373477013234 0ustar tseavertseaverCMF TODO's CMF 1.3 b2 (codename "FreshStart") Issues - Tracker #465: Adding title to TypeInfo breaks content_type_registry. - Tracker #474: FriendlyDateCriterion can't spell "today". - Tracker #475: FDC has broken ZPT edit form - Tracker #476: STX gobbling paragraphs with parens - Tracker #481: Reduntant syndication action on Skinned Folder - Tracker #483: listFolderContents doesn't filter properly - Tracker #485: "External" users don't get member folder (patch) - Tracker #489: Dependency on TAL breaks Zope 2.4.x - Tracker #495: Make logged-in userid available to actions providers - Tracker #496: Allow formatted expressions in DCWorkflow actions - Tracker #500: Skin cookie for deleted skin causes traceback - Tracker #501: addToFavorites loses if no favorites folder - Tracker #503: index_html/folder_view don't skip unauth folders - Tracker #506: Invalid relative URL on metadata_edit_form - Tracker #507: Link inconsistent on empty remote_url - Tracker #508: logged_in pukes if no email - Tracker #509: "Join" action not protected by right permission - Tracker #512: Incorrect use of string exception in PortalFolder. - Tracker #513: deleteReply is genocidal - Tracker #514: Document STX messing up anchors - Tracker #515: Improper default button for discussion reply - Tracker #516: Preview, then edit broken for ZPT discussion replies. - Tracker #519: CMFDefault.MembershipTool always creates member folder Tasks - (x) FriendlyDateCriterion fixes (x) Tracker #474: FriendlyDateCriterion can't spell "today". (x) Tracker #475: FDC has broken ZPT edit form - (x) STX Fixes (Seb Bacon) (/) Tracker #476: STX gobbling paragraphs with parens (x) Tracker #514: Document STX messing up anchors - (x) Actions fixes (x) Tracker #481: Reduntant syndication action on Skinned Folder (x) Tracker #495: Make logged-in userid available to actions providers (x) Tracker #496: Allow formatted expressions in DCWorkflow actions (x) Tracker #509: "Join" action not protected by right permission - (x) Membership fixes (x) Tracker #485: "External" users don't get member folder (patch) (x) Tracker #501: addToFavorites loses if no favorites folder (x) Tracker #508: logged_in pukes if no email (x) Tracker #519: CMFDefault.MembershipTool always creates member folder - (x) Folder fixes (Florent Guillaume) (x) Tracker #483: listFolderContents doesn't filter properly (x) Tracker #503: index_html/folder_view don't skip unauth folders (x) Tracker #512: Incorrect use of string exception in PortalFolder. - (x) Tool fixes (x) Tracker #465: Adding title to TypeInfo breaks content_type_registry. - (x) Skin fixes (/) Tracker #500: Skin cookie for deleted skin causes traceback (x) Tracker #506: Invalid relative URL on metadata_edit_form - (x) Content fixes (x) Tracker #507: Link inconsistent on empty remote_url (x) Tracker #513: deleteReply is genocidal (x) Tracker #515: Improper default button for discussion reply (x) Tracker #516: Preview, then edit broken for ZPT discussion replies. - (x) Documentation fixes (Tres Seaver) (x) Tracker #489: Dependency on TAL breaks Zope 2.4.x CMF-1.3/all_cmf_tests.py0100644000076500007650000000301407507702665015106 0ustar tseavertseaver#! /usr/bin/env python import unittest import sys # PackageName Required? CMF_PACKAGES = [ ( 'CMFCore', 1 ) , ( 'CMFDefault', 1 ) , ( 'CMFTopic', 1 ) , ( 'CMFCalendar', 0 ) , ( 'DCWorkflow', 0 ) ] PACKAGES_UNDER_TEST = [] def test_suite(): import Zope from Products.CMFCore.tests.base.utils import build_test_suite suite = unittest.TestSuite() packages = PACKAGES_UNDER_TEST or CMF_PACKAGES for package_name, required in packages: dotted = 'Products.%s.tests' % package_name suite.addTest( build_test_suite( dotted , [ 'test_all' ] , required=required ) ) return suite def usage(): USAGE = """\ all_cmf_tests.py [-?] * where package_name is the list of packages to be tested default: %s """ print USAGE % CMF_PACKAGES sys.exit( 2 ) def main(): import getopt try: opts, args = getopt.getopt( sys.argv[1:], 'v?' ) except getopt.GetoptError: usage() sys.argv[ 1: ] = [] PASSTHROUGH = ( '-v', ) for k, v in opts: if k in PASSTHROUGH: sys.argv.append( k ) if k == '-?' or k == '--help': usage() for arg in args: PACKAGES_UNDER_TEST.append( ( arg, 1 ) ) unittest.main(defaultTest='test_suite') if __name__ == '__main__': main() CMF-1.3/CMFCore/0040755000076500007650000000000007524010063013117 5ustar tseavertseaverCMF-1.3/CMFCore/dtml/0040755000076500007650000000000007524010063014057 5ustar tseavertseaverCMF-1.3/CMFCore/dtml/addCC.dtml0100644000076500007650000000231407245471213015704 0ustar tseavertseaver &dtml-form_title;

&dtml-form_title;

Id
CMF-1.3/CMFCore/dtml/addFSDirView.dtml0100644000076500007650000000176607245471213017233 0ustar tseavertseaver Add Directory View

Add Directory View

Directory
Id
(Leave blank to use the default.)

CMF-1.3/CMFCore/dtml/addInstance.dtml0100644000076500007650000000307307311544533017166 0ustar tseavertseaver &dtml-form_title;

&dtml-form_title;

Note: these are normally only useful inside a CMF site.
Id
CMF-1.3/CMFCore/dtml/addTypeInfo.dtml0100644000076500007650000000256707436460541017172 0ustar tseavertseaver &dtml-form_title;

&dtml-form_title;

Id
Use default type information
CMF-1.3/CMFCore/dtml/addWorkflow.dtml0100644000076500007650000000175407311214035017227 0ustar tseavertseaver
Id
Type
&dtml-sequence-item;
CMF-1.3/CMFCore/dtml/cachingPolicies.dtml0100644000076500007650000001053207446350666020046 0ustar tseavertseaver

Edit Caching Policies

Policy ID &dtml-getPolicyId;
Predicate No-cache?
Mod. Time No-store?
Max age (secs) Must-revalidate?


Add Caching Policy

Policy ID
Predicate No-cache?
Mod. time No-store?
Max age (secs) Most-revalidate?

CMF-1.3/CMFCore/dtml/catalogFind.dtml0100755000076500007650000000534207307251204017163 0ustar tseavertseaver

Use this form to locate objects to be cataloged. Those objects which are found will be automatically added to the catalog.

Find objects of type:
with ids:
containing:
expr:
modified:
where the roles:
have permission:
CMF-1.3/CMFCore/dtml/custdtml.dtml0100644000076500007650000000322307265130111016574 0ustar tseavertseaver Customize
Id
Size bytes
Last modified &dtml-getModTime;
Source file &dtml-getObjectFSPath;
Customize Select a destination folder and press the button to make a copy of this method that can be customized.
CMF-1.3/CMFCore/dtml/custimage.dtml0100644000076500007650000000341707265130111016723 0ustar tseavertseaver Customize
Id
Content Type &dtml-getContentType;
Dimensions &dtml-width; x &dtml-height;
Size bytes
Last modified &dtml-getModTime;
Source file &dtml-getObjectFSPath;
Customize Select a destination folder and press the button to make a copy of this method that can be customized.
CMF-1.3/CMFCore/dtml/custprops.dtml0100644000076500007650000000440507264134570017016 0ustar tseavertseaver Customize
Id
Source file &dtml-getObjectFSPath;
Customize Select a destination folder and press the button to make a copy of these properties that can be customized.
 
Name
Value
Type
&dtml-type;
CMF-1.3/CMFCore/dtml/custpt.dtml0100644000076500007650000000273607403764773016314 0ustar tseavertseaver
Id &dtml-getId;
Size bytes
Last modified &dtml-getModTime;
Source file &dtml-getObjectFSPath;
Customize Select a destination folder and press the button to make a copy of this template that can be customized.
CMF-1.3/CMFCore/dtml/custpy.dtml0100644000076500007650000000273607265130111016274 0ustar tseavertseaver
Id &dtml-getId;
Size bytes
Last modified &dtml-getModTime;
Source file &dtml-getObjectFSPath;
Customize Select a destination folder and press the button to make a copy of this script that can be customized.
CMF-1.3/CMFCore/dtml/custstx.dtml0100644000076500007650000000321507305060235016457 0ustar tseavertseaver Customize
Id
Size bytes
Last modified &dtml-getModTime;
Source file &dtml-getObjectFSPath;
Customize Select a destination folder and press the button to make a copy of this method that can be customized.

CMF-1.3/CMFCore/dtml/custzsql.dtml0100644000076500007650000000323507363577451016655 0ustar tseavertseaver Customize
Id
Size bytes
Last modified &dtml-getModTime;
Source file &dtml-getObjectFSPath;
Customize Select a destination folder and press the button to make a copy of this method that can be customized.
CMF-1.3/CMFCore/dtml/dirview_properties.dtml0100644000076500007650000000073407352430154020675 0ustar tseavertseaver

Directory View Properties

ID: &dtml-getId;
Filesystem path:

CMF-1.3/CMFCore/dtml/editActions.dtml0100644000076500007650000001047507340211363017215 0ustar tseavertseaver &dtml-form_title;

&dtml-form_title;

Name
Id
Action
Permission
Category
Visible?

Add an action

Name
Id
Action
Permission
Category
Visible?
CMF-1.3/CMFCore/dtml/editToolsActions.dtml0100644000076500007650000001135107415403554020240 0ustar tseavertseaver &dtml-form_title;

&dtml-form_title;

Name
Id
Action
Condition
Permission
Category
Visible?

Add an action

Name
Id
Action
Condition
Permission
Category
Visible?
CMF-1.3/CMFCore/dtml/explainActionsTool.dtml0100644000076500007650000000135007276657265020605 0ustar tseavertseaver

portal_actions Tool

This tool assembles the "actions" which are germane to the current user (anonymous, member, etc.) and context (content object or folder being viewed). These actions are drawn from several sources:

  • the actions tool itself, for "global" actions;
  • the workflow tool, for actions which depend on the workflow state of the content object;
  • the membership tool, for actions which depend on the member or user;
  • the types tool, for actions which are specific to a given content type.

CMF-1.3/CMFCore/dtml/explainCatalogTool.dtml0100644000076500007650000000044607276657265020564 0ustar tseavertseaver

portal_catalog Tool

This tool wraps the standard Zope ZCatalog, supplying additional indices, metadata, and policies which are specific to the operation of a CMFSite.

CMF-1.3/CMFCore/dtml/explainDiscussionTool.dtml0100644000076500007650000000040607276657265021331 0ustar tseavertseaver

portal_discussion Tool

This tool embodies the policies for a given CMFSite concerning the storage mechanisms for discussion about content.

CMF-1.3/CMFCore/dtml/explainMemberDataTool.dtml0100644000076500007650000000037307276657265021212 0ustar tseavertseaver

portal_memberdata Tool

This tool is responsible for handling the storage of the "membership" properties germane to the CMFSite.

CMF-1.3/CMFCore/dtml/explainMembershipTool.dtml0100644000076500007650000000054707276657265021307 0ustar tseavertseaver

portal_membership Tool

This tool encapsulates the authentication mechanism (the user folder) in a more usable, "member-centric" interface, insulating other CMF objects from changes or idiosyncracies in the particular member folder.

CMF-1.3/CMFCore/dtml/explainRegistrationTool.dtml0100644000076500007650000000036207276657265021661 0ustar tseavertseaver

portal_registration Tool

This tool embodies the policies of the CMFSite with respect to joining / adding a new member.

CMF-1.3/CMFCore/dtml/explainSkinsTool.dtml0100644000076500007650000000060707276657265020300 0ustar tseavertseaver

portal_skins Tool

This tool extends the user interface of the CMFSite by providing configurable, layered name lookup to "skin methods", which may be DTML Methods, images, Python Scripts, etc., made available to the content in the site in a controlled fashion.

CMF-1.3/CMFCore/dtml/explainTypesTool.dtml0100644000076500007650000000107007276657265020310 0ustar tseavertseaver

portal_types Tool

This tool manages the set of policies related to the content types which can be created on the CMFSite. These policies include:

  • the construction mechanism;
  • whether discussion is allowed for the type;
  • what types can be added (significant only for "folderish" types);
  • the "actions" which can be performed on objects of the type;

CMF-1.3/CMFCore/dtml/explainUndoTool.dtml0100644000076500007650000000040307276657265020110 0ustar tseavertseaver

portal_undo Tool

This tool provides an interface to the Zope undo machinery, without requiring access to the Zope Management Interface.

CMF-1.3/CMFCore/dtml/explainWorkflowTool.dtml0100644000076500007650000000063607276657265021025 0ustar tseavertseaver

portal_workflow Tool

This tool associates objects of a given content type with a "workflow"; workflows are "state machines", each representing the life cycle of a given family of content types, and specifying the "workflow actions" which are possible in any phase of the life cycle.

CMF-1.3/CMFCore/dtml/extensionWidget.dtml0100644000076500007650000000066107303631251020124 0ustar tseavertseaver This snippet is designed to be included in a larger method; it provides input elements for a predicate which tests filename extensions. Extensions: One or more filename extensions. CMF-1.3/CMFCore/dtml/majorMinorWidget.dtml0100644000076500007650000000112407303631251020220 0ustar tseavertseaver This snippet is designed to be included in a larger method; it provides input elements for a predicate which is based on major/minor content type patterns. Content type:  /  Major/minor content type (blank for all). CMF-1.3/CMFCore/dtml/manageActionProviders.dtml0100644000076500007650000000211507510140244021221 0ustar tseavertseaver &dtml-form_title;

&dtml-form_title;

Name
&dtml-sequence-item;
CMF-1.3/CMFCore/dtml/memberdataContents.dtml0100644000076500007650000000244107245471213020566 0ustar tseavertseaver Membership data tool contents

Membership data tool contents

Number of members stored:

Number of "orphaned" members without user record:

" method="post">

Number of members stored:

Number of "orphaned" members without user record:

" method="post">

No pruning needed

CMF-1.3/CMFCore/dtml/membershipRolemapping.dtml0100644000076500007650000000372707510140244021300 0ustar tseavertseaver &dtml-form_title;

&dtml-form_title;

Membership role mappings

Use this screen if you are using a userfolder other than the built-in folder to map existing role names to roles understood by the CMF.

Portal Role User Folder-defined Role  
">


Control creation of member areas

This feature controls whether users coming from an outside user source (such as an underlying user folder) will have their own folder created upon first login or not

Folders are created upon first login.

No Folders are created.

CMF-1.3/CMFCore/dtml/mimetypePredEdit.dtml0100644000076500007650000000065007303555042020217 0ustar tseavertseaver

Edit MIMEType Predicate: &dtml-getId;

Pattern:

CMF-1.3/CMFCore/dtml/patternWidget.dtml0100644000076500007650000000065407303631251017567 0ustar tseavertseaver This snippet is designed to be included in a larger method; it provides input elements for a predicate which is based on regular expressions. Pattern: Enter a regular expression. CMF-1.3/CMFCore/dtml/registryPredList.dtml0100644000076500007650000000335707511373500020271 0ustar tseavertseaver

Edit Predicate List

&dtml-sequence-key;
[&dtml-predType;]:


Add predicate:
CMF-1.3/CMFCore/dtml/registryTest.dtml0100644000076500007650000000130507303615552017456 0ustar tseavertseaver

Test Registry

Result: &dtml-testResults;

File name:
Content type:
Body:

CMF-1.3/CMFCore/dtml/selectWorkflows.dtml0100644000076500007650000000252407511421176020145 0ustar tseavertseaver &dtml-form_title;

&dtml-form_title;

Workflows by type

&dtml-id; (&dtml-title;)
(Default)

Click the button below to update the security settings of all workflow-aware objects in this portal.

CMF-1.3/CMFCore/dtml/skinProps.dtml0100644000076500007650000000604107510140244016727 0ustar tseavertseaver &dtml-form_title;

&dtml-form_title;

Skin selections
Name Layers (in order of precedence)
&dtml-sequence-key;

Default skin
REQUEST variable name
Skin flexibility checked="checked" />
Skin Cookie persistence checked="checked" />
CMF-1.3/CMFCore/dtml/zmi_workflows.dtml0100644000076500007650000000172707517542274017701 0ustar tseavertseaver

Workflows

This object's portal type is: &dtml-portal_type;.

This object belongs to the following workflows:

Workflow State
&dtml-wfid;

This object doesn't belong to any workflow.

CMF-1.3/CMFCore/ActionInformation.py0100644000076500007650000001545307523112215017122 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Information about customizable actions. $Id: ActionInformation.py,v 1.9.4.2 2025/08/04 02:42:21 efge Exp $ """ from AccessControl import ClassSecurityInfo from Globals import InitializeClass from Globals import DTMLFile from Acquisition import aq_inner, aq_parent from OFS.SimpleItem import SimpleItem from Expression import Expression from CMFCorePermissions import View from CMFCorePermissions import ManagePortal from utils import _dtmldir from utils import getToolByName class ActionInformation( SimpleItem ): """ Represent a single selectable action. Actions generate links to views of content, or to specific methods of the site. They can be filtered via their conditions. """ _isActionInformation = 1 __allow_access_to_unprotected_subobjects__ = 1 security = ClassSecurityInfo() def __init__( self , id , title='' , description='' , category='object' , condition='' , permissions=() , priority=10 , visible=1 , action='' ): """ Set up an instance. """ if condition and type( condition ) == type( '' ): condition = Expression( condition ) if action and type( action ) == type( '' ): action = Expression( action ) self.id = id self.title = title self.description = description self.category = category self.condition = condition self.permissions = permissions self.priority = priority self.visible = visible self.action = action security.declareProtected( View, 'Title' ) def Title(self): """ Return the Action title. """ return self.title or self.getId() security.declareProtected( View, 'Description' ) def Description( self ): """ Return a description of the action. """ return self.description security.declarePrivate( 'testCondition' ) def testCondition( self, ec ): """ Evaluate condition using context, 'ec', and return 0 or 1. """ if self.condition: return self.condition(ec) else: return 1 security.declarePublic( 'getAction' ) def getAction( self, ec ): """ Compute the action using context, 'ec'; return a mapping of info about the action. """ info = {} info['id'] = self.id info['name'] = self.Title() action_obj = self._getActionObject() info['url'] = action_obj and action_obj( ec ) or '' info['permissions'] = self.getPermissions() info['category'] = self.getCategory() info['visible'] = self.getVisibility() return info security.declarePrivate( '_getActionObject' ) def _getActionObject( self ): """ Find the action object, working around name changes. """ action = getattr( self, 'action', None ) if action is None: # Forward compatibility, used to be '_action' action = getattr( self, '_action', None ) if action is not None: self.action = self._action del self._action return action security.declarePublic( 'getActionExpression' ) def getActionExpression( self ): """ Return the text of the TALES expression for our URL. """ action = self._getActionObject() return action and action.text or '' security.declarePublic( 'getCondition' ) def getCondition(self): """ Return the text of the TALES expression for our condition. """ return getattr( self, 'condition', None ) and self.condition.text or '' security.declarePublic( 'getPermissions' ) def getPermissions( self ): """ Return the permission, if any, required to execute the action. Return an empty tuple if no permission is required. """ return self.permissions security.declarePublic( 'getCategory' ) def getCategory( self ): """ Return the category in which the action should be grouped. """ return self.category or 'object' security.declarePublic( 'getVisibility' ) def getVisibility( self ): """ Return whether the action should be visible in the CMF UI. """ return self.visible security.declarePrivate( 'clone' ) def clone( self ): """ Return a newly-created AI just like us. """ return self.__class__( id=self.id , title=self.title , description=self.description , category =self.category , condition=self.getCondition() , permissions=self.permissions , priority =self.priority , visible=self.visible , action=self.getActionExpression() ) InitializeClass( ActionInformation ) class oai: #Provided for backwards compatability # Provides information that may be needed when constructing the list of # available actions. __allow_access_to_unprotected_subobjects__ = 1 def __init__( self, tool, folder, object=None ): self.portal = portal = aq_parent(aq_inner(tool)) membership = getToolByName(tool, 'portal_membership') self.isAnonymous = membership.isAnonymousUser() self.user_id = membership.getAuthenticatedMember().getUserName() self.portal_url = portal.absolute_url() if folder is not None: self.folder_url = folder.absolute_url() self.folder = folder else: self.folder_url = self.portal_url self.folder = portal self.content = object if object is not None: self.content_url = object.absolute_url() else: self.content_url = None def __getitem__(self, name): # Mapping interface for easy string formatting. if name[:1] == '_': raise KeyError, name if hasattr(self, name): return getattr(self, name) raise KeyError, name CMF-1.3/CMFCore/ActionProviderBase.py0100644000076500007650000002230707511671624017231 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Implement a shared base for tools which provide actions. $Id: ActionProviderBase.py,v 1.13 2025/07/06 22:47:48 tseaver Exp $ """ from Globals import DTMLFile from AccessControl import ClassSecurityInfo from OFS.SimpleItem import SimpleItem from ActionInformation import ActionInformation from CMFCorePermissions import ManagePortal from Expression import Expression from utils import _dtmldir class ActionProviderBase: """ Provide ActionTabs and management methods for ActionProviders """ security = ClassSecurityInfo() _actions = () _actions_form = DTMLFile( 'editToolsActions', _dtmldir ) manage_options = ( { 'label' : 'Actions' , 'action' : 'manage_editActionsForm' } , ) # # ActionProvider interface # security.declarePrivate( 'listActions' ) def listActions( self, info=None ): """ Return all the actions defined by a provider. """ return self._actions or None # # ZMI methods # security.declareProtected( ManagePortal, 'manage_editActionsForm' ) def manage_editActionsForm( self, REQUEST, manage_tabs_message=None ): """ Show the 'Actions' management tab. """ actions = [] if self.listActions() is not None: for a in self.listActions(): a1 = {} a1['id'] = a.getId() a1['name'] = a.Title() p = a.getPermissions() if p: a1['permission'] = p[0] else: a1['permission'] = '' a1['category'] = a.getCategory() or 'object' a1['visible'] = a.getVisibility() a1['action'] = a.getActionExpression() a1['condition'] = a.getCondition() actions.append(a1) # possible_permissions is in AccessControl.Role.RoleManager. pp = self.possible_permissions() return self._actions_form( self , REQUEST , actions=actions , possible_permissions=pp , management_view='Actions' , manage_tabs_message=manage_tabs_message ) security.declareProtected( ManagePortal, 'addAction' ) def addAction( self , id , name , action , condition , permission , category , visible=1 , REQUEST=None ): """ Add an action to our list. """ if not name: raise ValueError('A name is required.') a_expr = action and Expression(text=str(action)) or '' c_expr = condition and Expression(text=str(condition)) or '' if type( permission ) != type( () ): permission = permission and (str(permission),) or () new_actions = self._cloneActions() new_action = ActionInformation( id=str(id) , title=str(name) , action=a_expr , condition=c_expr , permissions=permission , category=str(category) , visible=int(visible) ) new_actions.append( new_action ) self._actions = tuple( new_actions ) if REQUEST is not None: return self.manage_editActionsForm( REQUEST, manage_tabs_message='Added.') security.declareProtected( ManagePortal, 'changeActions' ) def changeActions( self, properties=None, REQUEST=None ): """ Update our list of actions. """ if properties is None: properties = REQUEST actions = [] for index in range( len( self._actions ) ): actions.append( self._extractAction( properties, index ) ) self._actions = tuple( actions ) if REQUEST is not None: return self.manage_editActionsForm(REQUEST, manage_tabs_message= 'Actions changed.') security.declareProtected( ManagePortal, 'deleteActions' ) def deleteActions( self, selections=(), REQUEST=None ): """ Delete actions indicated by indexes in 'selections'. """ sels = list( map( int, selections ) ) # Convert to a list of integers. old_actions = self._cloneActions() new_actions = [] for index in range( len( old_actions ) ): if index not in sels: new_actions.append( old_actions[ index ] ) self._actions = tuple( new_actions ) if REQUEST is not None: return self.manage_editActionsForm( REQUEST, manage_tabs_message=( 'Deleted %d action(s).' % len(sels))) security.declareProtected( ManagePortal, 'moveUpActions' ) def moveUpActions( self, selections=(), REQUEST=None ): """ Move the specified actions up one slot in our list. """ sels = list( map( int, selections ) ) # Convert to a list of integers. sels.sort() new_actions = self._cloneActions() for idx in sels: idx2 = idx - 1 if idx2 < 0: # Wrap to the bottom. idx2 = len(new_actions) - 1 # Swap. a = new_actions[idx2] new_actions[idx2] = new_actions[idx] new_actions[idx] = a self._actions = tuple( new_actions ) if REQUEST is not None: return self.manage_editActionsForm( REQUEST, manage_tabs_message=( 'Moved up %d action(s).' % len(sels))) security.declareProtected( ManagePortal, 'moveDownActions' ) def moveDownActions( self, selections=(), REQUEST=None ): """ Move the specified actions down one slot in our list. """ sels = list( map( int, selections ) ) # Convert to a list of integers. sels.sort() sels.reverse() new_actions = self._cloneActions() for idx in sels: idx2 = idx + 1 if idx2 >= len(new_actions): # Wrap to the top. idx2 = 0 # Swap. a = new_actions[idx2] new_actions[idx2] = new_actions[idx] new_actions[idx] = a self._actions = tuple( new_actions ) if REQUEST is not None: return self.manage_editActionsForm( REQUEST, manage_tabs_message=( 'Moved down %d action(s).' % len(sels))) # # Helper methods # security.declarePrivate( '_cloneActions' ) def _cloneActions( self ): """ Return a list of actions, cloned from our current list. """ return map( lambda x: x.clone(), list( self._actions ) ) security.declarePrivate( '_extractAction' ) def _extractAction( self, properties, index ): """ Extract an ActionInformation from the funky form properties. """ id = str( properties.get( 'id_%d' % index, '' ) ) name = str( properties.get( 'name_%d' % index, '' ) ) action = str( properties.get( 'action_%d' % index, '' ) ) condition = str( properties.get( 'condition_%d' % index, '' ) ) category = str( properties.get( 'category_%d' % index, '' )) visible = properties.get( 'visible_%d' % index, 0 ) permissions = properties.get( 'permission_%d' % index, () ) if not name: raise ValueError('A name is required.') if action is not '': action = Expression( text=action ) if condition is not '': condition = Expression( text=condition ) if category == '': category = 'object' if type( visible ) is not type( 0 ): try: visible = int( visible ) except: visible = 0 if type( permissions ) is type( '' ): permissions = ( permissions, ) return ActionInformation( id=id , title=name , action=action , condition=condition , permissions=permissions , category=category , visible=visible ) CMF-1.3/CMFCore/ActionsTool.py0100644000076500007650000002561507522303413015737 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Basic action list tool. $Id: ActionsTool.py,v 1.27.10.3 2025/08/01 19:07:55 tseaver Exp $ """ import OFS from utils import UniqueObject, SimpleItemWithProperties, _getAuthenticatedUser, _checkPermission from utils import getToolByName, _dtmldir, cookString import CMFCorePermissions from OFS.SimpleItem import SimpleItem from Globals import InitializeClass, DTMLFile, package_home from urllib import quote from Acquisition import aq_base, aq_inner, aq_parent from AccessControl import ClassSecurityInfo from string import join from Expression import Expression, createExprContext from ActionInformation import ActionInformation, oai from ActionProviderBase import ActionProviderBase from TypesTool import TypeInformation class ActionsTool(UniqueObject, OFS.Folder.Folder, ActionProviderBase): """ Weave together the various sources of "actions" which are apropos to the current user and context. """ id = 'portal_actions' _actions = [ActionInformation(id='folderContents' , title='Folder contents' , action=Expression( text='string: ${folder_url}/folder_contents') , condition=Expression( text='python: folder is not object') , permissions=('List folder contents',) , category='object' , visible=1 ) , ActionInformation(id='folderContents' , title='Folder contents' , action=Expression( text='string: ${folder_url}/folder_contents') , condition=Expression( text='python: folder is object') , permissions=('List folder contents',) , category='folder' , visible=1 )] meta_type = 'CMF Actions Tool' action_providers = ('portal_membership' , 'portal_actions' , 'portal_registration' , 'portal_discussion' , 'portal_undo' , 'portal_syndication' , 'portal_workflow' , 'portal_properties') security = ClassSecurityInfo() manage_options = ( ActionProviderBase.manage_options + ({'label' : 'Action Providers', 'action' : 'manage_actionProviders'} , { 'label' : 'Overview', 'action' : 'manage_overview' } , ) + OFS.Folder.Folder.manage_options ) # # ZMI methods # security.declareProtected( CMFCorePermissions.ManagePortal , 'manage_overview' ) manage_overview = DTMLFile( 'explainActionsTool', _dtmldir ) manage_actionProviders = DTMLFile('manageActionProviders', _dtmldir) # # Programmatically manipulate the list of action providers # security.declarePrivate('listActions') def listActions(self, info=None): """ Lists actions available through the tool. """ return self._actions security.declareProtected( CMFCorePermissions.ManagePortal , 'listActionProviders' ) def listActionProviders(self): """ returns a sequence of action providers known by this tool """ return self.action_providers security.declareProtected(CMFCorePermissions.ManagePortal , 'manage_aproviders') def manage_aproviders(self , apname='' , chosen=() , add_provider=0 , del_provider=0 , REQUEST=None): """ Manage TTW Action Providers """ providers = list(self.listActionProviders()) new_providers = [] if add_provider: providers.append(apname) elif del_provider: for item in providers: if item not in chosen: new_providers.append(item) providers = new_providers self.action_providers = providers if REQUEST is not None: return self.manage_actionProviders(self , REQUEST , manage_tabs_message='Properties changed.') security.declareProtected( CMFCorePermissions.ManagePortal , 'addActionProvider' ) def addActionProvider( self, provider_name ): """ add the name of a new action provider """ if hasattr( self, provider_name ): p_old = self.action_providers p_new = p_old + ( provider_name, ) self.action_providers = p_new security.declareProtected( CMFCorePermissions.ManagePortal , 'deleteActionProvider' ) def deleteActionProvider( self, provider_name ): """ remove an action provider """ if provider_name in self.action_providers: p_old = list( self.action_providers ) del p_old[p_old.index( provider_name)] self.action_providers = tuple( p_old ) # # 'portal_actions' interface methods # def _listActions(self,append,object,info,ec): a = object.listActions(info) if a and type(a[0]) is not type({}): for ai in a: if ai.testCondition(ec): append(ai.getAction(ec)) else: for i in a: append(i) security.declarePublic('listFilteredActionsFor') def listFilteredActionsFor(self, object=None): '''Gets all actions available to the user and returns a mapping containing user actions, object actions, and global actions. ''' portal = aq_parent(aq_inner(self)) if object is None or not hasattr(object, 'aq_base'): folder = portal else: folder = object # Search up the containment hierarchy until we find an # object that claims it's a folder. while folder is not None: if getattr(aq_base(folder), 'isPrincipiaFolderish', 0): # found it. break else: folder = aq_parent(aq_inner(folder)) ec = createExprContext(folder, portal, object) actions = [] append = actions.append info = oai(self, folder, object) # Include actions from specific tools. for provider_name in self.listActionProviders(): provider = getattr(self, provider_name) self._listActions(append,provider,info,ec) # Include actions from object. if object is not None: base = aq_base(object) types_tool = getToolByName( self, 'portal_types' ) # we might get None back from getTypeInfo. We construct # a dummy TypeInformation object here in that case (the 'or' # case). This prevents us from needing to check the condition. ti = types_tool.getTypeInfo( object ) or TypeInformation('Dummy') defs = ti.getActions() url = object_url = object.absolute_url() for d in defs: # we can't modify or expose the original actionsd... this # stems from the fact that getActions returns a ref to the # actual dictionary used to store actions instead of a # copy. We copy it here to prevent it from being modified. d = d.copy() d['id'] = d.get('id', None) if d['action']: url = '%s/%s' % (object_url, d['action']) d['url'] = url d['category'] = d.get('category', 'object') d['visible'] = d.get('visible', 1) actions.append(d) if hasattr(base, 'listActions'): self._listActions(append,object,info,ec) # Reorganize the actions by category, # filtering out disallowed actions. filtered_actions={'user':[], 'folder':[], 'object':[], 'global':[], 'workflow':[], } for action in actions: category = action['category'] permissions = action.get('permissions', None) visible = action.get('visible', 1) if not visible: continue verified = 0 if not permissions: # This action requires no extra permissions. verified = 1 else: if (object is not None and (category.startswith('object') or category.startswith('workflow'))): context = object elif (folder is not None and category.startswith('folder')): context = folder else: context = portal for permission in permissions: # The user must be able to match at least one of # the listed permissions. if _checkPermission(permission, context): verified = 1 break if verified: catlist = filtered_actions.get(category, None) if catlist is None: filtered_actions[category] = catlist = [] # Filter out duplicate actions by identity... if not action in catlist: catlist.append(action) # ...should you need it, here's some code that filters # by equality (use instead of the two lines above) #if not [a for a in catlist if a==action]: # catlist.append(action) return filtered_actions # listFilteredActions() is an alias. security.declarePublic('listFilteredActions') listFilteredActions = listFilteredActionsFor InitializeClass(ActionsTool) CMF-1.3/CMFCore/CMFCatalogAware.py0100644000076500007650000001745007522040643016362 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import Globals from Acquisition import aq_base from AccessControl import ClassSecurityInfo from CMFCorePermissions import ModifyPortalContent from CMFCorePermissions import AccessContentsInformation from CMFCorePermissions import ManagePortal from utils import getToolByName from utils import _dtmldir class CMFCatalogAware: """Mix-in for notifying portal_catalog and portal_workflow """ security = ClassSecurityInfo() # Cataloging methods # ------------------ security.declareProtected(ModifyPortalContent, 'indexObject') def indexObject(self): """ Index the object in the portal catalog. """ catalog = getToolByName(self, 'portal_catalog', None) if catalog is not None: catalog.indexObject(self) security.declareProtected(ModifyPortalContent, 'unindexObject') def unindexObject(self): """ Unindex the object from the portal catalog. """ catalog = getToolByName(self, 'portal_catalog', None) if catalog is not None: catalog.unindexObject(self) security.declareProtected(ModifyPortalContent, 'reindexObject') def reindexObject(self, idxs=[]): """ Reindex the object in the portal catalog. If idxs is present, only those indexes are reindexed. The metadata is always updated. Also update the modification date of the object, unless specific indexes were requested. """ if idxs == []: # Update the modification date. if hasattr(aq_base(self), 'notifyModified'): self.notifyModified() catalog = getToolByName(self, 'portal_catalog', None) if catalog is not None: catalog.reindexObject(self, idxs=idxs) security.declareProtected(ModifyPortalContent, 'reindexObjectSecurity') def reindexObjectSecurity(self): """ Reindex security-related indexes on the object (and its descendants). """ catalog = getToolByName(self, 'portal_catalog', None) if catalog is not None: path = '/'.join(self.getPhysicalPath()) for brain in catalog.searchResults(path=path): ob = brain.getObject() if ob is None: # Ignore old references to deleted objects. continue s = getattr(ob, '_p_changed', 0) catalog.reindexObject(ob, idxs=['allowedRolesAndUsers']) if s is None: ob._p_deactivate() # Reindex the object itself, as the PathIndex only gave us # the descendants. self.reindexObject(idxs=['allowedRolesAndUsers']) # Workflow methods # ---------------- security.declarePrivate('notifyWorkflowCreated') def notifyWorkflowCreated(self): """ Notify the workflow that self was just created. """ wftool = getToolByName(self, 'portal_workflow', None) if wftool is not None: wftool.notifyCreated(self) # Opaque subitems # --------------- security.declareProtected(AccessContentsInformation, 'opaqueItems') def opaqueItems(self): """ Return opaque items (subelements that are contained using something that is not an ObjectManager). """ # Since 'talkback' is the only opaque item on content # right now, I will return that. Should be replaced with # a list of tuples for every opaque item! if hasattr(aq_base(self), 'talkback'): talkback = self.talkback if talkback is not None: return ((talkback.id, talkback),) return () security.declareProtected(AccessContentsInformation, 'opaqueIds') def opaqueIds(self): """ Return opaque ids (subelements that are contained using something that is not an ObjectManager). """ return [t[0] for t in self.opaqueItems()] security.declareProtected(AccessContentsInformation, 'opaqueValues') def opaqueValues(self): """ Return opaque values (subelements that are contained using something that is not an ObjectManager). """ return [t[1] for t in self.opaqueItems()] # Hooks # ----- def manage_afterAdd(self, item, container): """ Add self to the catalog. (Called when the object is created or moved.) """ if aq_base(container) is not aq_base(self): self.indexObject() self.__recurse('manage_afterAdd', item, container) def manage_afterClone(self, item): """ Add self to the workflow. (Called when the object is cloned.) """ self.notifyWorkflowCreated() self.__recurse('manage_afterClone', item) def manage_beforeDelete(self, item, container): """ Remove self from the catalog. (Called when the object is deleted or moved.) """ if aq_base(container) is not aq_base(self): self.__recurse('manage_beforeDelete', item, container) self.unindexObject() def __recurse(self, name, *args): """ Recurse in both normal and opaque subobjects. """ values = self.objectValues() opaque_values = self.opaqueValues() for subobjects in values, opaque_values: for ob in subobjects: s = getattr(ob, '_p_changed', 0) if hasattr(aq_base(ob), name): getattr(ob, name)(*args) if s is None: ob._p_deactivate() # ZMI # --- manage_options = ({'label': 'Workflows', 'action': 'manage_workflowsTab', }, ) _manage_workflowsTab = Globals.DTMLFile('zmi_workflows', _dtmldir) security.declareProtected(ManagePortal, 'manage_workflowsTab') def manage_workflowsTab(self, REQUEST, manage_tabs_message=None): """ Tab displaying the current workflows for the content object. """ ob = self wftool = getToolByName(self, 'portal_workflow', None) # XXX None ? if wftool is not None: wf_ids = wftool.getChainFor(ob) states = {} chain = [] for wf_id in wf_ids: wf = wftool.getWorkflowById(wf_id) if wf is not None: # XXX a standard API would be nice if hasattr(wf, 'getReviewStateOf'): # Default Workflow state = wf.getReviewStateOf(ob) elif hasattr(wf, '_getWorkflowStateOf'): # DCWorkflow state = wf._getWorkflowStateOf(ob, id_only=1) else: state = '(Unknown)' states[wf_id] = state chain.append(wf_id) return self._manage_workflowsTab( REQUEST, chain=chain, states=states, management_view='Workflows', manage_tabs_message=manage_tabs_message) Globals.InitializeClass(CMFCatalogAware) CMF-1.3/CMFCore/CMFCorePermissions.py0100644000076500007650000000734207522303413017150 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Declare named permissions used throughout the CMF. $Id: CMFCorePermissions.py,v 1.10.36.1 2025/08/01 19:07:55 tseaver Exp $ """ import Globals, AccessControl, Products from AccessControl import Permissions # General Zope permissions View = Permissions.view AccessContentsInformation = Permissions.access_contents_information UndoChanges = Permissions.undo_changes ChangePermissions = Permissions.change_permissions ViewManagementScreens = Permissions.view_management_screens ManageProperties = Permissions.manage_properties FTPAccess = Permissions.ftp_access def setDefaultRoles(permission, roles): ''' Sets the defaults roles for a permission. ''' # XXX This ought to be in AccessControl.SecurityInfo. registered = AccessControl.Permission._registeredPermissions if not registered.has_key(permission): registered[permission] = 1 Products.__ac_permissions__=( Products.__ac_permissions__+((permission,(),roles),)) mangled = AccessControl.Permission.pname(permission) setattr(Globals.ApplicationDefaultPermissions, mangled, roles) # Note that we can only use the default Zope roles in calls to # setDefaultRoles(). The default Zope roles are: # Anonymous, Manager, and Owner. # # CMF Base Permissions # ListFolderContents = 'List folder contents' setDefaultRoles( ListFolderContents, ( 'Manager', 'Owner' ) ) ListUndoableChanges = 'List undoable changes' setDefaultRoles( ListUndoableChanges, ( 'Manager', 'Member' ) ) AccessInactivePortalContent = 'Access inactive portal content' setDefaultRoles(AccessInactivePortalContent, ('Manager',)) ModifyCookieCrumblers = 'Modify Cookie Crumblers' setDefaultRoles(ModifyCookieCrumblers, ('Manager',)) ReplyToItem = 'Reply to item' setDefaultRoles(ReplyToItem, ('Manager',)) # + Member ManagePortal = 'Manage portal' setDefaultRoles(ManagePortal, ('Manager',)) ModifyPortalContent = 'Modify portal content' setDefaultRoles(ModifyPortalContent, ('Manager',)) ManageProperties = 'Manage properties' setDefaultRoles(ModifyPortalContent, ('Owner','Manager',)) ListPortalMembers = 'List portal members' setDefaultRoles(ListPortalMembers, ('Manager', 'Member')) AddPortalFolders = 'Add portal folders' setDefaultRoles(AddPortalFolders, ('Owner','Manager')) # + Member AddPortalContent = 'Add portal content' setDefaultRoles(AddPortalContent, ('Owner','Manager',)) # + Member AddPortalMember = 'Add portal member' setDefaultRoles(AddPortalMember, ('Anonymous', 'Manager',)) SetOwnPassword = 'Set own password' setDefaultRoles(SetOwnPassword, ('Manager',)) # + Member SetOwnProperties = 'Set own properties' setDefaultRoles(SetOwnProperties, ('Manager',)) # + Member MailForgottenPassword = 'Mail forgotten password' setDefaultRoles(MailForgottenPassword, ('Anonymous', 'Manager',)) # # Workflow Permissions # RequestReview = 'Request review' setDefaultRoles(RequestReview, ('Owner', 'Manager',)) ReviewPortalContent = 'Review portal content' setDefaultRoles(ReviewPortalContent, ('Manager',)) # + Reviewer AccessFuturePortalContent = 'Access future portal content' setDefaultRoles(AccessFuturePortalContent, ('Manager',)) # + Reviewer CMF-1.3/CMFCore/CachingPolicyManager.py0100644000076500007650000003771107455100724017515 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Caching tool implementation. $Id: CachingPolicyManager.py,v 1.6 2025/04/10 18:17:56 tseaver Exp $ """ from App.Common import rfc1123_date from AccessControl import ClassSecurityInfo from DateTime.DateTime import DateTime from Globals import InitializeClass from Globals import DTMLFile from Globals import PersistentMapping from OFS.SimpleItem import SimpleItem from Products.PageTemplates.Expressions import getEngine from Products.PageTemplates.Expressions import SecureModuleImporter from Products.CMFCore.interfaces.CachingPolicyManager \ import CachingPolicyManager as ICachingPolicyManager from Products.CMFCore.ActionProviderBase import ActionProviderBase from Products.CMFCore.CMFCorePermissions import View from Products.CMFCore.CMFCorePermissions import ManagePortal from Products.CMFCore.Expression import Expression from Products.CMFCore.utils import _getAuthenticatedUser from Products.CMFCore.utils import _checkPermission from Products.CMFCore.utils import getToolByName from Products.CMFCore.utils import _dtmldir import time def createCPContext( content, view_method, keywords, time=None ): """ Construct an expression context for TALES expressions, for use by CachingPolicy objects. """ pm = getToolByName( content, 'portal_membership', None ) if not pm or pm.isAnonymousUser(): member = None else: member = pm.getAuthenticatedMember() if time is None: time = DateTime() data = { 'content' : content , 'view' : view_method , 'keywords' : keywords , 'request' : getattr( content, 'REQUEST', {} ) , 'member' : member , 'modules' : SecureModuleImporter , 'nothing' : None , 'time' : time } return getEngine().getContext( data ) class CachingPolicy: """ Represent a single class of cachable objects: - class membership is defined by 'predicate', a TALES expression with access to the following top-level names: 'content' -- the content object itself 'view' -- the name of the view method 'keywords' -- keywords passed to the request 'request' -- the REQUEST object itself 'member' -- the authenticated member, or None if anonymous 'modules' -- usual TALES access-with-import 'nothing' -- None - The "Last-modified" HTTP response header will be set using 'mtime_func', which is another TALES expression evaluated against the same namespace. If not specified explicitly, uses 'content/modified'. - The "Expires" HTTP response header and the "max-age" token of the "Cache-control" header will be set using 'max_age_secs', if passed; it should be an integer value in seconds. - Other tokens will be added to the "Cache-control" HTTP response header as follows: 'no_cache=1' argument => "no-cache" token 'no_store=1' argument => "no-store" token 'must_revalidate=1' argument => "must-revalidate" token """ def __init__( self , policy_id , predicate='' , mtime_func='' , max_age_secs=None , no_cache=0 , no_store=0 , must_revalidate=0 ): if not predicate: predicate = 'python:1' if not mtime_func: mtime_func = 'content/modified' if max_age_secs is not None: max_age_secs = int( max_age_secs ) self._policy_id = policy_id self._predicate = Expression( text=predicate ) self._mtime_func = Expression( text=mtime_func ) self._max_age_secs = max_age_secs self._no_cache = int( no_cache ) self._no_store = int( no_store ) self._must_revalidate = int( must_revalidate ) def getPolicyId( self ): """ """ return self._policy_id def getPredicate( self ): """ """ return self._predicate.text def getMTimeFunc( self ): """ """ return self._mtime_func.text def getMaxAgeSecs( self ): """ """ return self._max_age_secs def getNoCache( self ): """ """ return self._no_cache def getNoStore( self ): """ """ return self._no_store def getMustRevalidate( self ): """ """ return self._must_revalidate def getHeaders( self, expr_context ): """ Does this request match our predicate? If so, return a sequence of caching headers as ( key, value ) tuples. Otherwise, return an empty sequence. """ headers = [] if self._predicate( expr_context ): mtime = self._mtime_func( expr_context ) if type( mtime ) is type( '' ): mtime = DateTime( mtime ) if mtime is not None: mtime_flt = mtime.timeTime() mtime_str = rfc1123_date(mtime_flt) headers.append( ( 'Last-modified', mtime_str ) ) control = [] if self._max_age_secs is not None: now = expr_context.vars[ 'time' ] exp_time_str = rfc1123_date(now.timeTime() + self._max_age_secs) headers.append( ( 'Expires', exp_time_str ) ) control.append( 'max-age=%d' % self._max_age_secs ) if self._no_cache: control.append( 'no-cache' ) if self._no_store: control.append( 'no-store' ) if self._must_revalidate: control.append( 'must-revalidate' ) if control: headers.append( ( 'Cache-control', ', '.join( control ) ) ) return headers class CachingPolicyManager( SimpleItem ): """ Manage the set of CachingPolicy objects for the site; dispatch to them from skin methods. """ __implements__ = ICachingPolicyManager id = 'caching_policy_manager' meta_type = 'CMF Caching Policy Manager' security = ClassSecurityInfo() def __init__( self ): self._policy_ids = () self._policies = PersistentMapping() # # ZMI # manage_options = ( ( { 'label' : 'Policies' , 'action' : 'manage_cachingPolicies' } , ) + SimpleItem.manage_options ) security.declareProtected( ManagePortal, 'manage_cachingPolicies' ) manage_cachingPolicies = DTMLFile( 'cachingPolicies', _dtmldir ) security.declarePublic( 'listPolicies' ) def listPolicies( self ): """ Return a sequence of tuples, '( policy_id, ( policy, typeObjectName ) )' for all policies in the registry """ result = [] for policy_id in self._policy_ids: result.append( ( policy_id, self._policies[ policy_id ] ) ) return tuple( result ) security.declareProtected( ManagePortal, 'addPolicy' ) def addPolicy( self , policy_id , predicate # TALES expr (def. 'python:1') , mtime_func # TALES expr (def. 'content/modified') , max_age_secs # integer, seconds (def. 0) , no_cache # boolean (def. 0) , no_store # boolean (def. 0) , must_revalidate # boolean (def. 0) , REQUEST ): """ Add a caching policy. """ self._addPolicy( policy_id , predicate , mtime_func , max_age_secs , no_cache , no_store , must_revalidate ) REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/manage_cachingPolicies' + '?manage_tabs_message=Policy+added.' ) security.declareProtected( ManagePortal, 'updatePolicy' ) def updatePolicy( self , policy_id , predicate # TALES expr (def. 'python:1') , mtime_func # TALES expr (def. 'content/modified') , max_age_secs # integer, seconds , no_cache # boolean (def. 0) , no_store # boolean (def. 0) , must_revalidate # boolean (def. 0) , REQUEST ): """ Update a caching policy. """ self._updatePolicy( policy_id , predicate , mtime_func , max_age_secs , no_cache , no_store , must_revalidate ) REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/manage_cachingPolicies' + '?manage_tabs_message=Policy+updated.' ) security.declareProtected( ManagePortal, 'movePolicyUp' ) def movePolicyUp( self, policy_id, REQUEST ): """ Move a caching policy up in the list. """ policy_ids = list( self._policy_ids ) ndx = policy_ids.index( policy_id ) if ndx == 0: msg = "Policy+already+first." else: self._reorderPolicy( predicate_id, ndx - 1 ) msg = "Policy+moved." REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/manage_cachingPolicies' + '?manage_tabs_message=%s' % msg ) security.declareProtected( ManagePortal, 'movePolicyDown' ) def movePolicyDown( self, policy_id, REQUEST ): """ Move a caching policy down in the list. """ policy_ids = list( self._policy_ids ) ndx = policy_ids.index( policy_id ) if ndx == len( policy_ids ) - 1: msg = "Policy+already+last." else: self._reorderPolicy( policy_id, ndx + 1 ) msg = "Policy+moved." REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/manage_cachingPolicies' + '?manage_tabs_message=%s' % msg ) security.declareProtected( ManagePortal, 'removePolicy' ) def removePolicy( self, policy_id, REQUEST ): """ Remove a caching policy. """ self._removePolicy( policy_id ) REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/manage_cachingPolicies' + '?manage_tabs_message=Policy+removed.' ) # # Policy manipulation methods. # security.declarePrivate( '_addPolicy' ) def _addPolicy( self , policy_id , predicate , mtime_func , max_age_secs , no_cache , no_store , must_revalidate ): """ Add a policy to our registry. """ policy_id = str( policy_id ).strip() if not policy_id: raise ValueError, "Policy ID is required!" if policy_id in self._policy_ids: raise KeyError, "Policy %s already exists!" % policy_id self._policies[ policy_id ] = CachingPolicy( policy_id , predicate , mtime_func , max_age_secs , no_cache , no_store , must_revalidate ) idlist = list( self._policy_ids ) idlist.append( policy_id ) self._policy_ids = tuple( idlist ) security.declarePrivate( '_updatePolicy' ) def _updatePolicy( self , policy_id , predicate , mtime_func , max_age_secs , no_cache , no_store , must_revalidate ): """ Update a policy in our registry. """ if policy_id not in self._policy_ids: raise KeyError, "Policy %s does not exist!" % policy_id self._policies[ policy_id ] = CachingPolicy( policy_id , predicate , mtime_func , max_age_secs , no_cache , no_store , must_revalidate ) security.declarePrivate( '_reorderPolicy' ) def _reorderPolicy( self, policy_id, newIndex ): """ Reorder a policy in our registry. """ if policy_id not in self._policy_ids: raise KeyError, "Policy %s does not exist!" % policy_id idlist = list( self._policy_ids ) ndx = idlist.index( policy_id ) pred = idlist[ ndx ] idlist = idlist[ :ndx ] + idlist[ ndx+1: ] idlist.insert( newIndex, pred ) self._policy_ids = tuple( idlist ) security.declarePrivate( '_removePolicy' ) def _removePolicy( self, policy_id ): """ Remove a policy from our registry. """ if policy_id not in self._policy_ids: raise KeyError, "Policy %s does not exist!" % policy_id del self._policies[ policy_id ] idlist = list( self._policy_ids ) ndx = idlist.index( policy_id ) idlist = idlist[ :ndx ] + idlist[ ndx+1: ] self._policy_ids = tuple( idlist ) # # 'portal_caching' interface methods # security.declareProtected( View, 'getHTTPCachingHeaders' ) def getHTTPCachingHeaders( self, content, view_method, keywords, time=None): """ Return a list of HTTP caching headers based on 'content', 'view_method', and 'keywords'. """ context = createCPContext( content, view_method, keywords, time=time ) for policy_id, policy in self.listPolicies(): headers = policy.getHeaders( context ) if headers: return headers return () InitializeClass( CachingPolicyManager ) def manage_addCachingPolicyManager( self, REQUEST=None ): """ Add a CPM to self. """ id = CachingPolicyManager.id mgr = CachingPolicyManager() self._setObject( id, mgr ) if REQUEST is not None: REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/manage_main' + '?manage_tabs_message=Caching+Policy+Manager+added.' ) CMF-1.3/CMFCore/CatalogTool.py0100644000076500007650000002173607522303413015711 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Basic portal catalog. $Id: CatalogTool.py,v 1.30.4.6 2025/08/01 19:07:55 tseaver Exp $ """ import os from utils import UniqueObject, _checkPermission, _getAuthenticatedUser from utils import getToolByName, _dtmldir from Products.ZCatalog.ZCatalog import ZCatalog from Globals import InitializeClass, package_home, DTMLFile import urllib from DateTime import DateTime from string import join from AccessControl.PermissionRole import rolesForPermissionOn from AccessControl import ClassSecurityInfo from utils import _mergedLocalRoles from ActionProviderBase import ActionProviderBase from ActionInformation import ActionInformation from Expression import Expression import os import CMFCorePermissions from Acquisition import aq_base class IndexableObjectWrapper: def __init__(self, vars, ob): self.__vars = vars self.__ob = ob def __getattr__(self, name): vars = self.__vars if vars.has_key(name): return vars[name] return getattr(self.__ob, name) def allowedRolesAndUsers(self): """ Return a list of roles and users with View permission. Used by PortalCatalog to filter out items you're not allowed to see. """ ob = self.__ob allowed = {} for r in rolesForPermissionOn('View', ob): allowed[r] = 1 localroles = _mergedLocalRoles(ob) for user, roles in localroles.items(): for role in roles: if allowed.has_key(role): allowed['user:' + user] = 1 if allowed.has_key('Owner'): del allowed['Owner'] return list(allowed.keys()) class CatalogTool (UniqueObject, ZCatalog, ActionProviderBase): '''This is a ZCatalog that filters catalog queries. ''' id = 'portal_catalog' meta_type = 'CMF Catalog' security = ClassSecurityInfo() _actions = [] manage_options = ( ZCatalog.manage_options + ActionProviderBase.manage_options + ({ 'label' : 'Overview', 'action' : 'manage_overview' } , )) def __init__(self): ZCatalog.__init__(self, self.getId()) if not hasattr(self, 'Vocabulary'): # As of 2.6, the Catalog no longer adds a vocabulary in itself from Products.PluginIndexes.TextIndex.Vocabulary import Vocabulary vocabulary = Vocabulary('Vocabulary', 'Vocabulary', globbing=1) self._setObject('Vocabulary', vocabulary) self._initIndexes() # # Subclass extension interface # security.declarePrivate('listActions') def listActions(self, info=None): """ Return a list of action information instances provided via tool """ return self._actions security.declarePublic( 'enumerateIndexes' ) # Subclass can call def enumerateIndexes( self ): # Return a list of ( index_name, type ) pairs for the initial # index set. return ( ('Title', 'TextIndex') , ('Subject', 'KeywordIndex') , ('Description', 'TextIndex') , ('Creator', 'FieldIndex') , ('SearchableText', 'TextIndex') , ('Date', 'FieldIndex') , ('Type', 'FieldIndex') , ('created', 'FieldIndex') , ('effective', 'FieldIndex') , ('expires', 'FieldIndex') , ('modified', 'FieldIndex') , ('allowedRolesAndUsers', 'KeywordIndex') , ('review_state', 'FieldIndex') , ('in_reply_to', 'FieldIndex') , ('meta_type', 'FieldIndex') , ('id', 'FieldIndex') , ('path', 'PathIndex') , ('portal_type', 'FieldIndex') ) security.declarePublic( 'enumerateColumns' ) def enumerateColumns( self ): # Return a sequence of schema names to be cached. return ( 'Subject' , 'Title' , 'Description' , 'Type' , 'review_state' , 'Creator' , 'Date' , 'getIcon' , 'created' , 'effective' , 'expires' , 'modified' , 'CreationDate' , 'EffectiveDate' , 'ExpiresDate' , 'ModificationDate' , 'portal_type' ) def _initIndexes(self): base = aq_base(self) if hasattr(base, 'addIndex'): # Zope 2.4 addIndex = self.addIndex else: # Zope 2.3 and below addIndex = self._catalog.addIndex if hasattr(base, 'addColumn'): # Zope 2.4 addColumn = self.addColumn else: # Zope 2.3 and below addColumn = self._catalog.addColumn # Content indexes self._catalog.indexes.clear() for index_name, index_type in self.enumerateIndexes(): addIndex( index_name, index_type ) # Cached metadata self._catalog.names = () self._catalog.schema.clear() for column_name in self.enumerateColumns(): addColumn( column_name ) # # ZMI methods # security.declareProtected( CMFCorePermissions.ManagePortal , 'manage_overview' ) manage_overview = DTMLFile( 'explainCatalogTool', _dtmldir ) # # 'portal_catalog' interface methods # def _listAllowedRolesAndUsers( self, user ): result = list( user.getRoles() ) result.append( 'Anonymous' ) result.append( 'user:%s' % user.getUserName() ) return result # searchResults has inherited security assertions. def searchResults(self, REQUEST=None, **kw): """ Calls ZCatalog.searchResults with extra arguments that limit the results to what the user is allowed to see. """ user = _getAuthenticatedUser(self) kw[ 'allowedRolesAndUsers' ] = self._listAllowedRolesAndUsers( user ) if not _checkPermission( CMFCorePermissions.AccessInactivePortalContent, self ): base = aq_base( self ) now = DateTime() if hasattr( base, 'addIndex' ): # Zope 2.4 and above kw[ 'effective' ] = { 'query' : now, 'range' : 'max' } kw[ 'expires' ] = { 'query' : now, 'range' : 'min' } else: # Zope 2.3 kw[ 'effective' ] = kw[ 'expires' ] = now kw[ 'effective_usage'] = 'range:max' kw[ 'expires_usage' ] = 'range:min' return apply(ZCatalog.searchResults, (self, REQUEST), kw) __call__ = searchResults def __url(self, ob): return join(ob.getPhysicalPath(), '/') manage_catalogFind = DTMLFile( 'catalogFind', _dtmldir ) def catalog_object(self, object, uid, idxs=[]): # Wraps the object with workflow and accessibility # information just before cataloging. wf = getattr(self, 'portal_workflow', None) if wf is not None: vars = wf.getCatalogVariablesFor(object) else: vars = {} w = IndexableObjectWrapper(vars, object) ZCatalog.catalog_object(self, w, uid, idxs) security.declarePrivate('indexObject') def indexObject(self, object): '''Add to catalog. ''' url = self.__url(object) self.catalog_object(object, url) security.declarePrivate('unindexObject') def unindexObject(self, object): '''Remove from catalog. ''' url = self.__url(object) self.uncatalog_object(url) security.declarePrivate('reindexObject') def reindexObject(self, object, idxs=[]): '''Update catalog after object data has changed. The optional idxs argument is a list of specific indexes to update (all of them by default). ''' url = self.__url(object) ## Zope 2.3 ZCatalog is supposed to work better if ## you don't uncatalog_object() when reindexing. # self.uncatalog_object(url) if idxs != []: # Filter out invalid indexes. valid_indexes = self._catalog.indexes.keys() idxs = [i for i in idxs if i in valid_indexes] self.catalog_object(object, url, idxs=idxs) InitializeClass(CatalogTool) CMF-1.3/CMFCore/ContentTypeRegistry.py0100644000076500007650000004236007523112216017502 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Basic Site content type registry $Id: ContentTypeRegistry.py,v 1.9.4.2 2025/08/04 02:42:22 efge Exp $ """ from OFS.SimpleItem import SimpleItem, Item from AccessControl import ClassSecurityInfo from Globals import DTMLFile, InitializeClass, PersistentMapping from ZPublisher.mapply import mapply from CMFCorePermissions import ManagePortal from utils import _dtmldir, getToolByName import re, os, string, urllib class MajorMinorPredicate( SimpleItem ): """ Predicate matching on 'major/minor' content types. Empty major or minor implies wildcard (all match). """ major = minor = None PREDICATE_TYPE = 'major_minor' security = ClassSecurityInfo() def __init__( self, id ): self.id = id security.declareProtected( ManagePortal, 'getMajorType' ) def getMajorType( self, join=string.join ): """ """ if self.major is None: return 'None' return join( self.major ) security.declareProtected( ManagePortal, 'getMinorType' ) def getMinorType( self, join=string.join ): """ """ if self.minor is None: return 'None' return join( self.minor ) security.declareProtected( ManagePortal, 'edit' ) def edit( self, major, minor, COMMA_SPLIT=re.compile( r'[, ]' ) ): if major == 'None': major = None if type( major ) is type( '' ): major = filter( None, COMMA_SPLIT.split( major ) ) if minor == 'None': minor = None if type( minor ) is type( '' ): minor = filter( None, COMMA_SPLIT.split( minor ) ) self.major = major self.minor = minor # # ContentTypeRegistryPredicate interface # security.declareObjectPublic() def __call__( self, name, typ, body, SLASH_SPLIT=re.compile( '/' ) ): """ Return true if the rule matches, else false. """ if self.major is None: return 0 if self.minor is None: return 0 typ = typ or '/' if not '/' in typ: typ = typ + '/' major, minor = SLASH_SPLIT.split( typ ) if self.major and not major in self.major: return 0 if self.minor and not minor in self.minor: return 0 return 1 security.declareProtected( ManagePortal, 'getTypeLabel' ) def getTypeLabel( self ): """ Return a human-readable label for the predicate type. """ return self.PREDICATE_TYPE security.declareProtected( ManagePortal, 'predicateWidget' ) predicateWidget = DTMLFile( 'majorMinorWidget', _dtmldir ) InitializeClass( MajorMinorPredicate ) class ExtensionPredicate( SimpleItem ): """ Predicate matching on filename extensions. """ extensions = None PREDICATE_TYPE = 'extension' security = ClassSecurityInfo() def __init__( self, id ): self.id = id security.declareProtected( ManagePortal, 'getExtensions' ) def getExtensions( self, join=string.join ): """ """ if self.extensions is None: return 'None' return join( self.extensions ) security.declareProtected( ManagePortal, 'edit' ) def edit( self, extensions, COMMA_SPLIT=re.compile( r'[, ]' ) ): if extensions == 'None': extensions = None if type( extensions ) is type( '' ): extensions = filter( None, COMMA_SPLIT.split( extensions ) ) self.extensions = extensions # # ContentTypeRegistryPredicate interface # security.declareObjectPublic() def __call__( self, name, typ, body ): """ Return true if the rule matches, else false. """ if self.extensions is None: return 0 base, ext = os.path.splitext( name ) if ext and ext[ 0 ] == '.': ext = ext[ 1: ] return ext in self.extensions security.declareProtected( ManagePortal, 'getTypeLabel' ) def getTypeLabel( self ): """ Return a human-readable label for the predicate type. """ return self.PREDICATE_TYPE security.declareProtected( ManagePortal, 'predicateWidget' ) predicateWidget = DTMLFile( 'extensionWidget', _dtmldir ) InitializeClass( ExtensionPredicate ) class MimeTypeRegexPredicate( SimpleItem ): """ Predicate matching only on 'typ', using regex matching for string patterns (other objects conforming to 'match' can also be passed). """ pattern = None PREDICATE_TYPE = 'mimetype_regex' security = ClassSecurityInfo() def __init__( self, id ): self.id = id security.declareProtected( ManagePortal, 'getPatternStr' ) def getPatternStr( self ): if self.pattern is None: return 'None' return self.pattern.pattern security.declareProtected( ManagePortal, 'edit' ) def edit( self, pattern ): if pattern == 'None': pattern = None if type( pattern ) is type( '' ): pattern = re.compile( pattern ) self.pattern = pattern # # ContentTypeRegistryPredicate interface # security.declareObjectPublic() def __call__( self, name, typ, body ): """ Return true if the rule matches, else false. """ if self.pattern is None: return 0 return self.pattern.match( typ ) security.declareProtected( ManagePortal, 'getTypeLabel' ) def getTypeLabel( self ): """ Return a human-readable label for the predicate type. """ return self.PREDICATE_TYPE security.declareProtected( ManagePortal, 'predicateWidget' ) predicateWidget = DTMLFile( 'patternWidget', _dtmldir ) InitializeClass( MimeTypeRegexPredicate ) class NameRegexPredicate( SimpleItem ): """ Predicate matching only on 'name', using regex matching for string patterns (other objects conforming to 'match' and 'pattern' can also be passed). """ pattern = None PREDICATE_TYPE = 'name_regex' security = ClassSecurityInfo() def __init__( self, id ): self.id = id security.declareProtected( ManagePortal, 'getPatternStr' ) def getPatternStr( self ): """ Return a string representation of our pattern. """ if self.pattern is None: return 'None' return self.pattern.pattern security.declareProtected( ManagePortal, 'edit' ) def edit( self, pattern ): if pattern == 'None': pattern = None if type( pattern ) is type( '' ): pattern = re.compile( pattern ) self.pattern = pattern # # ContentTypeRegistryPredicate interface # security.declareObjectPublic() def __call__( self, name, typ, body ): """ Return true if the rule matches, else false. """ if self.pattern is None: return 0 return self.pattern.match( name ) security.declareProtected( ManagePortal, 'getTypeLabel' ) def getTypeLabel( self ): """ Return a human-readable label for the predicate type. """ return self.PREDICATE_TYPE security.declareProtected( ManagePortal, 'predicateWidget' ) predicateWidget = DTMLFile( 'patternWidget', _dtmldir ) InitializeClass( NameRegexPredicate ) _predicate_types = [] def registerPredicateType( typeID, klass ): """ Add a new predicate type. """ _predicate_types.append( ( typeID, klass ) ) for klass in ( MajorMinorPredicate , ExtensionPredicate , MimeTypeRegexPredicate , NameRegexPredicate ): registerPredicateType( klass.PREDICATE_TYPE, klass ) class ContentTypeRegistry( SimpleItem ): """ Registry for rules which map PUT args to a CMF Type Object. """ meta_type = 'Content Type Registry' id = 'content_type_registry' manage_options = ( { 'label' : 'Predicates' , 'action' : 'manage_predicates' } , { 'label' : 'Test' , 'action' : 'manage_testRegistry' } ) + SimpleItem.manage_options security = ClassSecurityInfo() def __init__( self ): self.predicate_ids = () self.predicates = PersistentMapping() # # ZMI # security.declarePublic( 'listPredicateTypes' ) def listPredicateTypes( self ): """ """ return map( lambda x: x[0], _predicate_types ) security.declareProtected( ManagePortal, 'manage_predicates' ) manage_predicates = DTMLFile( 'registryPredList', _dtmldir ) security.declareProtected( ManagePortal, 'doAddPredicate' ) def doAddPredicate( self, predicate_id, predicate_type, REQUEST ): """ """ self.addPredicate( predicate_id, predicate_type ) REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/manage_predicates' + '?manage_tabs_message=Predicate+added.' ) security.declareProtected( ManagePortal, 'doUpdatePredicate' ) def doUpdatePredicate( self , predicate_id , predicate , typeObjectName , REQUEST ): """ """ self.updatePredicate( predicate_id, predicate, typeObjectName ) REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/manage_predicates' + '?manage_tabs_message=Predicate+updated.' ) security.declareProtected( ManagePortal, 'doMovePredicateUp' ) def doMovePredicateUp( self, predicate_id, REQUEST ): """ """ predicate_ids = list( self.predicate_ids ) ndx = predicate_ids.index( predicate_id ) if ndx == 0: msg = "Predicate+already+first." else: self.reorderPredicate( predicate_id, ndx - 1 ) msg = "Predicate+moved." REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/manage_predicates' + '?manage_tabs_message=%s' % msg ) security.declareProtected( ManagePortal, 'doMovePredicateDown' ) def doMovePredicateDown( self, predicate_id, REQUEST ): """ """ predicate_ids = list( self.predicate_ids ) ndx = predicate_ids.index( predicate_id ) if ndx == len( predicate_ids ) - 1: msg = "Predicate+already+last." else: self.reorderPredicate( predicate_id, ndx + 1 ) msg = "Predicate+moved." REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/manage_predicates' + '?manage_tabs_message=%s' % msg ) security.declareProtected( ManagePortal, 'doRemovePredicate' ) def doRemovePredicate( self, predicate_id, REQUEST ): """ """ self.removePredicate( predicate_id ) REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/manage_predicates' + '?manage_tabs_message=Predicate+removed.' ) security.declareProtected( ManagePortal, 'manage_testRegistry' ) manage_testRegistry = DTMLFile( 'registryTest', _dtmldir ) security.declareProtected( ManagePortal, 'doTestRegistry' ) def doTestRegistry( self, name, content_type, body, REQUEST ): """ """ typeName = self.findTypeName( name, content_type, body ) if typeName is None: typeName = '' else: types_tool = getToolByName(self, 'portal_types') typeName = types_tool.getTypeInfo(typeName).Title() REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/manage_testRegistry' + '?testResults=Type:+%s' % urllib.quote( typeName ) ) # # Predicate manipulation # security.declarePublic( 'getPredicate' ) def getPredicate( self, predicate_id ): """ Find the predicate whose id is 'id'; return the predicate object, if found, or else None. """ return self.predicates.get( predicate_id, ( None, None ) )[0] security.declarePublic( 'listPredicates' ) def listPredicates( self ): """ Return a sequence of tuples, '( id, ( predicate, typeObjectName ) )' for all predicates in the registry """ result = [] for predicate_id in self.predicate_ids: result.append( ( predicate_id, self.predicates[ predicate_id ] ) ) return tuple( result ) security.declarePublic( 'getTypeObjectName' ) def getTypeObjectName( self, predicate_id ): """ Find the predicate whose id is 'id'; return the name of the type object, if found, or else None. """ return self.predicates.get( predicate_id, ( None, None ) )[1] security.declareProtected( ManagePortal, 'addPredicate' ) def addPredicate( self, predicate_id, predicate_type ): """ Add a predicate to this element of type 'typ' to the registry. """ if predicate_id in self.predicate_ids: raise ValueError, "Existing predicate: %s" % predicate_id klass = None for key, value in _predicate_types: if key == predicate_type: klass = value if klass is None: raise ValueError, "Unknown predicate type: %s" % predicate_type self.predicates[ predicate_id ] = ( klass( predicate_id ), None ) self.predicate_ids = self.predicate_ids + ( predicate_id, ) security.declareProtected( ManagePortal, 'updatePredicate' ) def updatePredicate( self, predicate_id, predicate, typeObjectName ): """ Update a predicate in this element. """ if not predicate_id in self.predicate_ids: raise ValueError, "Unknown predicate: %s" % predicate_id predObj = self.predicates[ predicate_id ][0] mapply( predObj.edit, (), predicate.__dict__ ) self.assignTypeName( predicate_id, typeObjectName ) security.declareProtected( ManagePortal, 'removePredicate' ) def removePredicate( self, predicate_id ): """ Remove a predicate from the registry. """ del self.predicates[ predicate_id ] idlist = list( self.predicate_ids ) ndx = idlist.index( predicate_id ) idlist = idlist[ :ndx ] + idlist[ ndx+1: ] self.predicate_ids = tuple( idlist ) security.declareProtected( ManagePortal, 'reorderPredicate' ) def reorderPredicate( self, predicate_id, newIndex ): """ Move a given predicate to a new location in the list. """ idlist = list( self.predicate_ids ) ndx = idlist.index( predicate_id ) pred = idlist[ ndx ] idlist = idlist[ :ndx ] + idlist[ ndx+1: ] idlist.insert( newIndex, pred ) self.predicate_ids = tuple( idlist ) security.declareProtected( ManagePortal, 'assignTypeName' ) def assignTypeName( self, predicate_id, typeObjectName ): """ Bind the given predicate to a particular type object. """ pred, oldTypeObjName = self.predicates[ predicate_id ] self.predicates[ predicate_id ] = ( pred, typeObjectName ) # # ContentTypeRegistry interface # def findTypeName( self, name, typ, body ): """ Perform a lookup over a collection of rules, returning the the name of the Type object corresponding to name/typ/body. Return None if no match found. """ for predicate_id in self.predicate_ids: pred, typeObjectName = self.predicates[ predicate_id ] if pred( name, typ, body ): return typeObjectName return None InitializeClass( ContentTypeRegistry ) def manage_addRegistry( self, REQUEST=None ): """ Add a CTR to self. """ CTRID = ContentTypeRegistry.id reg = ContentTypeRegistry() self._setObject( CTRID, reg ) reg = self._getOb( CTRID ) if REQUEST is not None: REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/manage_main' + '?manage_tabs_message=Registry+added.' ) CMF-1.3/CMFCore/CookieCrumbler.py0100644000076500007650000002662507523112216016410 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Cookie Crumbler: Enable cookies for non-cookie user folders. $Id: CookieCrumbler.py,v 1.12.36.3 2025/08/04 02:42:22 efge Exp $ """ from base64 import encodestring from urllib import quote, unquote from DateTime import DateTime from utils import SimpleItemWithProperties from AccessControl import ClassSecurityInfo from ZPublisher import BeforeTraverse import Globals import CMFCorePermissions from Globals import HTMLFile from zLOG import LOG, ERROR import sys from ZPublisher.HTTPRequest import HTTPRequest # Constants. ATTEMPT_DISABLED = -1 # Disable cookie crumbler ATTEMPT_NONE = 0 # No attempt at authentication ATTEMPT_LOGIN = 1 # Attempt to log in ATTEMPT_RESUME = 2 # Attempt to resume session class CookieCrumbler (SimpleItemWithProperties): ''' Reads cookies during traversal and simulates the HTTP authentication headers. ''' meta_type = 'Cookie Crumbler' security = ClassSecurityInfo() security.declareProtected(CMFCorePermissions.ModifyCookieCrumblers, 'manage_editProperties', 'manage_changeProperties') security.declareProtected(CMFCorePermissions.ViewManagementScreens, 'manage_propertiesForm') _properties = ({'id':'auth_cookie', 'type': 'string', 'mode':'w', 'label':'Authentication cookie name'}, {'id':'name_cookie', 'type': 'string', 'mode':'w', 'label':'User name form variable'}, {'id':'pw_cookie', 'type': 'string', 'mode':'w', 'label':'User password form variable'}, {'id':'persist_cookie', 'type': 'string', 'mode':'w', 'label':'User name persistence form variable'}, {'id':'auto_login_page', 'type': 'string', 'mode':'w', 'label':'Auto-login page ID'}, {'id':'logout_page', 'type': 'string', 'mode':'w', 'label':'Logout page ID'}, ) auth_cookie = '__ac' name_cookie = '__ac_name' pw_cookie = '__ac_password' persist_cookie = '__ac_persistent' auto_login_page = 'login_form' logout_page = 'logged_out' security.declarePrivate('delRequestVar') def delRequestVar(self, req, name): # No errors of any sort may propagate, and we don't care *what* # they are, even to log them. try: del req.other[name] except: pass try: del req.form[name] except: pass try: del req.cookies[name] except: pass try: del req.environ[name] except: pass # Allow overridable cookie set/expiration methods. security.declarePrivate('getCookieMethod') def getCookieMethod( self, name='setAuthCookie', default=None ): return getattr( self.aq_inner.aq_parent, name, default ) security.declarePrivate('defaultSetAuthCookie') def defaultSetAuthCookie( self, resp, cookie_name, cookie_value ): resp.setCookie( cookie_name, cookie_value, path='/') security.declarePrivate('defaultExpireAuthCookie') def defaultExpireAuthCookie( self, resp, cookie_name ): resp.expireCookie( cookie_name, path='/') security.declarePrivate('modifyRequest') def modifyRequest(self, req, resp): # Returns flags indicating what the user is trying to do. if req.__class__ is not HTTPRequest: return ATTEMPT_DISABLED if not req[ 'REQUEST_METHOD' ] in ( 'GET', 'PUT', 'POST' ): return ATTEMPT_DISABLED if req.environ.has_key( 'WEBDAV_SOURCE_PORT' ): return ATTEMPT_DISABLED if req._auth and not getattr(req, '_cookie_auth', 0): # Using basic auth. return ATTEMPT_DISABLED else: if req.has_key(self.pw_cookie) and req.has_key(self.name_cookie): # Attempt to log in and set cookies. name = req[self.name_cookie] pw = req[self.pw_cookie] ac = encodestring('%s:%s' % (name, pw)) req._auth = 'Basic %s' % ac req._cookie_auth = 1 resp._auth = 1 if req.get(self.persist_cookie, 0): # Persist the user name (but not the pw or session) expires = (DateTime() + 365).toZone('GMT').rfc822() resp.setCookie(self.name_cookie, name, path='/', expires=expires) else: # Expire the user name resp.expireCookie(self.name_cookie, path='/') method = self.getCookieMethod( 'setAuthCookie' , self.defaultSetAuthCookie ) method( resp, self.auth_cookie, quote( ac ) ) self.delRequestVar(req, self.name_cookie) self.delRequestVar(req, self.pw_cookie) return ATTEMPT_LOGIN elif req.has_key(self.auth_cookie): # Copy __ac to the auth header. ac = unquote(req[self.auth_cookie]) req._auth = 'Basic %s' % ac req._cookie_auth = 1 resp._auth = 1 self.delRequestVar(req, self.auth_cookie) return ATTEMPT_RESUME return ATTEMPT_NONE def __call__(self, container, req): '''The __before_publishing_traverse__ hook.''' resp = self.REQUEST['RESPONSE'] attempt = self.modifyRequest(req, resp) if attempt == ATTEMPT_DISABLED: return if not req.get('disable_cookie_login__', 0): if attempt == ATTEMPT_LOGIN or attempt == ATTEMPT_NONE: # Modify the "unauthorized" response. req._hold(ResponseCleanup(resp)) resp.unauthorized = self.unauthorized resp._unauthorized = self._unauthorized if attempt != ATTEMPT_NONE: phys_path = self.getPhysicalPath() if self.logout_page: # Cookies are in use. page = getattr(container, self.logout_page, None) if page is not None: # Provide a logout page. req._logout_path = phys_path + ('logout',) req._credentials_changed_path = ( phys_path + ('credentialsChanged',)) security.declarePublic('credentialsChanged') def credentialsChanged(self, user, name, pw): ac = encodestring('%s:%s' % (name, pw)) method = self.getCookieMethod( 'setAuthCookie' , self.defaultSetAuthCookie ) resp = self.REQUEST['RESPONSE'] method( resp, self.auth_cookie, quote( ac ) ) def _cleanupResponse(self): resp = self.REQUEST['RESPONSE'] # No errors of any sort may propagate, and we don't care *what* # they are, even to log them. try: del resp.unauthorized except: pass try: del resp._unauthorized except: pass return resp security.declarePrivate('unauthorized') def unauthorized(self): resp = self._cleanupResponse() # If we set the auth cookie before, delete it now. if resp.cookies.has_key(self.auth_cookie): del resp.cookies[self.auth_cookie] # Redirect if desired. url = self.getLoginURL() if url is not None: raise 'Redirect', url # Fall through to the standard unauthorized() call. resp.unauthorized() def _unauthorized(self): resp = self._cleanupResponse() # If we set the auth cookie before, delete it now. if resp.cookies.has_key(self.auth_cookie): del resp.cookies[self.auth_cookie] # Redirect if desired. url = self.getLoginURL() if url is not None: resp.redirect(url, lock=1) # We don't need to raise an exception. return # Fall through to the standard _unauthorized() call. resp._unauthorized() security.declarePublic('getLoginURL') def getLoginURL(self): ''' Redirects to the login page. ''' if self.auto_login_page: req = self.REQUEST resp = req['RESPONSE'] iself = getattr(self, 'aq_inner', self) parent = getattr(iself, 'aq_parent', None) page = getattr(parent, self.auto_login_page, None) if page is not None: retry = getattr(resp, '_auth', 0) and '1' or '' came_from = req.get('came_from', None) if came_from is None: came_from = req['URL'] url = '%s?came_from=%s&retry=%s' % ( page.absolute_url(), quote(came_from), retry) return url return None security.declarePublic('logout') def logout(self): ''' Logs out the user and redirects to the logout page. ''' req = self.REQUEST resp = req['RESPONSE'] method = self.getCookieMethod( 'expireAuthCookie' , self.defaultExpireAuthCookie ) method( resp, cookie_name=self.auth_cookie ) redir = 0 if self.logout_page: iself = getattr(self, 'aq_inner', self) parent = getattr(iself, 'aq_parent', None) page = getattr(parent, self.logout_page, None) if page is not None: redir = 1 resp.redirect(page.absolute_url()) if not redir: # Should not normally happen. return 'Logged out.' # Installation and removal of traversal hooks. def manage_beforeDelete(self, item, container): if item is self: handle = self.meta_type + '/' + self.getId() BeforeTraverse.unregisterBeforeTraverse(container, handle) def manage_afterAdd(self, item, container): if item is self: handle = self.meta_type + '/' + self.getId() container = container.this() nc = BeforeTraverse.NameCaller(self.getId()) BeforeTraverse.registerBeforeTraverse(container, nc, handle) Globals.InitializeClass(CookieCrumbler) class ResponseCleanup: def __init__(self, resp): self.resp = resp def __del__(self): # Free the references. # # No errors of any sort may propagate, and we don't care *what* # they are, even to log them. try: del self.resp.unauthorized except: pass try: del self.resp._unauthorized except: pass try: del self.resp except: pass manage_addCCForm = HTMLFile('dtml/addCC', globals()) manage_addCCForm.__name__ = 'addCC' def manage_addCC(self, id, REQUEST=None): ' ' ob = CookieCrumbler() ob.id = id self._setObject(id, ob) if REQUEST is not None: return self.manage_main(self, REQUEST) CMF-1.3/CMFCore/DirectoryView.py0100644000076500007650000004355707522303413016305 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Views of filesystem directories as folders. $Id: DirectoryView.py,v 1.23.6.1 2025/08/01 19:07:55 tseaver Exp $ """ import Globals from Globals import HTMLFile, Persistent, package_home, DTMLFile import os from os import path, listdir, stat from Acquisition import aq_inner, aq_parent, aq_base from string import split, rfind, strip, join from App.Common import package_home from OFS.ObjectManager import bad_id from OFS.Folder import Folder from AccessControl import ClassSecurityInfo from CMFCorePermissions import AccessContentsInformation, ManagePortal import CMFCorePermissions from FSObject import BadFile from utils import expandpath, minimalpath from zLOG import LOG, ERROR from sys import exc_info from types import StringType _dtmldir = os.path.join( package_home( globals() ), 'dtml' ) __reload_module__ = 0 # Ignore version control subdirectories # and special names. def _filter(name): return name not in ('CVS', 'SVN', '.', '..') def _filtered_listdir(path): n = filter(_filter, listdir(path)) return n # This walker is only used on the Win32 version of _changed def _walker (listdir, dirname, names): names[:]=filter(_filter,names) listdir.extend(names) class DirectoryInformation: data = None _v_last_read = 0 _v_last_filelist = [] # Only used on Win32 def __init__(self, expanded_fp, minimal_fp): self.filepath = minimal_fp subdirs = [] for entry in _filtered_listdir(expanded_fp): e_fp = path.join(expanded_fp, entry) if path.isdir(e_fp): subdirs.append(entry) self.subdirs = tuple(subdirs) def getSubdirs(self): return self.subdirs def _isAllowableFilename(self, entry): if entry[-1:] == '~': return 0 if entry[:1] in ('_', '#'): return 0 return 1 def reload(self): self.data = None def _readTypesFile(self): ''' Reads the .objects file produced by FSDump. ''' types = {} fp = expandpath(self.filepath) try: f = open(path.join(fp, '.objects'), 'rt') except: pass else: lines = f.readlines() f.close() for line in lines: try: obname, meta_type = split(line, ':') except: pass else: types[strip(obname)] = strip(meta_type) return types def _readProperties(self, fp): """Reads the properties file next to an object. """ try: f = open(fp, 'rt') except IOError: return None else: lines = f.readlines() f.close() props = {} for line in lines: try: key, value = split(line, '=') except: pass else: props[strip(key)] = strip(value) return props def _readSecurity(self, fp): """Reads the security file next to an object. """ try: f = open(fp, 'rt') except IOError: return None else: lines = f.readlines() f.close() prm = {} for line in lines: try: c1 = line.index(':')+1 c2 = line.index(':',c1) permission = line[:c1-1] acquire = not not line[c1:c2] # get boolean proles = line[c2+1:].split(',') roles=[] for role in proles: role = role.strip() if role: roles.append(role) except: LOG('DirectoryView', ERROR, 'Error reading permission from .security file', error=exc_info()) prm[permission]=(acquire,roles) return prm if Globals.DevelopmentMode and os.name=='nt': def _changed(self): mtime=0 filelist=[] try: fp = expandpath(self.filepath) mtime = stat(fp)[8] # some Windows directories don't change mtime # when a file is added to or deleted from them :-( # So keep a list of files as well, and see if that # changes path.walk(fp,_walker,filelist) filelist.sort() except: LOG('DirectoryView', ERROR, 'Error checking for directory modification', error=exc_info()) if mtime != self._v_last_read or filelist != self._v_last_filelist: self._v_last_read = mtime self._v_last_filelist = filelist return 1 return 0 elif Globals.DevelopmentMode: def _changed(self): try: mtime = stat(expandpath(self.filepath))[8] except: mtime = 0 if mtime != self._v_last_read: self._v_last_read = mtime return 1 return 0 else: def _changed(self): return 0 def getContents(self, registry): changed = self._changed() if self.data is None or changed: try: self.data, self.objects = self.prepareContents(registry, register_subdirs=changed) except: LOG('DirectoryView', ERROR, 'Error during prepareContents:', error=exc_info()) self.data = {} self.objects = () return self.data, self.objects def prepareContents(self, registry, register_subdirs=0): # Creates objects for each file. fp = expandpath(self.filepath) data = {} objects = [] types = self._readTypesFile() for entry in _filtered_listdir(fp): if not self._isAllowableFilename(entry): continue e_filepath = path.join(self.filepath, entry) e_fp = expandpath(e_filepath) if path.isdir(e_fp): # Add a subdirectory only if it was previously registered, # unless register_subdirs is set. info = registry.getDirectoryInfo(e_filepath) if info is None and register_subdirs: # Register unknown subdirs registry.registerDirectoryByPath(e_fp) info = registry.getDirectoryInfo(e_filepath) if info is not None: mt = types.get(entry) t = None if mt is not None: t = registry.getTypeByMetaType(mt) if t is None: t = DirectoryView ob = t(entry, e_filepath) ob_id = ob.getId() data[ob_id] = ob objects.append({'id': ob_id, 'meta_type': ob.meta_type}) else: pos = rfind(entry, '.') if pos >= 0: name = entry[:pos] ext = path.normcase(entry[pos + 1:]) else: name = entry ext = '' if not name or name == 'REQUEST': # Not an allowable id. continue mo = bad_id(name) if mo is not None and mo != -1: # Both re and regex formats # Not an allowable id. continue t = None mt = types.get(entry, None) if mt is None: mt = types.get(name, None) if mt is not None: t = registry.getTypeByMetaType(mt) if t is None: t = registry.getTypeByExtension(ext) if t is not None: properties = self._readProperties( e_fp + '.properties') try: ob = t(name, e_filepath, fullname=entry, properties=properties) except: import traceback typ, val, tb = exc_info() try: exc_lines = traceback.format_exception( typ, val, tb ) LOG( 'DirectoryView', ERROR, join( exc_lines, '\n' ) ) ob = BadFile( name, e_filepath, exc_str=join( exc_lines, '\r\n' ), fullname=entry ) finally: tb = None # Avoid leaking frame! # FS-based security try: permissions = self._readSecurity(e_fp + '.security') if permissions is not None: for name in permissions.keys(): acquire,roles = permissions[name] ob.manage_permission(name,roles,acquire) except: LOG('DirectoryView', ERROR, 'Error setting permission from .security file information', error=exc_info()) ob_id = ob.getId() data[ob_id] = ob objects.append({'id': ob_id, 'meta_type': ob.meta_type}) return data, tuple(objects) class DirectoryRegistry: def __init__(self): self._meta_types = {} self._object_types = {} self._directories = {} def registerFileExtension(self, ext, klass): self._object_types[ext] = klass def registerMetaType(self, mt, klass): self._meta_types[mt] = klass def getTypeByExtension(self, ext): return self._object_types.get(ext, None) def getTypeByMetaType(self, mt): return self._meta_types.get(mt, None) def registerDirectory(self, name, _prefix, subdirs=1): if not isinstance(_prefix, StringType): _prefix = package_home(_prefix) filepath = path.join(_prefix, name) self.registerDirectoryByPath(filepath, subdirs) def registerDirectoryByPath(self, filepath, subdirs=1): fp = minimalpath(filepath) normfilepath = path.normpath(filepath) self._directories[fp] = di = DirectoryInformation(normfilepath, fp) if subdirs: for entry in di.getSubdirs(): e_filepath = path.join(normfilepath, entry) self.registerDirectoryByPath(e_filepath, subdirs) def reloadDirectory(self, filepath): info = self.getDirectoryInfo(filepath) if info is not None: info.reload() def getDirectoryInfo(self, filepath): # Can return None. return self._directories.get(os.path.normpath(filepath), None) def listDirectories(self): dirs = self._directories.keys() dirs.sort() return dirs _dirreg = DirectoryRegistry() registerDirectory = _dirreg.registerDirectory registerFileExtension = _dirreg.registerFileExtension registerMetaType = _dirreg.registerMetaType def listFolderHierarchy(ob, path, rval, adding_meta_type=None): if not hasattr(ob, 'objectValues'): return values = ob.objectValues() for subob in ob.objectValues(): base = getattr(subob, 'aq_base', subob) if getattr(base, 'isPrincipiaFolderish', 0): if adding_meta_type is not None and hasattr( base, 'filtered_meta_types'): # Include only if the user is allowed to # add the given meta type in this location. meta_types = subob.filtered_meta_types() found = 0 for mt in meta_types: if mt['name'] == adding_meta_type: found = 1 break if not found: continue if path: subpath = path + '/' + subob.getId() else: subpath = subob.getId() title = getattr(subob, 'title', None) if title: name = '%s (%s)' % (subpath, title) else: name = subpath rval.append((subpath, name)) listFolderHierarchy(subob, subpath, rval, adding_meta_type) class DirectoryView (Persistent): ''' ''' meta_type = 'Filesystem Directory View' _dirpath = None _objects = () def __init__(self, id, dirpath, fullname=None): self.id = id self._dirpath = dirpath def __of__(self, parent): info = _dirreg.getDirectoryInfo(self._dirpath) if info is not None: info = info.getContents(_dirreg) if info is None: data = {} objects = () else: data, objects = info s = DirectoryViewSurrogate(self, data, objects) res = s.__of__(parent) return res def getId(self): return self.id Globals.InitializeClass(DirectoryView) class DirectoryViewSurrogate (Folder): meta_type = 'Filesystem Directory View' all_meta_types = () _isDirectoryView = 1 security = ClassSecurityInfo() def __init__(self, real, data, objects): d = self.__dict__ d.update(data) d.update(real.__dict__) d['_real'] = real d['_objects'] = objects def __setattr__(self, name, value): d = self.__dict__ d[name] = value setattr(d['_real'], name, value) security.declareProtected(ManagePortal, 'manage_propertiesForm') manage_propertiesForm = DTMLFile( 'dirview_properties', _dtmldir ) security.declareProtected(ManagePortal, 'manage_properties') def manage_properties( self, dirpath, REQUEST=None ): """ Update the directory path of the DV. """ self.__dict__['_real']._dirpath = dirpath if REQUEST is not None: REQUEST['RESPONSE'].redirect( '%s/manage_propertiesForm' % self.absolute_url() ) security.declareProtected(AccessContentsInformation, 'getCustomizableObject') def getCustomizableObject(self): ob = aq_parent(aq_inner(self)) while getattr(ob, '_isDirectoryView', 0): ob = aq_parent(aq_inner(ob)) return ob security.declareProtected(AccessContentsInformation, 'listCustFolderPaths') def listCustFolderPaths(self, adding_meta_type=None): ''' Returns a list of possible customization folders as key, value pairs. ''' rval = [] ob = self.getCustomizableObject() listFolderHierarchy(ob, '', rval, adding_meta_type) rval.sort() return rval security.declareProtected(AccessContentsInformation, 'getDirPath') def getDirPath(self): return self.__dict__['_real']._dirpath security.declarePublic('getId') def getId(self): return self.id Globals.InitializeClass(DirectoryViewSurrogate) manage_addDirectoryViewForm = HTMLFile('dtml/addFSDirView', globals()) def createDirectoryView(parent, filepath, id=None): ''' Adds either a DirectoryView or a derivative object. ''' info = _dirreg.getDirectoryInfo(filepath) if dir is None: raise ValueError('Not a registered directory: %s' % filepath) if not id: id = path.split(filepath)[-1] else: id = str(id) ob = DirectoryView(id, filepath) parent._setObject(id, ob) def addDirectoryViews(ob, name, _prefix): ''' Adds a directory view for every subdirectory of the given directory. ''' # Meant to be called by filesystem-based code. # Note that registerDirectory() still needs to be called # by product initialization code to satisfy # persistence demands. if not isinstance(_prefix, StringType): _prefix = package_home(_prefix) fp = path.join(_prefix, name) filepath = minimalpath(fp) info = _dirreg.getDirectoryInfo(filepath) if info is None: raise ValueError('Not a registered directory: %s' % filepath) for entry in info.getSubdirs(): filepath2 = path.join(filepath, entry) createDirectoryView(ob, filepath2, entry) def manage_addDirectoryView(self, filepath, id=None, REQUEST=None): ''' Adds either a DirectoryView or a derivative object. ''' createDirectoryView(self, filepath, id) if REQUEST is not None: return self.manage_main(self, REQUEST) def manage_listAvailableDirectories(*args): ''' ''' return list(_dirreg.listDirectories()) CMF-1.3/CMFCore/DiscussionTool.py0100644000076500007650000001337407522303413016461 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Basic portal discussion access tool. $Id: DiscussionTool.py,v 1.6.16.1 2025/08/01 19:07:55 tseaver Exp $ """ from utils import UniqueObject, getToolByName from utils import getToolByName, _dtmldir import CMFCorePermissions from OFS.SimpleItem import SimpleItem from Globals import InitializeClass, DTMLFile import Acquisition from AccessControl import ClassSecurityInfo class OldDiscussable(Acquisition.Implicit): """ Adapter for PortalContent to implement "old-style" discussions. """ _isDiscussable = 1 security = ClassSecurityInfo() def __init__( self, content ): self.content = content security.declareProtected(CMFCorePermissions.ReplyToItem, 'createReply') def createReply(self, title, text, REQUEST, RESPONSE): """ Create a reply in the proper place """ location, id = self.getReplyLocationAndID(REQUEST) location.addDiscussionItem(id, title, title, 'structured-text', text, self.content) RESPONSE.redirect( self.absolute_url() + '/view' ) def getReplyLocationAndID(self, REQUEST): # It is not yet clear to me what the correct location for this hook is # Find the folder designated for replies, creating if missing membershiptool = getToolByName(self.content, 'portal_membership') home = membershiptool.getHomeFolder() if not hasattr(home, 'Correspondence'): home.manage_addPortalFolder('Correspondence') location = home.Correspondence location.manage_permission(CMFCorePermissions.View, ['Anonymous'], 1) location.manage_permission( CMFCorePermissions.AccessContentsInformation, ['Anonymous'], 1) # Find an unused id in location id = int(DateTime().timeTime()) while hasattr(location, `id`): id = id + 1 return location, `id` security.declareProtected(CMFCorePermissions.View, 'getReplyResults') def getReplyResults(self): """ Return the ZCatalog results that represent this object's replies. Often, the actual objects are not needed. This is less expensive than fetching the objects. """ catalog = getToolByName(self.content, 'portal_catalog') return catalog.searchResults(in_reply_to= urllib.unquote('/'+self.absolute_url(1))) security.declareProtected(CMFCorePermissions.View, 'getReplies') def getReplies(self): """ Return a sequence of the DiscussionResponse objects which are associated with this Discussable """ catalog = getToolByName(self.content, 'portal_catalog') results = self.getReplyResults() rids = map(lambda x: x.data_record_id_, results) objects = map(catalog.getobject, rids) return objects def quotedContents(self): """ Return this object's contents in a form suitable for inclusion as a quote in a response. """ return "" class DiscussionTool (UniqueObject, SimpleItem): id = 'portal_discussion' meta_type = 'Oldstyle CMF Discussion Tool' # This tool is used to find the discussion for a given content object. security = ClassSecurityInfo() manage_options = ( { 'label' : 'Overview', 'action' : 'manage_overview' } , ) + SimpleItem.manage_options # # ZMI methods # security.declareProtected( CMFCorePermissions.ManagePortal , 'manage_overview' ) manage_overview = DTMLFile( 'explainDiscussionTool', _dtmldir ) # # 'portal_discussion' interface methods # security.declarePublic('getDiscussionFor') def getDiscussionFor(self, content): '''Gets the PortalDiscussion object that applies to content. ''' return OldDiscussable( content ).__of__( content ) security.declarePublic('isDiscussionAllowedFor') def isDiscussionAllowedFor(self, content): ''' Returns a boolean indicating whether a discussion is allowed for the specified content. ''' if hasattr( content, 'allow_discussion' ): return content.allow_discussion typeInfo = getToolByName(self, 'portal_types').getTypeInfo( content ) if typeInfo: return typeInfo.allowDiscussion() return 0 security.declarePrivate('listActions') def listActions(self, info): # Return actions for reply and show replies content = info.content if content is None or not self.isDiscussionAllowedFor(content): return [] discussion = self.getDiscussionFor(content) if discussion.aq_base == content.aq_base: discussion_url = info.content_url else: discussion_url = discussion.absolute_url() actions = ( {'name': 'Reply', 'url': discussion_url + '/discussion_reply_form', 'permissions': ['Reply to item'], 'category': 'object' }, ) return actions InitializeClass(DiscussionTool) CMF-1.3/CMFCore/DynamicType.py0100644000076500007650000000561607523332620015731 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ DynamicType: Mixin for dynamic properties. $Id: DynamicType.py,v 1.11.4.2 2025/08/04 23:15:28 efge Exp $ """ from AccessControl import ClassSecurityInfo from utils import getToolByName import Globals from urllib import quote class DynamicType: """ Mixin for portal content that allows the object to take on a dynamic type property. """ portal_type = None security = ClassSecurityInfo() def _setPortalTypeName(self, pt): ''' Called by portal_types during construction, records an ID that will be used later to locate the correct ContentTypeInformation. ''' self.portal_type = pt security.declarePublic('getPortalTypeName') def getPortalTypeName(self): """ Returns the portal type name that can be passed to portal_types. If the object is uninitialized, returns None. """ pt = self.portal_type if callable( pt ): pt = pt() return pt _getPortalTypeName = getPortalTypeName security.declarePublic('getTypeInfo') def getTypeInfo(self): ''' Returns an object that supports the ContentTypeInformation interface. ''' tool = getToolByName(self, 'portal_types', None) if tool is None: return None return tool.getTypeInfo(self) # Can return None. # Support for dynamic icons security.declarePublic('getIcon') def getIcon(self, relative_to_portal=0): """ Using this method allows the content class creator to grab icons on the fly instead of using a fixed attribute on the class. """ ti = self.getTypeInfo() if ti is not None: icon = quote(ti.getIcon()) if icon: if relative_to_portal: return icon else: # Relative to REQUEST['BASEPATH1'] portal_url = getToolByName( self, 'portal_url' ) res = portal_url(relative=1) + '/' + icon while res[:1] == '/': res = res[1:] return res return 'misc_/OFSP/dtmldoc.gif' security.declarePublic('icon') icon = getIcon # For the ZMI Globals.InitializeClass (DynamicType) CMF-1.3/CMFCore/Expression.py0100644000076500007650000000512707522303413015634 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Expressions in a web-configurable workflow. $Id: Expression.py,v 1.4.10.1 2025/08/01 19:07:55 tseaver Exp $ """ import Globals from Globals import Persistent from Acquisition import aq_inner, aq_parent from AccessControl import getSecurityManager, ClassSecurityInfo from utils import getToolByName from Products.PageTemplates.Expressions import getEngine from Products.PageTemplates.TALES import SafeMapping from Products.PageTemplates.Expressions import SecureModuleImporter class Expression (Persistent): text = '' _v_compiled = None security = ClassSecurityInfo() def __init__(self, text): self.text = text self._v_compiled = getEngine().compile(text) def __call__(self, econtext): compiled = self._v_compiled if compiled is None: compiled = self._v_compiled = getEngine().compile(self.text) # ?? Maybe expressions should manipulate the security # context stack. res = compiled(econtext) if isinstance(res, Exception): raise res #print 'returning %s from %s' % (`res`, self.text) return res Globals.InitializeClass(Expression) def createExprContext(folder, portal, object): ''' An expression context provides names for TALES expressions. ''' pm = getToolByName(portal, 'portal_membership') if object is None: object_url = '' else: object_url = object.absolute_url() if pm.isAnonymousUser(): member = None else: member = pm.getAuthenticatedMember() data = { 'object_url': object_url, 'folder_url': folder.absolute_url(), 'portal_url': portal.absolute_url(), 'object': object, 'folder': folder, 'portal': portal, 'nothing': None, 'request': getattr( object, 'REQUEST', None ), 'modules': SecureModuleImporter, 'member': member, } return getEngine().getContext(data) CMF-1.3/CMFCore/FSDTMLMethod.py0100644000076500007650000001166107522303413015627 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Customizable DTML methods that come from the filesystem. $Id: FSDTMLMethod.py,v 1.9.36.1 2025/08/01 19:07:55 tseaver Exp $ """ from string import split from os import path, stat import Globals from AccessControl import ClassSecurityInfo, getSecurityManager, Permissions from OFS.DTMLMethod import DTMLMethod, decapitate, guess_content_type from utils import _dtmldir from CMFCorePermissions import View, ViewManagementScreens, FTPAccess from DirectoryView import registerFileExtension, registerMetaType, expandpath from FSObject import FSObject try: # Zope 2.4.x from AccessControl.DTML import RestrictedDTML except ImportError: class RestrictedDTML: pass class FSDTMLMethod(RestrictedDTML, FSObject, Globals.HTML): """FSDTMLMethods act like DTML methods but are not directly modifiable from the management interface.""" meta_type = 'Filesystem DTML Method' manage_options=( ( {'label':'Customize', 'action':'manage_main'}, {'label':'View', 'action':'', 'help':('OFSP','DTML-DocumentOrMethod_View.stx')}, ) ) # Use declarative security security = ClassSecurityInfo() security.declareObjectProtected(View) security.declareProtected(ViewManagementScreens, 'manage_main') manage_main = Globals.DTMLFile('custdtml', _dtmldir) def __init__(self, id, filepath, fullname=None, properties=None): FSObject.__init__(self, id, filepath, fullname, properties) # Normally called via HTML.__init__ but we don't need the rest that # happens there. self.initvars(None, {}) def _createZODBClone(self): """Create a ZODB (editable) equivalent of this object.""" return DTMLMethod(self.read(), __name__=self.getId()) def _readFile(self, reparse): fp = expandpath(self._filepath) file = open(fp, 'rb') try: data = file.read() finally: file.close() self.raw = data if reparse: self.cook() # Hook up chances to reload in debug mode security.declarePrivate('read_raw') def read_raw(self): self._updateFromFS() return Globals.HTML.read_raw(self) #### The following is mainly taken from OFS/DTMLMethod.py ### index_html=None # Prevent accidental acquisition # Documents masquerade as functions: func_code = DTMLMethod.func_code default_content_type = 'text/html' def __call__(self, client=None, REQUEST={}, RESPONSE=None, **kw): """Render the document given a client object, REQUEST mapping, Response, and key word arguments.""" self._updateFromFS() kw['document_id'] =self.getId() kw['document_title']=self.title security=getSecurityManager() security.addContext(self) try: if client is None: # Called as subtemplate, so don't need error propagation! r=apply(Globals.HTML.__call__, (self, client, REQUEST), kw) if RESPONSE is None: result = r else: result = decapitate(r, RESPONSE) return result r=apply(Globals.HTML.__call__, (self, client, REQUEST), kw) if type(r) is not type('') or RESPONSE is None: return r finally: security.removeContext(self) have_key=RESPONSE.headers.has_key if not (have_key('content-type') or have_key('Content-Type')): if self.__dict__.has_key('content_type'): c=self.content_type else: c, e=guess_content_type(self.getId(), r) RESPONSE.setHeader('Content-Type', c) result = decapitate(r, RESPONSE) return result # Zope 2.3.x way: def validate(self, inst, parent, name, value, md=None): return getSecurityManager().validate(inst, parent, name, value) security.declareProtected(FTPAccess, 'manage_FTPget') security.declareProtected(ViewManagementScreens, 'PrincipiaSearchSource', 'document_src') manage_FTPget = DTMLMethod.manage_FTPget PrincipiaSearchSource = DTMLMethod.PrincipiaSearchSource document_src = DTMLMethod.document_src Globals.InitializeClass(FSDTMLMethod) registerFileExtension('dtml', FSDTMLMethod) registerFileExtension('css', FSDTMLMethod) registerMetaType('DTML Method', FSDTMLMethod) CMF-1.3/CMFCore/FSImage.py0100644000076500007650000001253207522303413014746 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Customizable image objects that come from the filesystem. $Id: FSImage.py,v 1.11.8.1 2025/08/01 19:07:55 tseaver Exp $ """ import string, os import Globals from DateTime import DateTime from AccessControl import ClassSecurityInfo from webdav.common import rfc1123_date from OFS.Image import Image, getImageInfo from utils import _dtmldir from CMFCorePermissions import ViewManagementScreens, View, FTPAccess from FSObject import FSObject from DirectoryView import registerFileExtension, registerMetaType, expandpath class FSImage(FSObject): """FSImages act like images but are not directly modifiable from the management interface.""" # Note that OFS.Image.Image is not a base class because it is mutable. meta_type = 'Filesystem Image' _data = None manage_options=( {'label':'Customize', 'action':'manage_main'}, ) security = ClassSecurityInfo() security.declareObjectProtected(View) def __init__(self, id, filepath, fullname=None, properties=None): id = fullname or id # Use the whole filename. FSObject.__init__(self, id, filepath, fullname, properties) security.declareProtected(ViewManagementScreens, 'manage_main') manage_main = Globals.DTMLFile('custimage', _dtmldir) content_type = 'unknown/unknown' def _createZODBClone(self): return Image(self.getId(), '', self._readFile(1)) def _readFile(self, reparse): fp = expandpath(self._filepath) file = open(fp, 'rb') try: data = self._data = file.read() finally: file.close() if reparse or self.content_type == 'unknown/unknown': ct, width, height = getImageInfo( data ) self.content_type = ct self.width = width self.height = height return data #### The following is mainly taken from OFS/Image.py ### __str__ = Image.__str__ _image_tag = Image.tag security.declareProtected(View, 'tag') def tag(self, *args, **kw): # Hook into an opportunity to reload metadata. self._updateFromFS() return apply(self._image_tag, args, kw) security.declareProtected(View, 'index_html') def index_html(self, REQUEST, RESPONSE): """ The default view of the contents of a File or Image. Returns the contents of the file or image. Also, sets the Content-Type HTTP header to the objects content type. """ self._updateFromFS() data = self._data # HTTP If-Modified-Since header handling. header=REQUEST.get_header('If-Modified-Since', None) if header is not None: header=string.split(header, ';')[0] # Some proxies seem to send invalid date strings for this # header. If the date string is not valid, we ignore it # rather than raise an error to be generally consistent # with common servers such as Apache (which can usually # understand the screwy date string as a lucky side effect # of the way they parse it). try: mod_since=long(DateTime(header).timeTime()) except: mod_since=None if mod_since is not None: last_mod = self._file_mod_time if last_mod > 0 and last_mod <= mod_since: # Set header values since apache caching will return # Content-Length of 0 in response if size is not set here RESPONSE.setHeader('Last-Modified', rfc1123_date(last_mod)) RESPONSE.setHeader('Content-Type', self.content_type) RESPONSE.setHeader('Content-Length', len(data)) RESPONSE.setStatus(304) return '' RESPONSE.setHeader('Last-Modified', rfc1123_date(self._file_mod_time)) RESPONSE.setHeader('Content-Type', self.content_type) RESPONSE.setHeader('Content-Length', len(data)) return data security.declareProtected(View, 'getContentType') def getContentType(self): """Get the content type of a file or image. Returns the content type (MIME type) of a file or image. """ self._updateFromFS() return self.content_type security.declareProtected(View, 'get_size') def get_size( self ): """ Return the size of the image. """ self._updateFromFS() return self._data and len( self._data ) or 0 security.declareProtected(FTPAccess, 'manage_FTPget') manage_FTPget = index_html Globals.InitializeClass(FSImage) registerFileExtension('gif', FSImage) registerFileExtension('jpg', FSImage) registerFileExtension('jpeg', FSImage) registerFileExtension('png', FSImage) registerMetaType('Image', FSImage) CMF-1.3/CMFCore/FSObject.py0100644000076500007650000001552607522303413015140 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Customizable objects that come from the filesystem (base class). $Id: FSObject.py,v 1.9.10.1 2025/08/01 19:07:55 tseaver Exp $ """ from string import split from os import path, stat import Acquisition, Globals from AccessControl import ClassSecurityInfo from OFS.SimpleItem import Item from DateTime import DateTime from utils import expandpath, getToolByName import CMFCorePermissions class FSObject(Acquisition.Implicit, Item): """FSObject is a base class for all filesystem based look-alikes. Subclasses of this class mimic ZODB based objects like Image and DTMLMethod, but are not directly modifiable from the management interface. They provide means to create a TTW editable copy, however. """ # Always empty for FS based, non-editable objects. title = '' security = ClassSecurityInfo() security.declareObjectProtected(CMFCorePermissions.View) _file_mod_time = 0 _parsed = 0 def __init__(self, id, filepath, fullname=None, properties=None): if properties: # Since props come from the filesystem, this should be # safe. self.__dict__.update(properties) if fullname and properties.get('keep_extension', 0): id = fullname self.id = id self.__name__ = id # __name__ is used in traceback reporting self._filepath = filepath fp = expandpath(self._filepath) try: self._file_mod_time = stat(fp)[8] except: pass self._readFile(0) security.declareProtected(CMFCorePermissions.ViewManagementScreens, 'manage_doCustomize') def manage_doCustomize(self, folder_path, RESPONSE=None): """Makes a ZODB Based clone with the same data. Calls _createZODBClone for the actual work. """ obj = self._createZODBClone() id = obj.getId() fpath = tuple(split(folder_path, '/')) portal_skins = getToolByName(self,'portal_skins') folder = portal_skins.restrictedTraverse(fpath) folder._verifyObjectPaste(obj, validate_src=0) folder._setObject(id, obj) if RESPONSE is not None: RESPONSE.redirect('%s/%s/manage_main' % ( folder.absolute_url(), id)) def _createZODBClone(self): """Create a ZODB (editable) equivalent of this object.""" raise NotImplemented, "This should be implemented in a subclass." def _readFile(self, reparse): """Read the data from the filesystem. Read the file indicated by exandpath(self._filepath), and parse the data if necessary. 'reparse' is set when reading the second time and beyond. """ raise NotImplemented, "This should be implemented in a subclass." # Refresh our contents from the filesystem if that is newer and we are # running in debug mode. def _updateFromFS(self): parsed = self._parsed if not parsed or Globals.DevelopmentMode: fp = expandpath(self._filepath) try: mtime=stat(fp)[8] except: mtime=0 if not parsed or mtime != self._file_mod_time: self._parsed = 1 self._file_mod_time = mtime self._readFile(1) security.declareProtected(CMFCorePermissions.View, 'get_size') def get_size(self): """Get the size of the underlying file.""" fp = expandpath(self._filepath) return path.getsize(fp) security.declareProtected(CMFCorePermissions.View, 'getModTime') def getModTime(self): """Return the last_modified date of the file we represent. Returns a DateTime instance. """ self._updateFromFS() return DateTime(self._file_mod_time) security.declareProtected(CMFCorePermissions.ViewManagementScreens, 'getObjectFSPath') def getObjectFSPath(self): """Return the path of the file we represent""" self._updateFromFS() return self._filepath Globals.InitializeClass(FSObject) class BadFile( FSObject ): """ Represent a file which was not readable or parseable as its intended type. """ meta_type = 'Bad File' icon = 'p_/broken' BAD_FILE_VIEW = """\

Bad Filesystem Object: &dtml-getId;

File Contents


Exception


""" manage_options=( {'label':'Error', 'action':'manage_showError'}, ) def __init__( self, id, filepath, exc_str='' , fullname=None, properties=None): id = fullname or id # Use the whole filename. self.exc_str = exc_str self.file_contents = '' FSObject.__init__(self, id, filepath, fullname, properties) security = ClassSecurityInfo() showError = Globals.HTML( BAD_FILE_VIEW ) security.declareProtected( CMFCorePermissions.ManagePortal , 'manage_showError' ) def manage_showError( self, REQUEST ): """ """ return self.showError( self, REQUEST ) security.declarePrivate( '_readFile' ) def _readFile( self, reparse ): """Read the data from the filesystem. Read the file indicated by exandpath(self._filepath), and parse the data if necessary. 'reparse' is set when reading the second time and beyond. """ try: fp = expandpath(self._filepath) file = open(fp, 'rb') try: data = self.file_contents = file.read() finally: file.close() except: # No errors of any sort may propagate data = self.file_contents = None #give up return data security.declarePublic( 'getFileContents' ) def getFileContents( self ): """ Return the contents of the file, if we could read it. """ return self.file_contents security.declarePublic( 'getExceptionText' ) def getExceptionText( self ): """ Return the exception thrown while reading or parsing the file. """ return self.exc_str Globals.InitializeClass( BadFile ) CMF-1.3/CMFCore/FSPageTemplate.py0100644000076500007650000001526307522303413016300 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ########################################################################## """ Customizable page templates that come from the filesystem. $Id: FSPageTemplate.py,v 1.8.8.3 2025/08/01 19:07:55 tseaver Exp $ """ from string import split, replace from os import stat import re import Globals, Acquisition from DateTime import DateTime from DocumentTemplate.DT_Util import html_quote from Acquisition import aq_parent from AccessControl import getSecurityManager, ClassSecurityInfo from Shared.DC.Scripts.Script import Script from Products.PageTemplates.PageTemplate import PageTemplate from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate, Src from DirectoryView import registerFileExtension, registerMetaType, expandpath from CMFCorePermissions import ViewManagementScreens, View, FTPAccess from FSObject import FSObject from utils import getToolByName xml_detect_re = re.compile('^\s*<\?xml\s+') class FSPageTemplate(FSObject, Script, PageTemplate): "Wrapper for Page Template" meta_type = 'Filesystem Page Template' manage_options=( ( {'label':'Customize', 'action':'manage_main'}, {'label':'Test', 'action':'ZScriptHTML_tryForm'}, ) ) security = ClassSecurityInfo() security.declareObjectProtected(View) security.declareProtected(ViewManagementScreens, 'manage_main') manage_main = Globals.DTMLFile('dtml/custpt', globals()) # Declare security for unprotected PageTemplate methods. security.declarePrivate('pt_edit', 'write') def __init__(self, id, filepath, fullname=None, properties=None): FSObject.__init__(self, id, filepath, fullname, properties) self.ZBindings_edit(self._default_bindings) def _createZODBClone(self): """Create a ZODB (editable) equivalent of this object.""" obj = ZopePageTemplate(self.getId(), self._text, self.content_type) obj.expand = 0 obj.write(self.read()) return obj def ZCacheable_isCachingEnabled(self): return 0 def _readFile(self, reparse): fp = expandpath(self._filepath) file = open(fp, 'rb') try: data = file.read() finally: file.close() if reparse: if xml_detect_re.match(data): # Smells like xml self.content_type = 'text/xml' else: try: del self.content_type except (AttributeError, KeyError): pass self.write(data) security.declarePrivate('read') def read(self): # Tie in on an opportunity to auto-update self._updateFromFS() return FSPageTemplate.inheritedAttribute('read')(self) ### The following is mainly taken from ZopePageTemplate.py ### expand = 0 func_defaults = None func_code = ZopePageTemplate.func_code _default_bindings = ZopePageTemplate._default_bindings security.declareProtected(View, '__call__') def pt_macros(self): # Tie in on an opportunity to auto-reload self._updateFromFS() return FSPageTemplate.inheritedAttribute('pt_macros')(self) def pt_render(self, source=0, extra_context={}): self._updateFromFS() # Make sure the template has been loaded. try: if not source: # Hook up to caching policy. REQUEST = getattr( self, 'REQUEST', None ) if REQUEST: content = aq_parent( self ) mgr = getToolByName( content , 'caching_policy_manager' , None ) if mgr: view_name = self.getId() RESPONSE = REQUEST[ 'RESPONSE' ] headers = mgr.getHTTPCachingHeaders( content , view_name , extra_context ) for key, value in headers: RESPONSE.setHeader( key, value ) return FSPageTemplate.inheritedAttribute('pt_render')( self, source, extra_context ) except RuntimeError: if Globals.DevelopmentMode: err = FSPageTemplate.inheritedAttribute( 'pt_errors' )( self ) err_type = err[0] err_msg = '
%s
' % replace( err[1], "\'", "'" ) msg = 'FS Page Template %s has errors: %s.
%s' % ( self.id, err_type, html_quote(err_msg) ) raise RuntimeError, msg else: raise security.declarePrivate( '_ZPT_exec' ) _ZPT_exec = ZopePageTemplate._exec security.declarePrivate( '_exec' ) def _exec(self, bound_names, args, kw): """Call a FSPageTemplate""" try: response = self.REQUEST.RESPONSE except AttributeError: response = None # call "inherited" result = self._ZPT_exec( bound_names, args, kw ) if response is not None: response.setHeader( 'content-type', self.content_type ) return result # Copy over more methods security.declareProtected(FTPAccess, 'manage_FTPget') security.declareProtected(View, 'get_size') security.declareProtected(ViewManagementScreens, 'PrincipiaSearchSource', 'document_src') pt_getContext = ZopePageTemplate.pt_getContext ZScriptHTML_tryParams = ZopePageTemplate.ZScriptHTML_tryParams manage_FTPget = ZopePageTemplate.manage_FTPget get_size = ZopePageTemplate.get_size getSize = get_size PrincipiaSearchSource = ZopePageTemplate.PrincipiaSearchSource document_src = ZopePageTemplate.document_src d = FSPageTemplate.__dict__ d['source.xml'] = d['source.html'] = Src() Globals.InitializeClass(FSPageTemplate) registerFileExtension('pt', FSPageTemplate) registerFileExtension('html', FSPageTemplate) registerFileExtension('htm', FSPageTemplate) registerMetaType('Page Template', FSPageTemplate) CMF-1.3/CMFCore/FSPropertiesObject.py0100644000076500007650000001220607522303413017205 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Customizable properties that come from the filesystem. $Id: FSPropertiesObject.py,v 1.8.16.4 2025/08/01 19:07:55 tseaver Exp $ """ from string import split, strip import Globals import Acquisition from OFS.Folder import Folder from OFS.PropertyManager import PropertyManager from ZPublisher.Converters import get_converter from AccessControl import ClassSecurityInfo from utils import _dtmldir from DirectoryView import registerFileExtension, registerMetaType, expandpath from CMFCorePermissions import ViewManagementScreens from FSObject import FSObject class FSPropertiesObject (FSObject, PropertyManager): """FSPropertiesObjects simply hold properties.""" meta_type = 'Filesystem Properties Object' manage_options = ({'label':'Customize', 'action':'manage_main'},) security = ClassSecurityInfo() security.declareProtected(ViewManagementScreens, 'manage_main') manage_main = Globals.DTMLFile('custprops', _dtmldir) # Declare all (inherited) mutating methods private. security.declarePrivate('manage_addProperty', 'manage_editProperties', 'manage_delProperties', 'manage_changeProperties', 'manage_propertiesForm', 'manage_propertyTypeForm', 'manage_changePropertyTypes',) security.declareProtected(ViewManagementScreens, 'manage_doCustomize') def manage_doCustomize(self, folder_path, RESPONSE=None): """Makes a ZODB Based clone with the same data. Calls _createZODBClone for the actual work. """ # Overridden here to provide a different redirect target. FSObject.manage_doCustomize(self, folder_path, RESPONSE) if RESPONSE is not None: fpath = tuple(split(folder_path, '/')) folder = self.restrictedTraverse(fpath) RESPONSE.redirect('%s/%s/manage_propertiesForm' % ( folder.absolute_url(), self.getId())) def _createZODBClone(self): """Create a ZODB (editable) equivalent of this object.""" # Create a Folder to hold the properties. obj = Folder() obj.id = self.getId() map = [] for p in self._properties: # This should be secure since the properties come # from the filesystem. setattr(obj, p['id'], getattr(self, p['id'])) map.append({'id': p['id'], 'type': p['type'], 'mode': 'wd',}) obj._properties = tuple(map) return obj def _readFile(self, reparse): """Read the data from the filesystem. Read the file (indicated by exandpath(self._filepath), and parse the data if necessary. """ fp = expandpath(self._filepath) file = open(fp, 'rb') try: lines = file.readlines() finally: file.close() map = [] lino=0 for line in lines: lino = lino + 1 line = strip( line ) if not line or line[0] == '#': continue try: propname, proptv = split( line, ':' ) #XXX multi-line properties? proptype, propvstr = proptv.split( '=', 1 ) propname = strip(propname) proptype = strip(proptype) propvstr = strip(propvstr) converter = get_converter( proptype, lambda x: x ) propvalue = converter( strip( propvstr ) ) # Should be safe since we're loading from # the filesystem. setattr(self, propname, propvalue) map.append({'id':propname, 'type':proptype, 'mode':'', 'default_value':propvalue, }) except: raise ValueError, ( 'Error processing line %s of %s:\n%s' % (lino,fp,line) ) self._properties = tuple(map) if Globals.DevelopmentMode: # Provide an opportunity to update the properties. def __of__(self, parent): self = Acquisition.ImplicitAcquisitionWrapper(self, parent) self._updateFromFS() return self Globals.InitializeClass(FSPropertiesObject) registerFileExtension('props', FSPropertiesObject) registerMetaType('Properties Object', FSPropertiesObject) CMF-1.3/CMFCore/FSPythonScript.py0100644000076500007650000001671707522303413016403 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Customizable Python scripts that come from the filesystem. $Id: FSPythonScript.py,v 1.16.10.1 2025/08/01 19:07:55 tseaver Exp $ """ from string import strip, split from os import path, stat import new import Globals from AccessControl import ClassSecurityInfo, getSecurityManager from Products.PythonScripts.PythonScript import PythonScript from Shared.DC.Scripts.Script import Script from ComputedAttribute import ComputedAttribute from utils import _dtmldir from CMFCorePermissions import ViewManagementScreens, View, FTPAccess from DirectoryView import registerFileExtension, registerMetaType, expandpath from FSObject import FSObject class FSPythonScript (FSObject, Script): """FSPythonScripts act like Python Scripts but are not directly modifiable from the management interface.""" meta_type = 'Filesystem Script (Python)' _params = _body = '' _v_f = None manage_options=( ( {'label':'Customize', 'action':'manage_main'}, {'label':'Test', 'action':'ZScriptHTML_tryForm', 'help': ('PythonScripts', 'PythonScript_test.stx')}, ) ) # Use declarative security security = ClassSecurityInfo() security.declareObjectProtected(View) security.declareProtected(View, 'index_html',) # Prevent the bindings from being edited TTW security.declarePrivate('ZBindings_edit','ZBindingsHTML_editForm','ZBindingsHTML_editAction') security.declareProtected(ViewManagementScreens, 'manage_main') manage_main = Globals.DTMLFile('custpy', _dtmldir) def _createZODBClone(self): """Create a ZODB (editable) equivalent of this object.""" obj = PythonScript(self.getId()) obj.write(self.read()) return obj def _readFile(self, reparse): """Read the data from the filesystem. Read the file (indicated by exandpath(self._filepath), and parse the data if necessary. """ fp = expandpath(self._filepath) file = open(fp, 'r') try: data = file.read() finally: file.close() if reparse: self._write(data, reparse) def _validateProxy(self, roles=None): pass def __render_with_namespace__(self, namespace): '''Calls the script.''' self._updateFromFS() return Script.__render_with_namespace__(self, namespace) def __call__(self, *args, **kw): '''Calls the script.''' self._updateFromFS() return Script.__call__(self, *args, **kw) #### The following is mainly taken from PythonScript.py ### def _exec(self, bound_names, args, kw): """Call a Python Script Calling a Python Script is an actual function invocation. """ # Prepare the function. f = self._v_f if f is None: # The script has errors. raise RuntimeError, '%s has errors.' % self._filepath __traceback_info__ = bound_names, args, kw, self.func_defaults if bound_names: # Updating func_globals directly is not thread safe here. # In normal PythonScripts, every thread has its own # copy of the function. But in FSPythonScripts # there is only one copy. So here's another way. new_globals = f.func_globals.copy() new_globals.update(bound_names) if f.func_defaults: f = new.function(f.func_code, new_globals, f.func_name, f.func_defaults) else: f = new.function(f.func_code, new_globals, f.func_name) # Execute the function in a new security context. security=getSecurityManager() security.addContext(self) try: result = apply(f, args, kw) return result finally: security.removeContext(self) security.declareProtected(ViewManagementScreens, 'read', 'getModTime', 'get_size', 'ZScriptHTML_tryForm', 'PrincipiaSearchSource', 'document_src', 'params', 'body') def ZScriptHTML_tryParams(self): """Parameters to test the script with.""" param_names = [] for name in split(self._params, ','): name = strip(name) if name and name[0] != '*': param_names.append(split(name, '=', 1)[0]) return param_names def read(self): self._updateFromFS() return self._source def document_src(self, REQUEST=None, RESPONSE=None): """Return unprocessed document source.""" if RESPONSE is not None: RESPONSE.setHeader('Content-Type', 'text/plain') return self._source def PrincipiaSearchSource(self): "Support for searching - the document's contents are searched." return "%s\n%s" % (self._params, self._body) def params(self): return self._params def body(self): return self._body def get_size(self): return len(self.read()) security.declareProtected(FTPAccess, 'manage_FTPget') def manage_FTPget(self): "Get source for FTP download" self.REQUEST.RESPONSE.setHeader('Content-Type', 'text/plain') return self.read() def _write(self, text, compile): ''' Parses the source, storing the body, params, title, bindings, and source in self. If compile is set, compiles the function. ''' ps = PythonScript(self.id) ps.write(text) if compile: ps._makeFunction(1) self._v_f = f = ps._v_f if f is not None: self.func_code = f.func_code self.func_defaults = f.func_defaults else: # There were errors in the compile. # No signature. self.func_code = None self.func_defaults = None self._body = ps._body self._params = ps._params self.title = ps.title self._setupBindings(ps.getBindingAssignments().getAssignedNames()) self._source = ps.read() # Find out what the script sees. def func_defaults(self): # This ensures func_code and func_defaults are # set when the code hasn't been compiled yet, # just in time for mapply(). Truly odd, but so is mapply(). :P self._updateFromFS() return self.__dict__.get('func_defaults', None) func_defaults = ComputedAttribute(func_defaults, 1) def func_code(self): # See func_defaults. self._updateFromFS() return self.__dict__.get('func_code', None) func_code = ComputedAttribute(func_code, 1) def title(self): # See func_defaults. self._updateFromFS() return self.__dict__.get('title', None) title = ComputedAttribute(title, 1) Globals.InitializeClass(FSPythonScript) registerFileExtension('py', FSPythonScript) registerMetaType('Script (Python)', FSPythonScript) CMF-1.3/CMFCore/FSSTXMethod.py0100644000076500007650000001137207516644314015557 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """FSSTXMethod: Filesystem methodish Structured Text document. $Id: FSSTXMethod.py,v 1.5.36.1 2025/07/21 23:50:36 tseaver Exp $ """ import Globals from AccessControl import ClassSecurityInfo import StructuredText from DirectoryView import registerFileExtension from DirectoryView import registerMetaType from DirectoryView import expandpath from FSObject import FSObject from CMFCorePermissions import View from CMFCorePermissions import ViewManagementScreens from CMFCorePermissions import FTPAccess from utils import _dtmldir from utils import format_stx class FSSTXMethod( FSObject ): """ A chunk of StructuredText, rendered as a skin method of a CMFSite. """ meta_type = 'Filesystem STX Method' manage_options=( { 'label' : 'Customize' , 'action' : 'manage_main' } , { 'label' : 'View' , 'action' : '' , 'help' : ('OFSP' ,'DTML-DocumentOrMethod_View.stx' ) } ) security = ClassSecurityInfo() security.declareObjectProtected( View ) security.declareProtected( ViewManagementScreens, 'manage_main') manage_main = Globals.DTMLFile( 'custstx', _dtmldir ) # # FSObject interface # def _createZODBClone(self): """ Create a ZODB (editable) equivalent of this object. """ # XXX: do this soon raise NotImplemented, "See next week's model." def _readFile( self, reparse ): fp = expandpath( self._filepath ) file = open( fp, 'r' ) # not binary, we want CRLF munging here. try: data = file.read() finally: file.close() self.raw = data if reparse: self.cook() # # "Wesleyan" interface (we need to be "methodish"). # class func_code: pass func_code=func_code() func_code.co_varnames= () func_code.co_argcount=0 func_code.__roles__=() func_defaults__roles__=() func_defaults=() index_html = None # No accidental acquisition default_content_type = 'text/html' def cook( self ): if not hasattr( self, '_v_cooked' ): self._v_cooked = format_stx( text=self.raw ) return self._v_cooked _default_template = Globals.HTML( """\
""" ) def __call__( self, REQUEST={}, RESPONSE=None, **kw ): """ Return our rendered StructuredText. """ self._updateFromFS() if RESPONSE is not None: RESPONSE.setHeader( 'Content-Type', 'text/html' ) return apply( self._render, ( REQUEST, RESPONSE ), kw ) security.declarePrivate( '_render' ) def _render( self, REQUEST={}, RESPONSE=None, **kw ): """ Find the appropriate rendering template and use it to render us. """ template = getattr( self, 'stxmethod_view', self._default_template ) if getattr( template, 'isDocTemp', 0 ): posargs = ( self, REQUEST ) else: posargs = () return apply( template, posargs, { 'cooked' : self.cook() } ) security.declareProtected( FTPAccess, 'manage_FTPget' ) def manage_FTPget( self ): """ Fetch our source for delivery via FTP. """ return self.raw security.declareProtected( ViewManagementScreens , 'PrincipiaSearchSource' ) def PrincipiaSearchSource( self ): """ Fetch our source for indexing in a catalog. """ return self.raw security.declareProtected( ViewManagementScreens , 'document_src' ) def document_src( self ): """ Fetch our source for indexing in a catalog. """ return self.raw Globals.InitializeClass( FSSTXMethod ) registerFileExtension( 'stx', FSSTXMethod ) registerMetaType( 'STX Method', FSSTXMethod ) CMF-1.3/CMFCore/FSZSQLMethod.py0100644000076500007650000001120007522303413015645 0ustar tseavertseaver# Copyright (c) 2001 New Information Paradigms Ltd # # This Software is released under the MIT License: # http://www.opensource.org/licenses/mit-license.html # See license.txt for more details. # # $Id: FSZSQLMethod.py,v 1.2.36.1 2025/08/01 19:07:55 tseaver Exp $ """ (not yet)Customizable ZSQL methods that come from the filesystem. $Id: FSZSQLMethod.py,v 1.2.36.1 2025/08/01 19:07:55 tseaver Exp $ """ import Globals from AccessControl import ClassSecurityInfo from zLOG import LOG,ERROR from Products.CMFCore.CMFCorePermissions import View, ViewManagementScreens from Products.CMFCore.DirectoryView import registerFileExtension, registerMetaType, expandpath from Products.CMFCore.FSObject import FSObject from Products.ZSQLMethods.SQL import SQL from utils import _dtmldir import Acquisition class FSZSQLMethod(SQL, FSObject): """FSZSQLMethods act like Z SQL Methods but are not directly modifiable from the management interface.""" meta_type = 'Filesystem Z SQL Method' manage_options=( ( {'label':'Customize', 'action':'manage_customise'}, {'label':'Test', 'action':'manage_testForm', 'help':('ZSQLMethods','Z-SQL-Method_Test.stx')}, ) ) # Use declarative security security = ClassSecurityInfo() security.declareObjectProtected(View) # Make mutators private security.declarePrivate('manage_main','manage_edit','manage_advanced','manage_advancedForm') manage=None security.declareProtected(ViewManagementScreens, 'manage_customise') manage_customise = Globals.DTMLFile('custzsql', _dtmldir) def __init__(self, id, filepath, fullname=None, properties=None): FSObject.__init__(self, id, filepath, fullname, properties) def _createZODBClone(self): """Create a ZODB (editable) equivalent of this object.""" # I guess it's bad to 'reach inside' ourselves like this, # but Z SQL Methods don't have accessor methdods ;-) s = SQL(self.id, self.title, self.connection_id, self.arguments_src, self.src) s.manage_advanced(self.max_rows_, self.max_cache_, self.cache_time_, '', '') return s def _readFile(self, reparse): fp = expandpath(self._filepath) file = open(fp, 'rb') try: data = file.read() finally: file.close() # parse parameters parameters={} start = data.find('') end = data.find('') if start==-1 or end==-1 or start>end: raise ValueError,'Could not find parameter block' block = data[start+14:end] for line in block.split('\n'): pair = line.split(':',1) if len(pair)!=2: continue parameters[pair[0].strip().lower()]=pair[1].strip() # check for required an optional parameters try: title = parameters.get('title','') connection_id = parameters.get('connection id',parameters['connection_id']) arguments = parameters.get('arguments','') max_rows = parameters.get('max_rows',1000) max_cache = parameters.get('max_cache',100) cache_time = parameters.get('cache_time',0) except KeyError,e: raise ValueError,"The '%s' parameter is required but was not supplied" % e self.manage_edit(title, connection_id, arguments, template=data) self.manage_advanced(max_rows, max_cache, cache_time, '', # don't really see any point in allowing '') # brain specification... # do we need to do anything on reparse? if Globals.DevelopmentMode: # Provide an opportunity to update the properties. def __of__(self, parent): try: self = Acquisition.ImplicitAcquisitionWrapper(self, parent) self._updateFromFS() return self except: from zLOG import LOG, ERROR import sys LOG('FS Z SQL Method', ERROR, 'Error during __of__', error=sys.exc_info()) raise Globals.InitializeClass(FSZSQLMethod) registerFileExtension('zsql', FSZSQLMethod) registerMetaType('Z SQL Method', FSZSQLMethod) CMF-1.3/CMFCore/MemberDataTool.py0100644000076500007650000003116007522303413016330 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Basic member data tool. $Id: MemberDataTool.py,v 1.15.8.2 2025/08/01 19:07:55 tseaver Exp $ """ import string from utils import UniqueObject, getToolByName, _dtmldir from OFS.SimpleItem import SimpleItem from OFS.PropertyManager import PropertyManager from Globals import Acquisition, Persistent, DTMLFile import Globals from AccessControl.Role import RoleManager from BTrees.OOBTree import OOBTree from ZPublisher.Converters import type_converters from Acquisition import aq_inner, aq_parent, aq_base from AccessControl import ClassSecurityInfo from CMFCorePermissions import ViewManagementScreens import CMFCorePermissions from ActionProviderBase import ActionProviderBase _marker = [] # Create a new marker object. class MemberDataTool (UniqueObject, SimpleItem, PropertyManager, ActionProviderBase): '''This tool wraps user objects, making them act as Member objects. ''' id = 'portal_memberdata' meta_type = 'CMF Member Data Tool' _actions = [] _v_temps = None _properties = () security = ClassSecurityInfo() manage_options=( ActionProviderBase.manage_options + ({ 'label' : 'Overview' , 'action' : 'manage_overview' } , { 'label' : 'Contents' , 'action' : 'manage_showContents' } ) + PropertyManager.manage_options + SimpleItem.manage_options ) # # ZMI methods # security.declareProtected( CMFCorePermissions.ManagePortal , 'manage_overview' ) manage_overview = DTMLFile( 'explainMemberDataTool', _dtmldir ) security.declareProtected( CMFCorePermissions.ViewManagementScreens , 'manage_showContents') manage_showContents = DTMLFile('memberdataContents', _dtmldir ) security.declareProtected( CMFCorePermissions.ViewManagementScreens , 'getContentsInformation',) def __init__(self): self._members = OOBTree() # Create the default properties. self._setProperty('email', '', 'string') self._setProperty('portal_skin', '', 'string') self._setProperty('listed', '', 'boolean') self._setProperty('login_time', '2025/01/01', 'date') self._setProperty('last_login_time', '2025/01/01', 'date') # # 'portal_memberdata' interface methods # security.declarePrivate('listActions') def listActions(self, info=None): """ Return actions provided via tool. """ return self._actions security.declarePrivate('getMemberDataContents') def getMemberDataContents(self): ''' Return the number of members stored in the _members BTree and some other useful info ''' membertool = getToolByName(self, 'portal_membership') members = self._members user_list = membertool.listMemberIds() member_list = members.keys() member_count = len(members) orphan_count = 0 for member in member_list: if member not in user_list: orphan_count = orphan_count + 1 return [{ 'member_count' : member_count, 'orphan_count' : orphan_count }] security.declarePrivate( 'searchMemberDataContents' ) def searchMemberDataContents( self, search_param, search_term ): """ Search members """ res = [] if search_param == 'username': search_param = 'id' for user_wrapper in self._members.values(): searched = getattr( user_wrapper, search_param, None ) if searched is not None and string.find( searched, search_term ) != -1: res.append( { 'username' : getattr( user_wrapper, 'id' ) , 'email' : getattr( user_wrapper, 'email', '' ) } ) return res security.declarePrivate('pruneMemberDataContents') def pruneMemberDataContents(self): ''' Compare the user IDs stored in the member data tool with the list in the actual underlying acl_users and delete anything not in acl_users ''' membertool= getToolByName(self, 'portal_membership') members = self._members user_list = membertool.listMemberIds() for tuple in members.items(): member_name = tuple[0] member_obj = tuple[1] if member_name not in user_list: del members[member_name] security.declarePrivate('wrapUser') def wrapUser(self, u): ''' If possible, returns the Member object that corresponds to the given User object. ''' id = u.getUserName() members = self._members if not members.has_key(id): # Get a temporary member that might be # registered later via registerMemberData(). temps = self._v_temps if temps is not None and temps.has_key(id): m = temps[id] else: base = aq_base(self) m = MemberData(base, id) if temps is None: self._v_temps = {id:m} else: temps[id] = m else: m = members[id] # Return a wrapper with self as containment and # the user as context. return m.__of__(self).__of__(u) security.declarePrivate('registerMemberData') def registerMemberData(self, m, id): ''' Adds the given member data to the _members dict. This is done as late as possible to avoid side effect transactions and to reduce the necessary number of entries. ''' self._members[id] = m Globals.InitializeClass(MemberDataTool) class MemberData (SimpleItem): security = ClassSecurityInfo() def __init__(self, tool, id): self.id = id # Make a temporary reference to the tool. # The reference will be removed by notifyModified(). self._tool = tool security.declarePrivate('notifyModified') def notifyModified(self): # Links self to parent for full persistence. tool = getattr(self, '_tool', None) if tool is not None: del self._tool tool.registerMemberData(self, self.getId()) security.declarePublic('getUser') def getUser(self): # The user object is our context, but it's possible for # restricted code to strip context while retaining # containment. Therefore we need a simple security check. parent = aq_parent(self) bcontext = aq_base(parent) bcontainer = aq_base(aq_parent(aq_inner(self))) if bcontext is bcontainer or not hasattr(bcontext, 'getUserName'): raise 'MemberDataError', "Can't find user data" # Return the user object, which is our context. return parent def getTool(self): return aq_parent(aq_inner(self)) security.declarePublic('getMemberId') def getMemberId(self): return self.getUser().getUserName() security.declareProtected(CMFCorePermissions.SetOwnProperties, 'setProperties') def setProperties(self, properties=None, **kw): '''Allows the authenticated member to set his/her own properties. Accepts either keyword arguments or a mapping for the "properties" argument. ''' if properties is None: properties = kw membership = getToolByName(self, 'portal_membership') registration = getToolByName(self, 'portal_registration', None) if not membership.isAnonymousUser(): member = membership.getAuthenticatedMember() if registration: failMessage = registration.testPropertiesValidity(properties, member) if failMessage is not None: raise 'Bad Request', failMessage member.setMemberProperties(properties) else: raise 'Bad Request', 'Not logged in.' security.declarePrivate('setMemberProperties') def setMemberProperties(self, mapping): '''Sets the properties of the member. ''' # Sets the properties given in the MemberDataTool. tool = self.getTool() for id in tool.propertyIds(): if mapping.has_key(id): if not self.__class__.__dict__.has_key(id): value = mapping[id] if type(value)==type(''): proptype = tool.getPropertyType(id) or 'string' if type_converters.has_key(proptype): value = type_converters[proptype](value) setattr(self, id, value) # Hopefully we can later make notifyModified() implicit. self.notifyModified() # XXX: s.b., getPropertyForMember(member, id, default)? security.declarePublic('getProperty') def getProperty(self, id, default=_marker): tool = self.getTool() base = aq_base( self ) # First, check the wrapper (w/o acquisition). value = getattr( base, id, _marker ) if value is not _marker: return value # Then, check the tool and the user object for a value. tool_value = tool.getProperty( id, _marker ) user_value = getattr( self.getUser(), id, _marker ) # If the tool doesn't have the property, use user_value or default if tool_value is _marker: if user_value is not _marker: return user_value elif default is not _marker: return default else: raise ValueError, 'The property %s does not exist' % id # If the tool has an empty property and we have a user_value, use it if not tool_value and user_value is not _marker: return user_value # Otherwise return the tool value return tool_value security.declarePrivate('getPassword') def getPassword(self): """Return the password of the user.""" return self.getUser()._getPassword() security.declarePrivate('setSecurityProfile') def setSecurityProfile(self, password=None, roles=None, domains=None): """Set the user's basic security profile""" u = self.getUser() # This is really hackish. The Zope User API needs methods # for performing these functions. if password is not None: u.__ = password if roles is not None: u.roles = roles if domains is not None: u.domains = domains def __str__(self): return self.getMemberId() ### User object interface ### security.declarePublic('getUserName') def getUserName(self): """Return the username of a user""" return self.getUser().getUserName() security.declarePublic('getId') def getId(self): """Get the ID of the user. The ID can be used, at least from Python, to get the user from the user's UserDatabase""" return self.getUser().getId() security.declarePublic('getRoles') def getRoles(self): """Return the list of roles assigned to a user.""" return self.getUser().getRoles() security.declarePublic('getRolesInContext') def getRolesInContext(self, object): """Return the list of roles assigned to the user, including local roles assigned in context of the passed in object.""" return self.getUser().getRolesInContext(object) security.declarePublic('getDomains') def getDomains(self): """Return the list of domain restrictions for a user""" return self.getUser().getDomains() security.declarePublic('has_role') def has_role(self, roles, object=None): """Check to see if a user has a given role or roles.""" return self.getUser().has_role(roles, object) # There are other parts of the interface but they are # deprecated for use with CMF applications. Globals.InitializeClass(MemberData) CMF-1.3/CMFCore/MembershipTool.py0100644000076500007650000004051707522303413016430 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Basic membership tool. $Id: MembershipTool.py,v 1.23.4.4 2025/08/01 19:07:55 tseaver Exp $ """ from string import find from utils import UniqueObject, _getAuthenticatedUser, _checkPermission from utils import getToolByName, _dtmldir from OFS.SimpleItem import SimpleItem from Globals import InitializeClass, DTMLFile, MessageDialog, \ PersistentMapping from Acquisition import aq_base from AccessControl.User import nobody from AccessControl import ClassSecurityInfo from CMFCorePermissions import ManagePortal import CMFCorePermissions from ActionProviderBase import ActionProviderBase default_member_content = '''Default page for %s This is the default document created for you when you joined this community. To change the content just select "Edit" in the Tool Box on the left. ''' class MembershipTool (UniqueObject, SimpleItem, ActionProviderBase): # This tool accesses member data through an acl_users object. # It can be replaced with something that accesses member data in # a different way. id = 'portal_membership' meta_type = 'CMF Membership Tool' _actions = [] security = ClassSecurityInfo() memberareaCreationFlag = 1 manage_options=( ({ 'label' : 'Configuration' , 'action' : 'manage_mapRoles' },) + ActionProviderBase.manage_options + ( { 'label' : 'Overview' , 'action' : 'manage_overview' }, ) + SimpleItem.manage_options) # # ZMI methods # security.declareProtected( CMFCorePermissions.ManagePortal , 'manage_overview' ) manage_overview = DTMLFile( 'explainMembershipTool', _dtmldir ) # # 'portal_membership' interface methods # security.declareProtected(ManagePortal, 'manage_mapRoles') manage_mapRoles = DTMLFile('membershipRolemapping', _dtmldir ) security.declareProtected(CMFCorePermissions.SetOwnPassword, 'setPassword') def setPassword(self, password, domains=None): '''Allows the authenticated member to set his/her own password. ''' registration = getToolByName(self, 'portal_registration', None) if not self.isAnonymousUser(): member = self.getAuthenticatedMember() if registration: failMessage = registration.testPasswordValidity(password) if failMessage is not None: raise 'Bad Request', failMessage member.setSecurityProfile(password=password, domains=domains) else: raise 'Bad Request', 'Not logged in.' security.declarePublic('getAuthenticatedMember') def getAuthenticatedMember(self): ''' Returns the currently authenticated member object or the Anonymous User. Never returns None. ''' u = _getAuthenticatedUser(self) if u is None: u = nobody return self.wrapUser(u) security.declarePrivate('wrapUser') def wrapUser(self, u, wrap_anon=0): ''' Sets up the correct acquisition wrappers for a user object and provides an opportunity for a portal_memberdata tool to retrieve and store member data independently of the user object. ''' b = getattr(u, 'aq_base', None) if b is None: # u isn't wrapped at all. Wrap it in self.acl_users. b = u u = u.__of__(self.acl_users) if (b is nobody and not wrap_anon) or hasattr(b, 'getMemberId'): # This user is either not recognized by acl_users or it is # already registered with something that implements the # member data tool at least partially. return u parent = self.aq_inner.aq_parent base = getattr(parent, 'aq_base', None) if hasattr(base, 'portal_memberdata'): # Apply any role mapping if we have it if hasattr(self, 'role_map'): for portal_role in self.role_map.keys(): if (self.role_map.get(portal_role) in u.roles and portal_role not in u.roles): u.roles.append(portal_role) # Get portal_memberdata to do the wrapping. md = getToolByName(parent, 'portal_memberdata') try: portal_user = md.wrapUser(u) # Check for the member area creation flag and # take appropriate (non-) action if getattr(self, 'memberareaCreationFlag', 0) != 0: if self.getHomeUrl(portal_user.getId()) is None: self.createMemberarea(portal_user.getId()) return portal_user except: from zLOG import LOG, ERROR import sys type,value,tb = sys.exc_info() try: LOG('CMFCore.MembershipTool', ERROR, 'Error during wrapUser:', "\nType:%s\nValue:%s\n" % (type,value)) finally: tb = None # Avoid leaking frame pass # Failed. return u security.declareProtected(ManagePortal, 'getPortalRoles') def getPortalRoles(self): """ Return all local roles defined by the portal itself, which means roles that are useful and understood by the portal object """ parent = self.aq_inner.aq_parent roles = list(parent.__ac_roles__) # This is *not* a local role in the portal but used by it roles.append('Manager') roles.append('Owner') return roles security.declareProtected(ManagePortal, 'setRoleMapping') def setRoleMapping(self, portal_role, userfolder_role): """ set the mapping of roles between roles understood by the portal and roles coming from outside user sources """ if not hasattr(self, 'role_map'): self.role_map = PersistentMapping() if len(userfolder_role) < 1: del self.role_map[portal_role] else: self.role_map[portal_role] = userfolder_role return MessageDialog( title ='Mapping updated', message='The Role mappings have been updated', action ='manage_mapRoles') security.declareProtected(ManagePortal, 'getMappedRole') def getMappedRole(self, portal_role): """ returns a role name if the portal role is mapped to something else or an empty string if it is not """ if hasattr(self, 'role_map'): return self.role_map.get(portal_role, '') else: return '' security.declareProtected(ManagePortal, 'getMemberareaCreationFlag') def getMemberareaCreationFlag(self): """ Returns the flag indicating whether the membership tool will create a member area if an authenticated user from an underlying user folder logs in first without going through the join process """ return self.memberareaCreationFlag security.declareProtected(ManagePortal, 'setMemberareaCreationFlag') def setMemberareaCreationFlag(self): """ sets the flag indicating whether the membership tool will create a member area if an authenticated user from an underlying user folder logs in first without going through the join process """ if not hasattr(self, 'memberareaCreationFlag'): self.memberareaCreationFlag = 0 if self.memberareaCreationFlag == 0: self.memberareaCreationFlag = 1 else: self.memberareaCreationFlag = 0 return MessageDialog( title ='Member area creation flag changed', message='Member area creation flag has been updated', action ='manage_mapRoles') security.declareProtected(ManagePortal, 'createMemberarea') def createMemberarea(self, member_id): """ create a member area """ parent = self.aq_inner.aq_parent members = getattr(parent, 'Members', None) user = self.acl_users.getUser( member_id ).__of__( self.acl_users ) if members is not None and user is not None: f_title = "%s's Home" % member_id members.manage_addPortalFolder( id=member_id, title=f_title ) f=getattr(members, member_id) f.manage_permission(CMFCorePermissions.View, ['Owner','Manager','Reviewer'], 0) f.manage_permission(CMFCorePermissions.AccessContentsInformation, ['Owner','Manager','Reviewer'], 0) # Grant ownership to Member try: f.changeOwnership(user) except AttributeError: pass # Zope 2.1.x compatibility f.manage_setLocalRoles(member_id, ['Owner']) security.declarePublic('isAnonymousUser') def isAnonymousUser(self): ''' Returns 1 if the user is not logged in. ''' u = _getAuthenticatedUser(self) if u is None or u.getUserName() == 'Anonymous User': return 1 return 0 security.declarePublic('checkPermission') def checkPermission(self, permissionName, object, subobjectName=None): ''' Checks whether the current user has the given permission on the given object or subobject. ''' if subobjectName is not None: object = getattr(object, subobjectName) return _checkPermission(permissionName, object) security.declarePublic('credentialsChanged') def credentialsChanged(self, password): ''' Notifies the authentication mechanism that this user has changed passwords. This can be used to update the authentication cookie. Note that this call should *not* cause any change at all to user databases. ''' if not self.isAnonymousUser(): acl_users = self.acl_users user = _getAuthenticatedUser(self) id = user.getUserName() if hasattr(acl_users.aq_base, 'credentialsChanged'): # Use an interface provided by LoginManager. acl_users.credentialsChanged(user, id, password) else: req = self.REQUEST p = getattr(req, '_credentials_changed_path', None) if p is not None: # Use an interface provided by CookieCrumbler. change = self.restrictedTraverse(p) change(user, id, password) security.declareProtected(ManagePortal, 'getMemberById') def getMemberById(self, id): ''' Returns the given member. ''' u = self.acl_users.getUser(id) if u is not None: u = self.wrapUser(u) return u def __getPUS(self): # Gets something we can call getUsers() and getUserNames() on. acl_users = self.acl_users if hasattr(acl_users, 'getUsers'): return acl_users else: # This hack works around the absence of getUsers() in LoginManager. # Gets the PersistentUserSource object that stores our users for us in acl_users.UserSourcesGroup.objectValues(): if us.meta_type == 'Persistent User Source': return us.__of__(acl_users) security.declareProtected(ManagePortal, 'listMemberIds') def listMemberIds(self): '''Lists the ids of all members. This may eventually be replaced with a set of methods for querying pieces of the list rather than the entire list at once. ''' return self.__getPUS().getUserNames() security.declareProtected(ManagePortal, 'listMembers') def listMembers(self): '''Gets the list of all members. ''' return map(self.wrapUser, self.__getPUS().getUsers()) security.declareProtected(CMFCorePermissions.View, 'searchMembers') def searchMembers( self, search_param, search_term ): """ Search the membership """ md = getToolByName( self, 'portal_memberdata' ) return md.searchMemberDataContents( search_param, search_term ) security.declareProtected(CMFCorePermissions.View, 'getCandidateLocalRoles') def getCandidateLocalRoles( self, obj ): """ What local roles can I assign? """ member = self.getAuthenticatedMember() if 'Manager' in member.getRoles(): return self.getPortalRoles() else: member_roles = list( member.getRolesInContext( obj ) ) del member_roles[member_roles.index( 'Member')] return tuple( member_roles ) security.declareProtected(CMFCorePermissions.View, 'setLocalRoles') def setLocalRoles( self, obj, member_ids, member_role, reindex=1 ): """ Set local roles on an item """ member = self.getAuthenticatedMember() my_roles = member.getRolesInContext( obj ) if 'Manager' in my_roles or member_role in my_roles: for member_id in member_ids: roles = list(obj.get_local_roles_for_userid( userid=member_id )) if member_role not in roles: roles.append( member_role ) obj.manage_setLocalRoles( member_id, roles ) if reindex: # It is assumed that all objects have the method # reindexObjectSecurity, which is in CMFCatalogAware and # thus PortalContent and PortalFolder. obj.reindexObjectSecurity() security.declareProtected(CMFCorePermissions.View, 'deleteLocalRoles') def deleteLocalRoles( self, obj, member_ids, reindex=1 ): """ Delete local roles for members member_ids """ member = self.getAuthenticatedMember() my_roles = member.getRolesInContext( obj ) if 'Manager' in my_roles or 'Owner' in my_roles: obj.manage_delLocalRoles( userids=member_ids ) if reindex: obj.reindexObjectSecurity() security.declarePrivate('addMember') def addMember(self, id, password, roles, domains, properties=None): '''Adds a new member to the user folder. Security checks will have already been performed. Called by portal_registration. ''' acl_users = self.acl_users if hasattr(acl_users, '_addUser'): acl_users._addUser(id, password, password, roles, domains) else: # The acl_users folder is a LoginManager. Search for a UserSource # with the needed support. for source in acl_users.UserSourcesGroup.objectValues(): if hasattr(source, 'addUser'): source.__of__(self).addUser(id, password, roles, domains) raise "Can't add Member", "No supported UserSources" if properties is not None: membership = getToolByName(self, 'portal_membership') member = membership.getMemberById(id) member.setMemberProperties(properties) security.declarePrivate('listActions') def listActions(self, info=None): return None security.declarePublic('getHomeFolder') def getHomeFolder(self, id=None, verifyPermission=0): """Returns a member's home folder object or None. Set verifyPermission to 1 to return None when the user doesn't have the View permission on the folder. """ return None security.declarePublic('getHomeUrl') def getHomeUrl(self, id=None, verifyPermission=0): """Returns the URL to a member's home folder or None. Set verifyPermission to 1 to return None when the user doesn't have the View permission on the folder. """ return None InitializeClass(MembershipTool) CMF-1.3/CMFCore/PortalContent.py0100644000076500007650000000763007522303413016272 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ PortalContent: Base class for all CMF content. $Id: PortalContent.py,v 1.34.10.3 2025/08/01 19:07:55 tseaver Exp $ """ import string, urllib from DateTime import DateTime from Globals import InitializeClass from Acquisition import aq_base from OFS.SimpleItem import SimpleItem from AccessControl import ClassSecurityInfo from CMFCorePermissions import AccessContentsInformation, View, FTPAccess from interfaces.Contentish import Contentish from DynamicType import DynamicType from utils import _checkPermission, _getViewFor from CMFCatalogAware import CMFCatalogAware try: from webdav.Lockable import ResourceLockedError except ImportError: class ResourceLockedError( Exception ): pass try: from webdav.WriteLockInterface import WriteLockInterface NoWL = 0 except ImportError: NoWL = 1 class PortalContent(DynamicType, CMFCatalogAware, SimpleItem): """ Base class for portal objects. Provides hooks for reviewing, indexing, and CMF UI. Derived classes must implement the interface described in interfaces/DublinCore.py. """ if not NoWL: __implements__ = (WriteLockInterface, Contentish,) else: __implements__ = (Contentish) isPortalContent = 1 _isPortalContent = 1 # More reliable than 'isPortalContent'. manage_options = ( ( { 'label' : 'Dublin Core' , 'action' : 'manage_metadata' } , { 'label' : 'Edit' , 'action' : 'manage_edit' } , { 'label' : 'View' , 'action' : 'view' } ) + CMFCatalogAware.manage_options + SimpleItem.manage_options ) security = ClassSecurityInfo() security.declareObjectProtected(View) # The security for FTP methods aren't set up by default in our # superclasses... :( security.declareProtected(FTPAccess, 'manage_FTPstat', 'manage_FTPget', 'manage_FTPlist',) def failIfLocked(self): """ Check if isLocked via webDav """ if self.wl_isLocked(): raise ResourceLockedError, 'This resource is locked via webDAV' return 0 # indexed methods # --------------- security.declareProtected(View, 'SearchableText') def SearchableText(self): "Returns a concatination of all searchable text" # Should be overriden by portal objects return "%s %s" % (self.Title(), self.Description()) # Contentish interface methods # ---------------------------- def __call__(self): ''' Invokes the default view. ''' view = _getViewFor(self) if getattr(aq_base(view), 'isDocTemp', 0): return apply(view, (self, self.REQUEST)) else: return view() index_html = None # This special value informs ZPublisher to use __call__ security.declareProtected(View, 'view') def view(self): ''' Returns the default view even if index_html is overridden. ''' return self() InitializeClass(PortalContent) CMF-1.3/CMFCore/PortalFolder.py0100644000076500007650000005372107523123734016104 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ PortalFolder: CMF-enabled Folder objects. $Id: PortalFolder.py,v 1.37.4.5 2025/08/04 04:04:44 efge Exp $ """ import sys import Globals, re, base64, marshal, string import CMFCorePermissions from CMFCorePermissions import View, ManageProperties, ListFolderContents from CMFCorePermissions import AddPortalFolders, AddPortalContent from CMFCatalogAware import CMFCatalogAware from OFS.Folder import Folder from OFS.ObjectManager import REPLACEABLE from Globals import DTMLFile from AccessControl import getSecurityManager, ClassSecurityInfo, Unauthorized from Acquisition import aq_parent, aq_inner, aq_base from DynamicType import DynamicType from utils import getToolByName, _checkPermission factory_type_information = ( { 'id' : 'Folder' , 'meta_type' : 'Portal Folder' , 'description' : """\ Use folders to put content in categories.""" , 'icon' : 'folder_icon.gif' , 'product' : 'CMFCore' , 'factory' : 'manage_addPortalFolder' , 'filter_content_types' : 0 , 'immediate_view' : 'folder_edit_form' , 'actions' : ( { 'id' : 'view' , 'name' : 'View' , 'action' : '' , 'permissions' : (View,) , 'category' : 'folder' } , { 'id' : 'edit' , 'name' : 'Edit' , 'action' : 'folder_edit_form' , 'permissions' : (ManageProperties,) , 'category' : 'folder' } , { 'id' : 'localroles' , 'name' : 'Local Roles' , 'action' : 'folder_localrole_form' , 'permissions' : (ManageProperties,) , 'category' : 'folder' } ) } , ) class PortalFolder(DynamicType, CMFCatalogAware, Folder): """ Implements portal content management, but not UI details. """ meta_type = 'Portal Folder' portal_type = 'Folder' security = ClassSecurityInfo() description = '' manage_options = Folder.manage_options + \ CMFCatalogAware.manage_options def __init__( self, id, title='' ): self.id = id self.title = title security.declareProtected( CMFCorePermissions.ManageProperties , 'setTitle') def setTitle( self, title ): """ Edit the folder title. """ self.title = title security.declareProtected( CMFCorePermissions.ManageProperties , 'setDescription') def setDescription( self, description ): """ Edit the folder description. """ self.description = description security.declareProtected(CMFCorePermissions.ManageProperties, 'edit') def edit(self, title='', description=''): """ Edit the folder title (and possibly other attributes later) """ self.setTitle( title ) self.setDescription( description ) self.reindexObject() security.declarePublic('allowedContentTypes') def allowedContentTypes( self ): """ List type info objects for types which can be added in this folder. """ result = [] portal_types = getToolByName(self, 'portal_types') myType = portal_types.getTypeInfo(self) if myType is not None: for contentType in portal_types.listTypeInfo(self): if myType.allowType( contentType.getId() ): result.append( contentType ) else: result = portal_types.listTypeInfo() return filter( lambda typ, container=self: typ.isConstructionAllowed( container ) , result ) security.declareProtected(AddPortalFolders, 'manage_addPortalFolder') def manage_addPortalFolder(self, id, title='', REQUEST=None): """Add a new PortalFolder object with id *id*. """ ob=PortalFolder(id, title) self._setObject(id, ob) if REQUEST is not None: return self.folder_contents( # XXX: ick! self, REQUEST, portal_status_message="Folder added") def _morphSpec(self, spec): ''' spec is a sequence of meta_types, a string containing one meta type, or None. If spec is empty or None, returns all contentish meta_types. Otherwise ensures all of the given meta types are contentish. ''' new_spec = [] types_tool = getToolByName(self, 'portal_types') types = types_tool.listContentTypes( by_metatype=1 ) if spec is not None: if type(spec) == type(''): spec = [spec] for meta_type in spec: if not meta_type in types: raise ValueError, ('%s is not a content type' % meta_type ) new_spec.append(meta_type) return new_spec or types def _filteredItems( self, ids, filt ): """ Apply filter, a mapping, to child objects indicated by 'ids', returning a sequence of ( id, obj ) tuples. """ # Restrict allowed content types if filt is None: filt = {} else: # We'll modify it, work on a copy. filt = filt.copy() pt = filt.get('portal_type', []) if type(pt) is type(''): pt = [pt] types_tool = getToolByName(self, 'portal_types') allowed_types = types_tool.listContentTypes() if not pt: pt = allowed_types else: pt = [t for t in pt if t in allowed_types] if not pt: # After filtering, no types remain, so nothing should be # returned. return [] filt['portal_type'] = pt query = apply( ContentFilter, (), filt ) result = [] append = result.append get = self._getOb for id in ids: obj = get( id ) include = 0 if query(obj): append( (id, obj) ) return result security.declarePublic('contentIds') def contentIds( self, spec=None, filter=None): """ Provide a filtered view onto 'objectIds', allowing only PortalFolders and PortalContent-derivatives to show through. If 'kw' passed, use them to filter the results further, qua the standard Zope filter interface. """ spec = self._morphSpec( spec ) ids = self.objectIds( spec ) return map( lambda item: item[0], self._filteredItems( ids, filter ) ) security.declarePublic('contentValues') def contentValues( self, spec=None, filter=None ): """ Provide a filtered view onto 'objectValues', allowing only PortalFolders and PortalContent-derivatives to show through. """ spec = self._morphSpec( spec ) ids = self.objectIds( spec ) return map( lambda item: item[1], self._filteredItems( ids, filter ) ) security.declareProtected( ListFolderContents , 'listFolderContents' ) def listFolderContents( self, spec=None, contentFilter=None ): """ Hook around 'contentValues' to let 'folder_contents' be protected. Duplicating skip_unauthorized behavior of dtml-in. """ items = self.contentValues(spec=spec, filter=contentFilter) l = [] for obj in items: id = obj.getId() v = obj # validate() can either raise Unauthorized or return 0 to # mean unauthorized. try: if getSecurityManager().validate(self, self, id, v): l.append(obj) except Unauthorized: pass return l security.declarePublic('contentItems') def contentItems( self, spec=None, filter=None ): """ Provide a filtered view onto 'objectItems', allowing only PortalFolders and PortalContent-derivatives to show through. """ spec = self._morphSpec( spec ) ids = self.objectIds( spec ) return self._filteredItems( ids, filter ) security.declareProtected(View, 'Title') def Title( self ): """ Implement dublin core Title """ return self.title security.declareProtected(View, 'Description') def Description( self ): """ Implement dublin core Description """ return self.description security.declareProtected(View, 'Type') def Type( self ): """ Implement dublin core type """ if hasattr(aq_base(self), 'getTypeInfo'): ti = self.getTypeInfo() if ti is not None: return ti.Title() return self.meta_type security.declarePublic('encodeFolderFilter') def encodeFolderFilter(self, REQUEST): """ Parse cookie string for using variables in dtml. """ filter = {} for key, value in REQUEST.items(): if key[:10] == 'filter_by_': filter[key[10:]] = value encoded = string.strip(base64.encodestring( marshal.dumps( filter ))) encoded = string.join(string.split(encoded, '\n'), '') return encoded security.declarePublic('decodeFolderFilter') def decodeFolderFilter(self, encoded): """ Parse cookie string for using variables in dtml. """ filter = {} if encoded: filter.update(marshal.loads(base64.decodestring(encoded))) return filter def content_type( self ): """ WebDAV needs this to do the Right Thing (TM). """ return None # Ensure pure PortalFolders don't get cataloged. # XXX We may want to revisit this. def indexObject(self): pass def unindexObject(self): pass def reindexObject(self, idxs=[]): pass def PUT_factory( self, name, typ, body ): """ Dispatcher for PUT requests to non-existent IDs. Returns an object of the appropriate type (or None, if we don't know what to do). """ registry = getToolByName( self, 'content_type_registry' ) if registry is None: return None typeObjectName = registry.findTypeName( name, typ, body ) if typeObjectName is None: return None self.invokeFactory( typeObjectName, name ) # XXX: this is butt-ugly. obj = aq_base( self._getOb( name ) ) self._delObject( name ) return obj security.declareProtected(AddPortalContent, 'invokeFactory') def invokeFactory( self , type_name , id , RESPONSE=None , *args , **kw ): ''' Invokes the portal_types tool. ''' pt = getToolByName( self, 'portal_types' ) myType = pt.getTypeInfo(self) if myType is not None: if not myType.allowType( type_name ): raise ValueError, 'Disallowed subobject type: %s' % type_name apply( pt.constructContent , (type_name, self, id, RESPONSE) + args , kw ) security.declareProtected(AddPortalContent, 'checkIdAvailable') def checkIdAvailable(self, id): try: self._checkId(id) except: if sys.exc_info()[0] == 'Bad Request': return 0 raise # Some other exception. else: return 1 def MKCOL_handler(self,id,REQUEST=None,RESPONSE=None): """ Handle WebDAV MKCOL. """ self.manage_addFolder( id=id, title='' ) def _checkId(self, id, allow_dup=0): PortalFolder.inheritedAttribute('_checkId')(self, id, allow_dup) # This method prevents people other than the portal manager # from overriding skinned names. if not allow_dup: if not _checkPermission( 'Manage portal', self): ob = self while ob is not None and not getattr(ob, '_isPortalRoot', 0): ob = aq_parent(aq_inner(ob)) if ob is not None: # If the portal root has an object by this name, # don't allow an override. # FIXME: needed to allow index_html for join code if hasattr(ob, id) and id != 'index_html': raise 'Bad Request', ( 'The id "%s" is reserved.' % id) # Otherwise we're ok. def _verifyObjectPaste(self, object, validate_src=1): # This assists the version in OFS.CopySupport. # It enables the clipboard to function correctly # with objects created by a multi-factory. if (hasattr(object, '__factory_meta_type__') and hasattr(self, 'all_meta_types')): mt = object.__factory_meta_type__ method_name=None permission_name = None meta_types = self.all_meta_types if callable(meta_types): meta_types = meta_types() for d in meta_types: if d['name']==mt: method_name=d['action'] permission_name = d.get('permission', None) break if permission_name is not None: if _checkPermission(permission_name,self): if not validate_src: # We don't want to check the object on the clipboard return try: parent = aq_parent(aq_inner(object)) except: parent = None if getSecurityManager().validate(None, parent, None, object): # validation succeeded return raise 'Unauthorized', object.getId() else: raise 'Unauthorized', permission_name # # Old validation for objects that may not have registered # themselves in the proper fashion. # elif method_name is not None: meth=self.unrestrictedTraverse(method_name) if hasattr(meth, 'im_self'): parent = meth.im_self else: try: parent = aq_parent(aq_inner(meth)) except: parent = None if getSecurityManager().validate(None, parent, None, meth): # Ensure the user is allowed to access the object on the # clipboard. if not validate_src: return try: parent = aq_parent(aq_inner(object)) except: parent = None if getSecurityManager().validate(None, parent, None, object): return id = object.getId() raise 'Unauthorized', id else: raise 'Unauthorized', method_name PortalFolder.inheritedAttribute( '_verifyObjectPaste')(self, object, validate_src) security.setPermissionDefault(AddPortalContent, ('Owner','Manager')) security.setPermissionDefault(AddPortalFolders, ('Owner','Manager')) def manage_addFolder( self , id , title='' , REQUEST=None ): """ Add a new folder-like object with id *id*. IF present, use the parent object's 'mkdir' action; otherwise, just add a PortalFolder. to take control of the process by checking for a 'mkdir' action. """ try: action = self.getTypeInfo().getActionById( 'mkdir' ) except TypeError: self.invokeFactory( type_name='Folder', id=id ) else: # call it getattr( self, action )( id=id ) ob = self._getOb( id ) ob.setTitle( title ) try: ob.reindexObject() except AttributeError: pass if REQUEST is not None: return self.manage_main(self, REQUEST, update_menu=1) Globals.InitializeClass(PortalFolder) class ContentFilter: """ Represent a predicate against a content object's metadata. """ MARKER = [] filterSubject = [] def __init__( self , Title=MARKER , Creator=MARKER , Subject=MARKER , Description=MARKER , created=MARKER , created_usage='range:min' , modified=MARKER , modified_usage='range:min' , Type=MARKER , portal_type=MARKER , **Ignored ): self.predicates = [] self.description = [] if Title is not self.MARKER: self.predicates.append( lambda x, pat=re.compile( Title ): pat.search( x.Title() ) ) self.description.append( 'Title: %s' % Title ) if Creator is not self.MARKER: self.predicates.append( lambda x, pat=re.compile( Creator ): pat.search( x.Creator() ) ) self.description.append( 'Creator: %s' % Creator ) if Subject and Subject is not self.MARKER: self.filterSubject = Subject self.predicates.append( self.hasSubject ) self.description.append( 'Subject: %s' % string.join( Subject, ', ' ) ) if Description is not self.MARKER: self.predicates.append( lambda x, pat=re.compile( Description ): pat.search( x.Description() ) ) self.description.append( 'Description: %s' % Description ) if created is not self.MARKER: if created_usage == 'range:min': self.predicates.append( lambda x, cd=created: cd <= x.created() ) self.description.append( 'Created since: %s' % created ) if created_usage == 'range:max': self.predicates.append( lambda x, cd=created: cd >= x.created() ) self.description.append( 'Created before: %s' % created ) if modified is not self.MARKER: if modified_usage == 'range:min': self.predicates.append( lambda x, md=modified: md <= x.modified() ) self.description.append( 'Modified since: %s' % modified ) if modified_usage == 'range:max': self.predicates.append( lambda x, md=modified: md >= x.modified() ) self.description.append( 'Modified before: %s' % modified ) if Type: if type( Type ) == type( '' ): Type = [ Type ] self.predicates.append( lambda x, Type=Type: x.Type() in Type ) self.description.append( 'Type: %s' % string.join( Type, ', ' ) ) if portal_type and portal_type is not self.MARKER: if type(portal_type) is type(''): portal_type = [portal_type] self.predicates.append(lambda x, pt=portal_type: x.getPortalTypeName() in pt) self.description.append('Portal Type: %s' % string.join(portal_type, ', ')) def hasSubject( self, obj ): """ Converts Subject string into a List for content filter view. """ for sub in obj.Subject(): if sub in self.filterSubject: return 1 return 0 def __call__( self, content ): for predicate in self.predicates: try: if not predicate( content ): return 0 except (AttributeError, KeyError, IndexError, ValueError): # predicates are *not* allowed to throw exceptions return 0 return 1 def __str__( self ): """ Return a stringified description of the filter. """ return string.join( self.description, '; ' ) manage_addPortalFolder = PortalFolder.manage_addPortalFolder manage_addPortalFolderForm = DTMLFile( 'folderAdd', globals() ) CMF-1.3/CMFCore/PortalObject.py0100644000076500007650000000327707523334712016100 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ PortalObject: The portal root object class $Id: PortalObject.py,v 1.3.38.4 2025/08/04 23:33:30 efge Exp $ """ from Globals import InitializeClass from PortalFolder import PortalFolder from Skinnable import SkinnableObjectManager from CMFCorePermissions import * from utils import getToolByName PORTAL_SKINS_TOOL_ID = 'portal_skins' class PortalObjectBase(PortalFolder, SkinnableObjectManager): meta_type = 'Portal Site' _isPortalRoot = 1 # Ensure certain attributes come from the correct base class. __getattr__ = SkinnableObjectManager.__getattr__ __of__ = SkinnableObjectManager.__of__ _checkId = SkinnableObjectManager._checkId # Ensure all necessary permissions exist. __ac_permissions__ = ( (AddPortalMember, ()), (SetOwnPassword, ()), (SetOwnProperties, ()), (MailForgottenPassword, ()), (RequestReview, ()), (ReviewPortalContent, ()), (AccessFuturePortalContent, ()), ) def getSkinsFolderName(self): return PORTAL_SKINS_TOOL_ID InitializeClass(PortalObjectBase) CMF-1.3/CMFCore/README.txt0100644000076500007650000000027307245471213014624 0ustar tseavertseaverCMFCore This product declares the key framework services for the Zope Content Management Framework (CMF). Please see the CMF "dogbowl site":http://cmf.zope.org, for more details. CMF-1.3/CMFCore/RegistrationTool.py0100644000076500007650000001403207516644314017013 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Basic user registration tool. $Id: RegistrationTool.py,v 1.13.4.1 2025/07/21 23:50:36 tseaver Exp $ """ from Globals import InitializeClass from Globals import DTMLFile from OFS.SimpleItem import SimpleItem from AccessControl import ClassSecurityInfo from ActionProviderBase import ActionProviderBase from CMFCorePermissions import AddPortalMember from CMFCorePermissions import MailForgottenPassword from CMFCorePermissions import SetOwnPassword from CMFCorePermissions import SetOwnProperties from CMFCorePermissions import ManagePortal from utils import UniqueObject from utils import _checkPermission from utils import _getAuthenticatedUser from utils import _limitGrantedRoles from utils import getToolByName from utils import _dtmldir class RegistrationTool(UniqueObject, SimpleItem, ActionProviderBase): """ Create and modify users by making calls to portal_membership. """ id = 'portal_registration' meta_type = 'CMF Registration Tool' security = ClassSecurityInfo() manage_options = (ActionProviderBase.manage_options + ({ 'label' : 'Overview', 'action' : 'manage_overview' } , ) + SimpleItem.manage_options) # # ZMI methods # security.declareProtected( ManagePortal , 'manage_overview' ) manage_overview = DTMLFile( 'explainRegistrationTool', _dtmldir ) # # 'portal_registration' interface methods # security.declarePrivate('listActions') def listActions(self, info): return None security.declarePublic('isRegistrationAllowed') def isRegistrationAllowed(self, REQUEST): '''Returns a boolean value indicating whether the user is allowed to add a member to the portal. ''' return _checkPermission('Add Portal Member', self.aq_inner.aq_parent) security.declarePublic('testPasswordValidity') def testPasswordValidity(self, password, confirm=None): '''If the password is valid, returns None. If not, returns a string explaining why. ''' return None security.declarePublic('testPropertiesValidity') def testPropertiesValidity(self, new_properties, member=None): '''If the properties are valid, returns None. If not, returns a string explaining why. ''' return None security.declarePublic('generatePassword') def generatePassword(self): '''Generates a password which is guaranteed to comply with the password policy. ''' import string, random chars = string.lowercase[:26] + string.uppercase[:26] + string.digits result = [] for n in range(6): result.append(random.choice(chars)) return string.join(result, '') security.declareProtected(AddPortalMember, 'addMember') def addMember(self, id, password, roles=('Member',), domains='', properties=None): '''Creates a PortalMember and returns it. The properties argument can be a mapping with additional member properties. Raises an exception if the given id already exists, the password does not comply with the policy in effect, or the authenticated user is not allowed to grant one of the roles listed (where Member is a special role that can always be granted); these conditions should be detected before the fact so that a cleaner message can be printed. ''' if not self.isMemberIdAllowed(id): raise ValueError('The login name you selected is already ' 'in use or is not valid. Please choose another.') failMessage = self.testPasswordValidity(password) if failMessage is not None: raise ValueError(failMessage) if properties is not None: failMessage = self.testPropertiesValidity(properties) if failMessage is not None: raise ValueError(failMessage) # Limit the granted roles. # Anyone is always allowed to grant the 'Member' role. _limitGrantedRoles(roles, self, ('Member',)) membership = getToolByName(self, 'portal_membership') membership.addMember(id, password, roles, domains, properties) member = membership.getMemberById(id) self.afterAdd(member, id, password, properties) return member import re __ALLOWED_MEMBER_ID_PATTERN = re.compile( "^[A-Za-z][A-Za-z0-9_]*$" ) security.declareProtected(AddPortalMember, 'isMemberIdAllowed') def isMemberIdAllowed(self, id): '''Returns 1 if the ID is not in use and is not reserved. ''' if len(id) < 1 or id == 'Anonymous User': return 0 if not self.__ALLOWED_MEMBER_ID_PATTERN.match( id ): return 0 membership = getToolByName(self, 'portal_membership') if membership.getMemberById(id) is not None: return 0 return 1 security.declarePublic('afterAdd') def afterAdd(self, member, id, password, properties): '''Called by portal_registration.addMember() after a member has been added successfully.''' pass security.declareProtected(MailForgottenPassword, 'mailPassword') def mailPassword(self, forgotten_userid, REQUEST): '''Email a forgotten password to a member. Raises an exception if user ID is not found. ''' raise 'NotImplemented' InitializeClass(RegistrationTool) CMF-1.3/CMFCore/Skinnable.py0100644000076500007650000001227407522303413015404 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Base class for object managers which can be "skinned". Skinnable object managers inherit attributes from a skin specified in the browser request. Skins are stored in a fixed-name subobject. $Id: Skinnable.py,v 1.4.4.2 2025/08/01 19:07:55 tseaver Exp $ """ import Globals from OFS.ObjectManager import ObjectManager from Acquisition import ImplicitAcquisitionWrapper, aq_base, aq_inner from ExtensionClass import Base from AccessControl import ClassSecurityInfo # superGetAttr is assigned to whatever ObjectManager.__getattr__ # used to do. try: superGetAttr = ObjectManager.__getattr__ except: try: superGetAttr = ObjectManager.inheritedAttribute('__getattr__') except: superGetAttr = None _marker = [] # Create a new marker object. class SkinnableObjectManager (ObjectManager): _v_skindata = None security = ClassSecurityInfo() security.declarePrivate('getSkinsFolderName') def getSkinsFolderName(self): # Not implemented. return None def __getattr__(self, name): ''' Looks for the name in an object with wrappers that only reach up to the root skins folder. This should be fast, flexible, and predictable. ''' if not name.startswith('_') and not name.startswith('aq_'): sd = self._v_skindata if sd is not None: request, ob, ignore = sd if not ignore.has_key(name): subob = getattr(ob, name, _marker) if subob is not _marker: # Return it in context of self, forgetting # its location and acting as if it were located # in self. return aq_base(subob) else: ignore[name] = 1 if superGetAttr is None: raise AttributeError, name return superGetAttr(self, name) security.declarePublic('setupCurrentSkin') def setupCurrentSkin(self, REQUEST=None): ''' Sets up _v_skindata so that __getattr__ can find it. Can also be called manually, allowing the user to change skins in the middle of a request. ''' if REQUEST is None: REQUEST = getattr(self, 'REQUEST', None) if REQUEST is None: # We are traversing without a REQUEST at the root. # Don't change the skin right now. (Otherwise # [un]restrictedTraverse messes up the skin data.) return if self._v_skindata is not None and self._v_skindata[0] is REQUEST: # Already set up for this request. return self._v_skindata = None sfn = self.getSkinsFolderName() if sfn is not None: # Note that our custom __getattr__ won't get confused # by skins at the moment because _v_skindata is None. sf = getattr(self, sfn, None) if sf is not None: try: sd = sf.getSkin(REQUEST) except: import sys from zLOG import LOG, ERROR LOG('CMFCore', ERROR, 'Unable to get skin', error=sys.exc_info()) else: if sd is not None: # Hide from acquisition. self._v_skindata = (REQUEST, sd, {}) def __of__(self, parent): ''' Sneakily sets up the portal skin then returns the wrapper that Acquisition.Implicit.__of__() would return. ''' w_self = ImplicitAcquisitionWrapper(aq_base(self), parent) w_self.setupCurrentSkin() return w_self def _checkId(self, id, allow_dup=0): ''' Override of ObjectManager._checkId(). Allows the user to create objects with IDs that match the ID of a skin object. ''' superCheckId = SkinnableObjectManager.inheritedAttribute('_checkId') if not allow_dup: # Temporarily disable _v_skindata. # Note that this depends heavily on Zope's current thread # behavior. sd = self._v_skindata self._v_skindata = None try: base = getattr(self, 'aq_base', self) if not hasattr(base, id): # Cause _checkId to not check for duplication. return superCheckId(self, id, allow_dup=1) finally: self._v_skindata = sd return superCheckId(self, id, allow_dup) Globals.InitializeClass(SkinnableObjectManager) CMF-1.3/CMFCore/SkinsContainer.py0100644000076500007650000000731207522303413016425 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Base class for objects that supply skins. $Id: SkinsContainer.py,v 1.4.36.1 2025/08/01 19:07:55 tseaver Exp $ """ from string import split, join, strip from Acquisition import aq_base from AccessControl import ClassSecurityInfo from Globals import InitializeClass class SkinPathError (Exception): 'Invalid skin path' pass class SkinsContainer: security = ClassSecurityInfo() security.declarePublic('getSkinPath') def getSkinPath(self, name): ''' Converts a skin name to a skin path. ''' raise 'Not implemented' security.declarePublic('getDefaultSkin') def getDefaultSkin(self): ''' Returns the default skin name. ''' raise 'Not implemented' security.declarePublic('getRequestVarname') def getRequestVarname(self): ''' Returns the variable name to look for in the REQUEST. ''' raise 'Not implemented' security.declarePrivate('getSkinByPath') def getSkinByPath(self, path, raise_exc=0): ''' Returns a skin at the given path. A skin path is of the format: "some/path, some/other/path, ..." The first part has precedence. ''' baseself = aq_base(self) skinob = baseself parts = list(split(path, ',')) parts.reverse() for part_path in parts: partob = baseself for name in split(strip(part_path), '/'): if name == '': continue if name[:1] == '_': # Not allowed. partob = None if raise_exc: raise SkinPathError('Underscores are not allowed') break # Allow acquisition tricks. partob = getattr(partob, name, None) if partob is None: # Not found. Cancel the search. if raise_exc: raise SkinPathError('Name not found: %s' % part_path) break if partob is not None: # Now partob has containment and context. # Build the final skinob by creating an object # that puts the former skinob in the context # of the new skinob. skinob = partob.__of__(skinob) return skinob security.declarePrivate('getSkinByName') def getSkinByName(self, name): path = self.getSkinPath(name) if path is None: return None return self.getSkinByPath(path) security.declarePrivate('getSkin') def getSkin(self, request): ''' Returns the requested skin. ''' varname = self.getRequestVarname() name = request.get(varname, None) skinob = None if name is not None: skinob = self.getSkinByName(name) if skinob is None: skinob = self.getSkinByName(self.getDefaultSkin()) if skinob is None: skinob = self.getSkinByPath('') return skinob InitializeClass( SkinsContainer ) CMF-1.3/CMFCore/SkinsTool.py0100644000076500007650000002366607523317542015443 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Portal skins tool. $Id: SkinsTool.py,v 1.15.4.2 2025/08/04 21:40:50 efge Exp $ """ from string import split from utils import UniqueObject, getToolByName, _dtmldir import Globals from Globals import DTMLFile, PersistentMapping from SkinsContainer import SkinsContainer from Acquisition import aq_base from DateTime import DateTime from AccessControl import ClassSecurityInfo from CMFCorePermissions import ManagePortal, AccessContentsInformation from ActionProviderBase import ActionProviderBase from ActionInformation import ActionInformation from Expression import Expression from OFS.Folder import Folder from OFS.Image import Image from OFS.DTMLMethod import DTMLMethod from OFS.ObjectManager import REPLACEABLE from Products.PythonScripts.PythonScript import PythonScript try: from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate SUPPORTS_PAGE_TEMPLATES=1 except ImportError: SUPPORTS_PAGE_TEMPLATES=0 import CMFCorePermissions def modifiedOptions(): # Remove the existing "Properties" option and add our own. rval = [] pos = -1 for o in Folder.manage_options: label = o.get('label', None) if label != 'Properties': rval.append(o) rval[1:1] = [{'label':'Properties', 'action':'manage_propertiesForm'}] return tuple(rval) class SkinsTool(UniqueObject, SkinsContainer, Folder, ActionProviderBase): ''' This tool is used to supply skins to a portal. ''' id = 'portal_skins' meta_type = 'CMF Skins Tool' _actions = [] cookie_persistence = 0 security = ClassSecurityInfo() manage_options = ( modifiedOptions() + ({ 'label' : 'Overview', 'action' : 'manage_overview' } , ) + ActionProviderBase.manage_options ) def __init__(self): self.selections = PersistentMapping() def _getSelections(self): sels = self.selections if sels is None: # Backward compatibility. self.selections = sels = PersistentMapping() return sels # # ZMI methods # security.declareProtected( CMFCorePermissions.ManagePortal , 'manage_overview' ) manage_overview = DTMLFile( 'explainSkinsTool', _dtmldir ) default_skin = '' request_varname = 'portal_skin' allow_any = 0 selections = None security.declarePrivate('listActions') def listActions(self, info=None): """ Return a list of actions information instances provided by the tool. """ return self._actions security.declareProtected(ManagePortal, 'manage_propertiesForm') manage_propertiesForm = DTMLFile('dtml/skinProps', globals()) security.declareProtected(ManagePortal, 'manage_properties') def manage_properties(self, default_skin='', request_varname='', allow_any=0, chosen=(), add_skin=0, del_skin=0, skinname='', skinpath='', cookie_persistence=0, REQUEST=None): ''' Changes portal_skin properties. ''' sels = self._getSelections() if add_skin: skinpath = str(skinpath) self.testSkinPath(skinpath) sels[str(skinname)] = skinpath elif del_skin: for name in chosen: del sels[name] else: self.default_skin = str(default_skin) self.request_varname = str(request_varname) self.allow_any = allow_any and 1 or 0 self.cookie_persistence = cookie_persistence and 1 or 0 if REQUEST is not None: for key in sels.keys(): fname = 'skinpath_%s' % key val = str(REQUEST[fname]) if sels[key] != val: self.testSkinPath(val) sels[key] = val if REQUEST is not None: return self.manage_propertiesForm( self, REQUEST, management_view='Properties', manage_tabs_message='Properties changed.') security.declarePrivate('PUT_factory') def PUT_factory( self, name, typ, body ): """ Dispatcher for PUT requests to non-existent IDs. Returns an object of the appropriate type (or None, if we don't know what to do). """ major, minor = split( typ, '/' ) if major == 'image': return Image( id=name , title='' , file='' , content_type=typ ) if major == 'text': if minor == 'x-python': return PythonScript( id=name ) if minor in ( 'html', 'xml' ) and SUPPORTS_PAGE_TEMPLATES: return ZopePageTemplate( name ) return DTMLMethod( __name__=name ) return None # Make the PUT_factory replaceable PUT_factory__replaceable__ = REPLACEABLE security.declarePrivate('testSkinPath') def testSkinPath(self, p): ''' Calls SkinsContainer.getSkinByName(). ''' self.getSkinByPath(p, raise_exc=1) # # 'portal_skins' interface methods # security.declareProtected(AccessContentsInformation, 'getSkinPath') def getSkinPath(self, name): ''' Converts a skin name to a skin path. Used by SkinsContainer. ''' sels = self._getSelections() p = sels.get(name, None) if p is None: if self.allow_any: return name return p # Can be None security.declareProtected(AccessContentsInformation, 'getDefaultSkin') def getDefaultSkin(self): ''' Returns the default skin name. Used by SkinsContainer. ''' return self.default_skin security.declareProtected(AccessContentsInformation, 'getRequestVarname') def getRequestVarname(self): ''' Returns the variable name to look for in the REQUEST. Used by SkinsContainer. ''' return self.request_varname security.declareProtected(AccessContentsInformation, 'getAllowAny') def getAllowAny(self): ''' Used by the management UI. Returns a flag indicating whether users are allowed to use arbitrary skin paths. ''' return self.allow_any security.declareProtected(AccessContentsInformation, 'getCookiePersistence') def getCookiePersistence(self): ''' Used by the management UI. Returns a flag indicating whether the skins cookie is persistent or not. ''' return self.cookie_persistence security.declareProtected(AccessContentsInformation, 'getSkinPaths') def getSkinPaths(self): ''' Used by the management UI. Returns the list of skin name to skin path mappings as a sorted list of tuples. ''' sels = self._getSelections() rval = [] for key, value in sels.items(): rval.append((key, value)) rval.sort() return rval security.declarePublic('getSkinSelections') def getSkinSelections(self): ''' Returns the sorted list of available skin names. ''' sels = self._getSelections() rval = list(sels.keys()) rval.sort() return rval security.declareProtected('View', 'updateSkinCookie') def updateSkinCookie(self): ''' If needed, updates the skin cookie based on the member preference. ''' pm = getToolByName(self, 'portal_membership') member = pm.getAuthenticatedMember() if hasattr(aq_base(member), 'portal_skin'): mskin = member.portal_skin if mskin: req = self.REQUEST cookie = req.cookies.get(self.request_varname, None) if cookie != mskin: resp = req.RESPONSE if not self.cookie_persistence: # *Don't* make the cookie persistent! resp.setCookie( self.request_varname, mskin, path='/' ) else: expires = ( DateTime( 'GMT' ) + 365 ).rfc822() resp.setCookie( self.request_varname , mskin , path='/' , expires=expires ) # Ensure updateSkinCookie() doesn't try again # within this request. req.cookies[self.request_varname] = mskin req[self.request_varname] = mskin return 1 return 0 security.declareProtected( 'View', 'clearSkinCookie' ) def clearSkinCookie(self): req = self.REQUEST resp = req.RESPONSE resp.expireCookie( self.request_varname, path='/' ) security.declareProtected(ManagePortal, 'addSkinSelection') def addSkinSelection(self, skinname, skinpath, test=0, make_default=0): ''' Adds a skin selection. ''' sels = self._getSelections() skinpath = str(skinpath) if test: self.testSkinPath(skinpath) sels[str(skinname)] = skinpath if make_default: self.default_skin = skinname Globals.InitializeClass(SkinsTool) CMF-1.3/CMFCore/TypesTool.py0100644000076500007650000006723607523566053015464 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Type registration tool. $Id: TypesTool.py,v 1.37.4.7 2025/08/05 21:20:43 shane Exp $ """ import OFS from Globals import InitializeClass, DTMLFile from utils import UniqueObject, SimpleItemWithProperties, tuplize from utils import _dtmldir, _checkPermission, cookString, getToolByName import string from AccessControl import getSecurityManager, ClassSecurityInfo, Unauthorized from Acquisition import aq_base import Products, CMFCorePermissions from ActionProviderBase import ActionProviderBase from ActionInformation import ActionInformation from Expression import Expression from zLOG import LOG, WARNING, ERROR import sys from CMFCorePermissions import View, ManagePortal, AccessContentsInformation _marker = [] # Create a new marker. class TypeInformation (SimpleItemWithProperties): """ Base class for information about a content type. """ _isTypeInformation = 1 manage_options = (SimpleItemWithProperties.manage_options[:1] + ({'label':'Actions', 'action':'manage_editActionsForm'},) + SimpleItemWithProperties.manage_options[1:]) security = ClassSecurityInfo() security.declareProtected(CMFCorePermissions.ManagePortal, 'manage_editProperties', 'manage_changeProperties', 'manage_propertiesForm', ) _basic_properties = ( {'id':'title', 'type': 'string', 'mode':'w', 'label':'Title'}, {'id':'description', 'type': 'text', 'mode':'w', 'label':'Description'}, {'id':'content_icon', 'type': 'string', 'mode':'w', 'label':'Icon'}, {'id':'content_meta_type', 'type': 'string', 'mode':'w', 'label':'Product meta type'}, ) _advanced_properties = ( {'id':'immediate_view', 'type': 'string', 'mode':'w', 'label':'Initial view name'}, {'id':'global_allow', 'type': 'boolean', 'mode':'w', 'label':'Implicitly addable?'}, {'id':'filter_content_types', 'type': 'boolean', 'mode':'w', 'label':'Filter content types?'}, {'id':'allowed_content_types' , 'type': 'multiple selection' , 'mode':'w' , 'label':'Allowed content types' , 'select_variable':'listContentTypes' }, { 'id': 'allow_discussion', 'type': 'boolean', 'mode': 'w' , 'label': 'Allow Discussion?' }, ) title = '' description = '' content_meta_type = '' content_icon = '' immediate_view = '' filter_content_types = 1 allowed_content_types = () allow_discussion = 0 global_allow = 1 _actions = () def __init__(self, id, **kw): self.id = id if kw: kw = kw.copy() # Get a modifiable dict. if (not kw.has_key('content_meta_type') and kw.has_key('meta_type')): kw['content_meta_type'] = kw['meta_type'] if (not kw.has_key('content_icon') and kw.has_key('icon')): kw['content_icon'] = kw['icon'] apply(self.manage_changeProperties, (), kw) if kw.has_key('actions'): aa = kw['actions'] actions = [] for action in aa: action = action.copy() # Some backward compatibility stuff. if not action.has_key('id'): action['id'] = cookString(action['name']) if not action.has_key('category'): action['category'] = 'object' actions.append(action) self._actions = tuple(actions) # # Accessors # security.declareProtected(View, 'Type') def Type(self): """ Deprecated. Use Title(). """ LOG('CMFCore.TypesTool', WARNING, 'TypeInformation.Type() is deprecated, use Title().') return self.Title() security.declareProtected(View, 'Title') def Title(self): """ Return the "human readable" type name (note that it may not map exactly to the 'portal_type', e.g., for l10n/i18n or where a single content class is being used twice, under different names. """ return self.title or self.getId() security.declareProtected(View, 'Description') def Description(self): """ Textual description of the class of objects (intended for display in a "constructor list"). """ return self.description security.declareProtected(View, 'Metatype') def Metatype(self): """ Returns the Zope 'meta_type' for this content object. May be used for building the list of portal content meta types. """ return self.content_meta_type security.declareProtected(View, 'getIcon') def getIcon(self): """ Returns the icon for this content object. """ return self.content_icon security.declarePublic('allowType') def allowType( self, contentType ): """ Can objects of 'contentType' be added to containers whose type object we are? """ if not self.filter_content_types: ti = self.getTypeInfo( contentType ) if ti is None or ti.globalAllow(): return 1 if contentType in self.allowed_content_types: return 1 # Backward compatibility for code that expected Type() to work. for ti in self.listTypeInfo(): if ti.Title() == contentType: return ti.getId() in self.allowed_content_types return 0 security.declarePublic('getId') def getId(self): return self.id security.declarePublic('allowDiscussion') def allowDiscussion( self ): """ Can this type of object support discussion? """ return self.allow_discussion security.declarePrivate('getActions') def getActions(self): """ Returns the customizable user actions. """ # Private because this returns the actual structure. return self._actions security.declarePublic('globalAllow') def globalAllow(self): """ Should this type be implicitly addable anywhere? """ return self.global_allow security.declarePublic('getActionById') def getActionById( self, id, default=_marker ): """ Return the URL of the action whose ID is id. """ for action in self.getActions(): if action.has_key('id'): if action['id'] == id: return action['action'] else: # Temporary backward compatibility. if string.lower(action['name']) == id: return action['action'] if default is _marker: raise TypeError, ( 'No action "%s" for type "%s"' % ( id, self.getId() ) ) else: return default # # Action editing interface # _actions_form = DTMLFile( 'editActions', _dtmldir ) security.declareProtected(ManagePortal, 'manage_editActionsForm') def manage_editActionsForm(self, REQUEST, manage_tabs_message=None): """ Shows the 'Actions' management tab. """ actions = [] for a in self.getActions(): a = a.copy() p = a['permissions'] if p: a['permission'] = p[0] else: a['permission'] = '' if not a.has_key('category'): a['category'] = 'object' if not a.has_key('id'): a['id'] = cookString(a['name']) if not a.has_key( 'visible' ): a['visible'] = 1 actions.append(a) # possible_permissions is in AccessControl.Role.RoleManager. pp = self.possible_permissions() return self._actions_form(self, REQUEST, actions=actions, possible_permissions=pp, management_view='Actions', manage_tabs_message=manage_tabs_message) security.declareProtected(ManagePortal, 'addAction') def addAction( self , id , name , action , permission , category , visible=1 , REQUEST=None ): """ Adds an action to the list. """ if not name: raise ValueError('A name is required.') new_actions = self._cloneActions() new_actions.append( { 'id' : str(id) , 'name' : str(name) , 'action' : str(action) , 'permissions' : (str(permission),) , 'category' : str(category) , 'visible' : int(visible) } ) self._actions = tuple( new_actions ) if REQUEST is not None: return self.manage_editActionsForm( REQUEST, manage_tabs_message='Added.') security.declareProtected(ManagePortal, 'changeActions') def changeActions(self, properties=None, REQUEST=None): """ Changes the _actions. """ if properties is None: properties = REQUEST actions = [] for idx in range(len(self._actions)): s_idx = str(idx) action = self._actions[idx].copy() action.update( { 'id': str(properties.get('id_' + s_idx, '')), 'name': str(properties.get('name_' + s_idx, '')), 'action': str(properties.get('action_' + s_idx, '')), 'permissions': (properties.get('permission_' + s_idx, ()),), 'category': str(properties.get('category_' + s_idx, '')), 'visible': not not properties.get('visible_' + s_idx, 0), } ) if not action['name']: raise ValueError('A name is required.') actions.append( action ) self._actions = tuple( actions ) if REQUEST is not None: return self.manage_editActionsForm(REQUEST, manage_tabs_message= 'Actions changed.') security.declareProtected(ManagePortal, 'deleteActions') def deleteActions(self, selections=(), REQUEST=None): """ Deletes actions. """ sels = list(map(int, selections)) # Convert to a list of integers. sels.sort() sels.reverse() new_actions = self._cloneActions() for idx in sels: del new_actions[idx] self._actions = tuple(new_actions) if REQUEST is not None: return self.manage_editActionsForm( REQUEST, manage_tabs_message=( 'Deleted %d action(s).' % len(sels))) security.declareProtected(ManagePortal, 'moveUpActions') def moveUpActions(self, selections=(), REQUEST=None): """ Moves the specified actions up one slot. """ sels = list(map(int, selections)) # Convert to a list of integers. sels.sort() new_actions = self._cloneActions() for idx in sels: idx2 = idx - 1 if idx2 < 0: # Wrap to the bottom. idx2 = len(new_actions) - 1 # Swap. a = new_actions[idx2] new_actions[idx2] = new_actions[idx] new_actions[idx] = a self._actions = tuple(new_actions) if REQUEST is not None: return self.manage_editActionsForm( REQUEST, manage_tabs_message=( 'Moved up %d action(s).' % len(sels))) security.declareProtected(ManagePortal, 'moveDownActions') def moveDownActions(self, selections=(), REQUEST=None): """ Moves the specified actions down one slot. """ sels = list(map(int, selections)) # Convert to a list of integers. sels.sort() sels.reverse() new_actions = self._cloneActions() for idx in sels: idx2 = idx + 1 if idx2 >= len(new_actions): # Wrap to the top. idx2 = 0 # Swap. a = new_actions[idx2] new_actions[idx2] = new_actions[idx] new_actions[idx] = a self._actions = tuple(new_actions) if REQUEST is not None: return self.manage_editActionsForm( REQUEST, manage_tabs_message=( 'Moved down %d action(s).' % len(sels))) security.declarePrivate( '_cloneActions' ) def _cloneActions( self ): """ Return a "deep copy" of our list of actions. """ return map( lambda x: x.copy(), list( self._actions ) ) security.declarePrivate('_finishConstruction') def _finishConstruction(self, ob): """ Finish the construction of a content object. Set its portal_type, insert it into the workflows. """ if hasattr(ob, '_setPortalTypeName'): ob._setPortalTypeName(self.getId()) ob.reindexObject(idxs=['portal_type', 'Type']) if hasattr(aq_base(ob), 'notifyWorkflowCreated'): ob.notifyWorkflowCreated() return ob InitializeClass( TypeInformation ) class FactoryTypeInformation (TypeInformation): """ Portal content factory. """ meta_type = 'Factory-based Type Information' security = ClassSecurityInfo() _properties = (TypeInformation._basic_properties + ( {'id':'product', 'type': 'string', 'mode':'w', 'label':'Product name'}, {'id':'factory', 'type': 'string', 'mode':'w', 'label':'Product factory method'}, ) + TypeInformation._advanced_properties) product = '' factory = '' # # Agent methods # def _getFactoryMethod(self, container): if not self.product or not self.factory: raise ValueError, ('Product factory for %s was undefined' % self.getId()) p = container.manage_addProduct[self.product] m = getattr(p, self.factory, None) if m is None: raise ValueError, ('Product factory for %s was invalid' % self.getId()) if getSecurityManager().validate(p, p, self.factory, m): return m raise Unauthorized, ('Cannot create %s' % self.getId()) def _queryFactoryMethod(self, container, default=None): if not self.product or not self.factory: return default try: p = container.manage_addProduct[self.product] m = getattr(p, self.factory, None) if m is None: return default try: # validate() can either raise Unauthorized or return 0 to # mean unauthorized. if getSecurityManager().validate(p, p, self.factory, m): return m except Unauthorized: pass return default except: LOG('Types Tool', ERROR, '_queryFactoryMethod raised an exception', error=sys.exc_info()) return default security.declarePublic('isConstructionAllowed') def isConstructionAllowed ( self, container ): """ a. Does the factory method exist? b. Is the factory method usable? c. Does the current user have the permission required in order to invoke the factory method? """ m = self._queryFactoryMethod(container) return (m is not None) security.declarePublic('constructInstance') def constructInstance( self, container, id, *args, **kw ): """ Build a "bare" instance of the appropriate type in 'container', using 'id' as its id. Return the object. """ # Get the factory method, performing a security check # in the process. m = self._getFactoryMethod(container) id = str(id) if getattr( m, 'isDocTemp', 0 ): args = ( m.aq_parent, self.REQUEST ) + args kw[ 'id' ] = id else: args = ( id, ) + args id = apply( m, args, kw ) or id # allow factory to munge ID ob = container._getOb( id ) return self._finishConstruction(ob) InitializeClass( FactoryTypeInformation ) class ScriptableTypeInformation( TypeInformation ): """ Invokes a script rather than a factory to create the content. """ meta_type = 'Scriptable Type Information' security = ClassSecurityInfo() _properties = (TypeInformation._basic_properties + ( {'id':'permission', 'type': 'string', 'mode':'w', 'label':'Constructor permission'}, {'id':'constructor_path', 'type': 'string', 'mode':'w', 'label':'Constructor path'}, ) + TypeInformation._advanced_properties) permission = '' constructor_path = '' # # Agent methods # security.declarePublic('isConstructionAllowed') def isConstructionAllowed( self, container ): """ Does the current user have the permission required in order to construct an instance? """ permission = self.permission if permission and not _checkPermission( permission, container ): return 0 return 1 security.declarePublic('constructInstance') def constructInstance( self, container, id, *args, **kw ): """ Build a "bare" instance of the appropriate type in 'container', using 'id' as its id. Return the object. """ if not self.isConstructionAllowed(container): raise Unauthorized constructor = self.restrictedTraverse( self.constructor_path ) # Rewrap to get into container's context. constructor = aq_base(constructor).__of__( container ) id = str(id) ob = apply(constructor, (container, id) + args, kw) return self._finishConstruction(ob) InitializeClass( ScriptableTypeInformation ) # Provide aliases for backward compatibility. ContentFactoryMetadata = FactoryTypeInformation ContentTypeInformation = ScriptableTypeInformation typeClasses = [ {'class':FactoryTypeInformation, 'name':FactoryTypeInformation.meta_type, 'action':'manage_addFactoryTIForm', 'permission':'Manage portal'}, {'class':ScriptableTypeInformation, 'name':ScriptableTypeInformation.meta_type, 'action':'manage_addScriptableTIForm', 'permission':'Manage portal'}, ] allowedTypes = [ 'Script (Python)', 'Python Method', 'DTML Method', 'External Method', ] class TypesTool( UniqueObject, OFS.Folder.Folder, ActionProviderBase ): """ Provides a configurable registry of portal content types. """ id = 'portal_types' meta_type = 'CMF Types Tool' _actions = [] security = ClassSecurityInfo() manage_options = ( OFS.Folder.Folder.manage_options + ActionProviderBase.manage_options + ({ 'label' : 'Overview', 'action' : 'manage_overview' } , )) # # ZMI methods # security.declareProtected( CMFCorePermissions.ManagePortal , 'manage_overview' ) manage_overview = DTMLFile( 'explainTypesTool', _dtmldir ) security.declarePrivate('listActions') def listActions(self, info=None): """ Return a list of action information instances for actions provided via tool """ return self._actions def all_meta_types(self): """Adds TypesTool-specific meta types.""" all = TypesTool.inheritedAttribute('all_meta_types')(self) return tuple(typeClasses) + tuple(all) def filtered_meta_types(self, user=None): # Filters the list of available meta types. allowed = {} for tc in typeClasses: allowed[tc['name']] = 1 for name in allowedTypes: allowed[name] = 1 all = TypesTool.inheritedAttribute('filtered_meta_types')(self) meta_types = [] for meta_type in self.all_meta_types(): if allowed.get(meta_type['name']): meta_types.append(meta_type) return meta_types security.declareProtected(ManagePortal, 'listDefaultTypeInformation') def listDefaultTypeInformation(self): # Scans for factory_type_information attributes # of all products and factory dispatchers within products. res = [] products = self.aq_acquire('_getProducts')() for product in products.objectValues(): if hasattr(aq_base(product), 'factory_type_information'): ftis = product.factory_type_information else: package = getattr(Products, product.getId(), None) dispatcher = getattr(package, '__FactoryDispatcher__', None) ftis = getattr(dispatcher, 'factory_type_information', None) if ftis is not None: if callable(ftis): ftis = ftis() for fti in ftis: mt = fti.get('meta_type', None) if mt: res.append((product.getId() + ': ' + mt, fti)) return res _addTIForm = DTMLFile( 'addTypeInfo', _dtmldir ) security.declareProtected(ManagePortal, 'manage_addFactoryTIForm') def manage_addFactoryTIForm(self, REQUEST): ' ' return self._addTIForm( self, REQUEST, add_meta_type=FactoryTypeInformation.meta_type, types=self.listDefaultTypeInformation()) security.declareProtected(ManagePortal, 'manage_addScriptableTIForm') def manage_addScriptableTIForm(self, REQUEST): ' ' return self._addTIForm( self, REQUEST, add_meta_type=ScriptableTypeInformation.meta_type, types=self.listDefaultTypeInformation()) security.declareProtected(ManagePortal, 'manage_addTypeInformation') def manage_addTypeInformation(self, add_meta_type, id=None, typeinfo_name=None, RESPONSE=None): """ Create a TypeInformation in self. """ fti = None if typeinfo_name: info = self.listDefaultTypeInformation() for (name, ft) in info: if name == typeinfo_name: fti = ft break if fti is None: raise 'Bad Request', ('%s not found.' % typeinfo_name) if not id: id = fti.get('id', None) if not id: raise 'Bad Request', 'An id is required.' for mt in typeClasses: if mt['name'] == add_meta_type: klass = mt['class'] break else: raise ValueError, ( 'Meta type %s is not a type class.' % add_meta_type) id = str(id) if fti is not None: fti = fti.copy() if fti.has_key('id'): del fti['id'] ob = apply(klass, (id,), fti) else: ob = apply(klass, (id,)) self._setObject(id, ob) if RESPONSE is not None: RESPONSE.redirect('%s/manage_main' % self.absolute_url()) security.declareProtected(AccessContentsInformation, 'getTypeInfo') def getTypeInfo( self, contentType ): """ Return an instance which implements the TypeInformation interface, corresponding to the specified 'contentType'. If contentType is actually an object, rather than a string, attempt to look up the appropriate type info using its portal_type. """ if type( contentType ) is not type( '' ): if hasattr(aq_base(contentType), '_getPortalTypeName'): contentType = contentType._getPortalTypeName() if contentType is None: return None else: return None ob = getattr( self, contentType, None ) if getattr(aq_base(ob), '_isTypeInformation', 0): return ob else: return None security.declarePrivate('_checkViewType') def _checkViewType(self,t): try: return getSecurityManager().validate(t, t, 'Title', t.Title) except Unauthorized: return 0 security.declareProtected(AccessContentsInformation, 'listTypeInfo') def listTypeInfo( self, container=None ): """ Return a sequence of instances which implement the TypeInformation interface, one for each content type registered in the portal. """ rval = [] for t in self.objectValues(): # Filter out things that aren't TypeInformation and # types for which the user does not have adequate permission. if not getattr(aq_base(t), '_isTypeInformation', 0): continue if not t.getId(): # XXX What's this used for ? # Not ready. continue # check we're allowed to access the type object if not self._checkViewType(t): continue if container is not None: if not t.isConstructionAllowed(container): continue rval.append(t) return rval security.declareProtected(AccessContentsInformation, 'listContentTypes') def listContentTypes( self, container=None, by_metatype=0 ): """ Return list of content types. """ typenames = {} for t in self.listTypeInfo( container ): if by_metatype: name = t.Metatype() else: name = t.getId() if name: typenames[ name ] = None result = typenames.keys() result.sort() return result security.declarePublic('constructContent') def constructContent( self , type_name , container , id , RESPONSE=None , *args , **kw ): """ Build an instance of the appropriate content class in 'container', using 'id'. """ info = self.getTypeInfo( type_name ) if info is None: raise 'ValueError', 'No such content type: %s' % type_name # check we're allowed to access the type object if not self._checkViewType(info): raise Unauthorized,info ob = apply(info.constructInstance, (container, id) + args, kw) if RESPONSE is not None: immediate_url = '%s/%s' % ( ob.absolute_url() , info.immediate_view ) RESPONSE.redirect( immediate_url ) InitializeClass( TypesTool ) CMF-1.3/CMFCore/UndoTool.py0100644000076500007650000001122307522303413015232 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Basic undo tool. $Id: UndoTool.py,v 1.6.8.2 2025/08/01 19:07:55 tseaver Exp $ """ from utils import UniqueObject, _getAuthenticatedUser, _checkPermission from utils import getToolByName, _dtmldir from OFS.SimpleItem import SimpleItem from Globals import InitializeClass, DTMLFile from string import split from AccessControl import ClassSecurityInfo, Unauthorized from Expression import Expression from ActionInformation import ActionInformation from ActionProviderBase import ActionProviderBase from CMFCorePermissions import ManagePortal, UndoChanges, ListUndoableChanges class UndoTool (UniqueObject, SimpleItem, ActionProviderBase): id = 'portal_undo' meta_type = 'CMF Undo Tool' # This tool is used to undo changes. _actions = [ActionInformation(id='undo' , title='Undo' , action=Expression( text='string: ${portal_url}/undo_form') , condition=Expression( text='member') , permissions=(ListUndoableChanges,) , category='global' , visible=1 )] security = ClassSecurityInfo() manage_options = ( ActionProviderBase.manage_options + SimpleItem.manage_options + ({ 'label' : 'Overview', 'action' : 'manage_overview' } , )) # # ZMI methods # security.declareProtected( ManagePortal , 'manage_overview' ) manage_overview = DTMLFile( 'explainUndoTool', _dtmldir ) security.declarePrivate('listActions') def listActions(self, info=None): """ List actions available through tool """ return self._actions # # 'portal_undo' interface methods # security.declareProtected( ListUndoableChanges , 'listUndoableTransactionsFor') def listUndoableTransactionsFor(self, object, first_transaction=None, last_transaction=None, PrincipiaUndoBatchSize=None): '''Lists all transaction IDs the user is allowed to undo. ''' # arg list for undoable_transactions() changed in Zope 2.2. portal = self.aq_inner.aq_parent transactions = portal.undoable_transactions( first_transaction=first_transaction, last_transaction=last_transaction, PrincipiaUndoBatchSize=PrincipiaUndoBatchSize) for t in transactions: # Ensure transaction ids don't have embedded LF. t['id'] = t['id'].replace('\n', '') if not _checkPermission('Manage portal', portal): # Filter out transactions done by other members of the portal. user_name = _getAuthenticatedUser(self).getUserName() transactions = filter( lambda record, user_name=user_name: split(record['user_name'])[-1] == user_name, transactions ) return transactions security.declarePublic('undo') def undo(self, object, transaction_info): """ Undo the list of transactions passed in 'transaction_info', first verifying that the current user is allowed to undo them. """ # Belt and suspenders: make sure that the user is actually # allowed to undo the transation(s) in transaction_info. xids = {} # set of allowed transaction IDs allowed = self.listUndoableTransactionsFor( object ) for xid in map( lambda x: x['id'], allowed ): xids[xid] = 1 if type( transaction_info ) == type( '' ): transaction_info = [ transaction_info ] for tinfo in transaction_info: if not xids.get( tinfo, None ): raise Unauthorized object.manage_undo_transactions(transaction_info) InitializeClass(UndoTool) CMF-1.3/CMFCore/WorkflowCore.py0100644000076500007650000000525607520011565016125 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Common pieces of the workflow architecture. $Id: WorkflowCore.py,v 1.10.36.2 2025/07/25 15:04:21 tseaver Exp $ """ import sys from Acquisition import aq_base from MethodObject import Method from utils import getToolByName class WorkflowException( Exception ): """ Exception while invoking workflow. """ class ObjectDeleted( Exception ): """ Raise to tell the workflow tool that the object has been deleted. Swallowed by the workflow tool. """ def __init__(self, result=None): self._r = result def getResult(self): return self._r class ObjectMoved( Exception ): """ Raise to tell the workflow tool that the object has moved. Swallowed by the workflow tool. """ def __init__(self, new_ob, result=None): self._ob = new_ob # Includes acquisition wrappers. self._r = result def getResult(self): return self._r def getNewObject(self): return self._ob class WorkflowMethod( Method ): """ Wrap a method to workflow-enable it. """ _need__name__=1 def __init__(self, method, id=None, reindex=1): self._m = method if id is None: id = method.__name__ self._id = id # reindex ignored since workflows now perform the reindexing. def __call__(self, instance, *args, **kw): """ Invoke the wrapped method, and deal with the results. """ wf = getToolByName(instance, 'portal_workflow', None) if wf is None or not hasattr(wf, 'wrapWorkflowMethod'): # No workflow tool found. try: res = apply(self._m, (instance,) + args, kw) except ObjectDeleted, ex: res = ex.getResult() else: if hasattr(aq_base(instance), 'reindexObject'): instance.reindexObject() else: res = wf.wrapWorkflowMethod(instance, self._id, self._m, (instance,) + args, kw) return res # Backward compatibility. WorkflowAction = WorkflowMethod CMF-1.3/CMFCore/WorkflowTool.py0100644000076500007650000006012607523334712016154 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Basic workflow tool. $Id: WorkflowTool.py,v 1.24.4.10 2025/08/04 23:33:30 efge Exp $ """ import sys from string import join, split, replace, strip from OFS.Folder import Folder from Globals import InitializeClass, PersistentMapping, DTMLFile from AccessControl import ClassSecurityInfo from Acquisition import aq_base, aq_inner, aq_parent from CMFCorePermissions import ManagePortal from WorkflowCore import WorkflowException, ObjectDeleted, ObjectMoved from interfaces.portal_workflow import portal_workflow from utils import UniqueObject from utils import _checkPermission from utils import getToolByName from utils import _dtmldir AUTO_MIGRATE_WORKFLOW_TOOLS = 0 # Set to 1 to auto-migrate _marker = [] # Create a new marker object. class WorkflowInformation: """ Shim implementation of ActionInformation, to enable querying actions without mediation of the 'portal_actions' tool. """ def __init__(self, object): self.content = object self.content_url = object.absolute_url() self.portal_url = self.folder_url = '' def __getitem__(self, name): if name[:1] == '_': raise KeyError, name if hasattr(self, name): return getattr(self, name) raise KeyError, name class WorkflowTool (UniqueObject, Folder): """ Mediator tool, mapping workflow objects """ id = 'portal_workflow' meta_type = 'CMF Workflow Tool' __implements__ = portal_workflow _chains_by_type = None # PersistentMapping _default_chain = ('default_workflow',) _default_cataloging = 1 security = ClassSecurityInfo() manage_options = ( { 'label' : 'Workflows' , 'action' : 'manage_selectWorkflows' } , { 'label' : 'Overview', 'action' : 'manage_overview' } ) + Folder.manage_options # # ZMI methods # security.declareProtected( ManagePortal, 'manage_overview' ) manage_overview = DTMLFile( 'explainWorkflowTool', _dtmldir ) if AUTO_MIGRATE_WORKFLOW_TOOLS: def __setstate__(self, state): # Adds default_workflow to persistent WorkflowTool instances. # This is temporary! WorkflowTool.inheritedAttribute('__setstate__')(self, state) if not self.__dict__.has_key('default_workflow'): try: from Products.CMFDefault import DefaultWorkflow except ImportError: pass else: self.default_workflow = ( DefaultWorkflow.DefaultWorkflowDefinition( 'default_workflow')) self._objects = self._objects + ( {'id': 'default_workflow', 'meta_type': self.default_workflow.meta_type},) _manage_addWorkflowForm = DTMLFile('addWorkflow', _dtmldir) security.declareProtected( ManagePortal, 'manage_addWorkflowForm') def manage_addWorkflowForm(self, REQUEST): """ Form for adding workflows. """ wft = [] for key in _workflow_factories.keys(): wft.append(key) wft.sort() return self._manage_addWorkflowForm(REQUEST, workflow_types=wft) security.declareProtected( ManagePortal, 'manage_addWorkflow') def manage_addWorkflow(self, workflow_type, id, RESPONSE=None): """ Adds a workflow from the registered types. """ factory = _workflow_factories[workflow_type] ob = factory(id) self._setObject(id, ob) if RESPONSE is not None: RESPONSE.redirect(self.absolute_url() + '/manage_main?management_view=Contents') def all_meta_types(self): return ( {'name': 'Workflow', 'action': 'manage_addWorkflowForm', 'permission': ManagePortal },) _manage_selectWorkflows = DTMLFile('selectWorkflows', _dtmldir) security.declareProtected( ManagePortal, 'manage_selectWorkflows') def manage_selectWorkflows(self, REQUEST, manage_tabs_message=None): """ Show a management screen for changing type to workflow connections. """ cbt = self._chains_by_type ti = self._listTypeInfo() types_info = [] for t in ti: id = t.getId() title = t.Title() if title == id: title = None if cbt is not None and cbt.has_key(id): chain = join(cbt[id], ', ') else: chain = '(Default)' types_info.append({'id': id, 'title': title, 'chain': chain}) return self._manage_selectWorkflows( REQUEST, default_chain=join(self._default_chain, ', '), types_info=types_info, management_view='Workflows', manage_tabs_message=manage_tabs_message) security.declareProtected( ManagePortal, 'manage_changeWorkflows') def manage_changeWorkflows(self, default_chain, props=None, REQUEST=None): """ Changes which workflows apply to objects of which type. """ if props is None: props = REQUEST cbt = self._chains_by_type if cbt is None: self._chains_by_type = cbt = PersistentMapping() ti = self._listTypeInfo() # Set up the chains by type. for t in ti: id = t.getId() field_name = 'chain_%s' % id chain = strip(props.get(field_name, '(Default)')) if chain == '(Default)': # Remove from cbt. if cbt.has_key(id): del cbt[id] else: chain = replace(chain, ',', ' ') ids = [] for wf_id in split(chain, ' '): if wf_id: if not self.getWorkflowById(wf_id): raise ValueError, ( '"%s" is not a workflow ID.' % wf_id) ids.append(wf_id) cbt[id] = tuple(ids) # Set up the default chain. default_chain = replace(default_chain, ',', ' ') ids = [] for wf_id in split(default_chain, ' '): if wf_id: if not self.getWorkflowById(wf_id): raise ValueError, ( '"%s" is not a workflow ID.' % wf_id) ids.append(wf_id) self._default_chain = tuple(ids) if REQUEST is not None: return self.manage_selectWorkflows(REQUEST, manage_tabs_message='Changed.') # # portal_workflow implementation. # security.declarePrivate('getCatalogVariablesFor') def getCatalogVariablesFor(self, ob): """ Returns a mapping of the catalog variables that apply to ob. o Invoked by portal_catalog. o Allows workflows to add variables to the catalog based on workflow status, making it possible to implement queues. """ wfs = self.getWorkflowsFor(ob) if wfs is None: return None # Iterate through the workflows backwards so that # earlier workflows can override later workflows. wfs.reverse() vars = {} for wf in wfs: v = wf.getCatalogVariablesFor(ob) if v is not None: vars.update(v) return vars security.declarePrivate('listActions') def listActions(self, info): """ Returns a list of actions to be displayed to the user. o Invoked by the portal_actions tool. o Allows workflows to include actions to be displayed in the actions box. o Object actions are supplied by workflows that apply to the object. o Global actions are supplied by all workflows. """ chain = self.getChainFor(info.content) did = {} actions = [] for wf_id in chain: did[wf_id] = 1 wf = self.getWorkflowById(wf_id) if wf is not None: a = wf.listObjectActions(info) if a is not None: actions.extend(a) a = wf.listGlobalActions(info) if a is not None: actions.extend(a) wf_ids = self.getWorkflowIds() for wf_id in wf_ids: if not did.has_key(wf_id): wf = self.getWorkflowById(wf_id) if wf is not None: a = wf.listGlobalActions(info) if a is not None: actions.extend(a) return actions security.declarePublic('getActionsFor') def getActionsFor(self, ob): """ Return a list of action dictionaries for 'ob', just as though queried via 'ActionsTool.listFilteredActionsFor'. """ return self.listActions( WorkflowInformation( ob ) ) security.declarePublic('doActionFor') def doActionFor(self, ob, action, wf_id=None, *args, **kw): """ Execute the given workflow action for the object. o Invoked by user interface code. o Allows the user to request a workflow action. o The workflow object must perform its own security checks. """ wfs = self.getWorkflowsFor(ob) if wfs is None: wfs = () if wf_id is None: if not wfs: raise WorkflowException('No workflows found.') found = 0 for wf in wfs: if wf.isActionSupported(ob, action): found = 1 break if not found: raise WorkflowException( 'No workflow provides the "%s" action.' % action) else: wf = self.getWorkflowById(wf_id) if wf is None: raise WorkflowException( 'Requested workflow definition not found.') return self._invokeWithNotification( wfs, ob, action, wf.doActionFor, (ob, action) + args, kw) security.declarePublic('getInfoFor') def getInfoFor(self, ob, name, default=_marker, wf_id=None, *args, **kw): """ Return a given workflow-specific property for an object. o Invoked by user interface code. o Allows the user to request information provided by the workflow. o The workflow object must perform its own security checks. """ if wf_id is None: wfs = self.getWorkflowsFor(ob) if wfs is None: if default is _marker: raise WorkflowException('No workflows found.') else: return default found = 0 for wf in wfs: if wf.isInfoSupported(ob, name): found = 1 break if not found: if default is _marker: raise WorkflowException( 'No workflow provides "%s" information.' % name) else: return default else: wf = self.getWorkflowById(wf_id) if wf is None: if default is _marker: raise WorkflowException( 'Requested workflow definition not found.') else: return default res = apply(wf.getInfoFor, (ob, name, default) + args, kw) if res is _marker: raise WorkflowException('Could not get info: %s' % name) return res security.declarePrivate('notifyCreated') def notifyCreated(self, ob): """ Notify all applicable workflows that an object has been created and put in its new place. """ wfs = self.getWorkflowsFor(ob) for wf in wfs: wf.notifyCreated(ob) self._reindexWorkflowVariables(ob) security.declarePrivate('notifyBefore') def notifyBefore(self, ob, action): """ Notifies all applicable workflows of an action before it happens, allowing veto by exception. o Unless an exception is thrown, either a notifySuccess() or notifyException() can be expected later on. o The action usually corresponds to a method name. """ wfs = self.getWorkflowsFor(ob) for wf in wfs: wf.notifyBefore(ob, action) security.declarePrivate('notifySuccess') def notifySuccess(self, ob, action, result=None): """ Notify all applicable workflows that an action has taken place. """ wfs = self.getWorkflowsFor(ob) for wf in wfs: wf.notifySuccess(ob, action, result) security.declarePrivate('notifyException') def notifyException(self, ob, action, exc): """ Notify all applicable workflows that an action failed. """ wfs = self.getWorkflowsFor(ob) for wf in wfs: wf.notifyException(ob, action, exc) security.declarePrivate('getHistoryOf') def getHistoryOf(self, wf_id, ob): """ Return the history of an object. o Invoked by workflow definitions. """ if hasattr(aq_base(ob), 'workflow_history'): wfh = ob.workflow_history return wfh.get(wf_id, None) return () security.declarePrivate('getStatusOf') def getStatusOf(self, wf_id, ob): """ Return the last entry of a workflow history. o Invoked by workflow definitions. """ wfh = self.getHistoryOf(wf_id, ob) if wfh: return wfh[-1] return None security.declarePrivate('setStatusOf') def setStatusOf(self, wf_id, ob, status): """ Append an entry to the workflow history. o Invoked by workflow definitions. """ wfh = None has_history = 0 if hasattr(aq_base(ob), 'workflow_history'): history = ob.workflow_history if history is not None: has_history = 1 wfh = history.get(wf_id, None) if wfh is not None: wfh = list(wfh) if not wfh: wfh = [] wfh.append(status) if not has_history: ob.workflow_history = PersistentMapping() ob.workflow_history[wf_id] = tuple(wfh) # # Administration methods # security.declareProtected( ManagePortal, 'setDefaultChain') def setDefaultChain(self, default_chain): """ Set the default chain for this tool """ default_chain = replace(default_chain, ',', ' ') ids = [] for wf_id in split(default_chain, ' '): if wf_id: if not self.getWorkflowById(wf_id): raise ValueError, ( '"%s" is not a workflow ID.' % wf_id) ids.append(wf_id) self._default_chain = tuple(ids) security.declareProtected( ManagePortal, 'setChainForPortalTypes') def setChainForPortalTypes(self, pt_names, chain): """ Set a chain for a specific portal type. """ cbt = self._chains_by_type if cbt is None: self._chains_by_type = cbt = PersistentMapping() if type(chain) is type(''): chain = map(strip, split(chain,',')) ti = self._listTypeInfo() for t in ti: id = t.getId() if id in pt_names: cbt[id] = tuple(chain) security.declareProtected( ManagePortal, 'updateRoleMappings') def updateRoleMappings(self, REQUEST=None): """ Allow workflows to update the role-permission mappings. """ wfs = {} for id in self.objectIds(): wf = self.getWorkflowById(id) if hasattr(aq_base(wf), 'updateRoleMappingsFor'): wfs[id] = wf portal = aq_parent(aq_inner(self)) count = self._recursiveUpdateRoleMappings(portal, wfs) if REQUEST is not None: return self.manage_selectWorkflows(REQUEST, manage_tabs_message= '%d object(s) updated.' % count) else: return count security.declarePrivate('getWorkflowById') def getWorkflowById(self, wf_id): """ Retrieve a given workflow. """ wf = getattr(self, wf_id, None) if getattr(wf, '_isAWorkflow', 0): return wf else: return None security.declarePrivate('getDefaultChainFor') def getDefaultChainFor(self, ob): """ Return the default chain, if applicable, for ob. """ types_tool = getToolByName( self, 'portal_types', None ) if ( types_tool is not None and types_tool.getTypeInfo( ob ) is not None ): return self._default_chain return () security.declarePrivate('getChainFor') def getChainFor(self, ob): """ Returns the chain that applies to the given object. If we get a string as the ob parameter, use it as the portal_type. """ cbt = self._chains_by_type if type(ob) == type(''): pt = ob elif hasattr(aq_base(ob), '_getPortalTypeName'): pt = ob._getPortalTypeName() else: pt = None if pt is None: return () chain = None if cbt is not None: chain = cbt.get(pt, None) # Note that if chain is not in cbt or has a value of # None, we use a default chain. if chain is None: chain = self.getDefaultChainFor(ob) if chain is None: return () return chain security.declarePrivate('getWorkflowIds') def getWorkflowIds(self): """ Return the list of workflow ids. """ wf_ids = [] for obj_name, obj in self.objectItems(): if getattr(obj, '_isAWorkflow', 0): wf_ids.append(obj_name) return tuple(wf_ids) security.declarePrivate('getWorkflowsFor') def getWorkflowsFor(self, ob): """ Find the workflows for the type of the given object. """ res = [] for wf_id in self.getChainFor(ob): wf = self.getWorkflowById(wf_id) if wf is not None: res.append(wf) return res security.declarePrivate('wrapWorkflowMethod') def wrapWorkflowMethod(self, ob, method_id, func, args, kw): """ To be invoked only by WorkflowCore. Allows a workflow definition to wrap a WorkflowMethod. """ wf = None wfs = self.getWorkflowsFor(ob) if wfs: for w in wfs: if (hasattr(w, 'isWorkflowMethodSupported') and w.isWorkflowMethodSupported(ob, method_id)): wf = w break else: wfs = () if wf is None: # No workflow wraps this method. return apply(func, args, kw) return self._invokeWithNotification( wfs, ob, method_id, wf.wrapWorkflowMethod, (ob, method_id, func, args, kw), {}) # # Helper methods # security.declarePrivate( '_listTypeInfo' ) def _listTypeInfo(self): """ List the portal types which are available. """ pt = getToolByName(self, 'portal_types', None) if pt is None: return () else: return pt.listTypeInfo() security.declarePrivate( '_invokeWithNotification' ) def _invokeWithNotification(self, wfs, ob, action, func, args, kw): """ Private utility method: call 'func', and deal with exceptions indicating that the object has been deleted or moved. """ reindex = 1 for w in wfs: w.notifyBefore(ob, action) try: res = apply(func, args, kw) except ObjectDeleted, ex: res = ex.getResult() reindex = 0 except ObjectMoved, ex: res = ex.getResult() ob = ex.getNewObject() except: exc = sys.exc_info() try: for w in wfs: w.notifyException(ob, action, exc) raise exc[0], exc[1], exc[2] finally: exc = None for w in wfs: w.notifySuccess(ob, action, res) if reindex: self._reindexWorkflowVariables(ob) return res security.declarePrivate( '_recursiveUpdateRoleMappings' ) def _recursiveUpdateRoleMappings(self, ob, wfs): """ Update roles-permission mappings recursively, and reindex special index. """ # Returns a count of updated objects. count = 0 wf_ids = self.getChainFor(ob) if wf_ids: changed = 0 for wf_id in wf_ids: wf = wfs.get(wf_id, None) if wf is not None: did = wf.updateRoleMappingsFor(ob) if did: changed = 1 if changed: count = count + 1 if hasattr(aq_base(ob), 'reindexObject'): # Reindex security-related indexes try: ob.reindexObject(idxs=['allowedRolesAndUsers']) except TypeError: # Catch attempts to reindex portal_catalog. pass if hasattr(aq_base(ob), 'objectItems'): obs = ob.objectItems() if obs: for k, v in obs: changed = getattr(v, '_p_changed', 0) count = count + self._recursiveUpdateRoleMappings(v, wfs) if changed is None: # Re-ghostify. v._p_deactivate() return count security.declarePrivate( '_setDefaultCataloging' ) def _setDefaultCataloging( self, value ): """ Toggle whether '_reindexWorkflowVariables' actually touches the catalog (sometimes not desirable, e.g. when the workflow objects do this themselves only at particular points). """ self._default_cataloging = not not value security.declarePrivate('_reindexWorkflowVariables') def _reindexWorkflowVariables(self, ob): """ Reindex the variables that the workflow may have changed. """ if not self._default_cataloging: return if hasattr(aq_base(ob), 'reindexObject'): # XXX We only need the keys here, no need to compute values. mapping = self.getCatalogVariablesFor(ob) or {} mapping['allowedRolesAndUsers'] = None vars = mapping.keys() ob.reindexObject(idxs=vars) InitializeClass(WorkflowTool) _workflow_factories = {} def _makeWorkflowFactoryKey(factory, id=None, title=None): # The factory should take one argument, id. if id is None: id = getattr(factory, 'id', '') or getattr(factory, 'meta_type', '') if title is None: title = getattr(factory, 'title', '') key = id if title: key = key + ' (%s)' % title return key def addWorkflowFactory(factory, id=None, title=None): key = _makeWorkflowFactoryKey( factory, id, title ) _workflow_factories[key] = factory addWorkflowClass = addWorkflowFactory # bw compat. def _removeWorkflowFactory( factory, id=None, title=None ): """ Make teardown in unitcase cleaner. """ key = _makeWorkflowFactoryKey( factory, id, title ) try: del _workflow_factories[key] except KeyError: pass CMF-1.3/CMFCore/__init__.py0100644000076500007650000001210207523123734015232 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Portal services base objects $Id: __init__.py,v 1.15.8.3 2025/08/04 04:04:44 efge Exp $ """ import PortalObject, PortalContent, PortalFolder import MembershipTool, WorkflowTool, CatalogTool, DiscussionTool import ActionsTool, UndoTool, RegistrationTool, SkinsTool import MemberDataTool, TypesTool import DirectoryView, FSImage, FSPropertiesObject import FSDTMLMethod, FSPythonScript, FSSTXMethod import FSZSQLMethod import CookieCrumbler import ContentTypeRegistry import CachingPolicyManager import utils from AccessControl import ModuleSecurityInfo from CMFCorePermissions import AddPortalFolders prod_security = ModuleSecurityInfo( 'Products' ) prod_security.declarePublic( 'CMFCore' ) security = ModuleSecurityInfo( 'Products.CMFCore' ) security.declarePublic( 'utils' ) try: import FSPageTemplate except ImportError: HAS_PAGE_TEMPLATES = 0 else: HAS_PAGE_TEMPLATES = 1 # Old name that some third-party packages may need. ADD_FOLDERS_PERMISSION = AddPortalFolders bases = ( PortalObject.PortalObjectBase, PortalFolder.PortalFolder, PortalContent.PortalContent, ) tools = ( MembershipTool.MembershipTool, RegistrationTool.RegistrationTool, WorkflowTool.WorkflowTool, CatalogTool.CatalogTool, DiscussionTool.DiscussionTool, ActionsTool.ActionsTool, UndoTool.UndoTool, SkinsTool.SkinsTool, MemberDataTool.MemberDataTool, TypesTool.TypesTool, ) import sys this_module = sys.modules[ __name__ ] z_bases = utils.initializeBasesPhase1(bases, this_module) z_tool_bases = utils.initializeBasesPhase1(tools, this_module) FolderConstructorForm = ( 'manage_addPortalFolderForm' , PortalFolder.manage_addPortalFolderForm ) cmfcore_globals=globals() def initialize(context): utils.initializeBasesPhase2(z_bases, context) utils.initializeBasesPhase2(z_tool_bases, context) context.registerClass( DirectoryView.DirectoryViewSurrogate, constructors=(('manage_addDirectoryViewForm', DirectoryView.manage_addDirectoryViewForm), DirectoryView.manage_addDirectoryView, DirectoryView.manage_listAvailableDirectories, ), icon='images/dirview.gif' ) context.registerClass( CookieCrumbler.CookieCrumbler, constructors=(CookieCrumbler.manage_addCCForm, CookieCrumbler.manage_addCC), icon = 'images/cookie.gif' ) context.registerClass( ContentTypeRegistry.ContentTypeRegistry, constructors=( ContentTypeRegistry.manage_addRegistry, ), icon = 'images/registry.gif' ) context.registerClass( CachingPolicyManager.CachingPolicyManager, constructors=( CachingPolicyManager.manage_addCachingPolicyManager, ), icon = 'images/registry.gif' ) if HAS_PAGE_TEMPLATES: utils.registerIcon(FSPageTemplate.FSPageTemplate, 'images/fspt.gif', globals()) utils.registerIcon(FSDTMLMethod.FSDTMLMethod, 'images/fsdtml.gif', globals()) utils.registerIcon(FSPythonScript.FSPythonScript, 'images/fspy.gif', globals()) utils.registerIcon(FSImage.FSImage, 'images/fsimage.gif', globals()) utils.registerIcon(FSPropertiesObject.FSPropertiesObject, 'images/fsprops.gif', globals()) utils.registerIcon(FSZSQLMethod.FSZSQLMethod, 'images/fssqlmethod.gif', globals()) utils.registerIcon(TypesTool.FactoryTypeInformation, 'images/typeinfo.gif', globals()) utils.registerIcon(TypesTool.ScriptableTypeInformation, 'images/typeinfo.gif', globals()) try: context.registerHelpTitle( 'CMF Core Help' ) context.registerHelp(directory='interfaces') except: # AARGH!! pass utils.ToolInit( 'CMF Core Tool' , tools=tools , product_name='CMFCore' , icon='tool.gif' ).initialize( context ) utils.ContentInit( 'CMF Core Content' , content_types=( PortalFolder.PortalFolder, ) , permission=AddPortalFolders , extra_constructors=( PortalFolder.manage_addPortalFolder, ) , fti=PortalFolder.factory_type_information ).initialize( context ) CMF-1.3/CMFCore/folderAdd.dtml0100644000076500007650000000350407245471213015674 0ustar tseavertseaver Add Folder

Add Folder

A Folder contains other objects. Use Folders to organize your web objects in to logical groups.

The form below allows you to create a Folder. The Create public interface option creates an index_html DTML Method inside the Folder to give the Folder a default HTML representation. The Create user folder option creates a User Folder inside the Folder to hold authorization information for the Folder.

Id
Title


CMF-1.3/CMFCore/register.py0100644000076500007650000000527707522303413015327 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ register: register portal content types with the CMF. $Id: register.py,v 1.4.36.1 2025/08/01 19:07:55 tseaver Exp $ """ import urllib import OFS, Globals from PortalFolder import PortalFolder from AccessControl.PermissionRole import PermissionRole def registerPortalContent(instance_class, meta_type='', constructors=(), action='', permission="Add portal content", icon=None, productGlobals=None, ): """ instance_class is the PortalContent-derived class to register meta_type is it's plain-language name contructors is a sequence of constructor functions. The first is placed as a method on PortalFolder. action is the relative URL of the add form or wizard permission is the name of the permission required to instantiate this class icon is the name of an image file to use as the document's icon """ return if 0: meta_type = meta_type or getattr(instance_class, 'meta_type', '') if constructors: pr = PermissionRole(permission) for c in constructors: name = c.__name__ setattr(PortalFolder, name, c) setattr(PortalFolder, '%s__roles__' % name, pr) PortalFolder.content_meta_types=PortalFolder.content_meta_types+( {'name': meta_type, 'action': action, 'permission': permission}, ) if icon: path = 'misc_/CMF/%s' % urllib.quote(meta_type) instance_class.icon = path if not hasattr(OFS.misc_.misc_, 'CMF'): OFS.misc_.misc_.CMF = OFS.misc_.Misc_('CMF', {}) if type(icon) == type(''): try: if productGlobals is None: productGlobals = globals() OFS.misc_.misc_.CMF[meta_type] = Globals.ImageFile( icon, productGlobals) except IOError: pass else: OFS.misc_.misc_.CMF[meta_type] = icon CMF-1.3/CMFCore/tool.gif0100644000076500007650000000024607245471213014572 0ustar tseavertseaverGIF89almoxzx皜VVWabaKLJ!Made with GIMP! ,AI \+Ua@$ o#L(D  8 >|W;CMF-1.3/CMFCore/utils.py0100644000076500007650000004757107516644314014661 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import os from os import path as os_path import re import operator from types import StringType from ExtensionClass import Base from Acquisition import aq_get, aq_inner, aq_parent from AccessControl import ClassSecurityInfo from AccessControl import ModuleSecurityInfo from AccessControl import getSecurityManager from AccessControl.Permission import Permission from AccessControl.PermissionRole import rolesForPermissionOn from AccessControl.Role import gather_permissions from Globals import package_home from Globals import InitializeClass from Globals import HTMLFile from Globals import ImageFile from Globals import MessageDialog from OFS.PropertyManager import PropertyManager from OFS.SimpleItem import SimpleItem from OFS.PropertySheets import PropertySheets from OFS.misc_ import misc_ as misc_images from OFS.misc_ import Misc_ as MiscImage try: from OFS.ObjectManager import UNIQUE except ImportError: UNIQUE = 2 import StructuredText from StructuredText.HTMLWithImages import HTMLWithImages _STXDWI = StructuredText.DocumentWithImages.__class__ security = ModuleSecurityInfo( 'Products.CMFCore.utils' ) security.declarePublic( 'getToolByName' , 'cookString' , 'tuplize' , 'format_stx' , 'keywordsplitter' , 'normalize' , 'expandpath' , 'minimalpath' ) security.declarePrivate( '_getAuthenticatedUser' , '_checkPermission' , '_verifyActionPermissions' , '_getViewFor' , '_limitGrantedRoles' , '_mergedLocalRoles' , '_modifyPermissionMappings' , '_ac_inherited_permissions' ) _dtmldir = os_path.join( package_home( globals() ), 'dtml' ) # # Simple utility functions, callable from restricted code. # _marker = [] # Create a new marker object. def getToolByName(obj, name, default=_marker): """ Get the tool, 'toolname', by acquiring it. o Application code should use this method, rather than simply acquiring the tool by name, to ease forward migration (e.g., to Zope3). """ try: tool = aq_get(obj, name, default, 1) except AttributeError: if default is _marker: raise return default else: if tool is _marker: raise AttributeError, name return tool def cookString(text): """ Make a Zope-friendly ID from 'text'. o Remove any spaces o Lowercase the ID. """ rgx = re.compile(r'(^_|[^a-zA-Z0-9-_~\,\.])') cooked = re.sub(rgx, "",text).lower() return cooked def tuplize( valueName, value ): """ Make a tuple from 'value'. o Use 'valueName' to generate appropriate error messages. """ if type(value) == type(()): return value if type(value) == type([]): return tuple( value ) if type(value) == type(''): return tuple( value.split() ) raise ValueError, "%s of unsupported type" % valueName # # Security utilities, callable only from unrestricted code. # def _getAuthenticatedUser( self ): return getSecurityManager().getUser() def _checkPermission(permission, obj, StringType = type('')): roles = rolesForPermissionOn(permission, obj) if type(roles) is StringType: roles=[roles] if _getAuthenticatedUser( obj ).allowed( obj, roles ): return 1 return 0 def _verifyActionPermissions(obj, action): pp = action.get('permissions', ()) if not pp: return 1 for p in pp: if _checkPermission(p, obj): return 1 return 0 def _getViewFor(obj, view='view'): ti = obj.getTypeInfo() if ti is not None: actions = ti.getActions() for action in actions: if action.get('id', None) == view: if _verifyActionPermissions(obj, action): return obj.restrictedTraverse(action['action']) # "view" action is not present or not allowed. # Find something that's allowed. for action in actions: if _verifyActionPermissions(obj, action): return obj.restrictedTraverse(action['action']) raise 'Unauthorized', ('No accessible views available for %s' % '/'.join(obj.getPhysicalPath())) else: raise 'Not Found', ('Cannot find default view for "%s"' % '/'.join(obj.getPhysicalPath())) # If Zope ever provides a call to getRolesInContext() through # the SecurityManager API, the method below needs to be updated. def _limitGrantedRoles(roles, context, special_roles=()): # Only allow a user to grant roles already possessed by that user, # with the exception that all special_roles can also be granted. user = _getAuthenticatedUser(context) if user is None: user_roles = () else: user_roles = user.getRolesInContext(context) if 'Manager' in user_roles: # Assume all other roles are allowed. return for role in roles: if role not in special_roles and role not in user_roles: raise 'Unauthorized', 'Too many roles specified.' limitGrantedRoles = _limitGrantedRoles # XXX: Deprecated spelling def _mergedLocalRoles(object): """Returns a merging of object and its ancestors' __ac_local_roles__.""" # Modified from AccessControl.User.getRolesInContext(). merged = {} object = getattr(object, 'aq_inner', object) while 1: if hasattr(object, '__ac_local_roles__'): dict = object.__ac_local_roles__ or {} if callable(dict): dict = dict() for k, v in dict.items(): if merged.has_key(k): merged[k] = merged[k] + v else: merged[k] = v if hasattr(object, 'aq_parent'): object=object.aq_parent object=getattr(object, 'aq_inner', object) continue if hasattr(object, 'im_self'): object=object.im_self object=getattr(object, 'aq_inner', object) continue break return merged mergedLocalRoles = _mergedLocalRoles # XXX: Deprecated spelling def _ac_inherited_permissions(ob, all=0): # Get all permissions not defined in ourself that are inherited # This will be a sequence of tuples with a name as the first item and # an empty tuple as the second. d = {} perms = getattr(ob, '__ac_permissions__', ()) for p in perms: d[p[0]] = None r = gather_permissions(ob.__class__, [], d) if all: if hasattr(ob, '_subobject_permissions'): for p in ob._subobject_permissions(): pname=p[0] if not d.has_key(pname): d[pname]=1 r.append(p) r = list(perms) + r return r def _modifyPermissionMappings(ob, map): """ Modifies multiple role to permission mappings. """ # This mimics what AccessControl/Role.py does. # Needless to say, it's crude. :-( something_changed = 0 perm_info = _ac_inherited_permissions(ob, 1) for name, settings in map.items(): cur_roles = rolesForPermissionOn(name, ob) t = type(cur_roles) if t is StringType: cur_roles = [cur_roles] else: cur_roles = list(cur_roles) changed = 0 for (role, allow) in settings.items(): if not allow: if role in cur_roles: changed = 1 cur_roles.remove(role) else: if role not in cur_roles: changed = 1 cur_roles.append(role) if changed: data = () # The list of methods using this permission. for perm in perm_info: n, d = perm[:2] if n == name: data = d break p = Permission(name, data, ob) p.setRoles(tuple(cur_roles)) something_changed = 1 return something_changed # # Base classes for tools # class ImmutableId(Base): """ Base class for objects which cannot be renamed. """ def _setId(self, id): """ Never allow renaming! """ if id != self.getId(): raise MessageDialog( title='Invalid Id', message='Cannot change the id of this object', action ='./manage_main',) class UniqueObject (ImmutableId): """ Base class for objects which cannot be "overridden" / shadowed. """ __replaceable__ = UNIQUE class SimpleItemWithProperties (PropertyManager, SimpleItem): """ A common base class for objects with configurable properties in a fixed schema. """ manage_options = ( PropertyManager.manage_options + SimpleItem.manage_options) security = ClassSecurityInfo() security.declarePrivate( 'manage_addProperty', 'manage_delProperties', 'manage_changePropertyTypes', ) def manage_propertiesForm(self, REQUEST, *args, **kw): 'An override that makes the schema fixed.' my_kw = kw.copy() my_kw['property_extensible_schema__'] = 0 return apply(PropertyManager.manage_propertiesForm, (self, self, REQUEST,) + args, my_kw) security.declarePublic('propertyLabel') def propertyLabel(self, id): """Return a label for the given property id """ for p in self._properties: if p['id'] == id: return p.get('label', id) return id InitializeClass( SimpleItemWithProperties ) # # "Omnibus" factory framework for tools. # class ToolInit: """ Utility class for generating the factories for several tools. """ __name__ = 'toolinit' security = ClassSecurityInfo() security.declareObjectPrivate() # equivalent of __roles__ = () def __init__(self, meta_type, tools, product_name, icon): self.meta_type = meta_type self.tools = tools self.product_name = product_name self.icon = icon def initialize(self, context): # Add only one meta type to the folder add list. context.registerClass( meta_type = self.meta_type, # This is a little sneaky: we add self to the # FactoryDispatcher under the name "toolinit". # manage_addTool() can then grab it. constructors = (manage_addToolForm, manage_addTool, self,), icon = self.icon ) for tool in self.tools: tool.__factory_meta_type__ = self.meta_type tool.icon = 'misc_/%s/%s' % (self.product_name, self.icon) InitializeClass( ToolInit ) addInstanceForm = HTMLFile('dtml/addInstance', globals()) def manage_addToolForm(self, REQUEST): """ Show the add tool form. """ # self is a FactoryDispatcher. toolinit = self.toolinit tl = [] for tool in toolinit.tools: tl.append(tool.meta_type) return addInstanceForm(addInstanceForm, self, REQUEST, factory_action='manage_addTool', factory_meta_type=toolinit.meta_type, factory_product_name=toolinit.product_name, factory_icon=toolinit.icon, factory_types_list=tl, factory_need_id=0) def manage_addTool(self, type, REQUEST=None): """ Add the tool specified by name. """ # self is a FactoryDispatcher. toolinit = self.toolinit obj = None for tool in toolinit.tools: if tool.meta_type == type: obj = tool() break if obj is None: raise 'NotFound', type self._setObject(obj.getId(), obj) if REQUEST is not None: return self.manage_main(self, REQUEST) # # Now, do the same for creating content factories. # class ContentInit: """ Utility class for generating factories for several content types. """ __name__ = 'contentinit' security = ClassSecurityInfo() security.declareObjectPrivate() def __init__( self , meta_type , content_types , permission=None , extra_constructors=() , fti=() ): self.meta_type = meta_type self.content_types = content_types self.permission = permission self.extra_constructors = extra_constructors self.fti = fti def initialize(self, context): # Add only one meta type to the folder add list. context.registerClass( meta_type = self.meta_type # This is a little sneaky: we add self to the # FactoryDispatcher under the name "contentinit". # manage_addContentType() can then grab it. , constructors = ( manage_addContentForm , manage_addContent , self , ('factory_type_information', self.fti) ) + self.extra_constructors , permission = self.permission ) for ct in self.content_types: ct.__factory_meta_type__ = self.meta_type InitializeClass( ContentInit ) def manage_addContentForm(self, REQUEST): """ Show the add content type form. """ # self is a FactoryDispatcher. ci = self.contentinit tl = [] for t in ci.content_types: tl.append(t.meta_type) return addInstanceForm(addInstanceForm, self, REQUEST, factory_action='manage_addContent', factory_meta_type=ci.meta_type, factory_icon=None, factory_types_list=tl, factory_need_id=1) def manage_addContent( self, id, type, REQUEST=None ): """ Add the content type specified by name. """ # self is a FactoryDispatcher. contentinit = self.contentinit obj = None for content_type in contentinit.content_types: if content_type.meta_type == type: obj = content_type( id ) break if obj is None: raise 'NotFound', type self._setObject( id, obj ) if REQUEST is not None: return self.manage_main(self, REQUEST) def initializeBasesPhase1(base_classes, module): """ Execute the first part of initialization of ZClass base classes. Stuffs a _ZClass_for_x class in the module for each base. """ rval = [] for base_class in base_classes: d={} zclass_name = '_ZClass_for_%s' % base_class.__name__ exec 'class %s: pass' % zclass_name in d Z = d[ zclass_name ] Z.propertysheets = PropertySheets() Z._zclass_ = base_class Z.manage_options = () Z.__module__ = module.__name__ setattr( module, zclass_name, Z ) rval.append(Z) return rval def initializeBasesPhase2(zclasses, context): """ Finishes ZClass base initialization. o 'zclasses' is the list returned by initializeBasesPhase1(). o 'context' is a ProductContext object. """ for zclass in zclasses: context.registerZClass(zclass) def registerIcon(klass, iconspec, _prefix=None): """ Make an icon available for a given class. o 'klass' is the class being decorated. o 'iconspec' is the path within the product where the icon lives. """ modname = klass.__module__ pid = modname.split('.')[1] name = os_path.split(iconspec)[1] klass.icon = 'misc_/%s/%s' % (pid, name) icon = ImageFile(iconspec, _prefix) icon.__roles__=None if not hasattr(misc_images, pid): setattr(misc_images, pid, MiscImage(pid, {})) getattr(misc_images, pid)[name]=icon # # StructuredText handling. # # XXX: This section is mostly workarounds for things fixed in the # core, and should go away soon. # class CMFDocumentClass( StructuredText.DocumentWithImages.__class__ ): """ Override DWI to get '_' into links, and also turn on inner/named links. """ text_types = [ 'doc_named_link', 'doc_inner_link', ] + _STXDWI.text_types _URL_AND_PUNC = r'([a-zA-Z0-9_\@\.\,\?\=\&\+\!\/\:\;\-\#\~]+)' def doc_href( self , s , expr1 = re.compile( _STXDWI._DQUOTEDTEXT + "(:)" + _URL_AND_PUNC + _STXDWI._SPACES ).search , expr2 = re.compile( _STXDWI._DQUOTEDTEXT + r'(\,\s+)' + _URL_AND_PUNC + _STXDWI._SPACES ).search ): return _STXDWI.doc_href( self, s, expr1, expr2 ) CMFDocumentClass = CMFDocumentClass() class CMFHtmlWithImages( HTMLWithImages ): """ Special subclass of HTMLWithImages, overriding document() """ def document(self, doc, level, output): """\ HTMLWithImages.document renders full HTML (head, title, body). For CMF Purposes, we don't want that. We just want those nice juicy body parts perfectly rendered. """ for c in doc.getChildNodes(): getattr(self, self.element_types[c.getNodeName()])(c, level, output) CMFHtmlWithImages = CMFHtmlWithImages() def format_stx( text, level=1 ): """ Render STX to HTML. """ st = StructuredText.Basic( text ) # Creates the basic DOM if not st: # If it's an empty object return "" # return now or have errors! doc = CMFDocumentClass( st ) html = CMFHtmlWithImages( doc, level ) return html _format_stx = format_stx # XXX: Deprecated spelling # # Metadata Keyword splitter utilities # KEYSPLITRE = re.compile(r'[,;]') def keywordsplitter( headers , names=('Subject', 'Keywords',) , splitter=KEYSPLITRE.split ): """ Split keywords out of headers, keyed on names. Returns list. """ out = [] for head in names: keylist = splitter(headers.get(head, '')) keylist = map(lambda x: x.strip(), keylist) out.extend(filter(operator.truth, keylist)) return out # # Directory-handling utilities # def normalize(p): return os_path.abspath(os_path.normcase(os_path.normpath(p))) normINSTANCE_HOME = normalize(INSTANCE_HOME) normSOFTWARE_HOME = normalize(SOFTWARE_HOME) separators = (os.sep, os.altsep) def expandpath(p): # Converts a minimal path to an absolute path. p = os_path.normpath(p) if os_path.isabs(p): return p abs = os_path.join(normINSTANCE_HOME, p) if os_path.exists(abs): return abs return os_path.join(normSOFTWARE_HOME, p) def minimalpath(p): # Trims INSTANCE_HOME or SOFTWARE_HOME from a path. p = os_path.abspath(p) abs = normalize(p) l = len(normINSTANCE_HOME) if abs[:l] != normINSTANCE_HOME: l = len(normSOFTWARE_HOME) if abs[:l] != normSOFTWARE_HOME: # Can't minimize. return p p = p[l:] while p[:1] in separators: p = p[1:] return p CMF-1.3/CMFCore/version.txt0100644000076500007650000000000407523373537015355 0ustar tseavertseaver1.3 CMF-1.3/CMFCore/images/0040755000076500007650000000000007524010063014364 5ustar tseavertseaverCMF-1.3/CMFCore/images/cookie.gif0100644000076500007650000000041607245471214016333 0ustar tseavertseaverGIF89amB#ش^:߾`ԩlϮƙʟnb2Ϩv{Iŕ\۾!Cookie! , $1,E\ qh"؃ ѳ! /à U-<e haux"F0@ȵBab0: Ch   [ [ " E ,!;CMF-1.3/CMFCore/images/dirview.gif0100644000076500007650000000025307245471214016532 0ustar tseavertseaverGIF89aeee4.PPP!,XI+x:Hچt_P-+F 1G>GG"h:]XI)\lA+<rɷ|I<;CMF-1.3/CMFCore/images/fsdtml.gif0100644000076500007650000000017607245471214016356 0ustar tseavertseaverGIF89aeee4.@!,Cx- :&o6 G&p.RC\=0@,~0 h%jd ;CMF-1.3/CMFCore/images/fsimage.gif0100644000076500007650000000025607245471214016477 0ustar tseavertseaverGIF89aeee4.PPP@!,[A ;Cye&Q FgB ' re0gh>R9"@p5@.0b=jvLe^ne;CMF-1.3/CMFCore/images/fsprops.gif0100644000076500007650000000031507245471214016554 0ustar tseavertseaverGIF89aeee4.PPP!Filesystem Properties Object!,ZI<1ap(G>E!I JZ,ӂF\\f#L:`xbZc<&v4YlD;CMF-1.3/CMFCore/images/fspt.gif0100644000076500007650000000026507403764773016053 0ustar tseavertseaverGIF89aPPP@@@eee4.!,bI 2V&c}' je@s) 820h$8P bZm $"| bV7jmi6l4ymk;CMF-1.3/CMFCore/images/fspy.gif0100644000076500007650000000042007256170457016045 0ustar tseavertseaverGIF89a << Type object registry objects. """ from Interface import Attribute, Base class ContentTypeRegistryPredicate(Base): """\ Express a rule for matching a given name/typ/body. """ def __call__(name, typ, body): """ Return true if the rule matches, else false. """ def getTypeLabel(): """ Return a human-readable label for the predicate type. """ def edit(**kw): """ Update the predicate. """ def predicateWidget(): """\ Return a snipped of HTML suitable for editing the predicate; the snippet should arrange for values to be marshalled by ZPublisher as a ':record', with the ID of the predicate as the name of the record. The registry will call the predictate's 'edit' method, passing the fields of the record. """ class ContentTypeRegistry(Base): """ Registry for rules which map PUT args to a CMF Type Object. """ def findTypeName(name, typ, body): """\ Perform a lookup over a collection of rules, returning the the Type object corresponding to name/typ/body. Return None if no match found. """ CMF-1.3/CMFCore/interfaces/Contentish.py0100644000076500007650000000510207401232660017730 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import Interface class Contentish(Interface.Base): """ General interface for "contentish" items. These methods need to be implemented by any class that wants to be a first-class citizen in the Portal Content world. PortalContent implements this interface. """ def getIcon(relative_to_portal=0): """ This method returns the path to an object's icon. It is used in the folder_contents view to generate an appropriate icon for the items found in the folder. If the content item does not define an attribute named "icon" this method will return the path "/misc_/dtmldoc.gif", which is the icon used for DTML Documents. If 'relative_to_portal' is true, return only the portion of the icon's URL which finds it "within" the portal; otherwise, return it as an absolute URL. """ def listActions(): """ listAction returns a tuple containing dictionaries that describe a specific "action". An "action" shows up as a link in the PTK toolbox which has a title, a URL, a category (the action can be applied at the object- or user-level or everywhere) and the permissions needed to show the action link. listActions can be used to provide actions specific to your content object. """ def SearchableText(): """ SearchableText is called to provide the Catalog with textual information about your object. It is a string usually generated by concatenating the string attributes of your content class. This string can then be used by the catalog to index your document and make it findable through the catalog. """ def allowedRolesAndUsers(permission='View'): """ Return a list of roles and users with View permission. Used by PortalCatalog to filter out items you're not allowed to see. """ CMF-1.3/CMFCore/interfaces/Discussions.py0100644000076500007650000001347007401232660020127 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## def HTMLFile( unused ): """ Stub for method loaded from DTML document. """ class Discussable: """ Discussable is the interface for things which can have responses. It is implemented by PTKBase.Discussions.Discussable. That class is designed to mix-in with a PortalContent-derived class. It has already been mixed-in with the actual PortalContent class, so at present, any PTK object can support replies. This interface contains some bogosity. Things like replyForm, replyPreview and createReply really shouldn't be done here. The interface presently assumes that there is only one sort of object which the user would ever want to use to create a reply. This is a bad assumption, and needs to be addressed! """ threadView = HTMLFile('...') """ The threadView method should return an HTML page which displays this item's children (and optionally parents, though not all Discussables can have parents). Permission: View """ replyForm = HTMLFile('...') """ The replyForm method should return an HTML page which presents an interface to enter a reply. It should have buttons for submiting the form contents to replyPreview and createReply. Permission: Reply to item """ replyPreview = HTMLFile('...') """ This method needs to present the contents submitted from the replyForm form with a template similar to what the actual response object would use. It should provide submit buttons linked to replyForm (for 'Edit') and createReply (for 'Reply'). Permission: Reply to item """ def createReply(self, title, text, REQUEST, RESPONSE): """ Create a reply in the proper place. See the next method for more information. Permission: Reply to item Returns: HTML (directly or via redirect) """ def getReplyLocationAndID(self, REQUEST): """ This method determines where a user's reply should be stored, and what it's ID should be. You don't really want to force users to have to select a unique ID each time they want to reply to something. The present implementation selects a folder in the Member's home folder called 'Correspondence' (creating it if it is missing) and finds a free ID in that folder. createReply should use this method to determine what the reply it creates should be called, and where it should be placed. This method (and createReply, I expect) do not really belong in this interface. There should be a DiscussionManager singleton (probably the portal object itself) which handles this. Permissions: None assigned Returns: 2-tuple, containing the container object, and a string ID. """ def getReplyResults(self): """ Return the ZCatalog results that represent this object's replies. Often, the actual objects are not needed. This is less expensive than fetching the objects. Permissions: View Returns: sequence of ZCatalog results representing DiscussionResponses """ def getReplies(self): """ Return a sequence of the DiscussionResponse objects which are associated with this Discussable Permissions: View Returns: sequence of DiscussionResponses """ def quotedContents(self): """ Return this object's contents in a form suitable for inclusion as a quote in a response. The default implementation returns an empty string. It might be overridden to return a '>' quoted version of the item. """ def allowReplies(self): """ This method must return a logically true value if an object is willing to support replies. Permissions: None assigned Returns: truth value """ class DiscussionResponse: """ This interface describes the behaviour of a Discussion Response. It is implemented in PTKBase.Discussions.DiscussionResponse. This implementation is also designed to be mixed together with PortalContent. This has been done in the PTK.DiscussionItem.DiscussionItem class, which the PTK presently uses for all replies. """ def inReplyTo(self, REQUEST=None): """ Return the Discussable object which this item is associated with Permissions: None assigned Returns: a Discussable object """ def setReplyTo(self, reply_to): """ Make this object a response to the passed object. (Will also accept a path in the form of a string.) If reply_to does not support or accept replies, a ValueError will be raised. (This does not seem like the right exception.) Permissions: None assigned Returns: None """ def parentsInThread(self, size=0): """ Return the list of object which are this object's parents, from the point of view of the threaded discussion. Parents are ordered oldest to newest. If 'size' is not zero, only the closest 'size' parents will be returned. """ CMF-1.3/CMFCore/interfaces/DublinCore.py0100644000076500007650000001434407401232660017650 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import Interface class DublinCore(Interface.Base): """ Define which Dublin Core metadata elements are supported by the PTK, and the semantics therof. """ def Title(): """ Dublin Core element - resource name Return type: string Permissions: View """ def Creator(): """ Dublin Core element - resource creator Return the full name(s) of the author(s) of the content object. Return type: string Permission: View """ def Subject(): """ Dublin Core element - resource keywords Return zero or more keywords associated with the content object. Return type: sequence of strings Permission: View """ def Description(): """ Dublin Core element - resource summary Return a natural language description of this object. Return type: string Permissions: View """ def Publisher(): """ Dublin Core element - resource publisher Return full formal name of the entity or person responsible for publishing the resource. Return type: string Permission: View """ def Contributors(): """ Dublin Core element - resource collaborators Return zero or additional collaborators. Return type: sequence of strings Permission: View """ def Date(): """ Dublin Core element - default date Return type: string, formatted 'YYYY-MM-DD H24:MN:SS TZ' Permissions: View """ def CreationDate(): """ Dublin Core element - date resource created. Return type: string, formatted 'YYYY-MM-DD H24:MN:SS TZ' Permissions: View """ def EffectiveDate(): """ Dublin Core element - date resource becomes effective. Return type: string, formatted 'YYYY-MM-DD H24:MN:SS TZ' Permissions: View """ def ExpirationDate(): """ Dublin Core element - date resource expires. Return type: string, formatted 'YYYY-MM-DD H24:MN:SS TZ' Permissions: View """ def ModificationDate(): """ Dublin Core element - date resource last modified. Return type: string, formatted 'YYYY-MM-DD H24:MN:SS TZ' Permissions: View """ def Type(): """ Dublin Core element - resource type Return a human-readable type name for the resource (perhaps mapped from its Zope meta_type). Return type: string Permissions: View """ def Format(): """ Dublin Core element - resource format Return the resource's MIME type (e.g., 'text/html', 'image/png', etc.). Return type: string Permissions: View """ def Identifier(): """ Dublin Core element - resource ID Returns unique ID (a URL) for the resource. Return type: string Permissions: View """ def Language(): """ Dublin Core element - resource language Return the RFC language code (e.g., 'en-US', 'pt-BR') for the resource. Return type: string Permissions: View """ def Rights(): """ Dublin Core element - resource copyright Return a string describing the intellectual property status, if any, of the resource. for the resource. Return type: string Permissions: View """ class CatalogableDublinCore(Interface.Base): """ Provide Zope-internal date objects for cataloging purposes. """ def created(): """ Dublin Core element - date resource created, Return type: DateTime Permissions: View """ def effective(): """ Dublin Core element - date resource becomes effective, Return type: DateBound Permissions: View """ def expires(): """ Dublin Core element - date resource expires, Return type: DateBound Permissions: View """ def modified(): """ Dublin Core element - date resource last modified, Return type: DateTime Permissions: View """ class MutableDublinCore(Interface.Base): """ Update interface for mutable metadata. """ def setTitle(title): "Dublin Core element - update resource name" def setSubject(subject): "Dublin Core element - update resource keywords" def setDescription(description): "Dublin Core element - update resource summary" def setContributors(contributors): "Dublin Core element - update additional contributors to resource" def setEffectiveDate(effective_date): """ Dublin Core element - update date resource becomes effective. """ def setExpirationDate(expiration_date): """ Dublin Core element - update date resource expires. """ def setFormat(format): """ Dublin Core element - update resource format """ def setLanguage(language): """ Dublin Core element - update resource language """ def setRights(rights): """ Dublin Core element - update resource copyright """ CMF-1.3/CMFCore/interfaces/IndexableContent.py0100644000076500007650000000510007401232660021036 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## class IndexableContent: """ Interface for indexing by PortalCatalog. PortalContent implements this interface. The existing portions of this interface are firm. """ def Title(self): """ Dublin Core element - resource name Used for indexing. By default, simply returns self.title. Return: string Permission: 'View' """ def Creator(self): """ Dublin Core element - resource creator Used for indexing, but may be used anywhere. If there are multiple owners, returns only the name of the first found. Return: string Permission: 'View' """ def Date(self): """ Dublin Core element - effective date Used for indexing. This is not necessarily the creation or modification date-- object can be future-dated so that they can automagically appear on the portal at the appropreate time. Return: DateTime Permission: 'View' """ def Description(self): """ Dublin Core element - summary Used for indexing. This is typically a plain-english description of the contents of this particular object. Return: string Permission: 'View' """ def Subject(self): """ Dublin Core elment - Topical keywords This is a list of user-defined keywords. Return: list of strings """ def SearchableText(self): """ Returns a concatenation of all searchable text Used for indexing. Probably shouldn't be used elsewhere. PortalContent subclasses should use this to return a concatenation of any text you would like the user to be able to search against in a standard, full-text search. Return: string Permission: 'View' """ CMF-1.3/CMFCore/interfaces/Membership.py0100644000076500007650000001056207401232660017713 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## class Member: """ The Member interface includes the Zope BasicUser interface. This document describes the additional methods the PTK Member interface requires. A Member is also a PTK Toolbox Actions provider. Unlike ZClass property sheets, properties defined by a member's propertysheets are not necessarily available as attributes of the member. They should be explicitly referenced through the PropertySheet or PropertySheets interfaces. If you depend on them being available as attributes of the member, or upon members having a 'propertysheets' attribute, your code will break in the not-so-distant future. """ def getHomeUrl(self): """ Returns a URL to this user's Member folder. This URL is not necesarily sanity- or reality-checked in any way. This method is implemented by BTKBase.MemberBase and by PersistentUserSource.MemberMixin. Returns: string Permissions: None assigned """ def setMemberProperties(self, REQUEST): """ Search the user's propertysheets for properties with values in the REQUEST variable, and update them with the REQUEST's value. This method is implemented by BTKBase.MemberBase and by PersistentUserSource.MemberMixin. Returns: None Permissions: None assigned """ def PropertySheets(self): """ Return a list of all of the member's property sheets. This method is implemented by BTKBase.MemberBase and by PersistentUserSource.MemberMixin. Returns: list Permissions: none assigned """ def changeUser(self, password, roles, domains): """ Set the user's basic security properties. LoginManager will contain these basic properties in a designated propertysheet. Since this sheet does not yet exist, and since (in the interm) the PTK is supporting multiple user folder-like objects with different methods of handling these properties, changeUser is being provided as a Member method. Eventually, it will just be a shorthand which attempts to set the values of the appropreate property sheet. This method is implemented by BTKBase.MemberBase and by PersistentUserSource.MemberMixin. Returns: None Permissions: None assigned (this is probably an important one to fix) """ class MemberFolder: """ The Member Folder is the PTK's acl_users object. This interface has been threatening to dissapear for a couple weeks. This is because this interface does not add anything to the BasicUserFolder interface that could not be better placed elsewhere. (For example, the PortalObject.) * addMember has been moved to the Portal interface. This interface document describes the additional methods over BasicUserFolder which this interface requires. """ def is_ssl(self, REQUEST): """ This method does not seem to be used anywhere presently. It was inherited from my initial codebase. This will go away when PTK officially moves to LoginManager. If you need information like this, you can discover it with an appropreate LoginMethod. Returns: true if REQUEST came via an SSL connection, false otherwise Permissions: None assigned """ def __bobo_traverse__(self, REEQUEST, name=None): """ Handle object traversal to Member objects. LoginManager also provides this service. Returns: Member object, or a containted Zope object Raises: 'Not Found' if 'name' refers to an unknown resource Permissions: None assigned """ CMF-1.3/CMFCore/interfaces/PortalContentRegistration.py0100644000076500007650000000356507401232660023014 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ These functions are contained in the PTKBase.register module. They are used to register the existance of new PortalContent classes. """ def registerPortalContent(instance_class, meta_type='', constructors=(), action='', permission="Add portal content", icon=None, ): """ This function is used to register a new PortalContent class with the PTK. It will install a constructor and an icon, and register the class in PortalFolder's meta_types. For an example of use, see the end of PTKBase/Document.py. 'instance_class' is the PortalContent-derived class to register. 'meta_type' is it's plain-language name. If not explicitly given, the meta_type will be taken from the instance_class. 'contructors' is a sequence of constructor functions. The first is placed as a method on PortalFolder. The rest are presently discarded. 'action' is the relative URL of the add form or wizard. 'permission' is the name of the permission required to instantiate this class. 'icon' is the name of an image file to use as the document's icon. """ CMF-1.3/CMFCore/interfaces/ReviewableContent.py0100644000076500007650000000476507401232660021250 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## class ReviewableContent: """ Interface for Portal reviewing/publishing. Reviewing/publishing is the process of manipulating the 'View' permission of an object. Assigning different roles to the 'View' permission makes the object visible to different groups of people. This interface seems quite stable. A couple more hooks may be added. PortalContent implements this interface. """ def publish(self, REQUEST): """ Returns the reviewing management interface. Return: HTML page Permission: 'View management screens' """ def setReviewState(self, review_state, comments, effective_date, REQUEST): """ Handle a web request to change the review state. 'review_state' is the desired state. This method needs to verify that the authenticated member is allowed to change to this state, and that the change makes sense. 'comments' are the user-supplied comments to be associated with this action in the review history. 'effective_date' is a string representation of a date, which should be passwd to set_effective_date if it differs from the presently set effective_date. Return: HTML page Permission: None bound, checks for 'Request review', 'Review item'. """ # Effective date methods # --------------------- def set_effective_date(self, effective_date, REQUEST): """ Set the effective_date property This is when this resource becomes available to be published. TODO: Shouldn't return an HTML page in all cases. Should accept a DataTime object as well as a string. Arguments: effective_date -- a DateTime parsable string Return: HTML page Permission: 'Request review' """ CMF-1.3/CMFCore/interfaces/Syndicatable.py0100644000076500007650000000242507401232660020221 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """\ Declare interface for synContentValues """ import Interface class Syndicatable(Interface.Base): """\ Returns back a list of objects which implements the DublinCore. """ def synContentValues(self): """ Returns a list of results which is to be Syndicated. For example, the normal call contentValues (on PortalFolders) returns a list of subObjects of the current object (i.e. objectValues with filtering applied). For the case of a Topic, one would return a sequence of objects from a catalog query, not the subObjects of the Topic. What is returned must implement the DublinCore. """ CMF-1.3/CMFCore/interfaces/__init__.py0100644000076500007650000000004307405532116017353 0ustar tseavertseaver""" CMFCore.interfaces package """ CMF-1.3/CMFCore/interfaces/portal_actions.py0100644000076500007650000000675107522303413020645 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Actions tool interface description. $Id: portal_actions.py,v 1.7.22.1 2025/08/01 19:07:55 tseaver Exp $ """ from Interface import Base, Attribute class portal_actions(Base): '''Gathers a list of links which the user is allowed to view according to the current context. ''' id = Attribute('id', 'Must be set to "portal_actions"') # listActionProviders__roles__ = ( 'Manager', ) def listActionProviders(): """ Lists all action provider names registered with the actions tool. """ # addActionProvider__roles__ = ( 'Manager', ) def addActionProvider( provider_name ): """ Add a new action provider to the providers known by the actions tool. A provider must implement listActions. The provider is only added is the actions tool can find the object corresponding to the provider_name """ # deleteActionProvider__roles__ = ( 'Manager', ) def deleteActionProvider( provider_name ): """ Deletes an action provider name from the providers known to the actions tool. The deletion only takes place if provider_name is actually found among the known action providers. """ # listFilteredActionsFor__roles__ = None def listFilteredActionsFor(object): '''Gets all actions available to the user and returns a mapping containing a list of user actions, folder actions, object actions, and global actions. Each action has the following keys: name: An identifying action name url: The URL to visit to access the action permissions: A list. The user must have at least of the listed permissions to access the action. If the list is empty, the user is allowed. (Note that listFilteredActions() filters out actions according to this field.) category: One of "user", "folder", "object", or "globals". ''' # listFilteredActions__roles__ = None def listFilteredActions(): '''Gets all actions available to the user in no particular context. ''' class ActionProvider(Base): '''The interface expected of an object that can provide actions. ''' # listActions__roles__ = () # No permission. def listActions(info): '''Support for the old list of mappings is currently supported: Returns a list of mappings describing actions. Each action should contain the keys "name", "url", "permissions", and "category", conforming to the specs outlined in portal_actions.listFilteredActionsFor(). The info argument contains at least the following attributes, some of which may be set to "None": isAnonymous portal portal_url folder folder_url content content_url The new way of doing this is.... ''' CMF-1.3/CMFCore/interfaces/portal_catalog.py0100644000076500007650000000363007522303413020610 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Catalog tool interface description. $Id: portal_catalog.py,v 1.5.4.1 2025/08/01 19:07:55 tseaver Exp $ """ from Interface import Attribute, Base class portal_catalog(Base): '''This tool interacts with a customized ZCatalog. ''' id = Attribute('id', 'Must be set to "portal_catalog"') # searchResults inherits security assertions from ZCatalog. def searchResults(REQUEST=None, **kw): '''Calls SiteIndex.searchResults() with extra arguments that limit the results to what the user is allowed to see. ''' # __call__ inherits security assertions from ZCatalog. def __call__(REQUEST=None, **kw): '''Same as searchResults().''' # indexObject__roles__ = () # Called only by Python code. def indexObject(object): '''Add to catalog. ''' # unindexObject__roles__ = () def unindexObject(object): '''Remove from catalog. ''' # reindexObject__roles__ = () def reindexObject(object, idxs=[]): '''Update entry in catalog. The optional idxs argument is a list of specific indexes to update (all of them by default). ''' # getpath inherits security assertions from ZCatalog. def getpath(data_record_id_): '''Calls ZCatalog.getpath(). ''' CMF-1.3/CMFCore/interfaces/portal_discussion.py0100644000076500007650000000343707522303413021366 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Discussion tool interface description. $Id: portal_discussion.py,v 1.5.36.1 2025/08/01 19:07:55 tseaver Exp $ """ from Interface import Attribute, Base class portal_discussion(Base): """ Links content to discussions. """ id = Attribute('id', 'Must be set to "portal_discussion"') #getDiscussionFor__roles__ = None def getDiscussionFor(content): """ Find / create the DiscussionItemContainer for 'content'. """ #isDiscussionAllowedFor__roles__ = None def isDiscussionAllowedFor(content): """ Return a boolean indicating whether discussion is allowed for the specified content; this may be looked up via an object-specific value, or by place, or from a site-wide policy. """ #getDiscussionFor__roles__ = None def overrideDiscussionFor(content, allowDiscussion): """ if 'allowDiscussion' is None, then clear any overridden setting for discussability, letting the site's default policy apply. Otherwise, set the override to match the boolean equivalent of 'allowDiscussion'. """ CMF-1.3/CMFCore/interfaces/portal_memberdata.py0100644000076500007650000000373407522303413021304 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Membership data storage tool interface description. $Id: portal_memberdata.py,v 1.4.36.1 2025/08/01 19:07:55 tseaver Exp $ """ from Interface import Attribute, Base class portal_memberdata(Base): '''A helper for portal_membership that transparently adds member data to user objects. ''' id = Attribute('id', 'Must be set to "portal_memberdata"') ## wrapUser__roles__ = () # Private. def wrapUser(u): ''' If possible, returns the Member object that corresponds to the given User object. ''' ## getMemberDataContents__roles__ = () # Private. def getMemberDataContents(): ''' Returns a list containing a dictionary with information about the _members BTree contents: member_count is the total number of member instances stored in the memberdata- tool while orphan_count is the number of member instances that for one reason or another are no longer in the underlying acl_users user folder. The result is designed to be iterated over in a dtml-in ''' ## pruneMemberDataContents__roles__ = () # Private. def pruneMemberDataContents(): ''' Compare the user IDs stored in the member data tool with the list in the actual underlying acl_users and delete anything not in acl_users ''' CMF-1.3/CMFCore/interfaces/portal_membership.py0100644000076500007650000001156207522303413021334 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Membership tool interface description. $Id: portal_membership.py,v 1.4.36.1 2025/08/01 19:07:55 tseaver Exp $ """ from Interface import Attribute, Base class portal_membership(Base): '''Deals with the details of how and where to store and retrieve members and their member folders. ''' id = Attribute('id', 'Must be set to "portal_membership"') #getAuthenticatedMember__roles__ = None # Allow all. def getAuthenticatedMember(): ''' Returns the currently authenticated member object or the Anonymous User. ''' #isAnonymousUser__roles__ = None # Allow all. def isAnonymousUser(): ''' Returns 1 if the user is not logged in. ''' #checkPermission__roles__ = None # Allow all. def checkPermission(permissionName, object, subobjectName=None): ''' Checks whether the current user has the given permission on the given object or subobject. ''' #credentialsChanged__roles__ = None # Allow all. def credentialsChanged(password): ''' Notifies the authentication mechanism that this user has changed passwords. This can be used to update the authentication cookie. Note that this call should *not* cause any change at all to user databases. ''' # getHomeFolder__roles__ = None # Anonymous permission def getHomeFolder(id=None, verifyPermission=0): """Returns a member's home folder object or None. Set verifyPermission to 1 to return None when the user doesn't have the View permission on the folder. """ # getHomeUrl__roles__ = None # Anonymous permission def getHomeUrl(id=None, verifyPermission=0): """Returns the URL to a member's home folder or None. Set verifyPermission to 1 to return None when the user doesn't have the View permission on the folder. """ # permission: 'Manage portal' def getMemberById(id): ''' Returns the given member. ''' # permission: 'Manage portal' def listMemberIds(): '''Lists the ids of all members. This may eventually be replaced with a set of methods for querying pieces of the list rather than the entire list at once. ''' # permission: 'Manage portal' def listMembers(): '''Gets the list of all members. ''' #addMember__roles__ = () # No permission. def addMember(id, password, roles, domains): '''Adds a new member to the user folder. Security checks will have already been performed. Called by portal_registration. ''' # getPortalRoles__roles__ = () # Private def getPortalRoles(): """ Return all local roles defined by the portal itself, which means roles that are useful and understood by the portal object """ # setRoleMapping__roles__ = () # Private def setRoleMapping(portal_role, userfolder_role): """ set the mapping of roles between roles understood by the portal and roles coming from outside user sources """ # getMappedRole__roles__ = () # Private def getMappedRole(portal_role): """ returns a role name if the portal role is mapped to something else or an empty string if it is not """ # getMemberareaCreationFlag__roles__ = () # Private def getMemberareaCreationFlag(): """ Returns the flag indicating whether the membership tool will create a member area if an authenticated user from an underlying user folder logs in first without going through the join process """ # setMemberareaCreationFlag__roles__ = () # Private def setMemberareaCreationFlag(): """ sets the flag indicating whether the membership tool will create a member area if an authenticated user from an underlying user folder logs in first without going through the join process """ # createMemberarea__roles__ = () # Private def createMemberarea(member_id): """ create a member area, only used if members are sourced from an independent underlying user folder and not just from the join process """ CMF-1.3/CMFCore/interfaces/portal_metadata.py0100644000076500007650000000605507401232661020764 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Type registration tool interface description. """ from Interface import Attribute, Base class portal_metadata(Base): """ CMF metadata policies interface. """ # # Site-wide queries. # def getFullName(userid): """ Convert an internal userid to a "formal" name, if possible, perhaps using the 'portal_membership' tool. Used to map userid's for Creator, Contributor DCMI queries. """ def getPublisher(): """ Return the "formal" name of the publisher of the portal. """ # # Content-specific queries. # def listAllowedSubjects(content=None): """ List the allowed values of the 'Subject' DCMI element 'Subject' elements should be keywords categorizing their resource. Return only values appropriate for content's type, or all values if None. """ def listAllowedFormats(content=None): """ List the allowed values of the 'Format' DCMI element. These items should be usable as HTTP 'Content-type' values. Return only values appropriate for content's type, or all values if None. """ def listAllowedLanguages(content=None): """ List the allowed values of the 'Language' DCMI element. 'Language' element values should be suitable for generating HTTP headers. Return only values appropriate for content's type, or all values if None. """ def listAllowedRights(content=None): """ List the allowed values of the 'Rights' DCMI element. The 'Rights' element describes copyright or other IP declarations pertaining to a resource. Return only values appropriate for content's type, or all values if None. """ # # Validation policy hooks. # def setInitialMetadata(content): """ Set initial values for content metatdata, supplying any site-specific defaults. """ def validateMetadata(content): """ Enforce portal-wide policies about DCI, e.g., requiring non-empty title/description, etc. Called by the CMF immediately before saving changes to the metadata of an object. """ CMF-1.3/CMFCore/interfaces/portal_registration.py0100644000076500007650000000670607522303413021717 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Registration tool interface description. $Id: portal_registration.py,v 1.4.36.1 2025/08/01 19:07:55 tseaver Exp $ """ from Interface import Attribute, Base class portal_registration(Base): '''Establishes policies for member registration. Depends on portal_membership. Is not aware of membership storage details. ''' id = Attribute('id', 'Must be set to "portal_registration"') #isRegistrationAllowed__roles__ = None # Anonymous permission def isRegistrationAllowed(REQUEST): '''Returns a boolean value indicating whether the user is allowed to add a member to the portal. ''' #testPasswordValidity__roles__ = None # Anonymous permission def testPasswordValidity(password, confirm=None): '''If the password is valid, returns None. If not, returns a string explaining why. ''' #testPropertiesValidity__roles__ = None # Anonymous permission def testPropertiesValidity(new_properties, member=None): '''If the properties are valid, returns None. If not, returns a string explaining why. ''' #generatePassword__roles__ = None # Anonymous permission def generatePassword(): '''Generates a password which is guaranteed to comply with the password policy. ''' # permission: 'Add portal member' def addMember(id, password, roles=('Member',), domains='', properties=None): '''Creates a PortalMember and returns it. The properties argument can be a mapping with additional member properties. Raises an exception if the given id already exists, the password does not comply with the policy in effect, or the authenticated user is not allowed to grant one of the roles listed (where Member is a special role that can always be granted); these conditions should be detected before the fact so that a cleaner message can be printed. ''' # permission: 'Add portal member' def isMemberIdAllowed(id): '''Returns 1 if the ID is not in use and is not reserved. ''' #afterAdd__roles__ = () # No permission. def afterAdd(member, id, password, properties): '''Called by portal_registration.addMember() after a member has been added successfully.''' # permission: 'Mail forgotten password' def mailPassword(forgotten_userid, REQUEST): '''Email a forgotten password to a member. Raises an exception if user ID is not found. ''' # permission: 'Set own password' def setPassword(password, domains=None): '''Allows the authenticated member to set his/her own password. ''' # permission: 'Set own properties' def setProperties(properties=None, **kw): '''Allows the authenticated member to set his/her own properties. ''' CMF-1.3/CMFCore/interfaces/portal_skins.py0100644000076500007650000000262507522303413020330 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Skins tool interface description. $Id: portal_skins.py,v 1.4.36.1 2025/08/01 19:07:55 tseaver Exp $ """ from Interface import Attribute, Base class portal_skins(Base): '''An object that provides skins to a portal object. ''' id = Attribute('id', 'Must be set to "portal_skins"') # getSkin__roles__ = () # Private def getSkin(request): ''' Returns the requested skin object as a tuple: (skinob, skinpath). Note that self will not normally be wrapped in acquisition, but the request variable is provided so it is possible to access the REQUEST object. ''' # getSkinSelections__roles__ = None # Public def getSkinSelections(): ''' Returns the sorted list of available skin names. ''' CMF-1.3/CMFCore/interfaces/portal_types.py0100644000076500007650000000751707523332620020355 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Type registration tool interface description. $Id: portal_types.py,v 1.10.4.2 2025/08/04 23:15:28 efge Exp $ """ from Interface import Attribute, Base class ContentTypeInformation(Base): """ Registry entry interface. """ def Metatype(): """ Return the Zope 'meta_type' for this content object. """ def Title(): """ Return the "human readable" type name (note that it may not map exactly to the 'meta_type', e.g., for l10n/i18n or where a single content class is being used twice, under different names. """ def Description(): """ Textual description of the class of objects (intended for display in a "constructor list"). """ def isConstructionAllowed(container): """ Does the current user have the permission required in order to construct an instance? """ def allowType(contentType): """ Can objects of 'contentType' be added to containers whose type object we are? """ def constructInstance(container, id): """ Build a "bare" instance of the appropriate type in 'container', using 'id' as its id. Return the instance, seated in the container. """ def allowDiscussion(): """ Can this type of object support discussion? """ def getActionById(id): """ Return the URL of the action whose ID is id. """ def getIcon(): """ Returns the portal-relative icon for this type. """ class portal_types(Base): """ Provides a configurable registry of portal content types. """ id = Attribute('id', 'Must be set to "portal_types"') # getType__roles__ = None # Public def getTypeInfo(contentType): """ Return an instance which implements the ContentTypeInformation interface, corresponding to the specified 'contentType'. If contentType is actually an object, rather than a string, attempt to look up the appropriate type info using its portal_type. """ # listTypeInfo__roles__ = None # Public def listTypeInfo(container=None): """ Return a sequence of instances which implement the ContentTypeInformation interface, one for each content type regisetered in the portal. If the container is specified, the list will be filtered according to the user's permissions. """ def listContentTypes(container=None, by_metatype=0): """ Return list of content types, or the equivalent metatypes; if 'container' is passed, then filter the list to include only types which are addable in 'container'. """ def constructContent(contentType, container, id, RESPONSE=None , *args, **kw): """ Build an instance of the appropriate content class in 'container', using 'id'. If RESPONSE is provided, redirect to the new object's "initial view". """ CMF-1.3/CMFCore/interfaces/portal_undo.py0100644000076500007650000000255207522303413020145 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Undo tool interface description. $Id: portal_undo.py,v 1.3.36.1 2025/08/01 19:07:55 tseaver Exp $ """ from Interface import Attribute, Base class portal_undo(Base): '''Provides access to Zope undo functions. ''' id = Attribute('id', 'Must be set to "portal_undo"') # permission: 'Undo changes' def listUndoableTransactionsFor(object, first_transaction=None, last_transaction=None, PrincipiaUndoBatchSize=None): '''Lists all transaction IDs the user is allowed to undo. ''' # permission: 'Undo changes' def undo(object, transaction_info): '''Performs an undo operation. ''' CMF-1.3/CMFCore/interfaces/portal_workflow.py0100644000076500007650000001707407522303413021057 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Workflow tool interface description. $Id: portal_workflow.py,v 1.8.6.2 2025/08/01 19:07:55 tseaver Exp $ """ from Interface import Attribute, Base _marker = [] class portal_workflow(Base): '''This tool accesses and changes the workflow state of content. ''' id = Attribute('id', 'Must be set to "portal_workflow"') # security.declarePrivate('getCatalogVariablesFor') def getCatalogVariablesFor(ob): ''' Invoked by portal_catalog. Allows workflows to add variables to the catalog based on workflow status, making it possible to implement queues. Returns a mapping containing the catalog variables that apply to ob. ''' # security.declarePrivate('listActions') def listActions(info): ''' Invoked by the portal_actions tool. Allows workflows to include actions to be displayed in the actions box. Object actions are supplied by workflows that apply to the object. Global actions are supplied by all workflows. Returns the actions to be displayed to the user. ''' # security.declarePublic('getActionsFor') def getActionsFor(ob): ''' Return a list of action dictionaries for 'ob', just as though queried via 'ActionsTool.listFilteredActionsFor'. ''' # security.declarePublic('doActionFor') def doActionFor(ob, action, wf_id=None, *args, **kw): ''' Invoked by user interface code. Allows the user to request a workflow action. The workflow object must perform its own security checks. ''' # security.declarePublic('getInfoFor') def getInfoFor(ob, name, default=_marker, wf_id=None, *args, **kw): ''' Invoked by user interface code. Allows the user to request information provided by the workflow. The workflow object must perform its own security checks. ''' # security.declarePrivate('notifyCreated') def notifyCreated(ob): ''' Notifies all applicable workflows after an object has been created and put in its new place. ''' # security.declarePrivate('notifyBefore') def notifyBefore(ob, action): ''' Notifies all applicable workflows of an action before it happens, allowing veto by exception. Unless an exception is thrown, either a notifySuccess() or notifyException() can be expected later on. The action usually corresponds to a method name. ''' # security.declarePrivate('notifySuccess') def notifySuccess(ob, action, result=None): ''' Notifies all applicable workflows that an action has taken place. ''' # security.declarePrivate('notifyException') def notifyException(ob, action, exc): ''' Notifies all applicable workflows that an action failed. ''' # security.declarePrivate('getHistoryOf') def getHistoryOf(wf_id, ob): ''' Invoked by workflow definitions. Returns the history of an object. ''' # security.declarePrivate('getStatusOf') def getStatusOf(wf_id, ob): ''' Invoked by workflow definitions. Returns the last element of a history. ''' # security.declarePrivate('setStatusOf') def setStatusOf(wf_id, ob, status): ''' Invoked by workflow definitions. Appends to the workflow history. ''' class WorkflowDefinition(Base): '''The interface expected of workflow definitions objects. Accesses and changes the workflow state of objects. ''' # security.declarePrivate('getCatalogVariablesFor') def getCatalogVariablesFor(ob): ''' Invoked by the portal_workflow tool. Allows this workflow to make workflow-specific variables available to the catalog, making it possible to implement queues in a simple way. Returns a mapping containing the catalog variables that apply to ob. ''' #security.declarePrivate('updateRoleMappingsFor') def updateRoleMappingsFor(ob): ''' Updates the object permissions according to the current workflow state. ''' # security.declarePrivate('listObjectActions') def listObjectActions(info): ''' Invoked by the portal_workflow tool. Allows this workflow to include actions to be displayed in the actions box. Called only when this workflow is applicable to info.content. Returns the actions to be displayed to the user. ''' # security.declarePrivate('listGlobalActions') def listGlobalActions(info): ''' Invoked by the portal_workflow tool. Allows this workflow to include actions to be displayed in the actions box. Generally called on every request! Returns the actions to be displayed to the user. ''' # security.declarePrivate('isActionSupported') def isActionSupported(ob, action): ''' Invoked by the portal_workflow tool. Returns a true value if the given action name is supported. ''' # security.declarePrivate('doActionFor') def doActionFor(ob, action, *args, **kw): ''' Invoked by the portal_workflow tool. Allows the user to request a workflow action. This method must perform its own security checks. ''' # security.declarePrivate('isInfoSupported') def isInfoSupported(ob, name): ''' Invoked by the portal_workflow tool. Returns a true value if the given info name is supported. ''' # security.declarePrivate('getInfoFor') def getInfoFor(ob, name, default, *args, **kw): ''' Invoked by the portal_workflow tool. Allows the user to request information provided by the workflow. This method must perform its own security checks. ''' # security.declarePrivate('notifyCreated') def notifyCreated(ob): ''' Invoked by the portal_workflow tool. Notifies this workflow after an object has been created and put in its new place. ''' # security.declarePrivate('notifyBefore') def notifyBefore(ob, action): ''' Invoked by the portal_workflow tool. Notifies this workflow of an action before it happens, allowing veto by exception. Unless an exception is thrown, either a notifySuccess() or notifyException() can be expected later on. The action usually corresponds to a method name. ''' # security.declarePrivate('notifySuccess') def notifySuccess(ob, action, result): ''' Invoked by the portal_workflow tool. Notifies this workflow that an action has taken place. ''' # security.declarePrivate('notifyException') def notifyException(ob, action, exc): ''' Invoked by the portal_workflow tool. Notifies this workflow that an action failed. ''' CMF-1.3/CMFCore/tests/0040755000076500007650000000000007524010064014262 5ustar tseavertseaverCMF-1.3/CMFCore/tests/base/0040755000076500007650000000000007524010064015174 5ustar tseavertseaverCMF-1.3/CMFCore/tests/base/__init__.py0100644000076500007650000000006007433262536017312 0ustar tseavertseaver""" Generic stuff for unit testing the CMF. """ CMF-1.3/CMFCore/tests/base/content.py0100644000076500007650000000613707500233222017221 0ustar tseavertseaverDOCTYPE = '''''' HTML_TEMPLATE = '''\ %(title)s %(body)s ''' SIMPLE_HTML = '''\ Title in tag

Not a lot here

''' BASIC_HTML = '''\ Title in tag

Not a lot here

''' # A document with an html-qualifying *portion*. FAUX_HTML_LEADING_TEXT = '''\ The following would look like HTML but for this leading text: Title in tag

Not a lot here

''' ENTITY_IN_TITLE = '''\ &Auuml;rger

Not a lot here either

''' SIMPLE_STRUCTUREDTEXT = '''\ Title: My Document Description: A document by me Contributors: foo@bar.com; baz@bam.net; no@yes.maybe Subject: content management, zope This is the header Body body body body body body body body. o A list item o And another thing... ''' BASIC_STRUCTUREDTEXT = '''\ Title: My Document Description: A document by me Contributors: foo@bar.com; baz@bam.net; no@yes.maybe Subject: content management, zope Keywords: unit tests; , framework This is the header Body body body body body body body body. o A list item o And another thing... ''' STX_WITH_HTML = """\ Sometimes people do interesting things Sometimes people do interesting things like have examples of HTML inside their structured text document. We should be detecting that this is indeed a structured text document and **NOT** an HTML document:: Hello World

Hello world, I am Bruce.

All in favor say pi! """ STX_NO_HEADERS = """\ Title Phrase This is a "plain" STX file, with no headers. Saving with it shouldn't overwrite any metadata. """ STX_NO_HEADERS_BUT_COLON = """\ Plain STX: No magic! This is a "plain" STX file, with no headers. Saving with it shouldn't overwrite any metadata. """ CMF-1.3/CMFCore/tests/base/dummy.py0100644000076500007650000001307707523330772016720 0ustar tseavertseaverfrom Acquisition import Implicit, aq_inner, aq_parent from OFS.SimpleItem import Item from Products.CMFCore.PortalContent import PortalContent from Products.CMFCore.TypesTool import TypeInformation from Products.CMFCore.TypesTool import FactoryTypeInformation from Products.CMFCore.ActionProviderBase import ActionProviderBase class DummyObject(Implicit): """ A dummy callable object. Comes with getIcon and restrictedTraverse methods. """ def __init__(self, name='dummy',**kw): self.name = name self.__dict__.update( kw ) def __str__(self): return self.name def __call__(self): return self.name def restrictedTraverse( self, path ): return path and getattr( self, path ) or self def getIcon( self, relative=0 ): return 'Site: %s' % relative class DummyContent( PortalContent, Item ): """ A Dummy piece of PortalContent """ meta_type = 'Dummy' portal_type = 'Dummy Content' url = 'foo_url' after_add_called = before_delete_called = 0 def __init__( self, id='dummy', *args, **kw ): self.id = id self._args = args self._kw = {} self._kw.update( kw ) self.reset() self.catalog = kw.get('catalog',0) self.url = kw.get('url',None) def manage_afterAdd( self, item, container ): self.after_add_called = 1 if self.catalog: PortalContent.manage_afterAdd( self, item, container ) def manage_beforeDelete( self, item, container ): self.before_delete_called = 1 if self.catalog: PortalContent.manage_beforeDelete( self, item, container ) def absolute_url(self): return self.url def reset( self ): self.after_add_called = self.before_delete_called = 0 # Make sure normal Database export/import stuff doesn't trip us up. def _getCopy( self, container ): return DummyContent( self.id, catalog=self.catalog ) def _safe_get(self,attr): if self.catalog: return getattr(self,attr,'') else: return getattr(self,attr) def Title( self ): return self.title def Creator( self ): return self._safe_get('creator') def Subject( self ): return self._safe_get('subject') def Description( self ): return self._safe_get('description') def created( self ): return self._safe_get('created_date') def modified( self ): return self._safe_get('modified_date') def Type( self ): return 'Dummy Content Title' def addDummy( self, id ): """ Constructor method for DummyContent """ self._setObject( id, DummyContent() ) class DummyFactory: """ Dummy Product Factory """ def __init__( self, folder ): self._folder = folder def addFoo( self, id, *args, **kw ): if self._folder._prefix: id = '%s_%s' % ( self._folder._prefix, id ) foo = apply( DummyContent, ( id, ) + args, kw ) self._folder._setOb( id, foo ) if self._folder._prefix: return id __roles__ = ( 'FooAdder', ) __allow_access_to_unprotected_subobjects__ = { 'addFoo' : 1 } class DummyTypeInfo(TypeInformation): """ Dummy class of type info object """ meta_type = "Dummy Test Type Info" DummyFTI = FactoryTypeInformation( 'Dummy Content', title='Dummy Content Title', meta_type=DummyContent.meta_type, product='CMFDefault', factory='addDocument', actions= ( { 'name' : 'View', 'action' : 'view', 'permissions' : ('View', ) }, { 'name' : 'View2', 'action' : 'view2', 'permissions' : ('View', ) }, { 'name' : 'Edit', 'action' : 'edit', 'permissions' : ('forbidden permission',) } ) ) class DummyFolder( Implicit ): """ Dummy Container for testing """ def __init__( self, fake_product=0, prefix='' ): self._prefix = prefix if fake_product: self.manage_addProduct = { 'FooProduct' : DummyFactory( self ) } self._objects = {} def _setOb( self, id, obj ): self._objects[id] = obj def _getOb( self, id ): return self._objects[id] def _setObject(self,id,object): setattr(self,id,object) class DummyTool(Implicit,ActionProviderBase): """ This is a Dummy Tool that behaves as a a MemberShipTool, a URLTool and an Action Provider """ _actions = [ DummyObject(), DummyObject() ] root = 'DummyTool' def __init__(self, anon=1): self.anon = anon def isAnonymousUser(self): return self.anon def getAuthenticatedMember(self): return "member" def __call__( self ): return self.root getPortalPath = __call__ def getPortalObject( self ): return aq_parent( aq_inner( self ) ) def getIcon( self, relative=0 ): return 'Tool: %s' % relative CMF-1.3/CMFCore/tests/base/security.py0100644000076500007650000000365607433262536017440 0ustar tseavertseaverfrom Acquisition import Implicit class PermissiveSecurityPolicy: """ Very permissive security policy for unit testing purposes. """ # # Standard SecurityPolicy interface # def validate( self , accessed=None , container=None , name=None , value=None , context=None , roles=None , *args , **kw): return 1 def checkPermission( self, permission, object, context) : if permission == 'forbidden permission': return 0 return 1 class OmnipotentUser( Implicit ): """ Omnipotent User for unit testing purposes. """ def getId( self ): return 'all_powerful_Oz' getUserName = getId def allowed( self, object, object_roles=None ): return 1 class UserWithRoles( Implicit ): """ User with roles specified in constructor for unit testing purposes. """ def __init__( self, *roles ): self._roles = roles def getId( self ): return 'high_roller' getUserName = getId def allowed( self, object, object_roles=None ): if object_roles is None: object_roles=() for orole in object_roles: if orole in self._roles: return 1 return 0 class AnonymousUser( Implicit ): """ Anonymous USer for unit testing purposes. """ def getId( self ): return 'unit_tester' getUserName = getId def has_permission(self, permission, obj): # For types tool tests dealing with filtered_meta_types return 1 def allowed( self, object, object_roles=None ): # for testing permissions on actions if object.getId() == 'actions_dummy': if 'Anonymous' in object_roles: return 1 else: return 0 return 1 CMF-1.3/CMFCore/tests/base/testcase.py0100644000076500007650000000301407443123610017356 0ustar tseavertseaverimport Zope from unittest import TestCase from AccessControl.SecurityManagement import newSecurityManager from AccessControl.SecurityManagement import noSecurityManager from AccessControl.SecurityManager import setSecurityPolicy from Testing.makerequest import makerequest from security import PermissiveSecurityPolicy, AnonymousUser class TransactionalTest( TestCase ): def setUp( self ): get_transaction().begin() self.connection = Zope.DB.open() self.root = self.connection.root()[ 'Application' ] def tearDown( self ): get_transaction().abort() self.connection.close() class RequestTest( TransactionalTest ): def setUp(self): TransactionalTest.setUp(self) root = self.root = makerequest(self.root) self.REQUEST = root.REQUEST self.RESPONSE = root.REQUEST.RESPONSE class SecurityTest( TestCase ): def setUp(self): get_transaction().begin() self._policy = PermissiveSecurityPolicy() self._oldPolicy = setSecurityPolicy(self._policy) self.connection = Zope.DB.open() self.root = self.connection.root()[ 'Application' ] newSecurityManager( None, AnonymousUser().__of__( self.root ) ) def tearDown( self ): get_transaction().abort() self.connection.close() noSecurityManager() setSecurityPolicy(self._oldPolicy) class SecurityRequestTest( SecurityTest ): def setUp(self): SecurityTest.setUp(self) self.root = makerequest(self.root) CMF-1.3/CMFCore/tests/base/utils.py0100644000076500007650000000174507433262536016726 0ustar tseavertseaverfrom unittest import TestSuite from sys import modules def build_test_suite(package_name,module_names,required=1): """ Utlitity for building a test suite from a package name and a list of modules. If required is false, then ImportErrors will simply result in that module's tests not being added to the returned suite. """ suite = TestSuite() try: for name in module_names: the_name = package_name+'.'+name __import__(the_name,globals(),locals()) suite.addTest(modules[the_name].test_suite()) except ImportError: if required: raise return suite def has_path( catalog, path ): """ Verify that catalog has an object at path. """ if type( path ) is type( () ): path = '/'.join(path) rids = map( lambda x: x.data_record_id_, catalog.searchResults() ) for rid in rids: if catalog.getpath( rid ) == path: return 1 return 0 CMF-1.3/CMFCore/tests/__init__.py0100644000076500007650000000022507306060476016401 0ustar tseavertseaver"""\ Unit test package for CMFCore. As test suites are added, they should be added to the mega-test-suite in Products.CMFCore.tests.test_all.py """ CMF-1.3/CMFCore/tests/test_ActionInformation.py0100644000076500007650000000536507433262535021337 0ustar tseavertseaverimport Zope from unittest import TestSuite, makeSuite, main from Products.CMFCore.tests.base.testcase import \ TransactionalTest from Products.CMFCore.tests.base.dummy import \ DummyContent, DummyTool as DummyMembershipTool from Products.CMFCore.ActionInformation import ActionInformation from Products.CMFCore.Expression import Expression, createExprContext class ActionInformationTests(TransactionalTest): def setUp( self ): TransactionalTest.setUp( self ) root = self.root root._setObject('portal', DummyContent('portal', 'url_portal')) portal = self.portal = root.portal portal.portal_membership = DummyMembershipTool() self.folder = DummyContent('foo', 'url_foo') self.object = DummyContent('bar', 'url_bar') def test_basic_construction(self): ai = ActionInformation(id='view' ) self.assertEqual(ai.getId(), 'view') self.assertEqual(ai.Title(), 'view') self.assertEqual(ai.Description(), '') self.assertEqual(ai.getCondition(), '') self.assertEqual(ai.getActionExpression(), '') self.assertEqual(ai.getVisibility(), 1) self.assertEqual(ai.getCategory(), 'object') self.assertEqual(ai.getPermissions(), ()) def test_construction_with_Expressions(self): ai = ActionInformation(id='view' , title='View' , action=Expression( text='view') , condition=Expression( text='member') , category='global' , visible=0) self.assertEqual(ai.getId(), 'view') self.assertEqual(ai.Title(), 'View') self.assertEqual(ai.Description(), '') self.assertEqual(ai.getCondition(), 'member') self.assertEqual(ai.getActionExpression(), 'view') self.assertEqual(ai.getVisibility(), 0) self.assertEqual(ai.getCategory(), 'global') self.assertEqual(ai.getPermissions(), ()) def test_Condition(self): portal = self.portal folder = self.folder object = self.object ai = ActionInformation(id='view' , title='View' , action=Expression( text='view') , condition=Expression( text='member') , category='global' , visible=1) ec = createExprContext(folder, portal, object) self.failIf(ai.testCondition(ec)) def test_suite(): return TestSuite(( makeSuite(ActionInformationTests), )) if __name__ == '__main__': main(defaultTest='test_suite') CMF-1.3/CMFCore/tests/test_ActionProviderBase.py0100644000076500007650000001134307510602454021422 0ustar tseavertseaverimport unittest import Zope from Products.CMFCore.tests.base.dummy import DummyTool # # We have to import these here to make the "ugly sharing" test case go. # from Products.CMFCore.ActionProviderBase import ActionProviderBase from Products.CMFCore.ActionInformation import ActionInformation class DummyProvider( ActionProviderBase ): _actions = ( ActionInformation( id='an_id' , title='A Title' , action='' , condition='' , permissions='' , category='' , visible=0 ), ) class DummyAction: def __init__( self, value ): self.value = value def clone( self ): return self.__class__( self.value ) def __cmp__( self, other ): return ( cmp( type( self ), type( other ) ) or cmp( self.__class__, other.__class__ ) or cmp( self.value, other.value ) or 0 ) class ActionProviderBaseTests(unittest.TestCase): def _makeProvider( self, dummy=0 ): klass = dummy and DummyProvider or ActionProviderBase return klass() def test_addAction( self ): apb = self._makeProvider() self.failIf( apb._actions ) old_actions = apb._actions apb.addAction( id='foo' , name='foo_action' , action='' , condition='' , permission='' , category='' ) self.failUnless( apb._actions ) self.failIf( apb._actions is old_actions ) # make sure a blank permission gets stored as an empty tuple self.assertEqual( apb._actions[0].permissions, () ) def test_changeActions( self ): apb = DummyTool() old_actions = list( apb._actions ) keys = [ ( 'id_%d', None ) , ( 'name_%d', None ) , ( 'action_%d', '' ) , ( 'condition_%d', '' ) , ( 'permission_%d', None ) , ( 'category_%d', None ) , ( 'visible_%d', 0 ) ] properties = {} for i in range( len( old_actions ) ): for key, value in keys: token = key % i if value is None: value = token properties[ token ] = value apb.changeActions( properties=properties ) marker = [] for i in range( len( apb._actions ) ): for key, value in keys: attr = key[ : -3 ] if value is None: value = key % i if attr == 'name': # WAAAA attr = 'title' if attr == 'permission': # WAAAA attr = 'permissions' value = ( value, ) attr_value = getattr( apb._actions[i], attr, marker ) self.assertEqual( attr_value , value , '%s, %s != %s, %s' % ( attr, attr_value, key, value ) ) self.failIf( apb._actions is old_actions ) def test_deleteActions( self ): apb = self._makeProvider() apb._actions = tuple( map( DummyAction, [ '0', '1', '2' ] ) ) apb.deleteActions( selections=(0,2) ) self.assertEqual( len( apb._actions ), 1 ) self.failUnless( DummyAction('1') in apb._actions ) def test_DietersNastySharingBug( self ): one = self._makeProvider( dummy=1 ) another = self._makeProvider( dummy=1 ) def idify( x ): return id( x ) old_ids = one_ids = map( idify, one.listActions() ) another_ids = map( idify, another.listActions() ) self.assertEqual( one_ids, another_ids ) one.changeActions( { 'id_0' : 'different_id' , 'name_0' : 'A Different Title' , 'action_0' : 'arise_shine' , 'condition_0' : 'always' , 'permissions_0' : 'granted' , 'category_0' : 'quality' , 'visible_0' : 1 } ) one_ids = map( idify, one.listActions() ) another_ids = map( idify, another.listActions() ) self.failIf( one_ids == another_ids ) self.assertEqual( old_ids, another_ids ) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(ActionProviderBaseTests), )) if __name__ == '__main__': unittest.main(defaultTest='test_suite') CMF-1.3/CMFCore/tests/test_ActionsTool.py0100644000076500007650000001134707442442437020150 0ustar tseavertseaverimport Zope from unittest import TestCase,TestSuite,makeSuite,main from Products.CMFCore.tests.base.testcase import \ SecurityRequestTest from Products.CMFCore.ActionsTool import ActionsTool from Products.CMFCore.TypesTool import TypesTool from Products.CMFCore.PortalFolder import PortalFolder from Products.CMFCore.ActionInformation import ActionInformation from Products.CMFCore.Expression import Expression from Products.CMFDefault.URLTool import URLTool from Products.CMFDefault.RegistrationTool import RegistrationTool from Products.CMFDefault.MembershipTool import MembershipTool class ActionsToolTests( SecurityRequestTest ): def setUp( self ): SecurityRequestTest.setUp(self) root = self.root root._setObject( 'portal_actions', ActionsTool() ) root._setObject('foo', URLTool() ) root._setObject('portal_membership', MembershipTool()) root._setObject('portal_types', TypesTool()) self.tool = root.portal_actions self.ut = root.foo self.tool.action_providers = ('portal_actions',) def test_actionProviders(self): tool = self.tool self.assertEqual(tool.listActionProviders(), ('portal_actions',)) def test_addActionProvider(self): tool = self.tool tool.addActionProvider('foo') self.assertEqual(tool.listActionProviders(), ('portal_actions', 'foo')) def test_delActionProvider(self): tool = self.tool tool.deleteActionProvider('foo') self.assertEqual(tool.listActionProviders(), ('portal_actions',)) def test_listActionInformationActions(self): """ Check that listFilteredActionsFor works for objects that return ActionInformation objects """ root = self.root tool = self.tool root._setObject('portal_registration', RegistrationTool()) self.tool.action_providers = ('portal_actions','portal_registration') self.assertEqual(tool.listFilteredActionsFor(root.portal_registration), {'workflow': [], 'user': [], 'object': [{'permissions': ('List folder contents',), 'id': 'folderContents', 'url': ' http://foo/folder_contents', 'name': 'Folder contents', 'visible': 1, 'category': 'object'}], 'folder': [], 'global': []}) def test_listDictionaryActions(self): """ Check that listFilteredActionsFor works for objects that return dictionaries """ root = self.root tool = self.tool root._setObject('donkey', PortalFolder('donkey')) self.assertEqual(tool.listFilteredActionsFor(root.donkey), {'workflow': [], 'user': [], 'object': [], 'folder': [{'permissions': ('List folder contents',), 'id': 'folderContents', 'url': ' http://foo/donkey/folder_contents', 'name': 'Folder contents', 'visible': 1, 'category': 'folder'}], 'global': []}) def test_DuplicateActions(self): """ Check that listFilteredActionsFor filters out duplicate actions. """ root = self.root tool = self.tool action = ActionInformation(id='test', title='Test', action=Expression( text='string: a_url' ), condition='', permissions=(), category='object', visible=1 ) tool._actions = [action,action] self.tool.action_providers = ('portal_actions',) self.assertEqual(tool.listFilteredActionsFor(root)['object'], [{'permissions': (), 'id': 'test', 'url': ' a_url', 'name': 'Test', 'visible': 1, 'category': 'object'}]) def test_suite(): return TestSuite(( makeSuite(ActionsToolTests), )) if __name__ == '__main__': main(defaultTest='test_suite') CMF-1.3/CMFCore/tests/test_CachingPolicyManager.py0100644000076500007650000003613407510635777021731 0ustar tseavertseaverimport Zope from App.Common import rfc1123_date import unittest from DateTime.DateTime import DateTime ACCLARK = DateTime( '2025/01/01' ) class DummyContent: __allow_access_to_unprotected_subobjects__ = 1 def __init__(self, modified ): self.modified = modified def Type( self ): return 'Dummy' def modified( self ): return self.modified class CachingPolicyTests( unittest.TestCase ): def setUp(self): self._epoch = DateTime( '2025/01/01' ) def _makePolicy( self, policy_id, **kw ): from Products.CMFCore.CachingPolicyManager import CachingPolicy return CachingPolicy( policy_id, **kw ) def _makeContext( self, **kw ): from Products.CMFCore.CachingPolicyManager import createCPContext from Products.CMFCore.CachingPolicyManager import createCPContext return createCPContext( DummyContent(self._epoch) , 'foo_view', kw, self._epoch ) def test_empty( self ): policy = self._makePolicy( 'empty' ) context = self._makeContext() headers = policy.getHeaders( context ) self.assertEqual( len( headers ), 1 ) self.assertEqual( headers[0][0], 'Last-modified' ) self.assertEqual( headers[0][1] , rfc1123_date(self._epoch.timeTime()) ) def test_noPassPredicate( self ): policy = self._makePolicy( 'noPassPredicate', predicate='nothing' ) context = self._makeContext() headers = policy.getHeaders( context ) self.assertEqual( len( headers ), 0 ) def test_typePredicate( self ): policy = self._makePolicy( 'typePredicate' , predicate='python:content.Type() == "Dummy"' ) context = self._makeContext() headers = policy.getHeaders( context ) self.assertEqual( len( headers ), 1 ) self.assertEqual( headers[0][0] , 'Last-modified' ) self.assertEqual( headers[0][1] , rfc1123_date(self._epoch.timeTime()) ) def test_typePredicateMiss( self ): policy = self._makePolicy( 'typePredicate' , predicate='python:content.Type() == "Foolish"' ) context = self._makeContext() headers = policy.getHeaders( context ) self.assertEqual( len( headers ), 0 ) def test_viewPredicate( self ): policy = self._makePolicy( 'viewPredicate' , predicate='python:view == "foo_view"' ) context = self._makeContext() headers = policy.getHeaders( context ) self.assertEqual( len( headers ), 1 ) self.assertEqual( headers[0][0], 'Last-modified' ) self.assertEqual( headers[0][1] , rfc1123_date(self._epoch.timeTime()) ) def test_viewPredicateMiss( self ): policy = self._makePolicy( 'viewPredicateMiss' , predicate='python:view == "bar_view"' ) context = self._makeContext() headers = policy.getHeaders( context ) self.assertEqual( len( headers ), 0 ) def test_kwPredicate( self ): policy = self._makePolicy( 'kwPredicate' , predicate='python:"foo" in keywords.keys()' ) context = self._makeContext( foo=1 ) headers = policy.getHeaders( context ) self.assertEqual( len( headers ), 1 ) self.assertEqual( headers[0][0], 'Last-modified' ) self.assertEqual( headers[0][1] , rfc1123_date(self._epoch.timeTime()) ) def test_kwPredicateMiss( self ): policy = self._makePolicy( 'kwPredicateMiss' , predicate='python:"foo" in keywords.keys()' ) context = self._makeContext( bar=1 ) headers = policy.getHeaders( context ) self.assertEqual( len( headers ), 0 ) context = self._makeContext() headers = policy.getHeaders( context ) self.assertEqual( len( headers ), 0 ) def test_mtimeFunc( self ): policy = self._makePolicy( 'mtimeFunc' , mtime_func='string:2001/01/01' ) context = self._makeContext() headers = policy.getHeaders( context ) self.assertEqual( len( headers ), 1 ) self.assertEqual( headers[0][0], 'Last-modified' ) self.assertEqual( headers[0][1] , rfc1123_date(ACCLARK.timeTime()) ) def test_mtimeFuncNone( self ): policy = self._makePolicy( 'mtimeFuncNone' , mtime_func='nothing' ) context = self._makeContext() headers = policy.getHeaders( context ) self.assertEqual( len( headers ), 0 ) def test_maxAge( self ): policy = self._makePolicy( 'aged', max_age_secs=86400 ) context = self._makeContext() headers = policy.getHeaders( context ) self.assertEqual( len( headers ), 3 ) self.assertEqual( headers[0][0].lower() , 'last-modified' ) self.assertEqual( headers[0][1] , rfc1123_date(self._epoch.timeTime()) ) self.assertEqual( headers[1][0].lower() , 'expires' ) self.assertEqual( headers[1][1] , rfc1123_date((self._epoch+1).timeTime()) ) self.assertEqual( headers[2][0].lower() , 'cache-control' ) self.assertEqual( headers[2][1] , 'max-age=86400' ) def test_noCache( self ): policy = self._makePolicy( 'noCache', no_cache=1 ) context = self._makeContext() headers = policy.getHeaders( context ) self.assertEqual( len( headers ), 2 ) self.assertEqual( headers[0][0].lower() , 'last-modified' ) self.assertEqual( headers[0][1] , rfc1123_date(self._epoch.timeTime()) ) self.assertEqual( headers[1][0].lower() , 'cache-control' ) self.assertEqual( headers[1][1] , 'no-cache' ) def test_noStore( self ): policy = self._makePolicy( 'noStore', no_store=1 ) context = self._makeContext() headers = policy.getHeaders( context ) self.assertEqual( len( headers ), 2 ) self.assertEqual( headers[0][0].lower() , 'last-modified' ) self.assertEqual( headers[0][1] , rfc1123_date(self._epoch.timeTime()) ) self.assertEqual( headers[1][0].lower() , 'cache-control' ) self.assertEqual( headers[1][1] , 'no-store' ) def test_mustRevalidate( self ): policy = self._makePolicy( 'mustRevalidate', must_revalidate=1 ) context = self._makeContext() headers = policy.getHeaders( context ) self.assertEqual( len( headers ), 2 ) self.assertEqual( headers[0][0].lower() , 'last-modified' ) self.assertEqual( headers[0][1] , rfc1123_date(self._epoch.timeTime()) ) self.assertEqual( headers[1][0].lower() , 'cache-control' ) self.assertEqual( headers[1][1] , 'must-revalidate' ) def test_combined( self ): policy = self._makePolicy( 'noStore', no_cache=1, no_store=1 ) context = self._makeContext() headers = policy.getHeaders( context ) self.assertEqual( len( headers ), 2 ) self.assertEqual( headers[0][0].lower() , 'last-modified' ) self.assertEqual( headers[0][1] , rfc1123_date(self._epoch.timeTime()) ) self.assertEqual( headers[1][0].lower() , 'cache-control' ) self.assertEqual( headers[1][1] , 'no-cache, no-store' ) class CachingPolicyManagerTests( unittest.TestCase ): def setUp(self): self._epoch = DateTime() def _makeOne( self ): from Products.CMFCore.CachingPolicyManager import CachingPolicyManager return CachingPolicyManager() def assertEqualDelta( self, lhs, rhs, delta ): self.failUnless( abs( lhs - rhs ) <= delta ) def test_interface( self ): from Products.CMFCore.CachingPolicyManager import CachingPolicyManager from Products.CMFCore.interfaces.CachingPolicyManager \ import CachingPolicyManager as ICachingPolicyManager try: from Interface import verify_class_implementation as verifyClass except ImportError: from Interface.Verify import verifyClass verifyClass(ICachingPolicyManager, CachingPolicyManager) def test_empty( self ): mgr = self._makeOne() self.assertEqual( len( mgr.listPolicies() ), 0 ) headers = mgr.getHTTPCachingHeaders( content=DummyContent(self._epoch) , view_method='foo_view' , keywords={} , time=self._epoch ) self.assertEqual( len( headers ), 0 ) self.assertRaises( KeyError, mgr._updatePolicy , 'xyzzy', None, None, None, None, None, None ) self.assertRaises( KeyError, mgr._removePolicy, 'xyzzy' ) self.assertRaises( KeyError, mgr._reorderPolicy, 'xyzzy', -1 ) def test_addPolicy( self ): mgr = self._makeOne() mgr._addPolicy( 'first', 'python:1', None, 0, 0, 0, 0 ) headers = mgr.getHTTPCachingHeaders( content=DummyContent(self._epoch) , view_method='foo_view' , keywords={} , time=self._epoch ) self.assertEqual( len( headers ), 3 ) self.assertEqual( headers[0][0].lower() , 'last-modified' ) self.assertEqual( headers[0][1] , rfc1123_date(self._epoch.timeTime()) ) self.assertEqual( headers[1][0].lower() , 'expires' ) self.assertEqual( headers[1][1] , rfc1123_date(self._epoch.timeTime()) ) self.assertEqual( headers[2][0].lower() , 'cache-control' ) self.assertEqual( headers[2][1], 'max-age=0' ) def test_reorder( self ): mgr = self._makeOne() policy_ids = ( 'foo', 'bar', 'baz', 'qux' ) for policy_id in policy_ids: mgr._addPolicy( policy_id , 'python:"%s" in keywords.keys()' % policy_id , None, 0, 0, 0, 0 ) ids = tuple( map( lambda x: x[0], mgr.listPolicies() ) ) self.assertEqual( ids, policy_ids ) mgr._reorderPolicy( 'bar', 3 ) ids = tuple( map( lambda x: x[0], mgr.listPolicies() ) ) self.assertEqual( ids, ( 'foo', 'baz', 'qux', 'bar' ) ) def _makeOneWithPolicies( self ): mgr = self._makeOne() policy_tuples = ( ( 'foo', None ) , ( 'bar', 0 ) , ( 'baz', 3600 ) , ( 'qux', 86400 ) ) for policy_id, max_age_secs in policy_tuples: mgr._addPolicy( policy_id , 'python:"%s" in keywords.keys()' % policy_id , None, max_age_secs, 0, 0, 0 ) return mgr def test_lookupNoMatch( self ): mgr = self._makeOneWithPolicies() headers = mgr.getHTTPCachingHeaders( content=DummyContent(self._epoch) , view_method='foo_view' , keywords={} , time=self._epoch ) self.assertEqual( len( headers ), 0 ) def test_lookupMatchFoo( self ): mgr = self._makeOneWithPolicies() headers = mgr.getHTTPCachingHeaders( content=DummyContent(self._epoch) , view_method='foo_view' , keywords={ 'foo' : 1 } , time=self._epoch ) self.assertEqual( len( headers ), 1 ) self.assertEqual( headers[0][0].lower(), 'last-modified' ) self.assertEqual( headers[0][1] , rfc1123_date(self._epoch.timeTime()) ) def test_lookupMatchBar( self ): mgr = self._makeOneWithPolicies() headers = mgr.getHTTPCachingHeaders( content=DummyContent(self._epoch) , view_method='foo_view' , keywords={ 'bar' : 1 } , time=self._epoch ) self.assertEqual( len( headers ), 3 ) self.assertEqual( headers[0][0].lower() , 'last-modified' ) self.assertEqual( headers[0][1] , rfc1123_date(self._epoch.timeTime()) ) self.assertEqual( headers[1][0].lower() , 'expires' ) self.assertEqual( headers[1][1] , rfc1123_date(self._epoch.timeTime()) ) self.assertEqual( headers[2][0].lower() , 'cache-control' ) self.assertEqual( headers[2][1], 'max-age=0' ) def test_lookupMatchBaz( self ): mgr = self._makeOneWithPolicies() headers = mgr.getHTTPCachingHeaders( content=DummyContent(self._epoch) , view_method='foo_view' , keywords={ 'baz' : 1 } , time=self._epoch ) self.assertEqual( len( headers ), 3 ) self.assertEqual( headers[0][0].lower() , 'last-modified' ) self.assertEqual( headers[0][1] , rfc1123_date(self._epoch.timeTime()) ) self.assertEqual( headers[1][0].lower() , 'expires' ) exp_time = DateTime( headers[1][1] ) target = self._epoch + ( 1.0 / 24.0 ) self.assertEqualDelta( exp_time, target, 0.01 ) self.assertEqual( headers[2][0].lower() , 'cache-control' ) self.assertEqual( headers[2][1] , 'max-age=3600' ) def test_lookupMatchQux( self ): mgr = self._makeOneWithPolicies() headers = mgr.getHTTPCachingHeaders( content=DummyContent(self._epoch) , view_method='foo_view' , keywords={ 'qux' : 1 } , time=self._epoch ) self.assertEqual( len( headers ), 3 ) self.assertEqual( headers[0][0].lower() , 'last-modified' ) self.assertEqual( headers[0][1] , rfc1123_date(self._epoch.timeTime()) ) self.assertEqual( headers[1][0].lower() , 'expires' ) exp_time = DateTime( headers[1][1] ) target = self._epoch + 1.0 self.assertEqualDelta( exp_time, target, 0.01 ) self.assertEqual( headers[2][0].lower() , 'cache-control' ) self.assertEqual( headers[2][1] , 'max-age=86400' ) def test_suite(): return unittest.TestSuite(( unittest.makeSuite( CachingPolicyTests ), unittest.makeSuite( CachingPolicyManagerTests ), )) if __name__ == '__main__': unittest.main(defaultTest='test_suite') CMF-1.3/CMFCore/tests/test_CatalogTool.py0100644000076500007650000000135707433262535020121 0ustar tseavertseaverimport Zope from unittest import TestCase, TestSuite, makeSuite, main from Products.CMFCore.tests.base.dummy import \ DummyContent from Products.CMFCore.CatalogTool import CatalogTool class CatalogToolTests( TestCase ): def test_processActions( self ): """ Tracker #405: CatalogTool doesn't accept optional third argument, 'idxs', to 'catalog_object'. """ tool = CatalogTool() dummy = DummyContent(catalog=1) tool.catalog_object( dummy, '/dummy' ) tool.catalog_object( dummy, '/dummy', [ 'SearchableText' ] ) def test_suite(): return TestSuite(( makeSuite( CatalogToolTests ), )) if __name__ == '__main__': main(defaultTest='test_suite') CMF-1.3/CMFCore/tests/test_ContentTypeRegistry.py0100644000076500007650000001513007434534374021714 0ustar tseavertseaverimport Zope from unittest import TestCase, TestSuite, makeSuite, main from Products.CMFCore.ContentTypeRegistry import ContentTypeRegistry from Products.CMFCore.ContentTypeRegistry import MajorMinorPredicate from Products.CMFCore.ContentTypeRegistry import ExtensionPredicate from Products.CMFCore.ContentTypeRegistry import NameRegexPredicate from Products.CMFCore.ContentTypeRegistry import MimeTypeRegexPredicate class MajorMinorPredicateTests( TestCase ): def test_empty( self ): pred = MajorMinorPredicate( 'empty' ) assert pred.getMajorType() == 'None' assert pred.getMinorType() == 'None' assert not pred( 'foo', 'text/plain', 'asdfljksadf' ) def test_simple( self ): pred = MajorMinorPredicate( 'plaintext' ) pred.edit( 'text', 'plain' ) assert pred.getMajorType() == 'text' assert pred.getMinorType() == 'plain' assert pred( 'foo', 'text/plain', 'asdfljksadf' ) assert not pred( 'foo', 'text/html', 'asdfljksadf' ) assert not pred( '', '', '' ) assert not pred( '', 'asdf', '' ) def test_wildcard( self ): pred = MajorMinorPredicate( 'alltext' ) pred.edit( 'text', '' ) assert pred.getMajorType() == 'text' assert pred.getMinorType() == '' assert pred( 'foo', 'text/plain', 'asdfljksadf' ) assert pred( 'foo', 'text/html', 'asdfljksadf' ) assert not pred( 'foo', 'image/png', 'asdfljksadf' ) pred.edit( '', 'html' ) assert pred.getMajorType() == '' assert pred.getMinorType() == 'html' assert not pred( 'foo', 'text/plain', 'asdfljksadf' ) assert pred( 'foo', 'text/html', 'asdfljksadf' ) assert not pred( 'foo', 'image/png', 'asdfljksadf' ) class ExtensionPredicateTests( TestCase ): def test_empty( self ): pred = ExtensionPredicate( 'empty' ) assert pred.getExtensions() == 'None' assert not pred( 'foo', 'text/plain', 'asdfljksadf' ) assert not pred( 'foo.txt', 'text/plain', 'asdfljksadf' ) assert not pred( 'foo.bar', 'text/html', 'asdfljksadf' ) def test_simple( self ): pred = ExtensionPredicate( 'stardottext' ) pred.edit( 'txt' ) assert pred.getExtensions() == 'txt' assert not pred( 'foo', 'text/plain', 'asdfljksadf' ) assert pred( 'foo.txt', 'text/plain', 'asdfljksadf' ) assert not pred( 'foo.bar', 'text/html', 'asdfljksadf' ) def test_multi( self ): pred = ExtensionPredicate( 'stardottext' ) pred.edit( 'txt text html htm' ) assert pred.getExtensions() == 'txt text html htm' assert not pred( 'foo', 'text/plain', 'asdfljksadf' ) assert pred( 'foo.txt', 'text/plain', 'asdfljksadf' ) assert pred( 'foo.text', 'text/plain', 'asdfljksadf' ) assert pred( 'foo.html', 'text/plain', 'asdfljksadf' ) assert pred( 'foo.htm', 'text/plain', 'asdfljksadf' ) assert not pred( 'foo.bar', 'text/html', 'asdfljksadf' ) class MimeTypeRegexPredicateTests( TestCase ): def test_empty( self ): pred = MimeTypeRegexPredicate( 'empty' ) assert pred.getPatternStr() == 'None' assert not pred( 'foo', 'text/plain', 'asdfljksadf' ) def test_simple( self ): pred = MimeTypeRegexPredicate( 'plaintext' ) pred.edit( 'text/plain' ) assert pred.getPatternStr() == 'text/plain' assert pred( 'foo', 'text/plain', 'asdfljksadf' ) assert not pred( 'foo', 'text/html', 'asdfljksadf' ) def test_pattern( self ): pred = MimeTypeRegexPredicate( 'alltext' ) pred.edit( 'text/*' ) assert pred.getPatternStr() == 'text/*' assert pred( 'foo', 'text/plain', 'asdfljksadf' ) assert pred( 'foo', 'text/html', 'asdfljksadf' ) assert not pred( 'foo', 'image/png', 'asdfljksadf' ) class NameRegexPredicateTests( TestCase ): def test_empty( self ): pred = NameRegexPredicate( 'empty' ) assert pred.getPatternStr() == 'None' assert not pred( 'foo', 'text/plain', 'asdfljksadf' ) def test_simple( self ): pred = NameRegexPredicate( 'onlyfoo' ) pred.edit( 'foo' ) assert pred.getPatternStr() == 'foo' assert pred( 'foo', 'text/plain', 'asdfljksadf' ) assert not pred( 'fargo', 'text/plain', 'asdfljksadf' ) assert not pred( 'bar', 'text/plain', 'asdfljksadf' ) def test_pattern( self ): pred = NameRegexPredicate( 'allfwords' ) pred.edit( 'f.*' ) assert pred.getPatternStr() == 'f.*' assert pred( 'foo', 'text/plain', 'asdfljksadf' ) assert pred( 'fargo', 'text/plain', 'asdfljksadf' ) assert not pred( 'bar', 'text/plain', 'asdfljksadf' ) class ContentTypeRegistryTests( TestCase ): def setUp( self ): self.reg = ContentTypeRegistry() def test_empty( self ): reg=self.reg assert reg.findTypeName( 'foo', 'text/plain', 'asdfljksadf' ) is None assert reg.findTypeName( 'fargo', 'text/plain', 'asdfljksadf' ) is None assert reg.findTypeName( 'bar', 'text/plain', 'asdfljksadf' ) is None assert not reg.listPredicates() self.assertRaises( KeyError, reg.removePredicate, 'xyzzy' ) def test_reorder( self ): reg=self.reg predIDs = ( 'foo', 'bar', 'baz', 'qux' ) for predID in predIDs: reg.addPredicate( predID, 'name_regex' ) ids = tuple( map( lambda x: x[0], reg.listPredicates() ) ) assert ids == predIDs reg.reorderPredicate( 'bar', 3 ) ids = tuple( map( lambda x: x[0], reg.listPredicates() ) ) assert ids == ( 'foo', 'baz', 'qux', 'bar' ) def test_lookup( self ): reg=self.reg reg.addPredicate( 'image', 'major_minor' ) reg.getPredicate( 'image' ).edit( 'image', '' ) reg.addPredicate( 'onlyfoo', 'name_regex' ) reg.getPredicate( 'onlyfoo' ).edit( 'foo' ) reg.assignTypeName( 'onlyfoo', 'Foo' ) assert reg.findTypeName( 'foo', 'text/plain', 'asdfljksadf' ) == 'Foo' assert not reg.findTypeName( 'fargo', 'text/plain', 'asdfljksadf' ) assert not reg.findTypeName( 'bar', 'text/plain', 'asdfljksadf' ) assert reg.findTypeName( 'foo', '', '' ) == 'Foo' assert reg.findTypeName( 'foo', None, None ) == 'Foo' def test_suite(): return TestSuite(( makeSuite( MajorMinorPredicateTests ), makeSuite( ExtensionPredicateTests ), makeSuite( MimeTypeRegexPredicateTests ), makeSuite( NameRegexPredicateTests ), makeSuite( ContentTypeRegistryTests ), )) if __name__ == '__main__': main(defaultTest='test_suite') CMF-1.3/CMFCore/tests/test_DirectoryView.py0100644000076500007650000001251607470266273020513 0ustar tseavertseaverimport Zope from unittest import TestCase, TestSuite, makeSuite, main from Products.CMFCore.tests.base.dummy import \ DummyFolder from Products.CMFCore.DirectoryView import \ registerDirectory,addDirectoryViews,DirectoryViewSurrogate from Globals import package_home, DevelopmentMode from os import remove, mkdir, rmdir, curdir from os.path import join, abspath, dirname from shutil import copy2 from time import sleep try: __file__ except NameError: # Test was called directly, so no __file__ global exists. _prefix = abspath(curdir) else: # Test was called by another test. _prefix = abspath(dirname(__file__)) # the path of our fake skin skin_path_name = join(_prefix, 'fake_skins', 'fake_skin') def _registerDirectory(self=None): registerDirectory('fake_skins', _prefix) if self is not None: ob = self.ob = DummyFolder() addDirectoryViews(ob, 'fake_skins', _prefix) class DirectoryViewTests1( TestCase ): def test_registerDirectory( self ): """ Test registerDirectory """ _registerDirectory() class DirectoryViewTests2( TestCase ): def setUp( self ): _registerDirectory(self) def test_addDirectoryViews( self ): """ Test addDirectoryViews """ pass def test_DirectoryViewExists( self ): """ Check DirectoryView added by addDirectoryViews appears as a DirectoryViewSurrogate due to Acquisition hackery. """ self.failUnless(isinstance(self.ob.fake_skin,DirectoryViewSurrogate)) def test_DirectoryViewMethod( self ): """ Check if DirectoryView method works """ self.assertEqual(self.ob.fake_skin.test1(),'test1') def test_properties(self): """Make sure the directory view is reading properties""" self.assertEqual(self.ob.fake_skin.testPT.title, 'Zope Pope') test1path = join(skin_path_name,'test1.py') test2path = join(skin_path_name,'test2.py') test3path = join(skin_path_name,'test3') if DevelopmentMode: class DebugModeTests( TestCase ): def setUp( self ): # initialise skins _registerDirectory(self) # add a method to the fake skin folder f = open(test2path,'w') f.write("return 'test2'") f.close() # edit the test1 method copy2(test1path,test1path+'.bak') f = open(test1path,'w') f.write("return 'new test1'") f.close() # add a new folder mkdir(test3path) def tearDown( self ): # undo FS changes remove(test1path) copy2(test1path+'.bak',test1path) remove(test1path+'.bak') try: remove(test2path) except (IOError,OSError): # it might be gone already pass try: rmdir(test3path) except (IOError,OSError): # it might be gone already pass def test_AddNewMethod( self ): """ See if a method added to the skin folder can be found """ self.assertEqual(self.ob.fake_skin.test2(),'test2') def test_EditMethod( self ): """ See if an edited method exhibits its new behaviour """ self.assertEqual(self.ob.fake_skin.test1(),'new test1') def test_NewFolder( self ): """ See if a new folder shows up """ self.failUnless(isinstance(self.ob.fake_skin.test3,DirectoryViewSurrogate)) self.ob.fake_skin.test3.objectIds() def test_DeleteMethod( self ): """ Make sure a deleted method goes away """ remove(test2path) try: self.ob.fake_skin.test2 except AttributeError: pass else: self.fail('test2 still exists') def test_DeleteAddEditMethod( self ): """ Check that if we delete a method, then add it back, then edit it, the DirectoryView notices. This excecises yet another Win32 mtime weirdity. """ remove(test2path) try: self.ob.fake_skin.test2 except AttributeError: pass else: self.fail('test2 still exists') # add method back to the fake skin folder f = open(test2path,'w') f.write("return 'test2.2'") f.close() # we need to wait a second here or the mtime will actually # have the same value, no human makes two edits in less # than a second ;-) sleep(1) # check self.assertEqual(self.ob.fake_skin.test2(),'test2.2') # edit method f = open(test2path,'w') f.write("return 'test2.3'") f.close() # check self.assertEqual(self.ob.fake_skin.test2(),'test2.3') def test_DeleteFolder( self ): """ Make sure a deleted folder goes away """ rmdir(test3path) try: self.ob.fake_skin.test3 except AttributeError: pass else: self.fail('test3 still exists') else: class DebugModeTests( TestCase ): pass def test_suite(): return TestSuite(( # makeSuite(DirectoryViewTests1), # makeSuite(DirectoryViewTests2), makeSuite(DebugModeTests), )) if __name__ == '__main__': main(defaultTest='test_suite') CMF-1.3/CMFCore/tests/test_Expression.py0100644000076500007650000000464007433262535020046 0ustar tseavertseaverimport Zope from unittest import TestSuite, makeSuite, main from Products.CMFCore.tests.base.testcase import \ SecurityTest from Products.CMFCore.tests.base.dummy import \ DummyContent, DummyTool as DummyMembershipTool from Products.CMFCore.ActionInformation import ActionInformation from Products.CMFCore.Expression import Expression, createExprContext class ExpressionTests( SecurityTest ): def setUp( self ): SecurityTest.setUp(self) root = self.root root._setObject('portal', DummyContent('portal', url='url_portal')) portal = self.portal = root.portal self.folder = DummyContent('foo', url='url_foo') self.object = DummyContent('bar', url='url_bar') self.ai = ActionInformation(id='view' , title='View' , action=Expression( text='view') , condition=Expression( text='member') , category='global' , visible=1) def test_anonymous_ec(self): self.portal.portal_membership = DummyMembershipTool() ec = createExprContext(self.folder, self.portal, self.object) member = ec.global_vars['member'] self.failIf(member) def test_authenticatedUser_ec(self): self.portal.portal_membership = DummyMembershipTool(anon=0) ec = createExprContext(self.folder, self.portal, self.object) member = ec.global_vars['member'] self.assertEqual(member, 'member') def test_ec_context(self): self.portal.portal_membership = DummyMembershipTool() ec = createExprContext(self.folder, self.portal, self.object) object = ec.global_vars['object'] portal = ec.global_vars['portal'] folder = ec.global_vars['folder'] self.failUnless(object) self.assertEqual(object.id, 'bar') self.assertEqual(object.absolute_url(), 'url_bar') self.failUnless(portal) self.assertEqual(portal.id, 'portal') self.assertEqual(portal.absolute_url(), 'url_portal') self.failUnless(folder) self.assertEqual(folder.id, 'foo') self.assertEqual(folder.absolute_url(), 'url_foo') def test_suite(): return TestSuite(( makeSuite(ExpressionTests), )) if __name__ == '__main__': main(defaultTest='test_suite') CMF-1.3/CMFCore/tests/test_FSImage.py0100644000076500007650000000654007510635777017173 0ustar tseavertseaverimport unittest import Zope class DummyCachingManager: def getHTTPCachingHeaders( self, content, view_name, keywords, time=None ): return ( ( 'foo', 'Foo' ), ( 'bar', 'Bar' ) ) from Products.CMFCore.tests.base.testcase import RequestTest, SecurityTest class FSImageTests( RequestTest ): def _makeOne( self, id, filename ): from Products.CMFCore.FSImage import FSImage from Products.CMFCore.tests.test_DirectoryView import skin_path_name import os.path return FSImage( id, os.path.join( skin_path_name, filename ) ) def _extractFile( self ): from Products.CMFCore.tests.test_DirectoryView import skin_path_name import os.path path = os.path.join( skin_path_name, 'test_image.gif' ) f = open( path, 'rb' ) try: data = f.read() finally: f.close() return path, data def test_ctor( self ): path, ref = self._extractFile() image = self._makeOne( 'test_image', 'test_image.gif' ) image = image.__of__( self.root ) self.assertEqual( image.get_size(), len( ref ) ) self.assertEqual( image._data, ref ) def test_index_html( self ): path, ref = self._extractFile() import os from webdav.common import rfc1123_date mod_time = os.stat( path )[ 8 ] image = self._makeOne( 'test_image', 'test_image.gif' ) image = image.__of__( self.root ) data = image.index_html( self.REQUEST, self.RESPONSE ) self.assertEqual( len( data ), len( ref ) ) self.assertEqual( data, ref ) # # ICK! 'HTTPResponse.getHeader' doesn't case-flatten the key! # self.assertEqual( self.RESPONSE.getHeader( 'Content-Length'.lower() ) , len( ref ) ) self.assertEqual( self.RESPONSE.getHeader( 'Content-Type'.lower() ) , 'image/gif' ) self.assertEqual( self.RESPONSE.getHeader( 'Last-Modified'.lower() ) , rfc1123_date( mod_time ) ) def test_index_html_with_304( self ): path, ref = self._extractFile() import os from webdav.common import rfc1123_date mod_time = os.stat( path )[ 8 ] image = self._makeOne( 'test_image', 'test_image.gif' ) image = image.__of__( self.root ) self.REQUEST.environ[ 'IF_MODIFIED_SINCE' ] = '%s;' % rfc1123_date( mod_time+3600 ) data = image.index_html( self.REQUEST, self.RESPONSE ) self.assertEqual( data, '' ) self.assertEqual( self.RESPONSE.getStatus(), 304 ) def test_index_html_without_304( self ): path, ref = self._extractFile() import os from webdav.common import rfc1123_date mod_time = os.stat( path )[ 8 ] image = self._makeOne( 'test_image', 'test_image.gif' ) image = image.__of__( self.root ) self.REQUEST.environ[ 'IF_MODIFIED_SINCE' ] = '%s;' % rfc1123_date( mod_time-3600 ) data = image.index_html( self.REQUEST, self.RESPONSE ) self.failUnless( data, '' ) self.assertEqual( self.RESPONSE.getStatus(), 200 ) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(FSImageTests), )) if __name__ == '__main__': unittest.main(defaultTest='test_suite') CMF-1.3/CMFCore/tests/test_FSPageTemplate.py0100644000076500007650000000702607522236354020510 0ustar tseavertseaverimport unittest import Zope class DummyCachingManager: def getHTTPCachingHeaders( self, content, view_name, keywords, time=None ): return ( ( 'foo', 'Foo' ), ( 'bar', 'Bar' ) ) from Products.CMFCore.tests.base.testcase import RequestTest, SecurityTest class FSPTMaker: def _makeOne( self, id, filename ): from Products.CMFCore.FSPageTemplate import FSPageTemplate from Products.CMFCore.tests.test_DirectoryView import skin_path_name from os.path import join return FSPageTemplate( id, join( skin_path_name, filename ) ) class FSPageTemplateTests( RequestTest, FSPTMaker ): def test_Call( self ): script = self._makeOne( 'testPT', 'testPT.pt' ) script = script.__of__(self.root) self.assertEqual(script(),'foo\n') def test_ContentType(self): script = self._makeOne( 'testXMLPT', 'testXMLPT.pt' ) script = script.__of__(self.root) script() self.assertEqual(script.content_type, 'text/xml') self.assertEqual(self.RESPONSE.getHeader('content-type'), 'text/xml') script = self._makeOne( 'testPT', 'testPT.pt' ) script = script.__of__(self.root) script() self.assertEqual(script.content_type, 'text/html') self.assertEqual(self.RESPONSE.getHeader('content-type'), 'text/html') def test_BadCall( self ): from Products.PageTemplates.TALES import Undefined script = self._makeOne( 'testPTbad', 'testPTbad.pt' ) script = script.__of__(self.root) try: # can't use assertRaises, because different types raised. script() except (Undefined, KeyError): pass else: self.fail('Calling a bad template did not raise an exception') def test_caching( self ): """ Test HTTP caching headers. """ self.root.caching_policy_manager = DummyCachingManager() original_len = len( self.RESPONSE.headers ) script = self._makeOne('testPT', 'testPT.pt') script = script.__of__(self.root) script() self.failUnless( len( self.RESPONSE.headers ) >= original_len + 2 ) self.failUnless( 'foo' in self.RESPONSE.headers.keys() ) self.failUnless( 'bar' in self.RESPONSE.headers.keys() ) class FSPageTemplateCustomizationTests( SecurityTest, FSPTMaker ): def setUp( self ): from OFS.Folder import Folder SecurityTest.setUp( self ) self.root._setObject( 'portal_skins', Folder( 'portal_skins' ) ) self.skins = self.root.portal_skins self.skins._setObject( 'custom', Folder( 'custom' ) ) self.custom = self.skins.custom self.skins._setObject( 'fsdir', Folder( 'fsdir' ) ) self.fsdir = self.skins.fsdir self.fsdir._setObject( 'testPT' , self._makeOne( 'testPT', 'testPT.pt' ) ) self.fsPT = self.fsdir.testPT def test_customize( self ): self.fsPT.manage_doCustomize( folder_path='custom' ) self.assertEqual( len( self.custom.objectIds() ), 1 ) self.failUnless( 'testPT' in self.custom.objectIds() ) def test_dontExpandOnCreation( self ): self.fsPT.manage_doCustomize( folder_path='custom' ) customized = self.custom.testPT self.failIf( customized.expand ) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(FSPageTemplateTests), unittest.makeSuite(FSPageTemplateCustomizationTests), )) if __name__ == '__main__': unittest.main(defaultTest='test_suite') CMF-1.3/CMFCore/tests/test_FSPythonScript.py0100644000076500007650000000120707433262535020602 0ustar tseavertseaverimport Zope from unittest import TestCase, TestSuite, makeSuite, main from Products.CMFCore.FSPythonScript import FSPythonScript from test_DirectoryView import skin_path_name from os.path import join script_path = join(skin_path_name,'test1.py') class FSPythonScriptTests( TestCase ): def test_GetSize( self ): """ Test get_size returns correct value """ script = FSPythonScript('test1', script_path) self.assertEqual(len(script.read()),script.get_size()) def test_suite(): return TestSuite(( makeSuite(FSPythonScriptTests), )) if __name__ == '__main__': main(defaultTest='test_suite') CMF-1.3/CMFCore/tests/test_FSSecurity.py0100644000076500007650000001417607470266273017760 0ustar tseavertseaverimport Zope from unittest import TestSuite, makeSuite, main from types import ListType from os import remove from os.path import join from time import sleep from AccessControl.Permission import Permission from Products.CMFCore.tests.base.testcase import RequestTest from test_DirectoryView import _registerDirectory, _prefix from Globals import DevelopmentMode class FSSecurityBase( RequestTest ): def _checkSettings(self,object,permissionname,acquire=0,roles=[]): # check the roles and acquire settings for a permission on an # object are as expected happy=0 for pstuff in object.ac_inherited_permissions(1): name,value = pstuff[:2] if name==permissionname: p = Permission(name,value,object) groles=p.getRoles(default=[]) acquired=isinstance(groles,ListType) expected={} for role in roles: expected[role]=1 got={} for role in groles: got[role]=1 self.assertEqual((acquire,expected),(acquired,got)) happy=1 if not happy: raise ValueError,"'%s' not found in permissions: %s" % (permissionname,all_names) _path = join(_prefix,'fake_skins','fake_skin') def _writeFile(self, filename, stuff): # write some stuff to a file on disk thePath = join(self._path,filename) f = open(thePath,'w') f.write(stuff) f.close() def _deleteFile(self,filename): # nuke it remove(join(self._path,filename)) def setUp( self ): # initialise skins _registerDirectory(self) # set up ZODB RequestTest.setUp(self) # put object in ZODB root=self.root try: root._delObject('fake_skin') except AttributeError: pass root._setObject( 'fake_skin', self.ob.fake_skin ) def tearDown( self ): try: self._deleteFile('test5.py.security') except: pass RequestTest.tearDown(self) class FSSecurityTests( FSSecurityBase ): def test_basicPermissions( self ): """ Test basic FS permissions """ # check a normal method is as we'd expect self._checkSettings(self.ob.fake_skin.test1,'View',1,[]) # now do some checks on the method with FS permissions self._checkSettings(self.ob.fake_skin.test4,'View',1,['Manager','Owner']) self._checkSettings(self.ob.fake_skin.test4,'Access contents information',0,[]) def test_invalidPermissionNames( self ): """ Test for an invalid permission name """ # baseline self._checkSettings(self.ob.fake_skin.test5,'View',1,[]) # add .rpm with dodgy permission name self._writeFile('test5.py.security','Access stoopid contents::') # check baseline self._checkSettings(self.ob.fake_skin.test5,'View',1,[]) def test_invalidAcquireNames( self ): """ Test for an invalid spelling of acquire """ # baseline self._checkSettings(self.ob.fake_skin.test5,'View',1,[]) # add dodgy .rpm self._writeFile('test5.py.security','View:aquire:') # check baseline self._checkSettings(self.ob.fake_skin.test5,'View',1,[]) if DevelopmentMode: class DebugModeTests( FSSecurityBase ): def test_addPRM( self ): """ Test adding of a .security """ # baseline self._checkSettings(self.ob.fake_skin.test5,'View',1,[]) # add self._writeFile('test5.py.security','View:acquire:Manager') # test self._checkSettings(self.ob.fake_skin.test5,'View',1,['Manager']) def test_delPRM( self ): """ Test deleting of a .security """ # baseline self._checkSettings(self.ob.fake_skin.test5,'View',1,[]) self._writeFile('test5.py.security','View:acquire:Manager') self._checkSettings(self.ob.fake_skin.test5,'View',1,['Manager']) # delete self._deleteFile('test5.py.security') # test self._checkSettings(self.ob.fake_skin.test5,'View',1,[]) def test_editPRM( self ): """ Test editing a .security """ # we need to wait a second here or the mtime will actually # have the same value as set in the last test. # Maybe someone brainier than me can figure out a way to make this # suck less :-( sleep(1) # baseline self._writeFile('test5.py.security','View::Manager,Anonymous') self._checkSettings(self.ob.fake_skin.test5,'View',0,['Manager','Anonymous']) # edit self._writeFile('test5.py.security','View:acquire:Manager') # test self._checkSettings(self.ob.fake_skin.test5,'View',1,['Manager']) def test_DelAddEditPRM( self ): """ Test deleting, then adding, then editing a .security file """ # baseline self._writeFile('test5.py.security','View::Manager') # delete self._deleteFile('test5.py.security') self._checkSettings(self.ob.fake_skin.test5,'View',1,[]) # we need to wait a second here or the mtime will actually # have the same value, no human makes two edits in less # than a second ;-) sleep(1) # add back self._writeFile('test5.py.security','View::Manager,Anonymous') self._checkSettings(self.ob.fake_skin.test5,'View',0,['Manager','Anonymous']) # edit self._writeFile('test5.py.security','View:acquire:Manager') # test self._checkSettings(self.ob.fake_skin.test5,'View',1,['Manager']) else: class DebugModeTests( FSSecurityBase ): pass def test_suite(): return TestSuite(( makeSuite(FSSecurityTests), makeSuite(DebugModeTests), )) if __name__ == '__main__': main(defaultTest='test_suite') CMF-1.3/CMFCore/tests/test_PortalFolder.py0100644000076500007650000005300507523330772020302 0ustar tseavertseaverfrom unittest import TestCase, TestSuite, makeSuite, main import Zope from DateTime import DateTime from Products.CMFCore.tests.base.dummy import DummyContent from Products.CMFCore.tests.base.dummy import DummyFTI from Products.CMFCore.tests.base.testcase import SecurityTest from Products.CMFCore.tests.base.testcase import newSecurityManager from Products.CMFCore.tests.base.utils import has_path from Products.CMFCore.tests.base.security import OmnipotentUser from Products.CMFCore.TypesTool import TypesTool from Products.CMFCore.TypesTool import FactoryTypeInformation as FTI from Products.CMFCore.CatalogTool import CatalogTool from Products.CMFCore.PortalFolder import PortalFolder from Products.CMFCore.PortalFolder import ContentFilter def extra_meta_types(): return [ { 'name' : 'Dummy', 'action' : 'manage_addFolder' } ] class PortalFolderFactoryTests( SecurityTest ): def setUp( self ): SecurityTest.setUp( self ) self.root._setObject( 'portal_types', TypesTool() ) types_tool = self.root.portal_types types_tool._setObject( 'Folder' , FTI( id='Folder' , title='Folder or Directory' , meta_type=PortalFolder.meta_type , product='CMFCore' , factory='manage_addPortalFolder' , filter_content_types=0 ) ) types_tool._setObject( 'Dummy Content', DummyFTI ) def _makeOne( self, id ): return PortalFolder( id ).__of__( self.root ) def test_invokeFactory( self ): f = self._makeOne( 'container' ) self.failIf( 'foo' in f.objectIds() ) f.invokeFactory( type_name='Dummy Content', id='foo' ) self.failUnless( 'foo' in f.objectIds() ) foo = f.foo self.assertEqual( foo.getId(), 'foo' ) self.assertEqual( foo._getPortalTypeName(), 'Dummy Content' ) self.assertEqual( foo.Type(), 'Dummy Content Title' ) def test_invokeFactory_disallowed_type( self ): f = self._makeOne( 'container' ) ftype = self.root.portal_types.Folder ftype.filter_content_types = 1 self.assertRaises( ValueError , f.invokeFactory, type_name='Folder', id='sub' ) ftype.allowed_content_types = ( 'Folder', ) f.invokeFactory( type_name='Folder', id='sub' ) self.failUnless( 'sub' in f.objectIds() ) self.assertRaises( ValueError , f.invokeFactory , type_name='Dummy Content', id='foo' ) class PortalFolderTests( SecurityTest ): def setUp( self ): SecurityTest.setUp(self) root = self.root try: root._delObject('test') except AttributeError: pass root._setObject( 'test', PortalFolder( 'test','' ) ) def test_deletePropagation( self ): test = self.root.test foo = DummyContent( 'foo' ) foo.reset() assert not foo.after_add_called assert not foo.before_delete_called test._setObject( 'foo', foo ) assert foo.after_add_called assert not foo.before_delete_called foo.reset() test._delObject( 'foo' ) assert not foo.after_add_called assert foo.before_delete_called foo.reset() test._setObject( 'foo', foo ) test._delOb( 'foo' ) # doesn't propagate assert foo.after_add_called assert not foo.before_delete_called def test_manageDelObjects( self ): test = self.root.test foo = DummyContent( 'foo' ) test._setObject( 'foo', foo ) foo.reset() test.manage_delObjects( ids=[ 'foo' ] ) assert not foo.after_add_called assert foo.before_delete_called def test_catalogUnindexAndIndex( self ): # # Test is a new object does get cataloged upon _setObject # and uncataloged upon manage_deleteObjects # test = self.root.test self.root._setObject( 'portal_types', TypesTool() ) types_tool = self.root.portal_types self.root._setObject( 'portal_catalog', CatalogTool() ) catalog = self.root.portal_catalog assert len( catalog ) == 0 test._setObject( 'foo', DummyContent( 'foo' , catalog=1 ) ) foo = test.foo assert foo.after_add_called assert not foo.before_delete_called assert len( catalog ) == 1 foo.reset() test._delObject( 'foo' ) assert not foo.after_add_called assert foo.before_delete_called assert len( catalog ) == 0 def test_tracker261( self ): # # Tracker issue #261 says that content in a deleted folder # is not being uncatalogued. Try creating a subfolder with # content object, and test. # test = self.root.test self.root._setObject( 'portal_types', TypesTool() ) types_tool = self.root.portal_types self.root._setObject( 'portal_catalog', CatalogTool() ) catalog = self.root.portal_catalog assert len( catalog ) == 0 test._setObject( 'sub', PortalFolder( 'sub', '' ) ) sub = test.sub sub._setObject( 'foo', DummyContent( 'foo', catalog=1 ) ) foo = sub.foo assert foo.after_add_called assert not foo.before_delete_called assert len( catalog ) == 1 foo.reset() test.manage_delObjects( ids=[ 'sub' ] ) assert not foo.after_add_called assert foo.before_delete_called assert len( catalog ) == 0 def test_folderMove( self ): # # Does the catalog stay synched when folders are moved? # test = self.root.test self.root._setObject( 'portal_types', TypesTool() ) types_tool = self.root.portal_types self.root._setObject( 'portal_catalog', CatalogTool() ) catalog = self.root.portal_catalog assert len( catalog ) == 0 test._setObject( 'folder', PortalFolder( 'folder', '' ) ) folder = test.folder folder._setObject( 'sub', PortalFolder( 'sub', '' ) ) sub = folder.sub sub._setObject( 'foo', DummyContent( 'foo', catalog=1 ) ) foo = sub.foo assert len( catalog ) == 1 assert 'foo' in catalog.uniqueValuesFor( 'id' ) assert has_path( catalog._catalog, '/test/folder/sub/foo' ) # WAAAA! must get _p_jar set old, sub._p_jar = sub._p_jar, self.root._p_jar try: folder.manage_renameObject( id='sub', new_id='new_sub' ) finally: sub._p_jar = old assert 'foo' in catalog.uniqueValuesFor( 'id' ) assert len( catalog ) == 1 assert has_path( catalog._catalog, '/test/folder/new_sub/foo' ) folder._setObject( 'bar', DummyContent( 'bar', catalog=1 ) ) bar = folder.bar assert 'bar' in catalog.uniqueValuesFor( 'id' ) assert len( catalog ) == 2 assert has_path( catalog._catalog, '/test/folder/bar' ) folder._setObject( 'sub2', PortalFolder( 'sub2', '' ) ) sub2 = folder.sub2 # Waaa! force sub2 to allow paste of Dummy object. sub2.all_meta_types = [] sub2.all_meta_types.extend( sub2.all_meta_types ) sub2.all_meta_types.extend( extra_meta_types() ) # WAAAA! must get _p_jar set old, bar._p_jar = sub._p_jar, self.root._p_jar try: cookie = folder.manage_cutObjects( ids=['bar'] ) sub2.manage_pasteObjects( cookie ) finally: bar._p_jar = old assert 'foo' in catalog.uniqueValuesFor( 'id' ) assert 'bar' in catalog.uniqueValuesFor( 'id' ) assert len( catalog ) == 2 assert has_path( catalog._catalog, '/test/folder/sub2/bar' ) def test_manageAddFolder( self ): # # Does MKDIR/MKCOL intercept work? # test = self.root.test test._setPortalTypeName( 'Folder' ) self.root.reindexObject = lambda: 0 self.root._setObject( 'portal_types', TypesTool() ) types_tool = self.root.portal_types types_tool._setObject( 'Folder' , FTI( id='Folder' , title='Folder or Directory' , meta_type=PortalFolder.meta_type , product='CMFCore' , factory='manage_addPortalFolder' , filter_content_types=0 ) ) types_tool._setObject( 'Grabbed' , FTI( 'Grabbed' , title='Grabbed Content' , meta_type=PortalFolder.meta_type , product='CMFCore' , factory='manage_addPortalFolder' ) ) # First, test default behavior test.manage_addFolder( id='simple', title='Simple' ) self.assertEqual( test.simple._getPortalTypeName(), 'Folder' ) self.assertEqual( test.simple.Type(), 'Folder or Directory' ) self.assertEqual( test.simple.getId(), 'simple' ) self.assertEqual( test.simple.Title(), 'Simple' ) # Now, test overridden behavior types_tool.Folder.addAction( id = 'mkdir' , name = 'MKDIR handler' , action = 'grabbed' , permission = '' , category = 'folder' , visible = 0 ) class Grabbed: _grabbed_with = None def __init__( self, context ): self._context = context def __call__( self, id ): self._grabbed_with = id self._context._setOb( id, PortalFolder( id ) ) self._context._getOb( id )._setPortalTypeName( 'Grabbed' ) self.root.grabbed = Grabbed( test ) test.manage_addFolder( id='indirect', title='Indirect' ) self.assertEqual( test.indirect._getPortalTypeName(), 'Grabbed' ) self.assertEqual( test.indirect.Type(), 'Grabbed Content' ) self.assertEqual( test.indirect.getId(), 'indirect' ) self.assertEqual( test.indirect.Title(), 'Indirect' ) def test_contentPaste( self ): # # Does copy / paste work? # test = self.root.test self.root._setObject( 'portal_types', TypesTool() ) types_tool = self.root.portal_types types_tool._setObject( 'Dummy Content', DummyFTI ) self.root._setObject( 'portal_catalog', CatalogTool() ) catalog = self.root.portal_catalog assert len( catalog ) == 0 test._setObject( 'sub1', PortalFolder( 'sub1', '' ) ) sub1 = test.sub1 test._setObject( 'sub2', PortalFolder( 'sub2', '' ) ) sub2 = test.sub2 test._setObject( 'sub3', PortalFolder( 'sub3', '' ) ) sub3 = test.sub3 sub1._setObject( 'dummy', DummyContent( 'dummy', catalog=1 ) ) dummy = sub1.dummy assert 'dummy' in sub1.objectIds() assert 'dummy' in sub1.contentIds() assert not 'dummy' in sub2.objectIds() assert not 'dummy' in sub2.contentIds() assert not 'dummy' in sub3.objectIds() assert not 'dummy' in sub3.contentIds() assert has_path( catalog._catalog, '/test/sub1/dummy' ) assert not has_path( catalog._catalog, '/test/sub2/dummy' ) assert not has_path( catalog._catalog, '/test/sub3/dummy' ) cookie = sub1.manage_copyObjects( ids = ( 'dummy', ) ) # Waaa! force sub2 to allow paste of Dummy object. sub2.all_meta_types = [] sub2.all_meta_types.extend( sub2.all_meta_types ) sub2.all_meta_types.extend( extra_meta_types() ) sub2.manage_pasteObjects( cookie ) assert 'dummy' in sub1.objectIds() assert 'dummy' in sub1.contentIds() assert 'dummy' in sub2.objectIds() assert 'dummy' in sub2.contentIds() assert not 'dummy' in sub3.objectIds() assert not 'dummy' in sub3.contentIds() assert has_path( catalog._catalog, '/test/sub1/dummy' ) assert has_path( catalog._catalog, '/test/sub2/dummy' ) assert not has_path( catalog._catalog, '/test/sub3/dummy' ) # WAAAA! must get _p_jar set old, dummy._p_jar = dummy._p_jar, self.root._p_jar try: cookie = sub1.manage_cutObjects( ids = ( 'dummy', ) ) # Waaa! force sub2 to allow paste of Dummy object. sub3.all_meta_types = [] sub3.all_meta_types.extend( sub3.all_meta_types ) sub3.all_meta_types.extend( extra_meta_types() ) sub3.manage_pasteObjects( cookie ) finally: dummy._p_jar = old assert not 'dummy' in sub1.objectIds() assert not 'dummy' in sub1.contentIds() assert 'dummy' in sub2.objectIds() assert 'dummy' in sub2.contentIds() assert 'dummy' in sub3.objectIds() assert 'dummy' in sub3.contentIds() assert not has_path( catalog._catalog, '/test/sub1/dummy' ) assert has_path( catalog._catalog, '/test/sub2/dummy' ) assert has_path( catalog._catalog, '/test/sub3/dummy' ) class ContentFilterTests( TestCase ): def setUp( self ): self.dummy=DummyContent('Dummy') def test_empty( self ): cfilter = ContentFilter() dummy = self.dummy assert cfilter( dummy ) desc = str( cfilter ) lines = filter( None, desc.split('; ') ) assert not lines def test_Type( self ): cfilter = ContentFilter( Type='foo' ) dummy = self.dummy assert not cfilter( dummy ) cfilter = ContentFilter( Type='Dummy Content Title' ) assert cfilter( dummy ) desc = str( cfilter ) lines = desc.split('; ') assert len( lines ) == 1 assert lines[0] == 'Type: Dummy Content Title' cfilter = ContentFilter( Type=( 'foo', 'bar' ) ) dummy = self.dummy assert not cfilter( dummy ) cfilter = ContentFilter( Type=( 'Dummy Content Title', 'something else' ) ) assert cfilter( dummy ) desc = str( cfilter ) lines = desc.split('; ') assert len( lines ) == 1 assert lines[0] == 'Type: Dummy Content Title, something else' def test_portal_type( self ): cfilter = ContentFilter( portal_type='some_pt' ) dummy = self.dummy assert not cfilter( dummy ) dummy.portal_type = 'asdf' assert not cfilter( dummy ) dummy.portal_type = 'some_ptyyy' assert not cfilter( dummy ) dummy.portal_type = 'xxxsome_ptyyy' assert not cfilter( dummy ) dummy.portal_type = 'some_pt' assert cfilter( dummy ) desc = str( cfilter ) lines = desc.split('; ') assert len( lines ) == 1 assert lines[0] == 'Portal Type: some_pt' def test_Title( self ): cfilter = ContentFilter( Title='foo' ) dummy = self.dummy assert not cfilter( dummy ) dummy.title = 'asdf' assert not cfilter( dummy ) dummy.title = 'foolish' assert cfilter( dummy ) dummy.title = 'ohsofoolish' assert cfilter( dummy ) desc = str( cfilter ) lines = desc.split('; ') assert len( lines ) == 1 assert lines[0] == 'Title: foo' def test_Creator( self ): cfilter = ContentFilter( Creator='moe' ) dummy = self.dummy assert not cfilter( dummy ) dummy.creator = 'curly' assert not cfilter( dummy ) dummy.creator = 'moe' self.failUnless(cfilter( dummy )) dummy.creator = 'shmoe' assert cfilter( dummy ) desc = str( cfilter ) lines = desc.split('; ') self.assertEqual(len( lines ),1) self.assertEqual(lines[0],'Creator: moe') def test_Description( self ): cfilter = ContentFilter( Description='funny' ) dummy = self.dummy assert not cfilter( dummy ) dummy.description = 'sad' assert not cfilter( dummy ) dummy.description = 'funny' assert cfilter( dummy ) dummy.description = 'it is funny you should mention it...' assert cfilter( dummy ) desc = str( cfilter ) lines = desc.split('; ') assert len( lines ) == 1 assert lines[0] == 'Description: funny' def test_Subject( self ): cfilter = ContentFilter( Subject=('foo',) ) dummy = self.dummy assert not cfilter( dummy ) dummy.subject = ( 'bar', ) assert not cfilter( dummy ) dummy.subject = ( 'foo', ) assert cfilter( dummy ) dummy.subject = ( 'foo', 'bar', ) assert cfilter( dummy ) desc = str( cfilter ) lines = desc.split('; ') assert len( lines ) == 1 assert lines[0] == 'Subject: foo' def test_Subject2( self ): # Now test with mutli-valued cfilter = ContentFilter( Subject=('foo', 'bar' ) ) dummy = self.dummy assert not cfilter( dummy ) dummy.subject = ( 'baz', ) assert not cfilter( dummy ) dummy.subject = ( 'bar', ) assert cfilter( dummy ) dummy.subject = ( 'foo', ) assert cfilter( dummy ) dummy.subject = ( 'foo', 'bar', ) assert cfilter( dummy ) desc = str( cfilter ) lines = desc.split('; ') assert len( lines ) == 1 assert lines[0] == 'Subject: foo, bar' def test_created( self ): cfilter = ContentFilter( created=DateTime( '2025/01/01' ) , created_usage='range:min' ) dummy = self.dummy assert not cfilter( dummy ) dummy.created_date = DateTime( '2024/12/31' ) assert not cfilter( dummy ) dummy.created_date = DateTime( '2024/12/31' ) assert cfilter( dummy ) dummy.created_date = DateTime( '2025/01/01' ) assert cfilter( dummy ) desc = str( cfilter ) lines = desc.split('; ') assert len( lines ) == 1 assert lines[0] == 'Created since: 2025/01/01' def test_created2( self ): cfilter = ContentFilter( created=DateTime( '2025/01/01' ) , created_usage='range:max' ) dummy = self.dummy assert not cfilter( dummy ) dummy.created_date = DateTime( '2024/12/31' ) assert cfilter( dummy ) dummy.created_date = DateTime( '2024/12/31' ) assert not cfilter( dummy ) dummy.created_date = DateTime( '2025/01/01' ) assert cfilter( dummy ) desc = str( cfilter ) lines = desc.split('; ') assert len( lines ) == 1 assert lines[0] == 'Created before: 2025/01/01' def test_modified( self ): cfilter = ContentFilter( modified=DateTime( '2025/01/01' ) , modified_usage='range:min' ) dummy = self.dummy assert not cfilter( dummy ) dummy.modified_date = DateTime( '2024/12/31' ) assert not cfilter( dummy ) dummy.modified_date = DateTime( '2024/12/31' ) assert cfilter( dummy ) dummy.modified_date = DateTime( '2025/01/01' ) assert cfilter( dummy ) desc = str( cfilter ) lines = desc.split('; ') assert len( lines ) == 1 assert lines[0] == 'Modified since: 2025/01/01' def test_modified2( self ): cfilter = ContentFilter( modified=DateTime( '2025/01/01' ) , modified_usage='range:max' ) dummy = self.dummy assert not cfilter( dummy ) dummy.modified_date = DateTime( '2024/12/31' ) assert cfilter( dummy ) dummy.modified_date = DateTime( '2024/12/31' ) assert not cfilter( dummy ) dummy.modified_date = DateTime( '2025/01/01' ) assert cfilter( dummy ) desc = str( cfilter ) lines = desc.split('; ') assert len( lines ) == 1 assert lines[0] == 'Modified before: 2025/01/01' def test_mixed( self ): cfilter = ContentFilter( created=DateTime( '2025/01/01' ) , created_usage='range:max' , Title='foo' ) dummy = self.dummy assert not cfilter( dummy ) dummy.created_date = DateTime( '2024/12/31' ) assert not cfilter( dummy ) dummy.created_date = DateTime( '2024/12/31' ) assert not cfilter( dummy ) dummy.created_date = DateTime( '2025/01/01' ) assert not cfilter( dummy ) dummy.title = 'ohsofoolish' del dummy.created_date assert not cfilter( dummy ) dummy.created_date = DateTime( '2024/12/31' ) assert cfilter( dummy ) dummy.created_date = DateTime( '2024/12/31' ) assert not cfilter( dummy ) dummy.created_date = DateTime( '2025/01/01' ) assert cfilter( dummy ) desc = str( cfilter ) lines = desc.split('; ') assert len( lines ) == 2, lines assert 'Created before: 2025/01/01' in lines assert 'Title: foo' in lines def test_suite(): return TestSuite(( makeSuite( PortalFolderFactoryTests ), makeSuite( PortalFolderTests ), makeSuite( ContentFilterTests ), )) if __name__ == '__main__': main(defaultTest='test_suite') CMF-1.3/CMFCore/tests/test_TypesTool.py0100644000076500007650000003437107523330772017654 0ustar tseavertseaverimport Zope from unittest import TestCase, TestSuite, makeSuite, main from Products.CMFCore.TypesTool import\ FactoryTypeInformation as FTI,\ ScriptableTypeInformation as STI,\ TypesTool,Unauthorized from Products.CMFCore.PortalFolder import PortalFolder from Products.CMFCore.utils import _getViewFor from Products.CMFCore.tests.base.testcase import \ SecurityRequestTest from Products.CMFCore.tests.base.security import \ OmnipotentUser, UserWithRoles from Products.CMFCore.tests.base.dummy import \ DummyObject, addDummy, DummyTypeInfo,\ DummyFolder, DummyFTI from AccessControl.SecurityManagement import newSecurityManager from AccessControl.SecurityManagement import noSecurityManager from Products.PythonScripts.standard import url_quote from webdav.NullResource import NullResource from Acquisition import aq_base class TypesToolTests( SecurityRequestTest ): def setUp( self ): SecurityRequestTest.setUp(self) root = self.root root.addDummy = addDummy root._setObject( 'portal_types', TypesTool() ) tool = root.portal_types tool._setObject( 'Dummy Content', DummyFTI ) def test_processActions( self ): """ Are the correct, permitted methods returned for actions? """ self.root._setObject( 'portal', PortalFolder( 'portal', '' ) ) portal = self.root.portal portal.invokeFactory( 'Dummy Content', 'actions_dummy' ) dummy = portal._getOb( 'actions_dummy' ) # so we can traverse to it: dummy.view = DummyObject("view") dummy.view2 = DummyObject("view2") dummy.edit = DummyObject("edit") default_view = dummy() custom_view = _getViewFor( dummy, view='view2' )() unpermitted_view = _getViewFor( dummy, view='edit' )() self.failUnlessEqual(default_view, 'view') self.failUnlessEqual(custom_view, 'view2') self.failIf(unpermitted_view == 'edit') self.failUnlessEqual(unpermitted_view, 'view') def test_allMetaTypes(self): """ Test that everything returned by allMetaTypes can be traversed to. """ tool = self.root.portal_types meta_types={} # Seems we get NullResource if the method couldn't be traverse to # so we check for that. If we've got it, something is b0rked. for factype in tool.all_meta_types(): meta_types[factype['name']]=1 # The url_quote below is necessary 'cos of the one in # main.dtml. Could be removed once that is gone. self.failIf(type(aq_base(tool.unrestrictedTraverse(url_quote(factype['action'])))) is NullResource) # Check the ones we're expecting are there self.failUnless(meta_types.has_key('Scriptable Type Information')) self.failUnless(meta_types.has_key('Factory-based Type Information')) class TypeInfoTests( TestCase ): def test_construction( self ): ti = self._makeInstance( 'Foo' , description='Description' , meta_type='Foo' , icon='foo.gif' ) self.assertEqual( ti.getId(), 'Foo' ) self.assertEqual( ti.Title(), 'Foo' ) self.assertEqual( ti.Description(), 'Description' ) self.assertEqual( ti.Metatype(), 'Foo' ) self.assertEqual( ti.getIcon(), 'foo.gif' ) self.assertEqual( ti.immediate_view, '' ) ti = self._makeInstance( 'Foo' , immediate_view='foo_view' ) self.assertEqual( ti.immediate_view, 'foo_view' ) def _makeAndSetInstance( self,id,**kw ): tool = self.tool t = apply( self._makeInstance, (id,), kw ) tool._setObject(id,t) return tool[id] def test_allowType( self ): self.tool = TypesTool() ti = self._makeAndSetInstance( 'Foo' ) self.failIf( ti.allowType( 'Foo' ) ) self.failIf( ti.allowType( 'Bar' ) ) ti = self._makeAndSetInstance( 'Foo2', allowed_content_types=( 'Bar', ) ) self.failUnless( ti.allowType( 'Bar' ) ) ti = self._makeAndSetInstance( 'Foo3', filter_content_types=0 ) self.failUnless( ti.allowType( 'Foo3' ) ) def test_GlobalHide( self ): self.tool = TypesTool() tnf = self._makeAndSetInstance( 'Folder', filter_content_types=0) taf = self._makeAndSetInstance( 'Allowing Folder', allowed_content_types=('Hidden','Not Hidden')) tih = self._makeAndSetInstance( 'Hidden' ,global_allow=0) tnh = self._makeAndSetInstance( 'Not Hidden') # make sure we're normally hidden but everything else is visible self.failIf ( tnf.allowType( 'Hidden' ) ) self.failUnless ( tnf.allowType( 'Not Hidden') ) # make sure we're available where we should be self.failUnless ( taf.allowType( 'Hidden' ) ) self.failUnless ( taf.allowType( 'Not Hidden') ) # make sure we're available in a non-content-type-filtered type # where we have been explicitly allowed taf2 = self._makeAndSetInstance( 'Allowing Folder2', allowed_content_types=('Hidden','Not Hidden'), filter_content_types=0) self.failUnless ( taf2.allowType( 'Hidden' ) ) self.failUnless ( taf2.allowType( 'Not Hidden') ) def test_allowDiscussion( self ): ti = self._makeInstance( 'Foo' ) self.failIf( ti.allowDiscussion() ) ti = self._makeInstance( 'Foo', allow_discussion=1 ) self.failUnless( ti.allowDiscussion() ) ACTION_LIST = \ ( { 'id' : 'view' , 'name' : 'View' , 'action' : 'foo_view' , 'permissions' : ( 'View', ) , 'category' : 'object' , 'visible' : 1 } , { 'name' : 'Edit' # Note: No ID passed , 'action' : 'foo_edit' , 'permissions' : ( 'Modify', ) , 'category' : 'object' , 'visible' : 1 } , { 'name' : 'Object Properties' # Note: No ID passed , 'action' : 'foo_properties' , 'permissions' : ( 'Modify', ) , 'category' : 'object' , 'visible' : 1 } , { 'id' : 'slot' , 'action' : 'foo_slot' , 'category' : 'object' , 'visible' : 0 } ) def _ripActionValues( self, key, actions ): return filter( None, map( lambda x, key=key: x.get( key, None ) , actions ) ) def test_listActions( self ): ti = self._makeInstance( 'Foo' ) self.failIf( ti.getActions() ) ti = self._makeInstance( 'Foo', actions=self.ACTION_LIST ) actions = ti.getActions() self.failUnless( actions ) ids = self._ripActionValues( 'id', actions ) self.failUnless( 'view' in ids ) self.failUnless( 'edit' in ids ) self.failUnless( 'objectproperties' in ids ) self.failUnless( 'slot' in ids ) names = self._ripActionValues( 'name', actions ) self.failUnless( 'View' in names ) self.failUnless( 'Edit' in names ) self.failUnless( 'Object Properties' in names ) self.failIf( 'slot' in names ) self.failIf( 'Slot' in names ) visible = filter( None, map( lambda x: x.get( 'visible', 0 ) and x['id'] , actions ) ) self.failUnless( 'view' in visible ) self.failUnless( 'edit' in visible ) self.failUnless( 'objectproperties' in visible ) self.failIf( 'slot' in visible ) def test_getActionById( self ): ti = self._makeInstance( 'Foo' ) marker = [] self.assertEqual( id( ti.getActionById( 'view', marker ) ) , id( marker ) ) self.assertRaises( TypeError, ti.getActionById, 'view' ) ti = self._makeInstance( 'Foo', actions=self.ACTION_LIST ) self.assertEqual( id( ti.getActionById( 'foo', marker ) ) , id( marker ) ) self.assertRaises( TypeError, ti.getActionById, 'foo' ) action = ti.getActionById( 'view' ) self.assertEqual( action, 'foo_view' ) action = ti.getActionById( 'edit' ) self.assertEqual( action, 'foo_edit' ) action = ti.getActionById( 'objectproperties' ) self.assertEqual( action, 'foo_properties' ) action = ti.getActionById( 'slot' ) self.assertEqual( action, 'foo_slot' ) class FTIDataTests( TypeInfoTests ): def _makeInstance( self, id, **kw ): return apply( FTI, ( id, ), kw ) def test_properties( self ): ti = self._makeInstance( 'Foo' ) self.assertEqual( ti.product, '' ) self.assertEqual( ti.factory, '' ) ti = self._makeInstance( 'Foo' , product='FooProduct' , factory='addFoo' ) self.assertEqual( ti.product, 'FooProduct' ) self.assertEqual( ti.factory, 'addFoo' ) class STIDataTests( TypeInfoTests ): def _makeInstance( self, id, **kw ): return apply( STI, ( id, ), kw ) def test_properties( self ): ti = self._makeInstance( 'Foo' ) self.assertEqual( ti.permission, '' ) self.assertEqual( ti.constructor_path, '' ) ti = self._makeInstance( 'Foo' , permission='Add Foos' , constructor_path='foo_add' ) self.assertEqual( ti.permission, 'Add Foos' ) self.assertEqual( ti.constructor_path, 'foo_add' ) class FTIConstructionTests( TestCase ): def setUp( self ): noSecurityManager() def _makeInstance( self, id, **kw ): return apply( FTI, ( id, ), kw ) def _makeFolder( self, fake_product=0 ): return DummyFolder( fake_product ) def test_isConstructionAllowed_wo_Container( self ): ti = self._makeInstance( 'foo' ) self.failIf( ti.isConstructionAllowed( None ) ) ti = self._makeInstance( 'Foo' , product='FooProduct' , factory='addFoo' ) self.failIf( ti.isConstructionAllowed( None ) ) def test_isConstructionAllowed_wo_ProductFactory( self ): ti = self._makeInstance( 'foo' ) folder = self._makeFolder() self.failIf( ti.isConstructionAllowed( folder ) ) folder = self._makeFolder( fake_product=1 ) self.failIf( ti.isConstructionAllowed( folder ) ) def test_isConstructionAllowed_wo_Security( self ): ti = self._makeInstance( 'Foo' , product='FooProduct' , factory='addFoo' ) folder = self._makeFolder( fake_product=1 ) self.failIf( ti.isConstructionAllowed( folder ) ) class FTIConstructionTests_w_Roles( TestCase ): def tearDown( self ): noSecurityManager() def _makeStuff( self, prefix='' ): ti = FTI( 'Foo' , product='FooProduct' , factory='addFoo' ) folder = DummyFolder( fake_product=1,prefix=prefix ) return ti, folder def test_isConstructionAllowed_for_Omnipotent( self ): ti, folder = self._makeStuff() newSecurityManager( None , OmnipotentUser().__of__( folder ) ) self.failUnless( ti.isConstructionAllowed( folder ) ) def test_isConstructionAllowed_w_Role( self ): ti, folder = self._makeStuff() newSecurityManager( None , UserWithRoles( 'FooAdder' ).__of__( folder ) ) self.failUnless( ti.isConstructionAllowed( folder ) ) def test_isConstructionAllowed_wo_Role( self ): ti, folder = self._makeStuff() newSecurityManager( None , UserWithRoles( 'FooViewer' ).__of__( folder ) ) def test_constructInstance_wo_Roles( self ): ti, folder = self._makeStuff() newSecurityManager( None , UserWithRoles( 'FooViewer' ).__of__( folder ) ) self.assertRaises( Unauthorized , ti.constructInstance, folder, 'foo' ) def test_constructInstance( self ): ti, folder = self._makeStuff() newSecurityManager( None , UserWithRoles( 'FooAdder' ).__of__( folder ) ) ti.constructInstance( folder, 'foo' ) foo = folder._getOb( 'foo' ) self.assertEqual( foo.id, 'foo' ) def test_constructInstance_w_args_kw( self ): ti, folder = self._makeStuff() newSecurityManager( None , UserWithRoles( 'FooAdder' ).__of__( folder ) ) ti.constructInstance( folder, 'bar', 0, 1 ) bar = folder._getOb( 'bar' ) self.assertEqual( bar.id, 'bar' ) self.assertEqual( bar._args, ( 0, 1 ) ) ti.constructInstance( folder, 'baz', frickle='natz' ) baz = folder._getOb( 'baz' ) self.assertEqual( baz.id, 'baz' ) self.assertEqual( baz._kw[ 'frickle' ], 'natz' ) ti.constructInstance( folder, 'bam', 0, 1, frickle='natz' ) bam = folder._getOb( 'bam' ) self.assertEqual( bam.id, 'bam' ) self.assertEqual( bam._args, ( 0, 1 ) ) self.assertEqual( bam._kw[ 'frickle' ], 'natz' ) def test_constructInstance_w_id_munge( self ): ti, folder = self._makeStuff( 'majyk' ) newSecurityManager( None , UserWithRoles( 'FooAdder' ).__of__( folder ) ) ti.constructInstance( folder, 'dust' ) majyk_dust = folder._getOb( 'majyk_dust' ) self.assertEqual( majyk_dust.id, 'majyk_dust' ) def test_suite(): return TestSuite(( makeSuite(TypesToolTests), makeSuite(FTIDataTests), makeSuite(STIDataTests), makeSuite(FTIConstructionTests), makeSuite(FTIConstructionTests_w_Roles), )) if __name__ == '__main__': main(defaultTest='test_suite') CMF-1.3/CMFCore/tests/test_WorkflowTool.py0100644000076500007650000002332407523330772020356 0ustar tseavertseaverimport unittest import Zope # Sigh, make product initialization happen from OFS.SimpleItem import SimpleItem class Dummy( SimpleItem ): def __init__( self, id ): self._id = id def getId( self ): return self._id class DummyWorkflow( Dummy ): meta_type = 'DummyWorkflow' _isAWorkflow = 1 _known_actions=() _known_info=() def __init__( self, id ): Dummy.__init__( self, id ) self._did_action = {} self._gave_info = {} self._notified = {} def setKnownActions( self, known_actions ): self._known_actions = known_actions def setKnownInfo( self, known_info ): self._known_info = known_info def didAction( self, action ): return self._did_action.setdefault( action, [] ) def gaveInfo( self, name ): return self._gave_info.setdefault( name, [] ) def notified( self, name ): return self._notified.setdefault( name, [] ) # # WorkflowDefinition interface # def getCatalogVariablesFor( self, ob ): return { 'dummy' : '%s: %s' % ( self.getId(), ob.getId() ) } def updateRoleMappingsFor( self, ob ): pass def listObjectActions( self, info ): return () #XXX def listGlobalActions( self, info ): return () #XXX def isActionSupported( self, ob, action ): return action in self._known_actions def doActionFor( self, ob, action, *args, **kw ): self.didAction( action ).append( ob ) def isInfoSupported( self, ob, name ): return name in self._known_info def getInfoFor( self, ob, name, default, *args, **kw ): self.gaveInfo( name ).append( ob ) return name in self._known_info and 1 or 0 def notifyCreated( self, ob ): self.notified( 'created' ).append( ( ob, ) ) def notifyBefore( self, ob, action ): self.notified( 'before' ).append( ( ob, action ) ) def notifySuccess( self, ob, action, result ): self.notified( 'success' ).append( ( ob, action, result ) ) def notifyException( self, ob, action, exc ): self.notified( 'exception' ).append( ( ob, action, exc ) ) class DummyContent( Dummy ): meta_type = 'Dummy' _isPortalContent = 1 def _getPortalTypeName(self): return 'Dummy Content' class DummyNotReallyContent( Dummy ): meta_type = 'Dummy Content' class DummyTypeInfo( Dummy ): pass class DummyTypesTool( SimpleItem ): def listTypeInfo( self ): return [ DummyTypeInfo( 'Dummy Content' ) ] def getTypeInfo( self, ob ): if getattr( ob, 'meta_type', None ) is 'Dummy': return DummyTypeInfo( 'Dummy Content' ) return None class WorkflowToolTests( unittest.TestCase ): def setUp( self ): from Products.CMFCore.WorkflowTool import addWorkflowFactory addWorkflowFactory( DummyWorkflow ) def tearDown( self ): from Products.CMFCore.WorkflowTool import _removeWorkflowFactory _removeWorkflowFactory( DummyWorkflow ) def _makeOne( self, workflow_ids=() ): from Products.CMFCore.WorkflowTool import WorkflowTool tool = WorkflowTool() for workflow_id in workflow_ids: tool.manage_addWorkflow( DummyWorkflow.meta_type, workflow_id ) return tool def _makeRoot( self ): from OFS.Folder import Folder root = Folder( 'root' ) tt = DummyTypesTool() root._setObject( 'portal_types', tt ) return root def _makeWithTypes( self ): root = self._makeRoot() return self._makeOne( workflow_ids=( 'a', 'b' ) ).__of__( root ) def _makeWithTypesAndChain( self ): tool = self._makeWithTypes() tool.setChainForPortalTypes( ( 'Dummy Content', ), ( 'a', 'b' ) ) return tool def test_interface( self ): from Products.CMFCore.WorkflowTool import WorkflowTool from Products.CMFCore.interfaces.portal_workflow import portal_workflow try: from Interface import verify_class_implementation as verifyClass except ImportError: from Interface.Verify import verifyClass verifyClass(portal_workflow, WorkflowTool) def test_empty( self ): from Products.CMFCore.WorkflowTool import WorkflowException tool = self._makeOne() self.failIf( tool.getWorkflowIds() ) self.assertEqual( tool.getWorkflowById( 'default_workflow' ), None ) self.assertEqual( tool.getWorkflowById( 'a' ), None ) self.assertRaises( WorkflowException, tool.getInfoFor, None, 'hmm' ) self.assertRaises( WorkflowException, tool.doActionFor, None, 'hmm' ) def test_new_with_wf( self ): from Products.CMFCore.WorkflowTool import WorkflowException tool = self._makeWithTypes() wfids = tool.getWorkflowIds() self.assertEqual( len( wfids ), 2 ) self.failUnless( 'a' in wfids ) self.failUnless( 'b' in wfids ) self.assertEqual( tool.getWorkflowById( 'default' ), None ) wf = tool.getWorkflowById( 'a' ) self.assertEqual( wf.getId(), 'a' ) wf = tool.getWorkflowById( 'b' ) self.assertEqual( wf.getId(), 'b' ) self.assertRaises( WorkflowException, tool.getInfoFor, None, 'hmm' ) self.assertRaises( WorkflowException, tool.doActionFor, None, 'hmm' ) def test_nonContent( self ): tool = self._makeWithTypesAndChain() self.assertEquals( len( tool.getDefaultChainFor( None ) ), 0 ) self.assertEquals( len( tool.getChainFor( None ) ), 0 ) self.assertEquals( len( tool.getCatalogVariablesFor( None ) ), 0 ) def test_notReallyContent( self ): tool = self._makeWithTypesAndChain() dummy = DummyNotReallyContent( 'doh' ) self.assertEquals( len( tool.getDefaultChainFor( dummy ) ), 0 ) self.assertEquals( len( tool.getChainFor( dummy ) ), 0 ) self.assertEquals( len( tool.getCatalogVariablesFor( dummy ) ), 0 ) def test_content_default_chain( self ): tool = self._makeWithTypes() dummy = DummyContent( 'dummy' ) self.assertEquals( len( tool.getDefaultChainFor( dummy ) ), 1 ) self.assertEquals( len( tool.getChainFor( dummy ) ), 1 ) self.assertEquals( len( tool.getCatalogVariablesFor( dummy ) ), 0 ) self.assertEquals( tool.getDefaultChainFor( dummy ) , tool.getChainFor( dummy ) ) def test_content_own_chain( self ): tool = self._makeWithTypesAndChain() dummy = DummyContent( 'dummy' ) self.assertEquals( len( tool.getDefaultChainFor( dummy ) ), 1 ) chain = tool.getChainFor( dummy ) self.assertEquals( len( chain ), 2 ) self.failUnless( 'a' in chain ) self.failUnless( 'b' in chain ) vars = tool.getCatalogVariablesFor( dummy ) self.assertEquals( len( vars ), 1 ) self.failUnless( 'dummy' in vars.keys() ) self.failUnless( 'a: dummy' in vars.values() ) def test_getCatalogVariablesFor( self ): tool = self._makeWithTypesAndChain() dummy = DummyContent( 'dummy' ) vars = tool.getCatalogVariablesFor( dummy ) self.assertEquals( len( vars ), 1 ) self.failUnless( 'dummy' in vars.keys() ) self.failUnless( 'a: dummy' in vars.values() ) def test_getInfoFor( self ): tool = self._makeWithTypesAndChain() tool.b.setKnownInfo( ( 'info', ) ) dummy = DummyContent( 'dummy' ) info = tool.getInfoFor( dummy, 'info' ) self.assertEqual( info, 1 ) self.failIf( tool.a.gaveInfo( 'info' ) ) self.failUnless( tool.b.gaveInfo( 'info' ) ) def test_doActionFor( self ): tool = self._makeWithTypesAndChain() tool.a.setKnownActions( ( 'action', ) ) dummy = DummyContent( 'dummy' ) tool.doActionFor( dummy, 'action' ) self.failUnless( tool.a.didAction( 'action' ) ) self.failIf( tool.b.didAction( 'action' ) ) def test_notifyCreated( self ): tool = self._makeWithTypesAndChain() ob = DummyContent( 'dummy' ) tool.notifyCreated( ob ) for wf in tool.a, tool.b: notified = wf.notified( 'created' ) self.assertEqual( len( notified ), 1 ) self.assertEqual( notified[0], ( ob, ) ) def test_notifyBefore( self ): tool = self._makeWithTypesAndChain() ob = DummyContent( 'dummy' ) tool.notifyBefore( ob, 'action' ) for wf in tool.a, tool.b: notified = wf.notified( 'before' ) self.assertEqual( len( notified ), 1 ) self.assertEqual( notified[0], ( ob, 'action' ) ) def test_notifySuccess( self ): tool = self._makeWithTypesAndChain() ob = DummyContent( 'dummy' ) tool.notifySuccess( ob, 'action' ) for wf in tool.a, tool.b: notified = wf.notified( 'success' ) self.assertEqual( len( notified ), 1 ) self.assertEqual( notified[0], ( ob, 'action', None ) ) def test_notifyException( self ): tool = self._makeWithTypesAndChain() ob = DummyContent( 'dummy' ) tool.notifyException( ob, 'action', 'exception' ) for wf in tool.a, tool.b: notified = wf.notified( 'exception' ) self.assertEqual( len( notified ), 1 ) self.assertEqual( notified[0], ( ob, 'action', 'exception' ) ) def xxx_test_updateRoleMappings( self ): """ Build a tree of objects, invoke tool.updateRoleMappings, and then check to see that the workflows each got called; check the resulting count, as well. """ def test_suite(): return unittest.TestSuite(( unittest.makeSuite(WorkflowToolTests), )) if __name__ == '__main__': unittest.main() CMF-1.3/CMFCore/tests/test_all.py0100644000076500007650000000127607470266273016465 0ustar tseavertseaverimport Zope from unittest import main from Products.CMFCore.tests.base.utils import build_test_suite def test_suite(): return build_test_suite('Products.CMFCore.tests',[ 'test_ContentTypeRegistry', 'test_PortalFolder', 'test_TypesTool', 'test_WorkflowTool', 'test_ActionsTool', 'test_ActionInformation', 'test_ActionProviderBase', 'test_Expression', 'test_CatalogTool', 'test_DirectoryView', 'test_FSPythonScript', 'test_FSPageTemplate', 'test_FSImage', 'test_CachingPolicyManager', 'test_FSSecurity', ]) if __name__ == '__main__': main(defaultTest='test_suite') CMF-1.3/CMFCore/tests/fake_skins/0040755000076500007650000000000007524010064016377 5ustar tseavertseaverCMF-1.3/CMFCore/tests/fake_skins/fake_skin/0040755000076500007650000000000007524010064020331 5ustar tseavertseaverCMF-1.3/CMFCore/tests/fake_skins/fake_skin/test1.py0100644000076500007650000000001707424237317021751 0ustar tseavertseaverreturn 'test1' CMF-1.3/CMFCore/tests/fake_skins/fake_skin/test4.py0100644000076500007650000000001707470266273021760 0ustar tseavertseaverreturn 'test1' CMF-1.3/CMFCore/tests/fake_skins/fake_skin/test4.py.security0100644000076500007650000000007007470266273023625 0ustar tseavertseaverView:acquire:Manager,Owner Access contents information::CMF-1.3/CMFCore/tests/fake_skins/fake_skin/test5.py0100644000076500007650000000001707470266273021761 0ustar tseavertseaverreturn 'test1' CMF-1.3/CMFCore/tests/fake_skins/fake_skin/testPT.pt0100644000076500007650000000005107431732327022124 0ustar tseavertseaver
CMF-1.3/CMFCore/tests/fake_skins/fake_skin/testPT.pt.properties0100644000076500007650000000002007455077275024325 0ustar tseavertseavertitle=Zope Pope CMF-1.3/CMFCore/tests/fake_skins/fake_skin/testPTbad.pt0100644000076500007650000000004407431732327022575 0ustar tseavertseaver
CMF-1.3/CMFCore/tests/fake_skins/fake_skin/testXMLPT.pt0100644000076500007650000000011707522236355022511 0ustar tseavertseaver This is only a test CMF-1.3/CMFCore/tests/fake_skins/fake_skin/test_image.gif0100644000076500007650000000032107465552076023154 0ustar tseavertseaverGIF89a̙ff3f3333f3ff!,~iNR*3;X%GbdRu3 % B0 `yTX0"[n;s< ~0V (!ppV!NP589y2)%'-1"$& ;CMF-1.3/CMFDefault/0040755000076500007650000000000007524010070013611 5ustar tseavertseaverCMF-1.3/CMFDefault/Extensions/0040755000076500007650000000000007524010065015754 5ustar tseavertseaverCMF-1.3/CMFDefault/Extensions/Upgrade.py0100644000076500007650000000340107417340257017722 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Utility functions for upgrading CMFDefault-based sites. """ from Acquisition import aq_inner import string def upgrade_decor_skins( self ): """ Upgrade old skin diretories loaded from 'CMFDecor' to load from 'CMFDefault' (and zap the 'zpt_images' one). """ log = [] DELETED_SKINS = ( 'zpt_images' , ) MOVED_SKINS = ( 'zpt_content' , 'zpt_control' , 'zpt_generic' ) skins_tool = aq_inner( self ).portal_skins # start from CMFSite! for deleted in DELETED_SKINS: try: skins_tool._delObject( deleted ) except AttributeError: pass else: log.append( 'Deleted CMFDecor skin directory: %s' % deleted ) for moved in MOVED_SKINS: skin_dir = getattr( skins_tool, moved, None ) if skin_dir is not None: skin_dir.manage_properties( dirpath='Products/CMFDefault/skins/%s' % moved ) log.append( 'Updated CMFDecor skin directory to CMFDefault: %s' % moved ) return string.join( log, '\n' ) CMF-1.3/CMFDefault/Extensions/fix_cmf_permissions.py0100644000076500007650000000076207311215145022376 0ustar tseavertseaver def fix_cmf_permissions(self): ''' Changes the permissions on each member folder to normal settings. ''' count = 0 m = self.Members for v in m.objectValues(): if hasattr(v, '_View_Permission'): del v._View_Permission if hasattr(v, '_Access_contents_information_Permission'): del v._Access_contents_information_Permission if v._p_changed: count = count + 1 return 'Changed permissions on %d objects.' % count CMF-1.3/CMFDefault/Extensions/migrate_ptk.py0100644000076500007650000003004407311215145020632 0ustar tseavertseaver from Acquisition import aq_base, aq_inner, aq_parent from ZODB.PersistentMapping import PersistentMapping from string import join import sys # # Routines generally useful for migration purposes. # class Converter: def allowDescendChildren(self): raise 'Not implemented' def convert(self, ob): raise 'Not implemented' def showDuplicationError(self): raise 'Not implemented' class Migrator: def __init__(self, conversions, skip): self.conversions = conversions self.skip = skip self.visited_folders = [] self.warnings = [] self.copied = [] self.skipped = [] def migrateObjectManager(self, src_folder, dst_folder, place=()): self.visited_folders.append(join(place, '/')) for id, s_ob in src_folder.objectItems(): d_ob = getattr(dst_folder, id, None) to_store = self.migrateObject(id, s_ob, d_ob, dst_folder, place + (id,)) if to_store is not None: owner = getattr(to_store, '_owner', None) if hasattr(dst_folder, '_setObject'): dst_folder._setObject(id, to_store) else: setattr(dst_folder, id, to_store) if owner is not None: # Retain ownership. to_store._owner = owner def migrateDiscussionContainer(self, src_folder, dst_folder, place=()): self.visited_folders.append(join(place, '/')) dst_container = getattr(dst_folder, '_container', None) if dst_container is None: dst_container = dst_folder._container = PersistentMapping() for id, s_ob in src_folder._container.items(): d_ob = dst_container.get(id) to_store = self.migrateObject(id, s_ob, d_ob, dst_folder, place + (id,)) if to_store is not None: dst_container[id] = aq_base(to_store) def migratePossibleContainer(self, s_ob, d_ob, place): base_ob = aq_base(s_ob) if hasattr(base_ob, 'objectItems'): self.migrateObjectManager(s_ob, d_ob, place) elif hasattr(base_ob, '_container'): self.migrateDiscussionContainer(s_ob, d_ob, place) def migrateObject(self, id, s_ob, d_ob, dst_folder, place): # Doesn't store changes, only returns the # object to store. conversions = self.conversions klass = s_ob.__class__ descend_ok = 1 base_ob = aq_base(s_ob) to_store = None pathname = join(place, '/') if self.skip.has_key(id): # Don't migrate objects by this name, but we can still # migrate subobjects. descend_ok = self.skip[id] if descend_ok and d_ob is None: descend_ok = 0 self.skipped.append(pathname + (descend_ok and ' (descended)' or ' ')) elif d_ob is not None: # The dest already has something with this ID. descend_ok = 1 show_message = 1 converter = conversions.get(klass, None) if converter is not None: descend_ok = converter.allowDescendChildren() show_message = converter.showDuplicationError() if show_message: self.warnings.append('Already existed: %s' % pathname) elif conversions.has_key(klass): # Invoke the appropriate converter. converter = conversions[klass] to_store = converter.convert(s_ob) self.copied.append(pathname) elif hasattr(base_ob, '_getCopy'): # Make a direct copy. to_store = s_ob._getCopy(dst_folder) self.warnings.append('Copied %s directly.' % pathname) descend_ok = 0 else: # No way to copy. descend_ok = 0 self.warnings.append('Could not copy %s' % pathname) if descend_ok: if to_store is not None: d_ob = to_store if d_ob is not None: try: d_ob._p_jar = dst_folder._p_jar except: pass self.migratePossibleContainer(s_ob, d_ob, place) return to_store class SimpleClassConverter (Converter): def __init__(self, to_class, descend, show_dup=1): self._klass = to_class self._descend = descend self._show_dup = show_dup def allowDescendChildren(self): return self._descend def showDuplicationError(self): return self._show_dup def convert(self, ob): # Creates a copy of ob without its children. ob = aq_base(ob) k = self._klass if hasattr(k, '__basicnew__'): newob = k.__basicnew__() else: newob = new.instance(k, {}) id = ob.id if callable(id): id = id() try: newob._setId(id) except AttributeError: newob.id = id newob.__dict__.update(ob.__dict__) if hasattr(newob, '_objects'): # Clear the children. for info in newob._objects: del newob.__dict__[info['id']] newob._objects = () if hasattr(newob, '_container'): # Clear the children. newob._container = PersistentMapping() return newob TupleType = type(()) def setupDirectConversion(old_prod, new_prod, modname, classname, conversions, descend=1, show_dup=1): try: old_module = sys.modules['Products.' + old_prod + '.' + modname] new_module = sys.modules['Products.' + new_prod + '.' + modname] old_class = getattr(old_module, classname) new_class = getattr(new_module, classname) conversions[old_class] = SimpleClassConverter(new_class, descend, show_dup) except: print 'Failed to set up conversion', old_prod, new_prod, modname, classname import traceback traceback.print_exc() def setupDirectConversions(old_prod, new_prod, modnames, conversions): for info in modnames: if type(info) is TupleType: modname, classname = info else: modname = classname = info setupDirectConversion(old_prod, new_prod, modname, classname, conversions) def _cleanupOwnership(ob, res, cleanup_children): ''' If the user name of the owner of the referenced object is not found in its current user database but is found in the local user database, this function changes the ownership of the object to the local database. ''' try: changed = ob._p_changed except: changed = 0 owner = getattr(ob, '_owner', None) if owner: udb, uid = owner #res.append('Owner of %s is %s!%s' % ( # join(ob.getPhysicalPath(), '/'), join(udb, '/'), uid,)) root = ob.getPhysicalRoot() try: db = root.unrestrictedTraverse(udb, None) user = db.getUserById(uid) if hasattr(ob, 'aq_inContextOf'): ucontext = aq_parent(aq_inner(db)) if not ob.aq_inContextOf(ucontext): # Not in the right context. user = None except: user = None if user is None: # Try to change to a local database. p = ob old_udb = udb udb = None while p is not None: if hasattr(p, 'acl_users'): acl_users = p.acl_users try: user = acl_users.getUserById(uid) except: user = None if user is not None: # Found the right database. udb = acl_users.getPhysicalPath()[1:] break p = aq_parent(aq_inner(p)) if udb is not None: ob._owner = udb, uid res.append('Changed ownership of %s from %s!%s to %s!%s' % (join(ob.getPhysicalPath(), '/'), join(old_udb, '/'), uid, join(udb, '/'), uid,)) else: res.append('Could not fix the ownership of %s, ' 'which is set to %s!%s' % (join(ob.getPhysicalPath(), '/'), join(old_udb, '/'), uid,)) if cleanup_children: if hasattr(ob, 'objectValues'): for subob in ob.objectValues(): _cleanupOwnership(subob, res, 1) # Deactivate object if possible. if changed is None: ob._p_deactivate() return res def _copyUsers(src_folder, dst_folder): source = src_folder.acl_users target = dst_folder.acl_users for user in source.getUsers(): target._addUser(name=user.name, password=user.__, confirm=user.__, roles=user.roles, domains=user.domains, REQUEST=None) # # PTK to CMF Migration script. # def migrate(self, src_path='', dest_path='', copy_users=0, ownership_only=0): if not src_path or not dest_path: return '''

Migrate PTK content to CMF site

Path (not including server URL) to PTK instance (source):

Path (not including server URL) to CMF site (destination):

Copy users:

''' % self.REQUEST['URL'] root = self.getPhysicalRoot() dst_folder = root.restrictedTraverse(dest_path) if not ownership_only: src_folder = root.restrictedTraverse(src_path) if copy_users: _copyUsers(src_folder, dst_folder) m = Migrator(ptk2cmf_conversions, ptk2cmf_skip) m.migrateObjectManager(src_folder, dst_folder) ownership_res = [] _cleanupOwnership(dst_folder, ownership_res, 1) return '''

Finished migration.

Warnings (if any):

  • %s

Visited folders:

  • %s

Skipped:

  • %s

Converted content:

%s

Fixed up ownership:

%s
''' % (join(m.warnings, '\n
  • '), join(m.visited_folders, '
  • \n
  • '), join(m.skipped, '
  • \n
  • '), join(m.copied, '\n'), join(ownership_res, '\n'), ) migrate_ptk = migrate # # PTK to CMF Conversion definitions. # ptk2cmf_conversions = {} ptk2cmf_skip = { 'portal_actions':0, 'portal_catalog':0, 'portal_discussion':0, 'portal_memberdata':0, 'portal_membership':0, 'portal_properties':0, 'portal_registration':0, 'portal_skins':1, 'portal_types':0, 'portal_undo':0, 'portal_url':0, 'portal_workflow':0, 'MailHost':0, 'cookie_authentication':0, } demo_conversions = ( 'Document', 'NewsItem', 'Image', 'File', 'Link', 'Favorite', 'DiscussionItem', ('DiscussionItem', 'DiscussionItemContainer'), ) BEFORE_CONTENT_MOVE = 0 if BEFORE_CONTENT_MOVE: content_product = 'PTKBase' else: content_product = 'PTKDemo' setupDirectConversions(content_product, 'CMFDefault', demo_conversions, ptk2cmf_conversions) setupDirectConversion('PTKBase', 'CMFCore', 'DirectoryView', 'DirectoryView', ptk2cmf_conversions, 0, 0) setupDirectConversion('PTKBase', 'CMFCore', 'PortalFolder', 'PortalFolder', ptk2cmf_conversions, 1) from OFS.Folder import Folder from Products.CMFCore.PortalFolder import PortalFolder ptk2cmf_conversions[Folder] = SimpleClassConverter(PortalFolder, 1) CMF-1.3/CMFDefault/Extensions/update_catalogIndexes.py0100644000076500007650000000117707426045201022626 0ustar tseavertseaverfrom Products.CMFCore.utils import getToolByName def update_catalogIndexes(self, REQUEST): ''' External method to drop, re-add, and rebuild catalog Indexes for migrated CMF sites from Zope 2.3 to 2.4+. ''' rIndexes = {'allowedRolesAndUsers': 'KeywordIndex' , 'effective': 'FieldIndex' , 'expires': 'FieldIndex'} ct = getToolByName(self, 'portal_catalog') map(lambda x, ct=ct: ct.delIndex(x), rIndexes.keys()) map(lambda x, ct=ct: ct.addIndex(x[0], x[1]), rIndexes.items()) ct.manage_reindexIndex(ids=rIndexes.keys(), REQUEST=REQUEST) return 'Catalog Indexes rebuilt.' CMF-1.3/CMFDefault/Extensions/update_discussion.py0100644000076500007650000000530307312217102022045 0ustar tseavertseaverimport string from Products.CMFCore.TypesTool import FactoryTypeInformation from Products.CMFDefault import DiscussionItem def update_discussion( self, split=string.split ): """ 1. Install (if it isn't there already) a type information object for DiscussionItems, so that they can get actions, etc. Erase the "(default)" workflow bound to it, to prevent showing the "Retract" options, etc. 2. Update all DiscussionItems to use the new marking for 'in_reply_to': - Items which are replies to the containing content object have None as their 'in_reply_to'; - Items which are replies to sibling items have the sibling's ID as their 'in_reply_to'. The representation we are converting from was: - Items which are replies to the containing content object have the portal-relative pathstring of the content object as their 'in_reply_to'; - Items which are replies to sibling items have the absolute path of the sibling as their 'in_reply_to'. """ log = [] a = log.append types_tool = self.portal_types if not getattr( types_tool, 'Discussion Item', None ): fti = apply( FactoryTypeInformation , () , DiscussionItem.factory_type_information[0] ) types_tool._setObject( 'Discussion Item', fti ) a( 'Added type object for DiscussionItem' ) workflow_tool = self.portal_workflow workflow_tool.setChainForPortalTypes( ( 'Discussion Item', ), () ) a( 'Erased workflow for DiscussionItem' ) items = self.portal_catalog.searchResults( meta_type='Discussion Item' ) a( 'DiscussionItems updated:' ) for item in items: object = item.getObject() talkback = object.aq_parent path = item.getPath() in_reply_to = object.in_reply_to if in_reply_to is None: # we've been here already continue irt_elements = split( in_reply_to, '/' ) if len( irt_elements ) == 1: if talkback._container.get( irt_elements[0] ): # we've been here already continue if irt_elements[0] == '': # absolute, so we are IRT a sibling sibling_id = irt_elements[ -1 ] if talkback._container.get( sibling_id, None ): in_reply_to = sibling_id else: in_reply_to = None else: in_reply_to = None object.in_reply_to = in_reply_to assert object.inReplyTo() # sanity check object.reindexObject() a( path ) return string.join( log, '\n' ) CMF-1.3/CMFDefault/DefaultWorkflow.py0100644000076500007650000002657307522303413017320 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ A simple submit/review/publish workflow. $Id: DefaultWorkflow.py,v 1.11.4.3 2025/08/01 19:07:55 tseaver Exp $ """ import sys import Globals from Acquisition import aq_base, aq_inner, aq_parent from AccessControl import ClassSecurityInfo from DateTime import DateTime from Products.CMFCore.utils import _modifyPermissionMappings from Products.CMFCore.utils import _checkPermission from Products.CMFCore.utils import getToolByName from Products.CMFCore.utils import SimpleItemWithProperties from Products.CMFCore.WorkflowCore import WorkflowException from Products.CMFCore.WorkflowTool import addWorkflowClass class DefaultWorkflowDefinition (SimpleItemWithProperties): meta_type = 'Workflow' id = 'default_workflow' title = 'Simple Review / Publish Policy' _isAWorkflow = 1 security = ClassSecurityInfo() def __init__(self, id): self.id = id security.declarePrivate('getReviewStateOf') def getReviewStateOf(self, ob): tool = aq_parent(aq_inner(self)) status = tool.getStatusOf(self.getId(), ob) if status is not None: review_state = status['review_state'] else: if hasattr(aq_base(ob), 'review_state'): # Backward compatibility. review_state = ob.review_state else: review_state = 'private' return review_state security.declarePrivate('getCatalogVariablesFor') def getCatalogVariablesFor(self, ob): ''' Allows this workflow to make workflow-specific variables available to the catalog, making it possible to implement queues in a simple way. Returns a mapping containing the catalog variables that apply to ob. ''' return {'review_state': self.getReviewStateOf(ob)} security.declarePrivate('listObjectActions') def listObjectActions(self, info): ''' Allows this workflow to include actions to be displayed in the actions box. Called only when this workflow is applicable to info.content. Returns the actions to be displayed to the user. ''' if info.isAnonymous: return None # The following operation is quite expensive. # We don't need to perform it if the user # doesn't have the required permission. content = info.content content_url = info.content_url content_creator = content.Creator() pm = getToolByName(self, 'portal_membership') current_user = pm.getAuthenticatedMember().getUserName() review_state = self.getReviewStateOf(content) actions = [] allow_review = _checkPermission('Review portal content', content) allow_request = _checkPermission('Request review', content) append_action = (lambda name, p, url=content_url, a=actions.append: a({'name': name, 'url': url + '/' + p, 'permissions': (), 'category': 'workflow'})) show_reject = 0 show_retract = 0 if review_state == 'private': if allow_review: append_action('Publish', 'content_publish_form') elif allow_request: append_action('Submit', 'content_submit_form') elif review_state == 'pending': if content_creator == current_user and allow_request: show_retract = 1 if allow_review: append_action('Publish', 'content_publish_form') show_reject = 1 elif review_state == 'published': if content_creator == current_user and allow_request: show_retract = 1 if allow_review: show_reject = 1 if show_retract: append_action('Retract', 'content_retract_form') if show_reject: append_action('Reject', 'content_reject_form') if allow_review or allow_request: append_action('Status history', 'content_status_history') return actions security.declarePrivate('listGlobalActions') def listGlobalActions(self, info): ''' Allows this workflow to include actions to be displayed in the actions box. Called on every request. Returns the actions to be displayed to the user. ''' if info.isAnonymous: return None actions = [] catalog = getToolByName(self, 'portal_catalog', None) if catalog is not None: pending = len(catalog.searchResults( review_state='pending')) if pending > 0: actions.append( {'name': 'Pending review (%d)' % pending, 'url': info.portal_url + '/search?review_state=pending', 'permissions': (), 'category': 'global'} ) return actions security.declarePrivate('isActionSupported') def isActionSupported(self, ob, action): ''' Returns a true value if the given action name is supported. ''' return (action in ('submit', 'retract', 'publish', 'reject',)) security.declarePrivate('doActionFor') def doActionFor(self, ob, action, comment=''): ''' Allows the user to request a workflow action. This method must perform its own security checks. ''' allow_review = _checkPermission('Review portal content', ob) allow_request = _checkPermission('Request review', ob) review_state = self.getReviewStateOf(ob) tool = aq_parent(aq_inner(self)) if action == 'submit': if not allow_request: raise 'Unauthorized', 'Not authorized' elif review_state != 'private': raise 'Unauthorized', 'Already in submit state' self.setReviewStateOf(ob, 'pending', action, comment) elif action == 'retract': if not allow_request: raise 'Unauthorized', 'Not authorized' elif review_state == 'private': raise 'Unauthorized', 'Already private' content_creator = ob.Creator() pm = getToolByName(self, 'portal_membership') current_user = pm.getAuthenticatedMember().getUserName() if (content_creator != current_user) and not allow_review: raise 'Unauthorized', 'Not creator or reviewer' self.setReviewStateOf(ob, 'private', action, comment) elif action == 'publish': if not allow_review: raise 'Unauthorized', 'Not authorized' self.setReviewStateOf(ob, 'published', action, comment) elif action == 'reject': if not allow_review: raise 'Unauthorized', 'Not authorized' self.setReviewStateOf(ob, 'private', action, comment) security.declarePrivate('isInfoSupported') def isInfoSupported(self, ob, name): ''' Returns a true value if the given info name is supported. ''' return (name in ('review_state', 'review_history')) security.declarePrivate('getInfoFor') def getInfoFor(self, ob, name, default): ''' Allows the user to request information provided by the workflow. This method must perform its own security checks. ''' # Treat this as public. if name == 'review_state': return self.getReviewStateOf(ob) allow_review = _checkPermission('Review portal content', ob) allow_request = _checkPermission('Request review', ob) if not allow_review and not allow_request: return default elif name == 'review_history': tool = aq_parent(aq_inner(self)) history = tool.getHistoryOf(self.getId(), ob) # Make copies for security. return tuple(map(lambda dict: dict.copy(), history)) security.declarePrivate('setReviewStateOf') def setReviewStateOf(self, ob, review_state, action, comment): tool = aq_parent(aq_inner(self)) pm = getToolByName(self, 'portal_membership') current_user = pm.getAuthenticatedMember().getUserName() status = { 'actor': current_user, 'action': action, 'review_state': review_state, 'time': DateTime(), 'comments': comment, } tool.setStatusOf(self.getId(), ob, status) self.updateRoleMappingsFor(ob) security.declarePrivate('notifyCreated') def notifyCreated(self, ob): ''' Notifies this workflow after an object has been created and put in its new place. ''' self.setReviewStateOf( ob, 'private', 'created', '' ) self.notifySuccess(ob, 'created', '') security.declarePrivate('notifyBefore') def notifyBefore(self, ob, action): ''' Notifies this workflow of an action before it happens, allowing veto by exception. Unless an exception is thrown, either a notifySuccess() or notifyException() can be expected later on. The action usually corresponds to a method name. ''' pass security.declarePrivate('notifySuccess') def notifySuccess(self, ob, action, result): ''' Notifies this workflow that an action has taken place. ''' pass security.declarePrivate('notifyException') def notifyException(self, ob, action, exc): ''' Notifies this workflow that an action failed. ''' pass security.declarePrivate('updateRoleMappingsFor') def updateRoleMappingsFor(self, ob): ''' Changes the object permissions according to the current review_state. ''' review_state = self.getReviewStateOf(ob) if review_state == 'private': anon_view = 0 owner_modify = 1 reviewer_view = 0 elif review_state == 'pending': anon_view = 0 owner_modify = 0 # Require a retraction for editing. reviewer_view = 1 elif review_state == 'published': anon_view = 1 owner_modify = 0 reviewer_view = 1 else: # This object is in an unknown state anon_view = 0 owner_modify = 1 reviewer_view = 0 # Modify role to permission mappings directly. new_map = { 'View': { 'Anonymous': anon_view , 'Reviewer': reviewer_view , 'Owner': 1 } , 'Modify portal content': {'Owner': owner_modify} } return _modifyPermissionMappings(ob, new_map) Globals.InitializeClass(DefaultWorkflowDefinition) addWorkflowClass(DefaultWorkflowDefinition) CMF-1.3/CMFDefault/DiscussionItem.py0100644000076500007650000003407207522123266017142 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import urllib, string from Globals import HTMLFile, Persistent, PersistentMapping, InitializeClass from AccessControl import ClassSecurityInfo from Acquisition import Implicit, aq_base, aq_inner, aq_parent from OFS.Traversable import Traversable from DateTime import DateTime from Products.CMFCore import CMFCorePermissions from Products.CMFCore.utils import getToolByName from Products.CMFCore.PortalContent import PortalContent from Document import Document from DublinCore import DefaultDublinCoreImpl factory_type_information = ( { 'id' : 'Discussion Item' , 'meta_type' : 'Discussion Item' , 'description' : """\ Discussion Items are documents which reply to other content. They should *not* be addable through the standard 'folder_factories' interface.""" , 'icon' : 'discussionitem_icon.gif' , 'product' : '' # leave blank to suppress , 'factory' : '' , 'immediate_view' : '' , 'actions' : ( { 'id' : 'view' , 'name' : 'View' , 'action' : 'discussionitem_view' , 'permissions' : ( CMFCorePermissions.View, ) } , ) } , ) def addDiscussionItem(self, id, title, description, text_format, text, reply_to, RESPONSE=None): """ Add a discussion item 'title' is also used as the subject header if 'description' is blank, it is filled with the contents of 'title' 'reply_to' is the object (or path to the object) which this is a reply to Otherwise, same as addDocument """ if not description: description = title item = DiscussionItem( id ) item.title = title item.description = description item.text_format = text_format item.text = text item.setReplyTo(reply_to) item._parse() self._setObject(id, item) if RESPONSE is not None: RESPONSE.redirect(self.absolute_url()) class DiscussionItem( Document , DefaultDublinCoreImpl ): """ Class for content which is a response to other content. """ __implements__ = ( PortalContent.__implements__ , DefaultDublinCoreImpl.__implements__ ) meta_type = 'Discussion Item' portal_type = 'Discussion Item' allow_discussion = 1 creator = 'unknown' in_reply_to = None review_state ='published' security = ClassSecurityInfo() security.declareProtected( CMFCorePermissions.View, 'Creator' ) def Creator( self ): """ We need to return user who replied, rather than executable owner. """ # XXX: revisit if Creator becomes "real" attribute for stock DC. return self.creator # # DiscussionResponse interface # security.declareProtected( CMFCorePermissions.View, 'inReplyTo' ) def inReplyTo( self, REQUEST=None ): """ Return the Discussable object to which we are a reply. Two cases obtain: - We are a "top-level" reply to a non-DiscussionItem piece of content; in this case, our 'in_reply_to' field will be None. - We are a nested reply; in this case, our 'in_reply_to' field will be the ID of the parent DiscussionItem. """ tool = getToolByName( self, 'portal_discussion' ) talkback = tool.getDiscussionFor( self ) return talkback._getReplyParent( self.in_reply_to ) security.declarePrivate( CMFCorePermissions.View, 'setReplyTo' ) def setReplyTo( self, reply_to ): """ Make this object a response to the passed object. """ if getattr( reply_to, 'meta_type', None ) == self.meta_type: self.in_reply_to = reply_to.getId() else: self.in_reply_to = None security.declareProtected( CMFCorePermissions.View, 'parentsInThread' ) def parentsInThread( self, size=0 ): """ Return the list of items which are "above" this item in the discussion thread. If 'size' is not zero, only the closest 'size' parents will be returned. """ parents = [] current = self while not size or len( parents ) < size: parent = current.inReplyTo() assert not parent in parents # sanity check parents.insert( 0, parent ) if parent.meta_type != self.meta_type: break current = parent return parents InitializeClass( DiscussionItem ) class DiscussionItemContainer( Persistent, Implicit, Traversable ): """ Store DiscussionItem objects. Discussable content that has DiscussionItems associated with it will have an instance of DiscussionItemContainer injected into it to hold the discussion threads. """ # for the security machinery to allow traversal #__roles__ = None security = ClassSecurityInfo() def __init__(self): self.id = 'talkback' self._container = PersistentMapping() security.declareProtected( CMFCorePermissions.View, 'getId' ) def getId( self ): return self.id security.declareProtected( CMFCorePermissions.View, 'getReply' ) def getReply( self, reply_id ): """ Return a discussion item, given its ID; raise KeyError if not found. """ return self._container.get( reply_id ).__of__(self) # Is this right? security.declareProtected( CMFCorePermissions.View, '__bobo_traverse__' ) def __bobo_traverse__(self, REQUEST, name): """ This will make this container traversable """ target = getattr(self, name, None) if target is not None: return target else: try: return self.getReply(name) except: parent = aq_parent( aq_inner( self ) ) if parent.getId() == name: return parent else: REQUEST.RESPONSE.notFoundError("%s\n%s" % (name, '')) security.declarePrivate('manage_afterAdd') def manage_afterAdd(self, item, container): """ We have juste been added or moved. Add the contained items to the catalog. """ if aq_base(container) is not aq_base(self): for obj in self.objectValues(): obj.__of__(self).manage_afterAdd(item, container) security.declarePrivate('manage_afterClone') def manage_afterClone(self, item): """ We have just been cloned. Notify the workflow about the contained items. """ for obj in self.objectValues(): obj.__of__(self).manage_afterClone(item) security.declarePrivate( 'manage_beforeDelete' ) def manage_beforeDelete(self, item, container): """ Remove the contained items from the catalog. """ if aq_base(container) is not aq_base(self): for obj in self.objectValues(): obj.__of__( self ).manage_beforeDelete( item, container ) # # OFS.ObjectManager query interface. # security.declareProtected( CMFCorePermissions.AccessContentsInformation , 'objectIds' ) def objectIds( self, spec=None ): """ Return a list of the ids of our DiscussionItems. """ if spec and spec is not DiscussionItem.meta_type: return [] return self._container.keys() security.declareProtected( CMFCorePermissions.AccessContentsInformation , 'objectItems' ) def objectItems(self, spec=None): """ Return a list of (id, subobject) tuples for our DiscussionItems. """ r=[] a=r.append g=self._container.get for id in self.objectIds(spec): a( (id, g( id ) ) ) return r security.declareProtected( CMFCorePermissions.AccessContentsInformation , 'objectValues' ) def objectValues(self): """ Return a list of our DiscussionItems. """ return self._container.values() # # Discussable interface # security.declareProtected( CMFCorePermissions.ReplyToItem, 'createReply' ) def createReply( self, title, text, Creator=None ): """ Create a reply in the proper place """ container = self._container id = int(DateTime().timeTime()) while self._container.get( str(id), None ) is not None: id = id + 1 id = str( id ) item = DiscussionItem( id, title=title, description=title ) item._edit( text_format='structured-text', text=text ) if Creator: item.creator = Creator item.__of__( self ).indexObject() item.setReplyTo( self._getDiscussable() ) self._container[ id ] = item return id security.declareProtected( CMFCorePermissions.ManagePortal, 'deleteReply' ) def deleteReply( self, reply_id ): """ Remove a reply from this container """ if self._container.has_key( reply_id ): reply = self._container.get( reply_id ).__of__( self ) my_replies = reply.talkback.getReplies() for my_reply in my_replies: my_reply_id = my_reply.getId() if hasattr( my_reply, 'unindexObject' ): my_reply.unindexObject() del self._container[my_reply_id] if hasattr( reply, 'unindexObject' ): reply.unindexObject() del self._container[reply_id] security.declareProtected( CMFCorePermissions.View, 'hasReplies' ) def hasReplies( self, content_obj ): """ Test to see if there are any dicussion items """ outer = self._getDiscussable( outer=1 ) if content_obj == outer: return not not len( self._container ) else: return not not len( content_obj.talkback._getReplyResults() ) security.declareProtected( CMFCorePermissions.View, 'replyCount' ) def replyCount( self, content_obj ): """ How many replies do i have? """ outer = self._getDiscussable( outer=1 ) if content_obj == outer: return len( self._container ) else: replies = content_obj.talkback.getReplies() return self._repcount( replies ) security.declarePrivate('_repcount') def _repcount( self, replies ): """ counts the total number of replies by recursing thru the various levels """ count = 0 for reply in replies: count = count + 1 #if there is at least one reply to this reply replies = reply.talkback.getReplies() if replies: count = count + self._repcount( replies ) return count security.declareProtected( CMFCorePermissions.View, 'getReplies' ) def getReplies( self ): """ Return a sequence of the DiscussionResponse objects which are associated with this Discussable """ objects = [] a = objects.append result_ids = self._getReplyResults() for id in result_ids: a( self._container.get( id ).__of__( self ) ) return objects security.declareProtected( CMFCorePermissions.View, 'quotedContents' ) def quotedContents(self): """ Return this object's contents in a form suitable for inclusion as a quote in a response. """ return "" # # Utility methods # security.declarePrivate( '_getReplyParent' ) def _getReplyParent( self, in_reply_to ): """ Return the object indicated by the 'in_reply_to', where 'None' represents the "outer" content object. """ outer = self._getDiscussable( outer=1 ) if in_reply_to is None: return outer parent = self._container[ in_reply_to ].__of__( aq_inner( self ) ) return parent.__of__( outer ) security.declarePrivate( '_getDiscussable' ) def _getDiscussable( self, outer=0 ): """ """ tb = outer and aq_inner( self ) or self return getattr( tb, 'aq_parent', None ) security.declarePrivate( '_getReplyResults' ) def _getReplyResults( self ): """ Get a list of ids of DiscussionItems which are replies to our Discussable. """ discussable = self._getDiscussable() outer = self._getDiscussable( outer=1 ) if discussable == outer: in_reply_to = None else: in_reply_to = discussable.getId() result = [] a = result.append for key, value in self._container.items(): if value.in_reply_to == in_reply_to: a( key ) return result InitializeClass( DiscussionItemContainer ) CMF-1.3/CMFDefault/DiscussionTool.py0100644000076500007650000001152707522303413017153 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Basic portal discussion access tool. $Id: DiscussionTool.py,v 1.9.4.1 2025/08/01 19:07:55 tseaver Exp $ """ from Globals import InitializeClass, DTMLFile from AccessControl import ClassSecurityInfo from OFS.SimpleItem import SimpleItem from Products.CMFCore.utils import UniqueObject, getToolByName from Products.CMFCore import CMFCorePermissions from utils import _dtmldir from DiscussionItem import DiscussionItemContainer from Products.CMFCore.ActionInformation import ActionInformation from Products.CMFCore.ActionProviderBase import ActionProviderBase from Products.CMFCore.Expression import Expression class DiscussionNotAllowed( Exception ): pass class DiscussionTool( UniqueObject, SimpleItem, ActionProviderBase ): id = 'portal_discussion' meta_type = 'Default Discussion Tool' _actions = [ActionInformation(id='reply' , title='Reply' , action=Expression( text='string: ${object_url}/discussion_reply_form') , condition=Expression( text='python: object is not None and ' + 'portal.portal_discussion.isDiscussionAllowedFor(object)') , permissions=('Reply to item',) , category='object' , visible=1 )] security = ClassSecurityInfo() manage_options = (ActionProviderBase.manage_options + ({ 'label' : 'Overview', 'action' : 'manage_overview' } , ) + SimpleItem.manage_options) # # ZMI methods # security.declareProtected( CMFCorePermissions.ManagePortal , 'manage_overview' ) manage_overview = DTMLFile( 'explainDiscussionTool', _dtmldir ) # # 'portal_discussion' interface methods # security.declarePrivate('listActions') def listActions(self, info=None): """ Return available actions via tool. """ return self._actions security.declarePublic( 'overrideDiscussionFor' ) def overrideDiscussionFor(self, content, allowDiscussion): """ Override discussability for a per object basis or clear and let the site default override. """ mtool = getToolByName( self, 'portal_membership' ) if not mtool.checkPermission( CMFCorePermissions.ModifyPortalContent , content ): raise "Unauthorized" if allowDiscussion is None or allowDiscussion == 'None': if hasattr(content, 'allow_discussion'): del content.allow_discussion else: content.allow_discussion = int(allowDiscussion) security.declarePublic( 'getDiscussionFor' ) def getDiscussionFor(self, content): """ Return the talkback for content, creating it if need be. """ if not self.isDiscussionAllowedFor( content ): raise DiscussionNotAllowed talkback = getattr( content, 'talkback', None ) if not talkback: talkback = self._createDiscussionFor( content ) return talkback security.declarePublic( 'isDiscussionAllowedFor' ) def isDiscussionAllowedFor( self, content ): ''' Returns a boolean indicating whether a discussion is allowed for the specified content. ''' if hasattr( content, 'allow_discussion' ): return content.allow_discussion typeInfo = getToolByName(self, 'portal_types').getTypeInfo( content ) if typeInfo: return typeInfo.allowDiscussion() return 0 # # Utility methods # security.declarePrivate( '_createDiscussionFor' ) def _createDiscussionFor( self, content ): """ Create the object that holds discussion items inside the object being discussed, if allowed. """ if not self.isDiscussionAllowedFor( content ): raise DiscussionNotAllowed content.talkback = DiscussionItemContainer() return content.talkback InitializeClass( DiscussionTool ) CMF-1.3/CMFDefault/Document.py0100644000076500007650000004141507523123734015756 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Basic textual content object, supporting both HTML and STX. $Id: Document.py,v 1.47.6.3 2025/08/04 04:04:44 efge Exp $ """ import Globals, StructuredText, string, utils, re from StructuredText.HTMLWithImages import HTMLWithImages from Globals import DTMLFile, InitializeClass from AccessControl import ClassSecurityInfo, getSecurityManager from Products.CMFCore.PortalContent import PortalContent from Products.CMFCore.PortalContent import NoWL, ResourceLockedError from Products.CMFCore import CMFCorePermissions from Products.CMFCore.WorkflowCore import WorkflowAction from Products.CMFCore.utils import format_stx, keywordsplitter from DublinCore import DefaultDublinCoreImpl from utils import parseHeadersBody, formatRFC822Headers from utils import SimpleHTMLParser, bodyfinder, _dtmldir from DocumentTemplate.DT_Util import html_quote factory_type_information = ( { 'id' : 'Document' , 'meta_type' : 'Document' , 'description' : """\ Documents can contain text that can be formatted using 'Structured Text.'""" , 'icon' : 'document_icon.gif' , 'product' : 'CMFDefault' , 'factory' : 'addDocument' , 'immediate_view' : 'metadata_edit_form' , 'actions' : ( { 'id' : 'view' , 'name' : 'View' , 'action' : 'document_view' , 'permissions' : ( CMFCorePermissions.View, ) } , { 'id' : 'edit' , 'name' : 'Edit' , 'action' : 'document_edit_form' , 'permissions' : ( CMFCorePermissions.ModifyPortalContent, ) } , { 'id' : 'metadata' , 'name' : 'Metadata' , 'action' : 'metadata_edit_form' , 'permissions' : ( CMFCorePermissions.ModifyPortalContent, ) } ) } , ) def addDocument(self, id, title='', description='', text_format='', text=''): """ Add a Document """ o = Document(id, title, description, text_format, text) self._setObject(id,o) class CMFHtmlWithImages(HTMLWithImages): """ Special subclass of HTMLWithImages, overriding document() """ def document(self, doc, level, output): """\ HTMLWithImages.document renders full HTML (head, title, body). For CMF Purposes, we don't want that. We just want those nice juicy body parts perfectly rendered. """ for c in doc.getChildNodes(): getattr(self, self.element_types[c.getNodeName()])(c, level, output) CMFHtmlWithImages = CMFHtmlWithImages() class Document(PortalContent, DefaultDublinCoreImpl): """ A Document - Handles both StructuredText and HTML """ __implements__ = ( PortalContent.__implements__ , DefaultDublinCoreImpl.__implements__ ) meta_type = 'Document' effective_date = expiration_date = None cooked_text = text = text_format = '' _isDiscussable = 1 _stx_level = 1 # Structured text level _last_safety_belt_editor = '' _last_safety_belt = '' _safety_belt = '' security = ClassSecurityInfo() def __init__(self, id, title='', description='', text_format='', text=''): DefaultDublinCoreImpl.__init__(self) self.id = id self.title = title self.description = description self._edit( text=text, text_format=text_format ) self.setFormat( text_format ) security.declareProtected(CMFCorePermissions.ModifyPortalContent, 'manage_edit') manage_edit = DTMLFile('zmi_editDocument', _dtmldir) security.declareProtected(CMFCorePermissions.ModifyPortalContent, 'manage_editDocument' ) def manage_editDocument( self, text, text_format, file='', REQUEST=None ): """ A ZMI (Zope Management Interface) level editing method """ Document.edit( self, text_format=text_format, text=text, file=file ) if REQUEST is not None: REQUEST['RESPONSE'].redirect( self.absolute_url() + '/manage_edit' + '?manage_tabs_message=Document+updated' ) def _edit(self, text, text_format='', safety_belt=''): """ Edit the Document - Parses headers and cooks the body""" headers = {} level = self._stx_level if not text_format: text_format = self.text_format if not safety_belt: safety_belt = headers.get('SafetyBelt', '') if not self._safety_belt_update(safety_belt=safety_belt): msg = ("Intervening changes from elsewhere detected." " Please refetch the document and reapply your changes." " (You may be able to recover your version using the" " browser 'back' button, but will have to apply them" " to a freshly fetched copy.)") raise 'EditingConflict', msg if text_format == 'html': self.text = self.cooked_text = text elif text_format == 'plain': self.text = text self.cooked_text = html_quote(text).replace('\n','
    ') else: self.cooked_text = format_stx(text=text, level=level) self.text = text security.declareProtected( CMFCorePermissions.ModifyPortalContent, 'edit' ) def edit( self , text_format , text , file='' , safety_belt='' ): """ *used to be WorkflowAction(_edit) To add webDav support, we need to check if the content is locked, and if so return ResourceLockedError if not, call _edit. Note that this method expects to be called from a web form, and so disables header processing """ self.failIfLocked() if file and (type(file) is not type('')): contents=file.read() if contents: text = self.text = contents text = bodyfinder(text) self.setFormat(value=text_format) self._edit(text=text, text_format=text_format, safety_belt=safety_belt) self.reindexObject() security.declareProtected(CMFCorePermissions.ModifyPortalContent, 'setMetadata') def setMetadata(self, headers): headers['Format'] = self.Format() new_subject = keywordsplitter(headers) headers['Subject'] = new_subject or self.Subject() haveheader = headers.has_key for key, value in self.getMetadataHeaders(): if key != 'Format' and not haveheader(key): headers[key] = value self._editMetadata(title=headers['Title'], subject=headers['Subject'], description=headers['Description'], contributors=headers['Contributors'], effective_date=headers['Effective_date'], expiration_date=headers['Expiration_date'], format=headers['Format'], language=headers['Language'], rights=headers['Rights'], ) security.declarePrivate('guessFormat') def guessFormat(self, text): """ Simple stab at guessing the inner format of the text """ if utils.html_headcheck(text): return 'html' else: return 'structured-text' security.declarePrivate('handleText') def handleText(self, text, format=None, stx_level=None): """ Handles the raw text, returning headers, body, cooked, format """ headers = {} level = stx_level or self._stx_level if not format: format = self.guessFormat(text) if format == 'html': parser = SimpleHTMLParser() parser.feed(text) headers.update(parser.metatags) if parser.title: headers['Title'] = parser.title bodyfound = bodyfinder(text) if bodyfound: body = bodyfound else: headers, body = parseHeadersBody(text, headers) self._stx_level = level return headers, body, format security.declarePublic( 'getMetadataHeaders' ) def getMetadataHeaders(self): """Return RFC-822-style header spec.""" hdrlist = DefaultDublinCoreImpl.getMetadataHeaders(self) hdrlist.append( ('SafetyBelt', self._safety_belt) ) return hdrlist security.declarePublic( 'SafetyBelt' ) def SafetyBelt(self): """Return the current safety belt setting. For web form hidden button.""" return self._safety_belt def _safety_belt_update(self, safety_belt=''): """Check validity of safety belt and update tracking if valid. Return 0 if safety belt is invalid, 1 otherwise. Note that the policy is deliberately lax if no safety belt value is present - "you're on your own if you don't use your safety belt". When present, either the safety belt token: - ... is the same as the current one given out, or - ... is the same as the last one given out, and the person doing the edit is the same as the last editor.""" this_belt = safety_belt this_user = getSecurityManager().getUser().getUserName() if (# we have a safety belt value: this_belt # and the current object has a safety belt (ie - not freshly made) and (self._safety_belt is not None) # and the safety belt doesn't match the current one: and (this_belt != self._safety_belt) # and safety belt and user don't match last safety belt and user: and not ((this_belt == self._last_safety_belt) and (this_user == self._last_safety_belt_editor))): # Fail. return 0 # We qualified - either: # - the edit was submitted with safety belt stripped, or # - the current safety belt was used, or # - the last one was reused by the last person who did the last edit. # In any case, update the tracking. self._last_safety_belt_editor = this_user self._last_safety_belt = this_belt self._safety_belt = str(self._p_mtime) return 1 ### Content accessor methods security.declareProtected(CMFCorePermissions.View, 'SearchableText') def SearchableText(self): """ Used by the catalog for basic full text indexing """ return "%s %s %s" % ( self.Title() , self.Description() , self.EditableBody() ) security.declareProtected(CMFCorePermissions.View, 'CookedBody') def CookedBody(self, stx_level=None, setlevel=0): """\ The prepared basic rendering of an object. For Documents, this means pre-rendered structured text, or what was between the tags of HTML. If the format is html, and 'stx_level' is not passed in or is the same as the object's current settings, return the cached cooked text. Otherwise, recook. If we recook and 'setlevel' is true, then set the recooked text and stx_level on the object. """ if (self.text_format == 'html' or self.text_format == 'plain' or (stx_level is None) or (stx_level == self._stx_level)): return self.cooked_text else: cooked = format_stx(self.text, stx_level) if setlevel: self._stx_level = stx_level self.cooked_text = cooked return cooked security.declareProtected(CMFCorePermissions.View, 'EditableBody') def EditableBody(self): """\ The editable body of text. This is the raw structured text, or in the case of HTML, what was between the tags. """ return self.text security.declareProtected(CMFCorePermissions.View, 'Description') def Description(self): """ Dublin core description, also important for indexing """ return self.description security.declareProtected(CMFCorePermissions.View, 'Format') def Format(self): """ Returns a content-type style format of the underlying source """ if self.text_format == 'html': return 'text/html' else: return 'text/plain' security.declareProtected(CMFCorePermissions.ModifyPortalContent, 'setFormat' ) def setFormat(self, value): value = str(value) if value == 'text/html' or value == 'html': self.text_format = 'html' elif value == 'plain': self.text_format = 'plain' else: self.text_format = 'structured-text' setFormat = WorkflowAction(setFormat) ## FTP handlers security.declareProtected(CMFCorePermissions.ModifyPortalContent, 'PUT') def PUT(self, REQUEST, RESPONSE): """ Handle HTTP (and presumably FTP?) PUT requests """ if not NoWL: self.dav__init(REQUEST, RESPONSE) self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1) body = REQUEST.get('BODY', '') guessedformat = REQUEST.get_header('Content-Type', 'text/plain') ishtml = (guessedformat == 'text/html') or utils.html_headcheck(body) if ishtml: self.setFormat('text/html') else: self.setFormat('text/plain') try: headers, body, format = self.handleText(text=body) safety_belt = headers.get('SafetyBelt', '') self.setMetadata(headers) self._edit(text=body, safety_belt=safety_belt) except 'EditingConflict', msg: # XXX Can we get an error msg through? Should we be raising an # exception, to be handled in the FTP mechanism? Inquiring # minds... get_transaction().abort() RESPONSE.setStatus(450) return RESPONSE except ResourceLockedError, msg: get_transaction().abort() RESPONSE.setStatus(423) return RESPONSE RESPONSE.setStatus(204) self.reindexObject() return RESPONSE _htmlsrc = ( '\n \n' ' %(title)s\n' '%(metatags)s\n' ' \n' ' \n%(body)s\n \n' '\n' ) security.declareProtected( CMFCorePermissions.View, 'manage_FTPget' ) def manage_FTPget(self): "Get the document body for FTP download (also used for the WebDAV SRC)" join = string.join lower = string.lower hdrlist = self.getMetadataHeaders() if self.Format() == 'text/html': hdrtext = '' for name, content in hdrlist: if lower(name) == 'title': continue else: hdrtext = '%s\n ' % ( hdrtext, name, content) bodytext = self._htmlsrc % { 'title': self.Title(), 'metatags': hdrtext, 'body': self.EditableBody(), } else: hdrtext = formatRFC822Headers( hdrlist ) bodytext = '%s\r\n\r\n%s' % ( hdrtext, self.text ) return bodytext security.declareProtected( CMFCorePermissions.View, 'get_size' ) def get_size( self ): """ Used for FTP and apparently the ZMI now too """ return len(self.manage_FTPget()) InitializeClass(Document) CMF-1.3/CMFDefault/DublinCore.py0100644000076500007650000003760707520063117016231 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import string, re from OFS.PropertyManager import PropertyManager from DateTime.DateTime import DateTime from Acquisition import aq_base from Products.CMFCore.WorkflowCore import WorkflowAction from Products.CMFCore.interfaces.DublinCore import DublinCore from Products.CMFCore.interfaces.DublinCore import CatalogableDublinCore from Products.CMFCore.interfaces.DublinCore import MutableDublinCore from utils import tuplize, _dtmldir, semi_split from Globals import InitializeClass, DTMLFile from AccessControl import ClassSecurityInfo from Products.CMFCore import CMFCorePermissions class DefaultDublinCoreImpl( PropertyManager ): """ Mix-in class which provides Dublin Core methods """ __implements__ = DublinCore, CatalogableDublinCore, MutableDublinCore security = ClassSecurityInfo() def __init__( self , title='' , subject=() , description='' , contributors=() , effective_date=None , expiration_date=None , format='text/html' , language='' , rights='' ): now = DateTime() self.creation_date = now self.modification_date = now self._editMetadata( title , subject , description , contributors , effective_date , expiration_date , format , language , rights ) # # Set-modification-date-related methods. # In DefaultDublinCoreImpl for lack of a better place. # # Class variable default for an upgrade. modification_date = None security.declarePrivate('notifyModified') def notifyModified(self): """ Take appropriate action after the resource has been modified. For now, change the modification_date. """ # XXX This could also store the id of the user doing modifications. self.setModificationDate() # XXX Could this be simply protected by ModifyPortalContent ? security.declarePrivate('setModificationDate') def setModificationDate(self, modification_date=None): """ Set the date when the resource was last modified. When called without an argument, sets the date to now. """ if modification_date is None: self.modification_date = DateTime() else: self.modification_date = self._datify(modification_date) # # DublinCore interface query methods # security.declarePublic( 'Title' ) def Title( self ): "Dublin Core element - resource name" return self.title security.declarePublic( 'Creator' ) def Creator( self ): # XXX: fixme using 'portal_membership' -- should iterate over # *all* owners "Dublin Core element - resource creator" owner = self.getOwner() if hasattr( owner, 'getUserName' ): return owner.getUserName() return 'No owner' security.declarePublic( 'Subject' ) def Subject( self ): "Dublin Core element - resource keywords" return getattr( self, 'subject', () ) # compensate for *old* content security.declarePublic( 'Publisher' ) def Publisher( self ): "Dublin Core element - resource publisher" # XXX: fixme using 'portal_metadata' return 'No publisher' security.declarePublic( 'Description' ) def Description( self ): "Dublin Core element - resource summary" return self.description security.declarePublic( 'Contributors' ) def Contributors( self ): "Dublin Core element - additional contributors to resource" # XXX: fixme return self.contributors security.declarePublic( 'Date' ) def Date( self ): "Dublin Core element - default date" # Return effective_date if set, modification date otherwise date = getattr(self, 'effective_date', None ) if date is None: date = self.modified() return date.ISO() security.declarePublic( 'CreationDate' ) def CreationDate( self ): """ Dublin Core element - date resource created. """ # return unknown if never set properly return self.creation_date and self.creation_date.ISO() or 'Unknown' security.declarePublic( 'EffectiveDate' ) def EffectiveDate( self ): """ Dublin Core element - date resource becomes effective. """ ed = getattr( self, 'effective_date', None ) return ed and ed.ISO() or 'None' security.declarePublic( 'ExpirationDate' ) def ExpirationDate( self ): """ Dublin Core element - date resource expires. """ ed = getattr( self, 'expiration_date', None ) return ed and ed.ISO() or 'None' security.declarePublic( 'ModificationDate' ) def ModificationDate( self ): """ Dublin Core element - date resource last modified. """ return self.modified().ISO() security.declarePublic( 'Type' ) def Type( self ): "Dublin Core element - Object type" if hasattr(aq_base(self), 'getTypeInfo'): ti = self.getTypeInfo() if ti is not None: return ti.Title() return self.meta_type security.declarePublic( 'Format' ) def Format( self ): """ Dublin Core element - resource format """ return self.format security.declarePublic( 'Identifier' ) def Identifier( self ): "Dublin Core element - Object ID" # XXX: fixme using 'portal_metadata' (we need to prepend the # right prefix to self.getPhysicalPath(). return self.absolute_url() security.declarePublic( 'Language' ) def Language( self ): """ Dublin Core element - resource language """ return self.language security.declarePublic( 'Rights' ) def Rights( self ): """ Dublin Core element - resource copyright """ return self.rights # # DublinCore utility methods # def content_type( self ): """ WebDAV needs this to do the Right Thing (TM). """ return self.Format() __FLOOR_DATE = DateTime( 1000, 0 ) # alwasy effective security.declarePublic( 'isEffective' ) def isEffective( self, date ): """ Is the date within the resource's effective range? """ pastEffective = ( self.effective_date is None or self.effective_date <= date ) beforeExpiration = ( self.expiration_date is None or self.expiration_date >= date ) return pastEffective and beforeExpiration # # CatalogableDublinCore methods # security.declarePublic( 'created' ) def created( self ): """ Dublin Core element - date resource created, returned as DateTime. """ # allow for non-existent creation_date, existed always date = getattr( self, 'creation_date', None ) return date is None and self.__FLOOR_DATE or date security.declarePublic( 'effective' ) def effective( self ): """ Dublin Core element - date resource becomes effective, returned as DateTime. """ marker = [] date = getattr( self, 'effective_date', marker ) if date is marker: date = getattr( self, 'creation_date', None ) return date is None and self.__FLOOR_DATE or date __CEILING_DATE = DateTime( 9999, 0 ) # never expires security.declarePublic( 'expires' ) def expires( self ): """ Dublin Core element - date resource expires, returned as DateTime. """ date = getattr( self, 'expiration_date', None ) return date is None and self.__CEILING_DATE or date security.declarePublic( 'modified' ) def modified( self ): """ Dublin Core element - date resource last modified, returned as DateTime. """ date = self.modification_date if date is None: # Upgrade. date = self.bobobase_modification_time() self.modification_date = date return date security.declarePublic( 'getMetadataHeaders' ) def getMetadataHeaders( self ): """ Return RFC-822-style headers. """ hdrlist = [] hdrlist.append( ( 'Title', self.Title() ) ) hdrlist.append( ( 'Subject', string.join( self.Subject(), ', ' ) ) ) hdrlist.append( ( 'Publisher', self.Publisher() ) ) hdrlist.append( ( 'Description', self.Description() ) ) hdrlist.append( ( 'Contributors', string.join( self.Contributors(), '; ' ) ) ) hdrlist.append( ( 'Effective_date', self.EffectiveDate() ) ) hdrlist.append( ( 'Expiration_date', self.ExpirationDate() ) ) hdrlist.append( ( 'Type', self.Type() ) ) hdrlist.append( ( 'Format', self.Format() ) ) hdrlist.append( ( 'Language', self.Language() ) ) hdrlist.append( ( 'Rights', self.Rights() ) ) return hdrlist # # MutableDublinCore methods # security.declarePrivate( '_datify' ) def _datify( self, attrib ): if attrib == 'None': attrib = None elif not isinstance( attrib, DateTime ): if attrib is not None: attrib = DateTime( attrib ) return attrib security.declareProtected( CMFCorePermissions.ModifyPortalContent , 'setTitle' ) def setTitle( self, title ): "Dublin Core element - resource name" self.title = title security.declareProtected( CMFCorePermissions.ModifyPortalContent , 'setSubject' ) def setSubject( self, subject ): "Dublin Core element - resource keywords" self.subject = tuplize( 'subject', subject ) security.declareProtected( CMFCorePermissions.ModifyPortalContent , 'setDescription' ) def setDescription( self, description ): "Dublin Core element - resource summary" self.description = description security.declareProtected( CMFCorePermissions.ModifyPortalContent , 'setContributors' ) def setContributors( self, contributors ): "Dublin Core element - additional contributors to resource" # XXX: fixme self.contributors = tuplize('contributors', contributors, semi_split) security.declareProtected( CMFCorePermissions.ModifyPortalContent , 'setEffectiveDate' ) def setEffectiveDate( self, effective_date ): """ Dublin Core element - date resource becomes effective. """ self.effective_date = self._datify( effective_date ) security.declareProtected( CMFCorePermissions.ModifyPortalContent , 'setExpirationDate' ) def setExpirationDate( self, expiration_date ): """ Dublin Core element - date resource expires. """ self.expiration_date = self._datify( expiration_date ) security.declareProtected( CMFCorePermissions.ModifyPortalContent , 'setFormat' ) def setFormat( self, format ): """ Dublin Core element - resource format """ self.format = format security.declareProtected( CMFCorePermissions.ModifyPortalContent , 'setLanguage' ) def setLanguage( self, language ): """ Dublin Core element - resource language """ self.language = language security.declareProtected( CMFCorePermissions.ModifyPortalContent , 'setRights' ) def setRights( self, rights ): """ Dublin Core element - resource copyright """ self.rights = rights # # Management tab methods # security.declarePrivate( '_editMetadata' ) def _editMetadata( self , title='' , subject=() , description='' , contributors=() , effective_date=None , expiration_date=None , format='text/html' , language='en-US' , rights='' ): """ Update the editable metadata for this resource. """ self.setTitle( title ) self.setSubject( subject ) self.setDescription( description ) self.setContributors( contributors ) self.setEffectiveDate( effective_date ) self.setExpirationDate( expiration_date ) self.setFormat( format ) self.setLanguage( language ) self.setRights( rights ) security.declareProtected( CMFCorePermissions.ModifyPortalContent , 'manage_metadata' ) manage_metadata = DTMLFile( 'zmi_metadata', _dtmldir ) security.declareProtected( CMFCorePermissions.ModifyPortalContent , 'manage_editMetadata' ) def manage_editMetadata( self , title , subject , description , contributors , effective_date , expiration_date , format , language , rights , REQUEST ): """ Update metadata from the ZMI. """ self._editMetadata( title, subject, description, contributors , effective_date, expiration_date , format, language, rights ) REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/manage_metadata' + '?manage_tabs_message=Metadata+updated.' ) security.declareProtected( CMFCorePermissions.ModifyPortalContent , 'editMetadata' ) def editMetadata(self , title='' , subject=() , description='' , contributors=() , effective_date=None , expiration_date=None , format='text/html' , language='en-US' , rights='' ): """ used to be: editMetadata = WorkflowAction(_editMetadata) Need to add check for webDAV locked resource for TTW methods. """ self.failIfLocked() self._editMetadata(title=title , subject=subject , description=description , contributors=contributors , effective_date=effective_date , expiration_date=expiration_date , format=format , language=language , rights=rights ) self.reindexObject() InitializeClass(DefaultDublinCoreImpl) CMF-1.3/CMFDefault/Favorite.py0100644000076500007650000001260207523027525015754 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Favorites are references to other objects within the same CMF site.. $Id: Favorite.py,v 1.16.4.2 2025/08/03 19:30:29 efge Exp $ """ import string import urlparse from Globals import InitializeClass from AccessControl import ClassSecurityInfo from Products.CMFCore.utils import getToolByName from Products.CMFCore.CMFCorePermissions import View, ModifyPortalContent from DublinCore import DefaultDublinCoreImpl from Link import Link factory_type_information = ( { 'id' : 'Favorite' , 'meta_type' : 'Favorite' , 'description' : """\ A Favorite is a Link to an intra-portal resource.""" , 'icon' : 'link_icon.gif' , 'product' : 'CMFDefault' , 'factory' : 'addFavorite' , 'immediate_view' : 'metadata_edit_form' , 'actions' : ( { 'id' : 'view' , 'name' : 'View' , 'action' : 'favorite_view' , 'permissions' : ( View, ) } , { 'id' : 'edit' , 'name' : 'Edit' , 'action' : 'link_edit_form' , 'permissions' : ( ModifyPortalContent, ) } , { 'id' : 'metadata' , 'name' : 'Metadata' , 'action' : 'metadata_edit_form' , 'permissions' : ( ModifyPortalContent, ) } ) } , ) def addFavorite(self, id, title='', remote_url='', description=''): """ Add a Favorite """ portal_url = getToolByName(self, 'portal_url') portal_obj = portal_url.getPortalObject() content_obj = portal_obj.restrictedTraverse( remote_url ) relUrl = portal_url.getRelativeUrl( content_obj ) o=Favorite( id, title, relUrl, description ) self._setObject(id,o) class Favorite( Link ): """ A Favorite (special kind of Link) """ __implements__ = Link.__implements__ # redundant, but explicit meta_type='Favorite' security = ClassSecurityInfo() def __init__( self , id , title='' , remote_url='' , description='' ): DefaultDublinCoreImpl.__init__(self) self.id=id self.title=title self.remote_url=remote_url self.description = description security.declareProtected(View, 'getRemoteUrl') def getRemoteUrl(self): """ returns the remote URL of the Link """ portal_url = getToolByName(self, 'portal_url') if self.remote_url: return portal_url() + '/' + self.remote_url else: return portal_url() security.declareProtected(View, 'getIcon') def getIcon(self, relative_to_portal=0): """ Instead of a static icon, like for Link objects, we want to display an icon based on what the Favorite links to. """ try: return self.getObject().getIcon(relative_to_portal) except: return 'p_/broken' security.declareProtected(View, 'getObject') def getObject(self): """ Return the actual object that the Favorite is linking to """ portal_url = getToolByName(self, 'portal_url') return portal_url.getPortalObject().restrictedTraverse(self.remote_url) security.declarePrivate('_edit') def _edit( self, remote_url ): """ Edit the Favorite. Unlike Links, Favorites have URLs that are relative to the root of the site. """ # strip off scheme and machine from URL if present tokens = urlparse.urlparse( remote_url, 'http' ) if tokens[1]: # There is a nethost, remove it t=('', '') + tokens[2:] remote_url=urlparse.urlunparse(t) # if URL begins with site URL, remove site URL portal_url = getToolByName(self, 'portal_url').getPortalPath() i=string.find(remote_url, portal_url) if i==0: remote_url=remote_url[len(portal_url):] # if site is still absolute, make it relative if remote_url[:1]=='/': remote_url=remote_url[1:] self.remote_url=remote_url InitializeClass(Favorite) CMF-1.3/CMFDefault/File.py0100644000076500007650000002230607524005671015055 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ This module implements a portal-managed File class. It is based on Zope's built-in File object, but modifies the behaviour slightly to make it more Portal-friendly. """ from Globals import InitializeClass from AccessControl import ClassSecurityInfo from Products.CMFCore.PortalContent import PortalContent from DublinCore import DefaultDublinCoreImpl from Products.CMFCore import CMFCorePermissions from Products.CMFCore.WorkflowCore import WorkflowAction factory_type_information = ( { 'id' : 'File' , 'meta_type' : 'Portal File' , 'description' : """\ File objects can contain arbitrary downloadable files.""" , 'icon' : 'file_icon.gif' , 'product' : 'CMFDefault' , 'factory' : 'addFile' , 'immediate_view' : 'metadata_edit_form' , 'actions' : ( { 'id' : 'view' , 'name' : 'View' , 'action' : 'file_view' , 'permissions' : ( CMFCorePermissions.View, ) } , { 'id' : 'download' , 'name' : 'Download' , 'action' : '' , 'permissions' : ( CMFCorePermissions.View, ) } , { 'id' : 'edit' , 'name' : 'Edit' , 'action' : 'file_edit_form' , 'permissions' : ( CMFCorePermissions.ModifyPortalContent, ) } , { 'id' : 'metadata' , 'name' : 'Metadata' , 'action' : 'metadata_edit_form' , 'permissions' : ( CMFCorePermissions.ModifyPortalContent, ) } ) } , ) import OFS.Image def addFile( self , id , title='' , file='' , content_type='' , precondition='' , subject=() , description='' , contributors=() , effective_date=None , expiration_date=None , format='text/html' , language='' , rights='' ): """ Add a File """ # cookId sets the id and title if they are not explicity specified id, title = OFS.Image.cookId(id, title, file) self=self.this() # Instantiate the object and set its description. fobj = File( id, title, '', content_type, precondition, subject , description, contributors, effective_date, expiration_date , format, language, rights ) # Add the File instance to self self._setObject(id, fobj) # 'Upload' the file. This is done now rather than in the # constructor because the object is now in the ZODB and # can span ZODB objects. self._getOb(id).manage_upload(file) class File( OFS.Image.File , PortalContent , DefaultDublinCoreImpl ): """ A Portal-managed File """ # The order of base classes is very significant in this case. # Image.File does not store it's id in it's 'id' attribute. # Rather, it has an 'id' method which returns the contents of the # instnace's __name__ attribute. Inheriting in the other order # obscures this method, resulting in much pulling of hair and # gnashing of teeth and fraying of nerves. Don't do it. # # Really. # # Note that if you use getId() to retrieve an object's ID, you will avoid # this problem altogether. getId is the new way, accessing .id is # deprecated. __implements__ = ( PortalContent.__implements__ , DefaultDublinCoreImpl.__implements__ ) meta_type='Portal File' effective_date = expiration_date = None _isDiscussable = 1 icon = PortalContent.icon security = ClassSecurityInfo() def __init__( self , id , title='' , file='' , content_type='' , precondition='' , subject=() , description='' , contributors=() , effective_date=None , expiration_date=None , format='text/html' , language='en-US' , rights='' ): OFS.Image.File.__init__( self, id, title, file , content_type, precondition ) DefaultDublinCoreImpl.__init__( self, title, subject, description , contributors, effective_date, expiration_date , format, language, rights ) security.declareProtected(CMFCorePermissions.View, 'SearchableText') def SearchableText(self): """ SeachableText is used for full text seraches of a portal. It should return a concatenation of all useful text. """ return "%s %s" % (self.title, self.description) security.declarePrivate('manage_beforeDelete') def manage_afterAdd(self, item, container): """Both of my parents have an afterAdd method""" OFS.Image.File.manage_afterAdd(self, item, container) PortalContent.manage_afterAdd(self, item, container) security.declarePrivate('manage_beforeDelete') def manage_beforeDelete(self, item, container): """Both of my parents have a beforeDelete method""" PortalContent.manage_beforeDelete(self, item, container) OFS.Image.File.manage_beforeDelete(self, item, container) security.declarePrivate('_isNotEmpty') def _isNotEmpty(self, file): """ Do various checks on 'file' to try to determine non emptiness. """ if not file: return 0 # Catches None, Missing.Value, '' elif file and (type(file) is type('')): return 1 elif getattr(file, 'filename', None): return 1 elif not hasattr(file, 'read'): return 0 else: file.seek(0,2) # 0 bytes back from end of file t = file.tell() # Report the location file.seek(0) # and return pointer back to 0 if t: return 1 else: return 0 security.declarePrivate('_edit') def _edit(self, precondition='', file=''): """ Perform changes for user """ if precondition: self.precondition = precondition elif self.precondition: del self.precondition if self._isNotEmpty(file): self.manage_upload(file) self.setFormat(self.content_type) security.declareProtected(CMFCorePermissions.ModifyPortalContent, 'edit') def edit(self, precondition='', file=''): """ Update and reindex. """ self._edit( precondition, file ) self.reindexObject() edit = WorkflowAction(edit) security.declareProtected(CMFCorePermissions.View, 'download') def download(self, REQUEST, RESPONSE): """Download this item. Calls OFS.Image.File.index_html to perform the actual transfer after first setting Content-Disposition to suggest a filename. This method is deprecated, use the URL of this object itself. Because the default view of a File object is to download, rather than view, this method is obsolete. Also note that certain browsers do not deal well with a Content-Disposition header. """ RESPONSE.setHeader('Content-Disposition', 'attachment; filename=%s' % self.getId()) return OFS.Image.File.index_html(self, REQUEST, RESPONSE) security.declareProtected(CMFCorePermissions.ModifyPortalContent, 'PUT') def PUT(self, REQUEST, RESPONSE): """ Handle HTTP (and presumably FTP?) PUT requests """ OFS.Image.File.PUT( self, REQUEST, RESPONSE ) self.reindexObject() InitializeClass(File) CMF-1.3/CMFDefault/Image.py0100644000076500007650000002066407524005671015225 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ This module implements a portal-managed Image class. It is based on Zope's built-in Image object. """ from Globals import InitializeClass from AccessControl import ClassSecurityInfo from Products.CMFCore.PortalContent import PortalContent from DublinCore import DefaultDublinCoreImpl from Products.CMFCore import CMFCorePermissions from Products.CMFCore.WorkflowCore import WorkflowAction factory_type_information = ( { 'id' : 'Image' , 'meta_type' : 'Portal Image' , 'description' : """\ Image objects can be embedded in Portal documents.""" , 'icon' : 'image_icon.gif' , 'product' : 'CMFDefault' , 'factory' : 'addImage' , 'immediate_view' : 'metadata_edit_form' , 'actions' : ( { 'id' : 'view' , 'name' : 'View' , 'action' : 'image_view' , 'permissions' : ( CMFCorePermissions.View, ) } , { 'id' : 'edit' , 'name' : 'Edit' , 'action' : 'image_edit_form' , 'permissions' : ( CMFCorePermissions.ModifyPortalContent, ) } , { 'id' : 'metadata' , 'name' : 'Metadata' , 'action' : 'metadata_edit_form' , 'permissions' : ( CMFCorePermissions.ModifyPortalContent, ) } ) } , ) import OFS.Image def addImage( self , id , title='' , file='' , content_type='' , precondition='' , subject=() , description='' , contributors=() , effective_date=None , expiration_date=None , format='image/png' , language='' , rights='' ): """ Add an Image """ # cookId sets the id and title if they are not explicity specified id, title = OFS.Image.cookId(id, title, file) self=self.this() # Instantiate the object and set its description. iobj = Image( id, title, '', content_type, precondition, subject , description, contributors, effective_date, expiration_date , format, language, rights ) # Add the Image instance to self self._setObject(id, iobj) # 'Upload' the image. This is done now rather than in the # constructor because it's faster (see File.py.) self._getOb(id).manage_upload(file) class Image( OFS.Image.Image , PortalContent , DefaultDublinCoreImpl ): """ A Portal-managed Image """ # The order of base classes is very significant in this case. # Image.Image does not store it's id in it's 'id' attribute. # Rather, it has an 'id' method which returns the contents of the # instnace's __name__ attribute. Inheriting in the other order # obscures this method, resulting in much pulling of hair and # gnashing of teeth and fraying of nerves. Don't do it. # # Really. # # Note that if you use getId() to retrieve an object's ID, you will avoid # this problem altogether. getId is the new way, accessing .id is # deprecated. __implements__ = ( PortalContent.__implements__ , DefaultDublinCoreImpl.__implements__ ) meta_type='Portal Image' effective_date = expiration_date = None _isDiscussable = 1 icon = PortalContent.icon security = ClassSecurityInfo() def __init__( self , id , title='' , file='' , content_type='' , precondition='' , subject=() , description='' , contributors=() , effective_date=None , expiration_date=None , format='image/png' , language='en-US' , rights='' ): OFS.Image.File.__init__( self, id, title, file , content_type, precondition ) DefaultDublinCoreImpl.__init__( self, title, subject, description , contributors, effective_date, expiration_date , format, language, rights ) security.declareProtected(CMFCorePermissions.View, 'SearchableText') def SearchableText(self): """ SeachableText is used for full text seraches of a portal. It should return a concatanation of all useful text. """ return "%s %s" % (self.title, self.description) security.declarePrivate('manage_beforeDelete') def manage_afterAdd(self, item, container): """Both of my parents have an afterAdd method""" OFS.Image.Image.manage_afterAdd(self, item, container) PortalContent.manage_afterAdd(self, item, container) security.declarePrivate('manage_beforeDelete') def manage_beforeDelete(self, item, container): """Both of my parents have a beforeDelete method""" PortalContent.manage_beforeDelete(self, item, container) OFS.Image.Image.manage_beforeDelete(self, item, container) security.declarePrivate('_isNotEmpty') def _isNotEmpty(self, file): """ Do various checks on 'file' to try to determine non emptiness. """ if not file: return 0 # Catches None, Missing.Value, '' elif file and (type(file) is type('')): return 1 elif getattr(file, 'filename', None): return 1 elif not hasattr(file, 'read'): return 0 else: file.seek(0,2) # 0 bytes back from end of file t = file.tell() # Report the location file.seek(0) # and return pointer back to 0 if t: return 1 else: return 0 security.declarePrivate('_edit') def _edit(self, precondition='', file=''): """ Update image. """ if precondition: self.precondition = precondition elif self.precondition: del self.precondition if self._isNotEmpty(file): self.manage_upload(file) self.setFormat(self.content_type) security.declareProtected(CMFCorePermissions.ModifyPortalContent, 'edit') def edit(self, precondition='', file=''): """ Update and reindex. """ self._edit( precondition, file ) self.reindexObject() edit = WorkflowAction(edit) security.declareProtected(CMFCorePermissions.View, 'index_html') def index_html(self, REQUEST, RESPONSE): """ Display the image, with or without standard_html_[header|footer], as appropriate. """ #if REQUEST['PATH_INFO'][-10:] == 'index_html': # return self.view(self, REQUEST) return OFS.Image.Image.index_html(self, REQUEST, RESPONSE) security.declareProtected(CMFCorePermissions.ModifyPortalContent, 'PUT') def PUT(self, REQUEST, RESPONSE): """ Handle HTTP (and presumably FTP?) PUT requests """ OFS.Image.Image.PUT( self, REQUEST, RESPONSE ) self.reindexObject() InitializeClass(Image) CMF-1.3/CMFDefault/Link.py0100644000076500007650000002070007523123734015067 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Link instances represent explicit links-as-content. $Id: Link.py,v 1.20.4.4 2025/08/04 04:04:44 efge Exp $ """ import string import urlparse from Globals import InitializeClass, DTMLFile from AccessControl import ClassSecurityInfo from Products.CMFCore import CMFCorePermissions from Products.CMFCore.PortalContent import PortalContent, NoWL from Products.CMFCore.PortalContent import ResourceLockedError from Products.CMFCore.WorkflowCore import WorkflowAction from Products.CMFCore.utils import keywordsplitter from DublinCore import DefaultDublinCoreImpl from utils import formatRFC822Headers, parseHeadersBody, _dtmldir factory_type_information = ( { 'id' : 'Link' , 'meta_type' : 'Link' , 'description' : """\ Link items are URLs that come with additional information.""" , 'icon' : 'link_icon.gif' , 'product' : 'CMFDefault' , 'factory' : 'addLink' , 'immediate_view' : 'metadata_edit_form' , 'actions' : ( { 'id' : 'view' , 'name' : 'View' , 'action' : 'link_view' , 'permissions' : ( CMFCorePermissions.View, ) } , { 'id' : 'edit' , 'name' : 'Edit' , 'action' : 'link_edit_form' , 'permissions' : ( CMFCorePermissions.ModifyPortalContent, ) } , { 'id' : 'metadata' , 'name' : 'Metadata' , 'action' : 'metadata_edit_form' , 'permissions' : ( CMFCorePermissions.ModifyPortalContent, ) } ) } , ) def addLink( self , id , title='' , remote_url='' , description='' ): """ Add a Link instance to 'self'. """ o=Link( id, title, remote_url, description ) self._setObject(id,o) class Link( PortalContent , DefaultDublinCoreImpl ): """ A Link """ __implements__ = ( PortalContent.__implements__ , DefaultDublinCoreImpl.__implements__ ) meta_type = 'Link' URL_FORMAT = format = 'text/url' effective_date = expiration_date = None _isDiscussable = 1 security = ClassSecurityInfo() def __init__( self , id , title='' , remote_url='' , description='' ): DefaultDublinCoreImpl.__init__(self) self.id=id self.title=title self.description=description self._edit(remote_url) self.format=self.URL_FORMAT security.declareProtected( CMFCorePermissions.ModifyPortalContent , 'manage_edit' ) manage_edit = DTMLFile( 'zmi_editLink', _dtmldir ) security.declareProtected( CMFCorePermissions.ModifyPortalContent , 'manage_editLink' ) def manage_editLink( self, remote_url, REQUEST=None ): """ Update the Link via the ZMI. """ self._edit( remote_url ) if REQUEST is not None: REQUEST['RESPONSE'].redirect( self.absolute_url() + '/manage_edit' + '?manage_tabs_message=Link+updated' ) security.declarePrivate( '_edit' ) def _edit( self, remote_url ): """ Edit the Link """ tokens = urlparse.urlparse( remote_url, 'http' ) if tokens[0] == 'http': if tokens[1]: # We have a nethost. All is well. url = urlparse.urlunparse(tokens) elif tokens[2:] == ('', '', '', ''): # Empty URL url = '' else: # Relative URL, keep it that way, without http: tokens = ('', '') + tokens[2:] url = urlparse.urlunparse(tokens) else: # Other scheme, keep original url = urlparse.urlunparse(tokens) self.remote_url = url security.declareProtected( CMFCorePermissions.ModifyPortalContent, 'edit' ) def edit(self, remote_url ): """ Update and reindex. """ self._edit( remote_url ) self.reindexObject() edit = WorkflowAction( edit ) security.declareProtected( CMFCorePermissions.View, 'SearchableText' ) def SearchableText(self): """ text for indexing """ return "%s %s" % (self.title, self.description) security.declareProtected( CMFCorePermissions.View, 'getRemoteUrl' ) def getRemoteUrl(self): """ returns the remote URL of the Link """ return self.remote_url security.declarePrivate( '_writeFromPUT' ) def _writeFromPUT( self, body ): headers = {} headers, body = parseHeadersBody(body, headers) lines = string.split( body, '\n' ) self.edit( lines[0] ) headers['Format'] = self.URL_FORMAT new_subject = keywordsplitter(headers) headers['Subject'] = new_subject or self.Subject() haveheader = headers.has_key for key, value in self.getMetadataHeaders(): if key != 'Format' and not haveheader(key): headers[key] = value self._editMetadata(title=headers['Title'], subject=headers['Subject'], description=headers['Description'], contributors=headers['Contributors'], effective_date=headers['Effective_date'], expiration_date=headers['Expiration_date'], format=headers['Format'], language=headers['Language'], rights=headers['Rights'], ) ## FTP handlers security.declareProtected( CMFCorePermissions.ModifyPortalContent, 'PUT') def PUT(self, REQUEST, RESPONSE): """ Handle HTTP / WebDAV / FTP PUT requests. """ if not NoWL: self.dav__init(REQUEST, RESPONSE) self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1) body = REQUEST.get('BODY', '') try: self._writeFromPUT( body ) RESPONSE.setStatus(204) return RESPONSE except ResourceLockedError, msg: get_transaction().abort() RESPONSE.setStatus(423) return RESPONSE security.declareProtected( CMFCorePermissions.View, 'manage_FTPget' ) def manage_FTPget(self): """ Get the link as text for WebDAV src / FTP download. """ join = string.join lower = string.lower hdrlist = self.getMetadataHeaders() hdrtext = formatRFC822Headers( hdrlist ) bodytext = '%s\n\n%s' % ( hdrtext, self.getRemoteUrl() ) return bodytext security.declareProtected( CMFCorePermissions.View, 'get_size' ) def get_size( self ): """ Used for FTP and apparently the ZMI now too """ return len(self.manage_FTPget()) InitializeClass( Link ) CMF-1.3/CMFDefault/MembershipTool.py0100644000076500007650000001777507512341261017140 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ CMFDefault portal_membership tool. $Id: MembershipTool.py,v 1.25.4.1 2025/07/08 16:49:53 efge Exp $ """ from Globals import InitializeClass, DTMLFile from AccessControl import ClassSecurityInfo from Products.CMFCore.MembershipTool import MembershipTool as BaseTool from Products.CMFCore.PortalFolder import manage_addPortalFolder from Products.CMFCore.utils import _getAuthenticatedUser from Products.CMFCore.utils import _checkPermission from Products.CMFCore.utils import getToolByName from Products.CMFCore.ActionsTool import ActionInformation as AI from Products.CMFCore.Expression import Expression from Products.CMFCore.CMFCorePermissions import View from Products.CMFCore.CMFCorePermissions import AccessContentsInformation from Products.CMFCore.CMFCorePermissions import ListPortalMembers from Products.CMFCore.CMFCorePermissions import AddPortalMember from Products.CMFCore.CMFCorePermissions import ManagePortal from Document import addDocument from utils import _dtmldir DEFAULT_MEMBER_CONTENT = """\ Default page for %s This is the default document created for you when you joined this community. To change the content just select "Edit" in the Tool Box on the left. """ class MembershipTool( BaseTool ): """ Implement 'portal_membership' interface using "stock" policies. """ _actions =[ AI( id='login' , title='Login' , description='Click here to Login' , action=Expression(text='string:${portal_url}/login_form') , permissions=(View,) , category='user' , condition=Expression(text='not: member') , visible=1 ) , AI( id='preferences' , title='Preferences' , description='Change your user preferences' , action=Expression(text='string:${portal_url}/personalize_form') , permissions=(View,) , category='user' , condition=Expression(text='member') , visible=1 ) , AI( id='logout' , title='Log out' , description='Click here to logout' , action=Expression(text='string:${portal_url}/logout') , permissions=(View,) , category='user' , condition=Expression(text='member') , visible=1 ) , AI( id='addFavorite' , title='Add to favorites' , description='Add this item to your favorites' , action=Expression(text='string:${object_url}/addtoFavorites') , permissions=(View,) , category='user' , condition=Expression(text= 'portal/portal_membership' + '/getHomeFolder') , visible=1 ) , AI( id='mystuff' , title='My stuff' , description='Goto your home folder' , action=Expression(text='string:${portal/portal_membership' + '/getHomeUrl}/folder_contents') , permissions=(View,) , category='user' , condition=Expression( text='python: member and ' + 'portal.portal_membership.getHomeFolder()') , visible=1 ) , AI( id='favorites' , title='My favorites' , description='Browse your favorites' , action=Expression(text='string:${portal/portal_membership' + '/getHomeUrl}/Favorites/folder_contents') , permissions=(View,) , category='user' , condition=Expression( text='python: member and ' + 'hasattr(portal.portal_membership.' + 'getHomeFolder(), "Favorites")') , visible=1 ) ] meta_type = 'Default Membership Tool' security = ClassSecurityInfo() # # ZMI methods # security.declareProtected( ManagePortal, 'manage_overview' ) manage_overview = DTMLFile( 'explainMembershipTool', _dtmldir ) # # 'portal_membership' interface methods # security.declareProtected( ListPortalMembers, 'getRoster' ) def getRoster(self): """ Return a list of mappings for 'listed' members. If Manager, return a list of all usernames. The mapping contains the id and listed variables. """ isManager = _checkPermission('Manage portal', self) roster = [] for member in self.listMembers(): if isManager or member.listed: roster.append({'id':member.getUserName(), 'listed':member.listed}) return roster security.declareProtected(ManagePortal, 'createMemberarea') def createMemberarea(self, member_id): """ Create a member area for 'member_id'. """ parent = self.aq_inner.aq_parent members = getattr(parent, 'Members', None) if members is not None and not hasattr(members, member_id): f_title = "%s's Home" % member_id members.manage_addPortalFolder( id=member_id, title=f_title ) f=getattr(members, member_id) # Grant ownership to Member acl_users = self.__getPUS() user = acl_users.getUser(member_id) if user is not None: user= user.__of__(acl_users) else: from AccessControl import getSecurityManager user= getSecurityManager().getUser() # check that we do not do something wrong if user.getUserName() != member_id: raise NotImplementedError, \ 'cannot get user for member area creation' f.changeOwnership(user) f.manage_setLocalRoles(member_id, ['Owner']) # Create Member's home page. # DEFAULT_MEMBER_CONTENT ought to be configurable per # instance of MembershipTool. addDocument( f , 'index_html' , member_id+"'s Home" , member_id+"'s front page" , "structured-text" , (DEFAULT_MEMBER_CONTENT % member_id) ) f.index_html._setPortalTypeName( 'Document' ) # Overcome an apparent catalog bug. f.index_html.reindexObject() wftool = getToolByName( f, 'portal_workflow' ) wftool.notifyCreated( f.index_html ) def getHomeFolder(self, id=None, verifyPermission=0): """ Return a member's home folder object, or None. """ if id is None: member = self.getAuthenticatedMember() if not hasattr(member, 'getMemberId'): return None id = member.getMemberId() if hasattr(self, 'Members'): try: folder = self.Members[id] if verifyPermission and not _checkPermission('View', folder): # Don't return the folder if the user can't get to it. return None return folder except KeyError: pass return None def getHomeUrl(self, id=None, verifyPermission=0): """ Return the URL to a member's home folder, or None. """ home = self.getHomeFolder(id, verifyPermission) if home is not None: return home.absolute_url() else: return None security.declarePrivate( 'listActions' ) def listActions(self, info=None): """ List actions available through the tool. """ return self._actions InitializeClass(MembershipTool) CMF-1.3/CMFDefault/MetadataTool.py0100644000076500007650000004615107511373501016554 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """\ CMFDefault portal_metadata tool. """ from OFS.SimpleItem import SimpleItem from Products.CMFCore.utils import UniqueObject from Globals import PersistentMapping from Globals import InitializeClass, DTMLFile from AccessControl import ClassSecurityInfo, getSecurityManager from Products.CMFCore import CMFCorePermissions from Products.CMFCore.ActionProviderBase import ActionProviderBase from utils import _dtmldir class MetadataElementPolicy( SimpleItem ): """ Represent a type-specific policy about a particular DCMI element. """ security = ClassSecurityInfo() # # Default values. # is_required = 0 supply_default = 0 default_value = '' enforce_vocabulary = 0 allowed_vocabulary = () def __init__( self, is_multi_valued=0 ): self.is_multi_valued = not not is_multi_valued # # Mutator. # security.declareProtected( CMFCorePermissions.ManagePortal , 'edit' ) def edit( self , is_required , supply_default , default_value , enforce_vocabulary , allowed_vocabulary ): self.is_required = not not is_required self.supply_default = not not supply_default self.default_value = default_value self.enforce_vocabulary = not not enforce_vocabulary self.allowed_vocabulary = tuple( allowed_vocabulary ) # # Query interface # security.declareProtected( CMFCorePermissions.View , 'isMultiValued' ) def isMultiValued( self ): """ Can this element hold multiple values? """ return self.is_multi_valued security.declareProtected( CMFCorePermissions.View , 'isRequired' ) def isRequired( self ): """ Must this element be supplied? """ return self.is_required security.declareProtected( CMFCorePermissions.View , 'supplyDefault' ) def supplyDefault( self ): """ Should the tool supply a default? """ return self.supply_default security.declareProtected( CMFCorePermissions.View , 'defaultValue' ) def defaultValue( self ): """ If so, what is the default? """ return self.default_value security.declareProtected( CMFCorePermissions.View , 'enforceVocabulary' ) def enforceVocabulary( self ): """ """ return self.enforce_vocabulary security.declareProtected( CMFCorePermissions.View , 'allowedVocabulary' ) def allowedVocabulary( self ): """ """ return self.allowed_vocabulary InitializeClass( MetadataElementPolicy ) DEFAULT_ELEMENT_SPECS = ( ( 'Title', 0 ) , ( 'Description', 0 ) , ( 'Subject', 1 ) , ( 'Format', 0 ) , ( 'Language', 0 ) , ( 'Rights', 0 ) ) class ElementSpec( SimpleItem ): """ Represent all the tool knows about a single metadata element. """ security = ClassSecurityInfo() # # Default values. # is_multi_valued = 0 def __init__( self, is_multi_valued ): self.is_multi_valued = is_multi_valued self.policies = PersistentMapping() self.policies[ None ] = self._makePolicy() # set default policy security.declarePrivate( '_makePolicy' ) def _makePolicy( self ): return MetadataElementPolicy( self.is_multi_valued ) security.declareProtected( CMFCorePermissions.View , 'isMultiValued' ) def isMultiValued( self ): """ Is this element multi-valued? """ return self.is_multi_valued security.declareProtected( CMFCorePermissions.View , 'getPolicy' ) def getPolicy( self, typ=None ): """ Find the policy this element for objects whose type object name is 'typ'; return a default, if none found. """ try: return self.policies[ typ ].__of__(self) except KeyError: return self.policies[ None ] security.declareProtected( CMFCorePermissions.View , 'listPolicies' ) def listPolicies( self ): """ Return a list of all policies for this element. """ res = [] for k, v in self.policies.items(): res.append((k, v.__of__(self))) return res security.declareProtected( CMFCorePermissions.ManagePortal , 'addPolicy' ) def addPolicy( self, typ ): """ Add a policy to this element for objects whose type object name is 'typ'. """ if typ is None: raise MetadataError, "Can't replace default policy." if self.policies.has_key( typ ): raise MetadataError, "Existing policy for content type:" + typ self.policies[ typ ] = self._makePolicy() security.declareProtected( CMFCorePermissions.ManagePortal, 'removePolicy' ) def removePolicy( self, typ ): """ Remove the policy from this element for objects whose type object name is 'typ' (*not* the default, however). """ if typ is None: raise MetadataError, "Can't remove default policy." del self.policies[ typ ] InitializeClass( ElementSpec ) class MetadataError( Exception ): pass class MetadataTool( UniqueObject, SimpleItem, ActionProviderBase ): id = 'portal_metadata' meta_type = 'Default Metadata Tool' _actions = [] security = ClassSecurityInfo() # # Default values. # publisher = '' element_specs = None #initial_values_hook = None #validation_hook = None def __init__( self , publisher=None #, initial_values_hook=None #, validation_hook=None , element_specs=DEFAULT_ELEMENT_SPECS ): self.editProperties( publisher #, initial_values_hook #, validation_hook ) self.element_specs = PersistentMapping() for name, is_multi_valued in element_specs: self.element_specs[ name ] = ElementSpec( is_multi_valued ) # # ZMI methods # manage_options = ( ActionProviderBase.manage_options + ( { 'label' : 'Overview' , 'action' : 'manage_overview' } , { 'label' : 'Properties' , 'action' : 'propertiesForm' } , { 'label' : 'Elements' , 'action' : 'elementPoliciesForm' } # TODO , { 'label' : 'Types' # , 'action' : 'typesForm' # } ) + SimpleItem.manage_options ) security.declareProtected( CMFCorePermissions.ManagePortal , 'manage_overview' ) manage_overview = DTMLFile( 'explainMetadataTool', _dtmldir ) security.declareProtected( CMFCorePermissions.ManagePortal , 'propertiesForm' ) propertiesForm = DTMLFile( 'metadataProperties', _dtmldir ) security.declarePrivate('listActions') def listActions(self, info=None): """ Return actions provided via tool. """ return self._actions security.declareProtected( CMFCorePermissions.ManagePortal , 'editProperties' ) def editProperties( self , publisher=None # TODO , initial_values_hook=None # TODO , validation_hook=None , REQUEST=None ): """ Form handler for "tool-wide" properties (including list of metadata elements). """ if publisher is not None: self.publisher = publisher # TODO self.initial_values_hook = initial_values_hook # TODO self.validation_hook = validation_hook if REQUEST is not None: REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/propertiesForm' + '?manage_tabs_message=Tool+updated.' ) security.declareProtected( CMFCorePermissions.ManagePortal , 'elementPoliciesForm' ) elementPoliciesForm = DTMLFile( 'metadataElementPolicies', _dtmldir ) security.declareProtected( CMFCorePermissions.ManagePortal , 'addElementPolicy' ) def addElementPolicy( self , element , content_type , is_required , supply_default , default_value , enforce_vocabulary , allowed_vocabulary , REQUEST=None ): """ Add a type-specific policy for one of our elements. """ if content_type == '': content_type = None spec = self.getElementSpec( element ) spec.addPolicy( content_type ) policy = spec.getPolicy( content_type ) policy.edit( is_required , supply_default , default_value , enforce_vocabulary , allowed_vocabulary ) if REQUEST is not None: REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/elementPoliciesForm' + '?element=' + element + '&manage_tabs_message=Policy+added.' ) security.declareProtected( CMFCorePermissions.ManagePortal , 'removeElementPolicy' ) def removeElementPolicy( self , element , content_type , REQUEST=None ): """ Remvoe a type-specific policy for one of our elements. """ if content_type == '': content_type = None spec = self.getElementSpec( element ) spec.removePolicy( content_type ) if REQUEST is not None: REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/elementPoliciesForm' + '?element=' + element + '&manage_tabs_message=Policy+removed.' ) security.declareProtected( CMFCorePermissions.ManagePortal , 'updateElementPolicy' ) def updateElementPolicy( self , element , content_type , is_required , supply_default , default_value , enforce_vocabulary , allowed_vocabulary , REQUEST=None ): """ Update a policy for one of our elements ('content_type' will be '' when we edit the default). """ if content_type == '': content_type = None spec = self.getElementSpec( element ) policy = spec.getPolicy( content_type ) policy.edit( is_required , supply_default , default_value , enforce_vocabulary , allowed_vocabulary ) if REQUEST is not None: REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/elementPoliciesForm' + '?element=' + element + '&manage_tabs_message=Policy+updated.' ) # # Element spec manipulation. # security.declareProtected( CMFCorePermissions.ManagePortal , 'listElementSpecs' ) def listElementSpecs( self ): """ Return a list of ElementSpecs representing the elements managed by the tool. """ res = [] for k, v in self.element_specs.items(): res.append((k, v.__of__(self))) return res security.declareProtected( CMFCorePermissions.ManagePortal , 'getElementSpec' ) def getElementSpec( self, element ): """ Return an ElementSpec representing the tool's knowledge of 'element'. """ return self.element_specs[ element ].__of__( self ) security.declareProtected( CMFCorePermissions.ManagePortal , 'addElementSpec' ) def addElementSpec( self, element, is_multi_valued, REQUEST=None ): """ Add 'element' to our list of managed elements. """ # Don't replace. if self.element_specs.has_key( element ): return self.element_specs[ element ] = ElementSpec( is_multi_valued ) if REQUEST is not None: REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/propertiesForm' + '?manage_tabs_message=Element+' + element + '+added.' ) security.declareProtected( CMFCorePermissions.ManagePortal , 'removeElementSpec' ) def removeElementSpec( self, element, REQUEST=None ): """ Remove 'element' from our list of managed elements. """ del self.element_specs[ element ] if REQUEST is not None: REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/propertiesForm' + '?manage_tabs_message=Element+' + element + '+removed.' ) security.declareProtected( CMFCorePermissions.ManagePortal, 'listPolicies' ) def listPolicies( self, typ=None ): """ Show all policies for a given content type, or the default if None. """ result = [] for element, spec in self.listElementSpecs(): result.append( ( element, spec.getPolicy( typ ) ) ) return result # # 'portal_metadata' interface # security.declarePrivate( 'getFullName' ) def getFullName( self, userid ): """ Convert an internal userid to a "formal" name, if possible, perhaps using the 'portal_membership' tool. Used to map userid's for Creator, Contributor DCMI queries. """ return userid # TODO: do lookup here security.declarePublic( 'getPublisher' ) def getPublisher( self ): """ Return the "formal" name of the publisher of the portal. """ return self.publisher security.declarePublic( 'listAllowedVocabulary' ) def listAllowedVocabulary( self, element, content=None, content_type=None ): """ List allowed keywords for a given portal_type, or all possible keywords if none supplied. """ spec = self.getElementSpec( element ) if content_type is None and content: content_type = content.getPortalTypeName() return spec.getPolicy( content_type ).allowedVocabulary() security.declarePublic( 'listAllowedSubjects' ) def listAllowedSubjects( self, content=None, content_type=None ): """ List allowed keywords for a given portal_type, or all possible keywords if none supplied. """ return self.listAllowedVocabulary( 'Subject', content, content_type ) security.declarePublic( 'listAllowedFormats' ) def listAllowedFormats( self, content=None, content_type=None ): """ List the allowed 'Content-type' values for a particular portal_type, or all possible formats if none supplied. """ return self.listAllowedVocabulary( 'Format', content, content_type ) security.declarePublic( 'listAllowedLanguages' ) def listAllowedLanguages( self, content=None, content_type=None ): """ List the allowed language values. """ return self.listAllowedVocabulary( 'Language', content, content_type ) security.declarePublic( 'listAllowedRights' ) def listAllowedRights( self, content=None, content_type=None ): """ List the allowed values for a "Rights" selection list; this gets especially important where syndication is involved. """ return self.listAllowedVocabulary( 'Rights', content, content_type ) security.declareProtected( CMFCorePermissions.ModifyPortalContent , 'setInitialMetadata' ) def setInitialMetadata( self, content ): """ Set initial values for content metatdata, supplying any site-specific defaults. """ for element, policy in self.listPolicies(content.getPortalTypeName()): if not getattr( content, element )(): if policy.supplyDefault(): setter = getattr( content, 'set%s' % element ) setter( policy.defaultValue() ) elif policy.isRequired(): raise MetadataError, \ 'Metadata element %s is required.' % element # TODO: Call initial_values_hook, if present security.declareProtected( CMFCorePermissions.View , 'validateMetadata' ) def validateMetadata( self, content ): """ Enforce portal-wide policies about DCI, e.g., requiring non-empty title/description, etc. Called by the CMF immediately before saving changes to the metadata of an object. """ for element, policy in self.listPolicies(content.getPortalTypeName()): value = getattr( content, element )() if not value and policy.isRequired(): raise MetadataError, \ 'Metadata element %s is required.' % element if policy.enforceVocabulary(): values = policy.isMultiValued() and value or [ value ] for value in values: if not value in policy.allowedVocabulary(): raise MetadataError, \ 'Value %s is not in allowed vocabulary for' \ 'metadata element %s.' % ( value, element ) # TODO: Call validation_hook, if present InitializeClass( MetadataTool ) CMF-1.3/CMFDefault/NewsItem.py0100644000076500007650000000734207523123734015734 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ """ from Globals import InitializeClass from Document import Document from utils import parseHeadersBody from Products.CMFCore import CMFCorePermissions from AccessControl import ClassSecurityInfo from Products.CMFCore.WorkflowCore import WorkflowAction factory_type_information = ( { 'id' : 'News Item' , 'meta_type' : 'News Item' , 'description' : """\ News Items contain short text articles and carry a title as well as an optional description.""" , 'icon' : 'newsitem_icon.gif' , 'product' : 'CMFDefault' , 'factory' : 'addNewsItem' , 'immediate_view' : 'metadata_edit_form' , 'actions' : ( { 'id' : 'view' , 'name' : 'View' , 'action' : 'newsitem_view' , 'permissions' : ( CMFCorePermissions.View, ) } , { 'id' : 'edit' , 'name' : 'Edit' , 'action' : 'newsitem_edit_form' , 'permissions' : ( CMFCorePermissions.ModifyPortalContent, ) } , { 'id' : 'metadata' , 'name' : 'Metadata' , 'action' : 'metadata_edit_form' , 'permissions' : ( CMFCorePermissions.ModifyPortalContent, ) } ) } , ) def addNewsItem( self , id , title='' , description='' , text='' , text_format='html' ): """ Add a NewsItem """ o=NewsItem( id=id , title=title , description=description , text=text , text_format=text_format ) self._setObject(id, o) class NewsItem( Document ): """ A News Item """ __implements__ = Document.__implements__ # redundant, but explicit meta_type='News Item' text_format = 'html' security = ClassSecurityInfo() security.declareProtected( CMFCorePermissions.ModifyPortalContent, 'edit' ) def edit( self, text, description=None, text_format=None ): """ Edit the News Item """ if text_format is None: text_format = getattr(self, 'text_format', 'html') if description is not None: self.setDescription( description ) Document.edit( self, text_format, text ) InitializeClass( NewsItem ) CMF-1.3/CMFDefault/Portal.py0100644000076500007650000003245207522303413015433 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Portal class $Id: Portal.py,v 1.30.4.2 2025/08/01 19:07:55 tseaver Exp $ """ import Globals from Products.CMFCore.PortalObject import PortalObjectBase from Products.CMFCore import PortalFolder from Products.CMFCore.TypesTool import ContentFactoryMetadata from Products.CMFCore.utils import getToolByName from Products.CMFTopic import Topic, topic_globals from DublinCore import DefaultDublinCoreImpl import Document, Image, File, Link, NewsItem, Favorite import DiscussionItem, SkinnedFolder factory_type_information = ( Document.factory_type_information + Image.factory_type_information + File.factory_type_information + Link.factory_type_information + NewsItem.factory_type_information + Favorite.factory_type_information + DiscussionItem.factory_type_information + SkinnedFolder.factory_type_information ) class CMFSite ( PortalObjectBase , DefaultDublinCoreImpl ): """ The *only* function this class should have is to help in the setup of a new CMFSite. It should not assist in the functionality at all. """ meta_type = 'CMF Site' _properties = ( {'id':'title', 'type':'string'}, {'id':'description', 'type':'text'}, ) title = '' description = '' __ac_permissions__=( ( 'Manage portal', ('manage_migrate_content',) ) , ( 'Add portal content', () ) , ( 'Add portal folders', () ) , ( 'List portal members', () ) , ( 'Reply to item', () ) , ( 'View', ( 'isEffective', ) ) ) def __init__( self, id, title='' ): PortalObjectBase.__init__( self, id, title ) DefaultDublinCoreImpl.__init__( self ) def isEffective( self, date ): """ Override DefaultDublinCoreImpl's test, since we are always viewable. """ return 1 def reindexObject( self, idxs=[] ): """ Override DefaultDublinCoreImpl's method (so that we can play in 'editMetadata'). """ pass # # The following two methods allow conversion of portal content from # PTK version 0.7.1. # DO NOT REMOVE!!! # if 0: def manage_migrate_content(self, REQUEST): """ Converts instances of Products.PTKBase. to instances of Products.PTKDemo.. """ import Products.PTKBase.Document import Products.PTKBase.File import Products.PTKBase.Image import Products.PTKBase.Link import Products.PTKBase.NewsItem import NewsItem, Link, Document, File, Image migrations = { Products.PTKBase.Document.Document : Document.Document, Products.PTKBase.File.File : File.File, Products.PTKBase.Image.Image : Image.Image, Products.PTKBase.Link.Link : Link.Link, Products.PTKBase.NewsItem.NewsItem : NewsItem.NewsItem, } visited = [] migrated = [] self.__migrate_branches(migrations, self, migrated, visited) from string import join return 'Converted:\n%s\n\nDone.' % join(migrated, '\n') def __migrate_branches(self, migrations, branch, migrated, visited): base = getattr(branch, 'aq_base', branch) if base in visited: # Don't visit again! return visited.append(base) try: changed = branch._p_changed except: changed = 1 for id in branch.objectIds(): obj = branch._getOb(id) obase = getattr(obj, 'aq_base', obj) klass = obase.__class__ if migrations.has_key(klass): # Replace this object. changed = 1 newob = migrations[klass](obase.id) newob.id = obase.id # This line activates obase. newob.__dict__.update(obase.__dict__) setattr(branch, id, newob) migrated.append(obj.absolute_url()) elif hasattr(obase, 'objectIds'): # Enter a sub-branch. self.__migrate_branches(migrations, obj, migrated, visited) else: # Unload this object if it has not been changed. try: if obj._p_changed is None: obj._p_deactivate() except: pass if changed is None: # Unload this branch. object._p_deactivate() del visited[-1] else: # placeholder def manage_migrate_content( self, REQUEST ): pass Globals.InitializeClass(CMFSite) class PortalGenerator: klass = CMFSite def setupTools(self, p): """Set up initial tools""" addCMFCoreTool = p.manage_addProduct['CMFCore'].manage_addTool addCMFCoreTool('CMF Actions Tool', None) addCMFCoreTool('CMF Catalog', None) addCMFCoreTool('CMF Member Data Tool', None) addCMFCoreTool('CMF Skins Tool', None) addCMFCoreTool('CMF Types Tool', None) addCMFCoreTool('CMF Undo Tool', None) addCMFCoreTool('CMF Workflow Tool', None) addCMFDefaultTool = p.manage_addProduct['CMFDefault'].manage_addTool addCMFDefaultTool('Default Discussion Tool', None) addCMFDefaultTool('Default Membership Tool', None) addCMFDefaultTool('Default Registration Tool', None) addCMFDefaultTool('Default URL Tool', None) addCMFDefaultTool('Default Properties Tool', None) addCMFDefaultTool('Default Metadata Tool', None) addCMFDefaultTool('Default Syndication Tool', None) def setupMailHost(self, p): p.manage_addProduct['MailHost'].manage_addMailHost( 'MailHost', smtp_host='localhost') def setupUserFolder(self, p): p.manage_addProduct['OFSP'].manage_addUserFolder() def setupCookieAuth(self, p): p.manage_addProduct['CMFCore'].manage_addCC( id='cookie_authentication') def setupMembersFolder(self, p): PortalFolder.manage_addPortalFolder(p, 'Members') p.Members.manage_addProduct['OFSP'].manage_addDTMLMethod( 'index_html', 'Member list', '') def setupRoles(self, p): # Set up the suggested roles. p.__ac_roles__ = ('Member', 'Reviewer',) def setupPermissions(self, p): # Set up some suggested role to permission mappings. mp = p.manage_permission mp('Set own password', ['Member','Manager',], 1) mp('Set own properties', ['Member','Manager',], 1) mp('List undoable changes', ['Member','Manager',], 1) mp('Add portal content', ['Owner','Manager',], 1) mp('Add portal folders', ['Owner','Manager',], 1) mp('Review portal content', ['Reviewer','Manager',], 1) mp('Access future portal content', ['Reviewer','Manager',], 1) mp('List portal members', ['Member','Manager',], 1) mp('Reply to item', ['Member','Manager',], 1) # Add some other permissions mappings that may be helpful. mp('Delete objects', ['Owner','Manager',], 1) mp('FTP access', ['Owner','Manager',], 1) mp('Manage properties', ['Owner','Manager',], 1) mp('Undo changes', ['Owner','Manager',], 1) mp('View management screens', ['Owner','Manager',], 1) def setupDefaultSkins(self, p): from Products.CMFCore.DirectoryView import addDirectoryViews ps = getToolByName(p, 'portal_skins') addDirectoryViews(ps, 'skins', globals()) addDirectoryViews(ps, 'skins', topic_globals) ps.manage_addProduct['OFSP'].manage_addFolder(id='custom') ps.addSkinSelection('Basic', 'custom, zpt_topic, zpt_content, zpt_generic,' + 'zpt_control, topic, content, generic, control, Images', make_default=1) ps.addSkinSelection('Nouvelle', 'nouvelle, custom, topic, content, generic, control, Images') ps.addSkinSelection('No CSS', 'no_css, custom, topic, content, generic, control, Images') p.setupCurrentSkin() def setupTypes(self, p, initial_types=factory_type_information): tool = getToolByName(p, 'portal_types', None) if tool is None: return for t in initial_types: cfm = apply(ContentFactoryMetadata, (), t) tool._setObject(t['id'], cfm) def setupMimetypes(self, p): p.manage_addProduct[ 'CMFCore' ].manage_addRegistry() reg = p.content_type_registry reg.addPredicate( 'link', 'extension' ) reg.getPredicate( 'link' ).edit( extensions="url, link" ) reg.assignTypeName( 'link', 'Link' ) reg.addPredicate( 'news', 'extension' ) reg.getPredicate( 'news' ).edit( extensions="news" ) reg.assignTypeName( 'news', 'News Item' ) reg.addPredicate( 'document', 'major_minor' ) reg.getPredicate( 'document' ).edit( major="text", minor="" ) reg.assignTypeName( 'document', 'Document' ) reg.addPredicate( 'image', 'major_minor' ) reg.getPredicate( 'image' ).edit( major="image", minor="" ) reg.assignTypeName( 'image', 'Image' ) reg.addPredicate( 'file', 'major_minor' ) reg.getPredicate( 'file' ).edit( major="application", minor="" ) reg.assignTypeName( 'file', 'File' ) def setupWorkflow(self, p): tool = getToolByName(p, 'portal_workflow', None) if tool is None: return from DefaultWorkflow import DefaultWorkflowDefinition id = 'default_workflow' tool._setObject(id, DefaultWorkflowDefinition(id)) # These objects don't participate in workflow by default. tool.setChainForPortalTypes( ( 'Folder', 'Topic' ), () ) def setup(self, p, create_userfolder): self.setupTools(p) self.setupMailHost(p) if int(create_userfolder) != 0: self.setupUserFolder(p) self.setupCookieAuth(p) self.setupMembersFolder(p) self.setupRoles(p) self.setupPermissions(p) self.setupDefaultSkins(p) # SkinnedFolders are only for customization; # they aren't a default type. default_types = tuple( filter( lambda x: x['id'] != 'Skinned Folder' , factory_type_information ) ) self.setupTypes(p, default_types ) self.setupTypes(p, PortalFolder.factory_type_information) self.setupTypes(p, Topic.factory_type_information) self.setupMimetypes(p) self.setupWorkflow(p) def create(self, parent, id, create_userfolder): id = str(id) portal = self.klass(id=id) parent._setObject(id, portal) # Return the fully wrapped object. p = parent.this()._getOb(id) self.setup(p, create_userfolder) return p def setupDefaultProperties(self, p, title, description, email_from_address, email_from_name, validate_email, ): p._setProperty('email_from_address', email_from_address, 'string') p._setProperty('email_from_name', email_from_name, 'string') p._setProperty('validate_email', validate_email and 1 or 0, 'boolean') p.title = title p.description = description manage_addCMFSiteForm = Globals.HTMLFile('dtml/addPortal', globals()) manage_addCMFSiteForm.__name__ = 'addPortal' def manage_addCMFSite(self, id, title='Portal', description='', create_userfolder=1, email_from_address='postmaster@localhost', email_from_name='Portal Administrator', validate_email=0, RESPONSE=None): ''' Adds a portal instance. ''' gen = PortalGenerator() from string import strip id = strip(id) p = gen.create(self, id, create_userfolder) gen.setupDefaultProperties(p, title, description, email_from_address, email_from_name, validate_email) if RESPONSE is not None: RESPONSE.redirect(p.absolute_url() + '/finish_portal_construction') CMF-1.3/CMFDefault/PropertiesTool.py0100644000076500007650000000630007522303413017155 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ CMFDefault portal_properties tool. $Id: PropertiesTool.py,v 1.6.18.1 2025/08/01 19:07:55 tseaver Exp $ """ from Products.CMFCore.utils import UniqueObject from OFS.SimpleItem import SimpleItem from Acquisition import aq_inner, aq_parent from Globals import InitializeClass, DTMLFile from AccessControl import ClassSecurityInfo from Products.CMFCore.ActionProviderBase import ActionProviderBase from Products.CMFCore.ActionInformation import ActionInformation from Products.CMFCore.Expression import Expression from Products.CMFCore import CMFCorePermissions from utils import _dtmldir class PropertiesTool(UniqueObject, SimpleItem, ActionProviderBase): id = 'portal_properties' meta_type = 'Default Properties Tool' _actions = [ActionInformation(id='configPortal' , title='Reconfigure Portal' , description='Reconfigure the portal' , action=Expression( text='string: ${portal_url}/reconfig_form') , permissions=(CMFCorePermissions.ManagePortal,) , category='global' , condition=None , visible=1 )] security = ClassSecurityInfo() manage_options = ( ActionProviderBase.manage_options + ({ 'label' : 'Overview', 'action' : 'manage_overview' } , ) + SimpleItem.manage_options ) # # ZMI methods # security.declareProtected( CMFCorePermissions.ManagePortal , 'manage_overview' ) manage_overview = DTMLFile( 'explainPropertiesTool', _dtmldir ) # # 'portal_properties' interface methods # security.declarePrivate('listActions') def listActions(self, info=None): """ Return actions provided by tool. """ return self._actions security.declareProtected( CMFCorePermissions.ManagePortal , 'editProperties' ) def editProperties(self, props): '''Change portal settings''' aq_parent(aq_inner(self)).manage_changeProperties(props) self.MailHost.smtp_host = props['smtp_server'] if hasattr(self, 'propertysheets'): ps = self.propertysheets if hasattr(ps, 'props'): ps.props.manage_changeProperties(props) def title(self): return self.aq_inner.aq_parent.title def smtp_server(self): return self.MailHost.smtp_host InitializeClass(PropertiesTool) CMF-1.3/CMFDefault/README.txt0100644000076500007650000000037707245471210015322 0ustar tseavertseaverCMFBasic This product declares basic content objects and provides default implementation of some of the framework services for the Zope Content Management Framework (CMF). Please see the CMF "dogbowl site":http://cmf.zope.org, for more details. CMF-1.3/CMFDefault/RegistrationTool.py0100644000076500007650000001674207507345743017525 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """CMFDefault portal_registration tool. $Id: RegistrationTool.py,v 1.13 2025/06/29 15:01:55 efge Exp $ """ from Globals import InitializeClass, DTMLFile from AccessControl import ClassSecurityInfo from Products.CMFCore.interfaces.portal_registration import portal_registration from Products.CMFCore.utils import UniqueObject from Products.CMFCore.utils import _checkPermission from Products.CMFCore.utils import getToolByName from Products.CMFCore.ActionInformation import ActionInformation from Products.CMFCore.Expression import Expression from Products.CMFCore.ActionProviderBase import ActionProviderBase from Products.CMFCore.RegistrationTool import RegistrationTool from Products.CMFCore.CMFCorePermissions import AddPortalMember from Products.CMFCore.CMFCorePermissions import ManagePortal from utils import _dtmldir class RegistrationTool (RegistrationTool, ActionProviderBase): """ Manage through-the-web signup policies. """ __implements__ = ( portal_registration, ) meta_type = 'Default Registration Tool' _actions = [ ActionInformation( id='join' , title='Join' , description='Click here to Join' , action=Expression( text='string: ${portal_url}/join_form') , permissions=(AddPortalMember,) , category='user' , condition=Expression(text='not: member') , visible=1 ) ] security = ClassSecurityInfo() # # ZMI methods # security.declareProtected( ManagePortal, 'manage_overview' ) manage_options = ( ActionProviderBase.manage_options + ( { 'label' : 'Overview' , 'action' : 'manage_overview' } , ) ) manage_overview = DTMLFile( 'explainRegistrationTool', _dtmldir ) # # ActionProvider interface # security.declarePublic('listActions') def listActions(self, info=None): """ Return actions provided via tool. """ return self._actions # # 'portal_registration' interface # security.declarePublic( 'testPasswordValidity' ) def testPasswordValidity(self, password, confirm=None): """ Verify that the password satisfies the portal's requirements. o If the password is valid, return None. o If not, return a string explaining why. """ if len(password) < 5 and not _checkPermission('Manage portal', self): return 'Your password must contain at least 5 characters.' if confirm is not None and confirm != password: return ( 'Your password and confirmation did not match. ' + 'Please try again.' ) return None security.declarePublic( 'testPropertiesValidity' ) def testPropertiesValidity(self, props, member=None): """ Verify that the properties supplied satisfy portal's requirements. o If the properties are valid, return None. o If not, return a string explaining why. """ if member is None: # New member. username = props.get('username', '') if not username: return 'You must enter a valid name.' if not self.isMemberIdAllowed(username): raise ('The login name you selected is already ' 'in use or is not valid. Please choose another.') if not props.get('email'): return 'You must enter a valid email address.' else: # Existing member. # Not allowed to clear an existing non-empty email. if (member.getProperty('email') and not props.get('email', 'NoPropIsOk')): return 'You must enter a valid email address.' return None security.declarePublic( 'mailPassword' ) def mailPassword(self, forgotten_userid, REQUEST): """ Email a forgotten password to a member. o Raise an exception if user ID is not found. """ membership = getToolByName(self, 'portal_membership') member = membership.getMemberById(forgotten_userid) if member is None: raise 'NotFound', 'The username you entered could not be found.' # Rather than have the template try to use the mailhost, we will # render the message ourselves and send it from here (where we # don't need to worry about 'UseMailHost' permissions). mail_text = self.mail_password_template( self , REQUEST , member=member , password=member.getPassword() ) host = self.MailHost host.send( mail_text ) return self.mail_password_response( self, REQUEST ) security.declarePublic( 'registeredNotify' ) def registeredNotify( self, new_member_id ): """ Handle mailing the registration / welcome message. """ membership = getToolByName( self, 'portal_membership' ) member = membership.getMemberById( new_member_id ) if member is None: raise 'NotFound', 'The username you entered could not be found.' password = member.getPassword() # Rather than have the template try to use the mailhost, we will # render the message ourselves and send it from here (where we # don't need to worry about 'UseMailHost' permissions). mail_text = self.registered_notify_template( self , self.REQUEST , member=member , password=password ) host = self.MailHost host.send( mail_text ) return self.mail_password_response( self, self.REQUEST ) security.declareProtected(ManagePortal, 'editMember') def editMember( self , member_id , properties=None , password=None , roles=None , domains=None ): """ Edit a user's properties and security settings o Checks should be done before this method is called using testPropertiesValidity and testPasswordValidity """ mtool = getToolByName(self, 'portal_membership') member = mtool.getMemberById(member_id) member.setMemberProperties(properties) member.setSecurityProfile(password,roles,domains) return member InitializeClass(RegistrationTool) CMF-1.3/CMFDefault/SkinnedFolder.py0100644000076500007650000001043207522136535016724 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Allow the "view" of a folder to be skinned by type. """ from Products.CMFCore.PortalFolder import PortalFolder from Products.CMFCore.utils import getToolByName from Products.CMFCore import CMFCorePermissions from AccessControl import ClassSecurityInfo, Owned from Globals import InitializeClass from ComputedAttribute import ComputedAttribute from Products.CMFCore.utils import _getViewFor from Products.CMFCore.CMFCatalogAware import CMFCatalogAware from Acquisition import aq_base factory_type_information = ( { 'id' : 'Skinned Folder' , 'meta_type' : 'Skinned Folder' , 'description' : """\ Skinned folders can define custom 'view' actions.""" , 'icon' : 'folder_icon.gif' , 'product' : 'CMFDefault' , 'factory' : 'addSkinnedFolder' , 'filter_content_types' : 0 , 'immediate_view' : 'folder_edit_form' , 'actions' : ( { 'id' : 'view' , 'name' : 'View' , 'action' : '' , 'permissions' : (CMFCorePermissions.View,) , 'category' : 'folder' } , { 'id' : 'edit' , 'name' : 'Edit' , 'action' : 'folder_edit_form' , 'permissions' : (CMFCorePermissions.ManageProperties,) , 'category' : 'folder' } , { 'id' : 'foldercontents' , 'name' : 'Folder contents' , 'action' : 'folder_contents' , 'permissions' : (CMFCorePermissions.ListFolderContents,) , 'category' : 'folder' } ) } , ) class SkinnedFolder(CMFCatalogAware, PortalFolder): """ """ meta_type = 'Skinned Folder' security = ClassSecurityInfo() manage_options = PortalFolder.manage_options def __call__(self): ''' Invokes the default view. ''' view = _getViewFor(self) if getattr(aq_base(view), 'isDocTemp', 0): return apply(view, (self, self.REQUEST)) else: return view() security.declareProtected( CMFCorePermissions.View, 'view' ) view = __call__ index_html = None # This special value informs ZPublisher to use __call__ security.declareProtected( CMFCorePermissions.View, 'Creator' ) def Creator( self ): """ Return the ID of our owner. """ return self.getOwner( info=1 )[1] # We derive from CMFCatalogAware first, so we are cataloged too. InitializeClass( SkinnedFolder ) def addSkinnedFolder( self, id, title='', description='', REQUEST=None ): """ """ sf = SkinnedFolder( id, title ) sf.description = description self._setObject( id, sf ) sf = self._getOb( id ) if REQUEST is not None: REQUEST['RESPONSE'].redirect( sf.absolute_url() + '/manage_main' ) CMF-1.3/CMFDefault/SyndicationInfo.py0100644000076500007650000000024107301312112017250 0ustar tseavertseaverfrom OFS.SimpleItem import SimpleItem class SyndicationInformation(SimpleItem): id='syndication_information' meta_type='SyndicationInformation' CMF-1.3/CMFDefault/SyndicationTool.py0100644000076500007650000003332407506742761017332 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ CMFDefault portal_syndication tool. Manage outbound RSS syndication of folder content. """ import os import string from Globals import HTMLFile, package_home, InitializeClass from AccessControl import ClassSecurityInfo, SecurityManagement from Acquisition import aq_base, aq_inner, aq_parent from DateTime import DateTime from OFS.SimpleItem import SimpleItem from Products.CMFCore.utils import UniqueObject from Products.CMFCore.utils import _checkPermission from Products.CMFCore.CMFCorePermissions import ManagePortal from Products.CMFCore.CMFCorePermissions import ManageProperties from Products.CMFCore.CMFCorePermissions import AccessContentsInformation from Products.CMFCore.ActionProviderBase import ActionProviderBase from Products.CMFCore.ActionInformation import ActionInformation from Products.CMFCore.Expression import Expression from Products.CMFCore.PortalFolder import PortalFolder from SyndicationInfo import SyndicationInformation _dtmldir = os.path.join( package_home( globals() ), 'dtml' ) class SyndicationTool (UniqueObject, SimpleItem, ActionProviderBase): """ The syndication tool manages the site-wide policy for syndication of folder content as RSS. """ id = 'portal_syndication' meta_type = 'Default Syndication Tool' _actions = [ ActionInformation( id='syndication' , title='Syndication' , action=Expression( text='string: ${folder_url}/synPropertiesForm') , condition=Expression( text='python: folder is object') , permissions=(ManageProperties,) , category='folder' , visible=1 ) ] security = ClassSecurityInfo() #Default Sitewide Values isAllowed = 0 syUpdatePeriod = 'daily' syUpdateFrequency = 1 syUpdateBase = DateTime() max_items = 15 #ZMI Methods manage_options = ( ActionProviderBase.manage_options + ( { 'label' : 'Overview' , 'action' : 'overview' , 'help' : ( 'CMFDefault' , 'Syndication-Tool_Overview.stx' ) } ,{ 'label' : 'Properties' , 'action' : 'propertiesForm' , 'help' : ( 'CMFDefault' , 'Syndication-Tool_Properties.stx' ) } ,{ 'label' : 'Policies' , 'action' : 'policiesForm' , 'help' : ( 'CMFDefault' , 'Syndication-Tool_Policies.stx' ) } ,{ 'label' : 'Reports' , 'action' : 'reportForm' , 'help' : ( 'CMFDefault' , 'Syndication-Tool_Reporting.stx' ) } ) ) security.declareProtected(ManagePortal, 'overview') overview = HTMLFile('synOverview', _dtmldir) security.declareProtected(ManagePortal, \ 'propertiesForm') propertiesForm = HTMLFile('synProps', _dtmldir) security.declareProtected(ManagePortal, 'policiesForm') policiesForm = HTMLFile('synPolicies', _dtmldir) security.declareProtected(ManagePortal, 'reportForm') reportForm = HTMLFile('synReports', _dtmldir) security.declarePrivate('listActions') def listActions(self, info=None): """ Return actions provided by tool """ return self._actions security.declareProtected(ManagePortal, 'editProperties') def editProperties( self , updatePeriod=None , updateFrequency=None , updateBase=None , isAllowed=None , max_items=None , REQUEST=None ): """ Edit the properties for the SystemWide defaults on the SyndicationTool. """ if isAllowed is not None: self.isAllowed = isAllowed if updatePeriod: self.syUpdatePeriod = updatePeriod else: try: del self.syUpdatePeriod except KeyError: pass if updateFrequency: self.syUpdateFrequency = updateFrequency else: try: del self.syUpdateFrequency except KeyError: pass if updateBase: if type( updateBase ) is type( '' ): updateBase = DateTime( updateBase ) self.syUpdateBase = updateBase else: try: del self.syUpdateBase except KeyError: pass if max_items: self.max_items = max_items else: try: del self.max_items except KeyError: pass if REQUEST is not None: REQUEST['RESPONSE'].redirect( self.absolute_url() + '/propertiesForm' + '?manage_tabs_message=Tool+Updated.' ) security.declarePublic( 'editSyInformationProperties' ) def editSyInformationProperties( self , obj , updatePeriod=None , updateFrequency=None , updateBase=None , max_items=None , REQUEST=None ): """ Edit syndication properties for the obj being passed in. These are held on the syndication_information object. Not Sitewide Properties. """ if not _checkPermission( ManageProperties, obj ): raise Unauthorized syInfo = getattr(obj, 'syndication_information', None) if syInfo is None: raise 'Syndication is Disabled' if updatePeriod: syInfo.syUpdatePeriod = updatePeriod else: syInfo.syUpdatePeriod = self.syUpdatePeriod if updateFrequency: syInfo.syUpdateFrequency = updateFrequency else: syInfo.syUpdateFrequency = self.syUpdateFrequency if updateBase: syInfo.syUpdateBase = updateBase else: syInfo.syUpdateBase = self.syUpdateBase if max_items: syInfo.max_items = max_items else: syInfo.max_items = self.max_items security.declarePublic('enableSyndication') def enableSyndication(self, obj): """ Enable syndication for the obj """ if not self.isSiteSyndicationAllowed(): raise 'Syndication is Disabled' if hasattr(aq_base(obj), 'syndication_information'): raise 'Syndication Information Exists' syInfo = SyndicationInformation() obj._setObject('syndication_information', syInfo) syInfo = obj._getOb('syndication_information') syInfo.syUpdatePeriod = self.syUpdatePeriod syInfo.syUpdateFrequency = self.syUpdateFrequency syInfo.syUpdateBase = self.syUpdateBase syInfo.max_items = self.max_items syInfo.description = "Channel Description" security.declarePublic('disableSyndication') def disableSyndication(self, obj): """ Disable syndication for the obj; and remove it. """ syInfo = getattr(obj, 'syndication_information', None) if syInfo is None: raise 'This object does not have Syndication Information' obj._delObject('syndication_information') security.declarePublic('getSyndicatableContent') def getSyndicatableContent(self, obj): """ An interface for allowing folderish items to implement an equivalent of PortalFolder.contentValues() """ if hasattr(obj, 'synContentValues'): values = obj.synContentValues() else: values = PortalFolder.contentValues(obj) return values security.declarePublic('buildUpdatePeriods') def buildUpdatePeriods(self): """ Return a list of possible update periods for the xmlns: sy """ updatePeriods = ( ('hourly', 'Hourly') , ('daily', 'Daily') , ('weekly', 'Weekly') , ('monthly', 'Monthly') , ('yearly', 'Yearly') ) return updatePeriods security.declarePublic('isSiteSyndicationAllowed') def isSiteSyndicationAllowed(self): """ Return sitewide syndication policy """ return self.isAllowed security.declarePublic('isSyndicationAllowed') def isSyndicationAllowed(self, obj=None): """ Check whether syndication is enabled for the site. This provides for extending the method to check for whether a particular obj is enabled, allowing for turning on only specific folders for syndication. """ syInfo = getattr(aq_base(obj), 'syndication_information', None) if syInfo is None: return 0 else: return self.isSiteSyndicationAllowed() security.declarePublic('getUpdatePeriod') def getUpdatePeriod( self, obj=None ): """ Return the update period for the RSS syn namespace. This is either on the object being passed or the portal_syndication tool (if a sitewide value or default is set) NOTE: Need to add checks for sitewide policies!!! """ if not self.isSiteSyndicationAllowed(): raise 'Syndication is Not Allowed' if obj is None: return self.syUpdatePeriod syInfo = getattr(obj, 'syndication_information', None) if syInfo is not None: return syInfo.syUpdatePeriod else: return 'Syndication is Not Allowed' security.declarePublic('getUpdateFrequency') def getUpdateFrequency(self, obj=None): """ Return the update frequency (as a positive integer) for the syn namespace. This is either on the object being pass or the portal_syndication tool (if a sitewide value or default is set). Note: Need to add checks for sitewide policies!!! """ if not self.isSiteSyndicationAllowed(): raise 'Syndication is not Allowed' if obj is None: return self.syUpdateFrequency syInfo = getattr(obj, 'syndication_information', None) if syInfo is not None: return syInfo.syUpdateFrequency else: return 'Syndication is not Allowed' security.declarePublic('getUpdateBase') def getUpdateBase(self, obj=None): """ Return the base date to be used with the update frequency and the update period to calculate a publishing schedule. Note: I'm not sure what's best here, creation date, last modified date (of the folder being syndicated) or some arbitrary date. For now, I'm going to build a updateBase time from zopetime and reformat it to meet the W3CDTF. Additionally, sitewide policy checks might have a place here... """ #import pdb; pdb.set_trace() if not self.isSiteSyndicationAllowed(): raise 'Syndication is not Allowed' if obj is None: when = self.syUpdateBase return when.ISO() syInfo = getattr(obj, 'syndication_information', None) if syInfo is not None: when = syInfo.syUpdateBase return when.ISO() else: return 'Syndication is not Allowed' security.declarePublic('getHTML4UpdateBase') def getHTML4UpdateBase(self, obj): """ Return HTML4 formated UpdateBase DateTime """ if not self.isSiteSyndicationAllowed(): raise 'Syndication is not Allowed' if obj is None: when = syUpdateBase return when.HTML4() syInfo = getattr(obj, 'syndication_information', None) if syInfo is not None: when = syInfo.syUpdateBase return when.HTML4() else: return 'Syndication is not Allowed' def getMaxItems(self, obj=None): """ Return the max_items to be displayed in the syndication """ if not self.isSiteSyndicationAllowed(): raise 'Syndication is not Allowed' if obj is None: return self.max_items syInfo = getattr(obj, 'syndication_information', None) if syInfo is not None: return syInfo.max_items else: return 'Syndication is not Allowed' InitializeClass(SyndicationTool) CMF-1.3/CMFDefault/URLTool.py0100644000076500007650000000772307522303413015475 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ CMFDefault portal_url tool. $Id: URLTool.py,v 1.10.6.1 2025/08/01 19:07:55 tseaver Exp $ """ from Products.CMFCore.utils import UniqueObject from OFS.SimpleItem import SimpleItem import string from Acquisition import aq_inner, aq_parent from Globals import InitializeClass, DTMLFile from AccessControl import ClassSecurityInfo from Products.CMFCore.ActionProviderBase import ActionProviderBase from Products.CMFCore.ActionInformation import ActionInformation from Products.CMFCore.Expression import Expression from Products.CMFCore import CMFCorePermissions from utils import _dtmldir class URLTool (UniqueObject, SimpleItem, ActionProviderBase): id = 'portal_url' meta_type = 'Default URL Tool' _actions = [] security = ClassSecurityInfo() security.declareObjectProtected( CMFCorePermissions.View ) manage_options = ( ActionProviderBase.manage_options + ({ 'label' : 'Overview', 'action' : 'manage_overview' } , ) + SimpleItem.manage_options ) # # ZMI methods # security.declareProtected( CMFCorePermissions.ManagePortal , 'manage_overview' ) manage_overview = DTMLFile( 'explainURLTool', _dtmldir ) # # 'portal_url' interface methods # security.declarePublic( '__call__' ) def __call__(self, relative=0, *args, **kw): ''' Returns the absolute URL of the portal. ''' return aq_parent(aq_inner(self)).absolute_url(relative=relative) security.declarePrivate('listActions') def listActions(self, info=None): """ Return a list of actions provided via the tool """ return self._actions security.declarePublic( 'getPortalObject' ) def getPortalObject( self ): """ Return the portal object itself. """ return self.aq_inner.aq_parent security.declarePublic( 'getRelativeContentPath' ) def getRelativeContentPath( self, content ): """ Return the path (sequence of IDs) for an object, relative to the portal root """ portal_path_length = len(self.aq_inner.aq_parent.getPhysicalPath()) content_location = content.getPhysicalPath() return content_location[portal_path_length:] security.declarePublic( 'getRelativeContentURL' ) def getRelativeContentURL( self, content ): """ Return the URL (slash-separated string) for an object, relative to the portal root """ return string.join( self.getRelativeContentPath( content ), '/' ) security.declarePublic( 'getRelativeUrl' ) def getRelativeUrl(self, content): """ Returns a URL for an object that is relative to the portal root. This is helpful for virtual hosting situations. """ portal_path_length = len(self.aq_inner.aq_parent.getPhysicalPath()) content_location = content.getPhysicalPath() rel_path = content_location[portal_path_length:] return string.join(rel_path, '/') security.declarePublic( 'getPortalPath' ) def getPortalPath(self): """ Returns the portal object's URL without the server URL component """ return string.join(self.aq_inner.aq_parent.getPhysicalPath(), '/') InitializeClass(URLTool) CMF-1.3/CMFDefault/__init__.py0100644000076500007650000001210107523123734015725 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ """ from AccessControl import ModuleSecurityInfo prod_security = ModuleSecurityInfo( 'Products' ) prod_security.declarePublic( 'CMFDefault' ) security = ModuleSecurityInfo( 'Products.CMFDefault' ) security.declarePublic( 'utils' ) import Portal import Document, Link, NewsItem, File, Image, Favorite, SkinnedFolder import DiscussionItem import PropertiesTool, MembershipTool, MetadataTool import RegistrationTool, URLTool, DublinCore, DiscussionTool import SyndicationTool from Products.CMFCore import utils from Products.CMFCore.CMFCorePermissions import AddPortalContent import Products.CMFCore from Products.CMFCore.DirectoryView import registerDirectory import DefaultWorkflow # Old name that some third-party packages may need. ADD_CONTENT_PERMISSION = AddPortalContent #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # N.B.: The following symbol controls whether we "inject" the # content types which formerly lived in CMFCore back into # it. While it is initially true (to allow existing portal # content to load), in a future release it will be set to # false; the behavior it governs will eventually be removed # altogether. YOU HAVE BEEN WARNED!!! #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! SUPPLY_DEPRECATED_PTK_BASE_ALIASES = 0 if SUPPLY_DEPRECATED_PTK_BASE_ALIASES: # Get the old module names aliased into sys.modules... __module_aliases__ = ( ( 'Products.PTKBase.Document', Document ) , ( 'Products.PTKBase.File', File ) , ( 'Products.PTKBase.Image', Image ) , ( 'Products.PTKBase.Link', Link ) , ( 'Products.PTKBase.NewsItem', NewsItem ) ) # ...and make sure we can find them in PTKBase when we do # 'manage_migrate_content()'. Products.PTKBase.Document = Document Products.PTKBase.File = File Products.PTKBase.Image = Image Products.PTKBase.Link = Link Products.PTKBase.NewsItem = NewsItem contentClasses = ( Document.Document , File.File , Image.Image , Link.Link , Favorite.Favorite , NewsItem.NewsItem , SkinnedFolder.SkinnedFolder ) contentConstructors = ( Document.addDocument , File.addFile , Image.addImage , Link.addLink , Favorite.addFavorite , NewsItem.addNewsItem , SkinnedFolder.addSkinnedFolder ) bases = ( ( Portal.CMFSite , DublinCore.DefaultDublinCoreImpl , DiscussionItem.DiscussionItem ) + contentClasses ) tools = ( DiscussionTool.DiscussionTool , MembershipTool.MembershipTool , RegistrationTool.RegistrationTool , PropertiesTool.PropertiesTool , URLTool.URLTool , MetadataTool.MetadataTool , SyndicationTool.SyndicationTool ) import sys this_module = sys.modules[ __name__ ] z_bases = utils.initializeBasesPhase1( bases, this_module ) z_tool_bases = utils.initializeBasesPhase1( tools, this_module ) cmfdefault_globals=globals() # Make the skins available as DirectoryViews. registerDirectory('skins', globals()) registerDirectory('help', globals()) def initialize( context ): utils.initializeBasesPhase2( z_bases, context ) utils.initializeBasesPhase2( z_tool_bases, context ) utils.ToolInit('CMFDefault Tool', tools=tools, product_name='CMFDefault', icon='tool.gif', ).initialize( context ) utils.ContentInit( 'CMFDefault Content' , content_types=contentClasses , permission=AddPortalContent , extra_constructors=contentConstructors , fti=Portal.factory_type_information ).initialize( context ) context.registerClass(Portal.CMFSite, constructors=(Portal.manage_addCMFSiteForm, Portal.manage_addCMFSite, ), icon='portal.gif') utils.registerIcon(DefaultWorkflow.DefaultWorkflowDefinition, 'images/workflow.gif', globals()) context.registerHelp() context.registerHelpTitle('CMF Default Help') CMF-1.3/CMFDefault/portal.gif0100644000076500007650000000043107245471210015603 0ustar tseavertseaverGIF89a33\ A39;;b3f)R3fBBh'Z/^)V~sOOrFsMf̽X!,'~@ Qhā$ rL<H`tB!i: xP(3T @ Be`|&Ak.  ah   =Q"<>D 9# j246H- #!;CMF-1.3/CMFDefault/tool.gif0100644000076500007650000000024607245471210015263 0ustar tseavertseaverGIF89aVfHT^nM^i}x9CrCN~1|W;CMF-1.3/CMFDefault/utils.py0100644000076500007650000002330307516644455015346 0ustar tseavertseaver""" Utility functions. """ from sgmllib import SGMLParser import re import os from Globals import package_home from AccessControl import ModuleSecurityInfo security = ModuleSecurityInfo( 'Products.CMFDefault.utils' ) security.declarePublic( 'formatRFC822Headers' , 'parseHeadersBody' , 'semi_split' , 'comma_split' , 'seq_strip' , 'tuplize' , 'scrubHTML' , 'isHTMLSafe' , 'bodyfinder' , 'html_headcheck' ) security.declarePrivate( '_dtmldir' , '_bodyre' , '_endbodyre' , '_htfinder' ) _dtmldir = os.path.join( package_home( globals() ), 'dtml' ) def formatRFC822Headers( headers ): """ Convert the key-value pairs in 'headers' to valid RFC822-style headers, including adding leading whitespace to elements which contain newlines in order to preserve continuation-line semantics. """ munged = [] linesplit = re.compile( r'[\n\r]+?' ) for key, value in headers: vallines = linesplit.split( value ) munged.append( '%s: %s' % ( key, '\r\n '.join( vallines ) ) ) return '\r\n'.join( munged ) def parseHeadersBody( body, headers=None, rc=re.compile( r'\n|\r\n' ) ): """ Parse any leading 'RFC-822'-ish headers from an uploaded document, returning a dictionary containing the headers and the stripped body. E.g.:: Title: Some title Creator: Tres Seaver Format: text/plain X-Text-Format: structured Overview This document ..... First Section .... would be returned as:: { 'Title' : 'Some title' , 'Creator' : 'Tres Seaver' , 'Format' : 'text/plain' , 'text_format': 'structured' } as the headers, plus the body, starting with 'Overview' as the first line (the intervening blank line is a separator). Allow passing initial dictionary as headers. """ # Split the lines apart, taking into account Mac|Unix|Windows endings lines = rc.split(body) i = 0 if headers is None: headers = {} else: headers = headers.copy() hdrlist = [] for line in lines: if not line.strip(): break tokens = line.split( ': ' ) if len( tokens ) > 1: hdrlist.append( ( tokens[0], ': '.join( tokens[1:] ) ) ) elif i == 0: return headers, body # no headers, just return those passed in. else: # continuation last, hdrlist = hdrlist[ -1 ], hdrlist[ :-1 ] hdrlist.append( ( last[ 0 ] , '\n'.join( ( last[1], line.lstrip() ) ) ) ) i = i + 1 for hdr in hdrlist: headers[ hdr[0] ] = hdr[ 1 ] return headers, '\n'.join( lines[ i+1: ] ) def semi_split(s): """ Split 's' on semicolons. """ return map(lambda x: x.strip(), s.split( ';' ) ) def comma_split(s): """ Split 's' on commas. """ return map(lambda x: x.strip(), s.split( ',') ) def seq_strip (seq, stripper=lambda x: x.strip() ): """ Strip a sequence of strings. """ if type( seq ) == type( [] ): return map( stripper, seq ) if type( seq ) == type( () ): return tuple( map( stripper, seq ) ) raise ValueError, "%s of unsupported sequencetype %s" % ( seq, type( seq ) ) def tuplize( valueName, value, splitter=lambda x: x.strip() ): if type( value ) == type( () ): return seq_strip( value ) if type( value ) == type( [] ): return seq_strip( tuple( value ) ) if type( value ) == type( '' ): return seq_strip( tuple( splitter( value ) ) ) raise ValueError, "%s of unsupported type" % valueName class SimpleHTMLParser( SGMLParser ): #from htmlentitydefs import entitydefs def __init__( self, verbose=0 ): SGMLParser.__init__( self, verbose ) self.savedata = None self.title = '' self.metatags = {} self.body = '' def handle_data( self, data ): if self.savedata is not None: self.savedata = self.savedata + data def handle_charref( self, ref ): self.handle_data( "&#%s;" % ref ) def handle_entityref( self, ref ): self.handle_data( "&%s;" % ref ) def save_bgn( self ): self.savedata = '' def save_end( self ): data = self.savedata self.savedata = None return data def start_title( self, attrs ): self.save_bgn() def end_title( self ): self.title = self.save_end() def do_meta( self, attrs ): name = '' content = '' for attrname, value in attrs: value = value.strip() if attrname == "name": name = value.capitalize() if attrname == "content": content = value if name: self.metatags[ name ] = content def unknown_startag( self, tag, attrs ): self.setliteral() def unknown_endtag( self, tag ): self.setliteral() # # HTML cleaning code # # These are the HTML tags that we will leave intact VALID_TAGS = { 'a' : 1 , 'b' : 1 , 'base' : 1 , 'blockquote' : 1 , 'body' : 1 , 'br' : 1 , 'caption' : 1 , 'cite' : 1 , 'code' : 1 , 'div' : 1 , 'dl' : 1 , 'dt' : 1 , 'dd' : 1 , 'em' : 1 , 'h1' : 1 , 'h2' : 1 , 'h3' : 1 , 'h4' : 1 , 'h5' : 1 , 'h6' : 1 , 'head' : 1 , 'hr' : 1 , 'html' : 1 , 'i' : 1 , 'img' : 1 , 'kbd' : 1 , 'li' : 1 # , 'link' : 1 type="script" hoses us , 'meta' : 1 , 'ol' : 1 , 'p' : 1 , 'pre' : 1 , 'span' : 1 , 'strong' : 1 , 'table' : 1 , 'tbody' : 1 , 'td' : 1 , 'th' : 1 , 'title' : 1 , 'tr' : 1 , 'tt' : 1 , 'ul' : 1 } NASTY_TAGS = { 'script' : 1 , 'object' : 1 , 'embed' : 1 , 'applet' : 1 } class IllegalHTML( ValueError ): pass class StrippingParser( SGMLParser ): """ Pass only allowed tags; raise exception for known-bad. """ from htmlentitydefs import entitydefs # replace entitydefs from sgmllib def __init__( self ): SGMLParser.__init__( self ) self.result = "" def handle_data( self, data ): if data: self.result = self.result + data def handle_charref( self, name ): self.result = "%s&#%s;" % ( self.result, name ) def handle_entityref(self, name): if self.entitydefs.has_key(name): x = ';' else: # this breaks unstandard entities that end with ';' x = '' self.result = "%s&%s%s" % (self.result, name, x) def unknown_starttag(self, tag, attrs): """ Delete all tags except for legal ones. """ if VALID_TAGS.get( tag ): self.result = self.result + '<' + tag for k, v in attrs: if k.lower().startswith( 'on' ): raise IllegalHTML, 'Javascipt event "%s" not allowed.' % k if v.lower().startswith( 'javascript:' ): raise IllegalHTML, 'Javascipt URI "%s" not allowed.' % v self.result = '%s %s="%s"' % (self.result, k, v) endTag = '' % tag self.result = self.result + '>' elif NASTY_TAGS.get( tag ): raise IllegalHTML, 'Dynamic tag "%s" not allowed.' % tag else: pass # omit tag def unknown_endtag(self, tag): if VALID_TAGS.get( tag ): self.result = "%s" % (self.result, tag) remTag = '' % tag def scrubHTML( html ): """ Strip illegal HTML tags from string text. """ parser = StrippingParser() parser.feed( html ) parser.close() return parser.result def isHTMLSafe( html ): """ Would current HTML be permitted to be saved? """ try: scrubHTML( html ) except IllegalHTML: return 0 else: return 1 _bodyre = re.compile( r'^\s*', re.DOTALL | re.I ) _endbodyre = re.compile( r' &dtml-form_title;

    &dtml-form_title;

    Enter an ID and click the button below to create a new CMF site.
    Id
    Title
    Membership source
    Description
    CMF-1.3/CMFDefault/dtml/discussionEdit.dtml0100644000076500007650000000242107264134572020437 0ustar tseavertseaver

    &dtml-message;


    Edit &dtml-getId;

    Title
    Description
    Format checked id="cb_structuredtext" /> checked id="cb_html" />
    Upload File
    Content

    CMF-1.3/CMFDefault/dtml/discussionView.dtml0100644000076500007650000000045207303516016020455 0ustar tseavertseaver
    CMF-1.3/CMFDefault/dtml/explainDiscussionTool.dtml0100644000076500007650000000067507276657567022042 0ustar tseavertseaver

    portal_discussion Tool

    This tool embodies the policies for a given CMFSite concerning the storage mechanisms for discussion about content. In particular, this version implements the policy that replies to a piece of content are stored on a special "talkback" subobject of the content, and are threaded by it.

    CMF-1.3/CMFDefault/dtml/explainMembershipTool.dtml0100644000076500007650000000054707276657567022010 0ustar tseavertseaver

    portal_membership Tool

    This tool encapsulates the authentication mechanism (the user folder) in a more usable, "member-centric" interface, insulating other CMF objects from changes or idiosyncracies in the particular member folder.

    CMF-1.3/CMFDefault/dtml/explainMetadataTool.dtml0100644000076500007650000000044507276657567021432 0ustar tseavertseaver

    portal_metadata Tool

    This tool embodies site-wide policies concerning required metadata for each content type, as well as default values and controlled vocabularies.

    CMF-1.3/CMFDefault/dtml/explainPropertiesTool.dtml0100644000076500007650000000034007276657567022040 0ustar tseavertseaver

    portal_properties Tool

    This tool provides a common interface for accessing "portal-wide" properties.

    CMF-1.3/CMFDefault/dtml/explainRegistrationTool.dtml0100644000076500007650000000051307276657567022360 0ustar tseavertseaver

    portal_registration Tool

    This tool embodies the policies of the CMFSite with respect to joining / adding a new member; in particular, it sets the default policies for allowable member IDs and passwords.

    CMF-1.3/CMFDefault/dtml/explainURLTool.dtml0100644000076500007650000000043307276657567020351 0ustar tseavertseaver

    portal_url Tool

    This tool provides a common mechanism for finding the "root" object of a CMFSite, and for computing paths to objects relative to that root.

    CMF-1.3/CMFDefault/dtml/metadataElementPolicies.dtml0100644000076500007650000001123107511373501022217 0ustar tseavertseaver

    Update Element Metadata Policies

    Element: &dtml-key;   &dtml-key;  

    Content type '"> &dtml-typ; &dtml-typ; (deleted) Required?
    Supply default? Default
    Enforce vocabulary? Vocabulary

    <new type>
    Content type Required?
    Supply default? Default
    Enforce vocabulary? Vocabulary


    CMF-1.3/CMFDefault/dtml/metadataProperties.dtml0100644000076500007650000000315707331423514021302 0ustar tseavertseaver

    Update Metadata Tool Properties

    Publisher:

    Add Metadata Element

    Element:
    Multi-valued?

    Remove Metadata Element

    Element:

    CMF-1.3/CMFDefault/dtml/synOverview.dtml0100644000076500007650000000146407510140244017777 0ustar tseavertseaver &dtml-form_title;

    &dtml-form_title;

    Syndication Tool Overview

    See the online help by clicking the 'Help' link on the Management Forms. To turn on syndication, visit the Properties Tab in the management interface.
    More online documentation is available at: Syndication Administration CMF-1.3/CMFDefault/dtml/synPolicies.dtml0100644000076500007650000000106607301312113017730 0ustar tseavertseaver

    Default Syndication Policies

    Sy Module Policies
    Policies are not yet implemented  

    CMF-1.3/CMFDefault/dtml/synProps.dtml0100644000076500007650000000433007422065414017276 0ustar tseavertseaverportal_syndication properties

    Sitewide Default Syndication Properties

    Sy Module Properties
    Element Default Value
    UpdatePeriod
    UpdateFrequency
    UpdateBase
    Max Syndicated Items

    CMF-1.3/CMFDefault/dtml/synReports.dtml0100644000076500007650000000110507301312113017611 0ustar tseavertseaver

    Sitewide Syndication Reporting Facility

    Syndication Reports
    Reports are not yet implemented  

    CMF-1.3/CMFDefault/dtml/zmi_editDocument.dtml0100644000076500007650000000257707452156370020764 0ustar tseavertseaver

    Edit &dtml-getId;

    Title
    Description
    Format checked /> checked /> checked />
    Upload
    Edit

    CMF-1.3/CMFDefault/dtml/zmi_editLink.dtml0100644000076500007650000000065007416572172020073 0ustar tseavertseaver

    Edit &dtml-getId;

    Remote Url

    CMF-1.3/CMFDefault/dtml/zmi_metadata.dtml0100644000076500007650000000443407276665725020127 0ustar tseavertseaver

    Standard Resource Metadata

    Identifier
    Title
    Description
    Subject Contributors
    Creation Date Last Modified Date
    Effective Date Expiration Date
    Format
    Language
    Rights

    CMF-1.3/CMFDefault/help/0040755000076500007650000000000007524010066014546 5ustar tseavertseaverCMF-1.3/CMFDefault/help/ActorDefinitions.stx0100644000076500007650000000477507360424232020566 0ustar tseavertseaverCMF Actors **Site Manager** -- This actor is responsible for implementing site policies such as security, workflow associations, metadata and syndication policies. The Site Manager is also responsible for the overall organizational structure of the site. See "Actor: Site Manager":Actor_SiteManager. **Membership Manager** -- This actor is responsible for managing who has access to a site (particularly back-end line of business users), and controls the privileges and properties of users. See "Actor: Membership Manager":Actor_MembershipManager. **Site Developer** -- This actor is responsible for implementing new functionality for a site and making changes to existing site capabilities. This is a "programmer" type of role, and users acting the Site Developer capacity are technical people. See "Actor: Site Developer":Actor_SiteDeveloper. **Add-on Developer** -- This actor is responsible for implementing new functionality that is suitable for distribution to one or more sites. ** See "Actor: Add-On Developer":Actor_AddOnDeveloper. **Site Designer** -- The Site Designer is responsible for producing and maintaining the "look and feel" of a site. This includes graphics, layout, navigation and other human factors. See "Actor: Site Designer":Actor_SiteDesigner. **Workflow Designer** -- The Workflow Designer is responsible for defining new workflows and customizing existing workflows to meet business goals. See "Actor: Workflow Designer":Actor_WorkflowDesigner. **Content Creator** -- Content Creators are responsible for producing and maintaining the actual content of a site. See "Actor: Content Creator":Actor_ContentCreator. **Reviewer** -- This actor is responsible for ensuring the quality and correctness of site content. See "Actor: Reviewer":Actor_Reviewer. **Site Visitor** -- A Site Visitor is an "end user" of the site. The visitor may or may not have an identity known to the system. Visitors with a known identity are referred to as "Members" of the site, and often can do more on a site than visitors without a known identity ("Guests"). Member visitors often have a participatory role on the site. Site Visitors have some general goals that are applicable to most sites, but many of the specific goals and expectations of Site Visitors are dependent upon the specific CMF site. See "Actor: Site Visitor":Actor_SiteVisitor. CMF-1.3/CMFDefault/help/Actor_ContentCreator.stx0100644000076500007650000000324607360424232021374 0ustar tseavertseaverContent Creator Goals * Make information available to the end users of the site. - "Create a content object":CreateNewContent o Add Content form with a slightly more involved description of each content type that comes with the CMF. o Next form in succession is the "Standard Resource Metadata" form which is now a separate use case. Since this is a succession of three steps ("Add Content", "Metadata", "Edit") "Define content metadata" use case should be merged in with this one. o "Edit" form. - **XXX** "Define content metadata":ChangeMetadata This should mainly be pointers back to "Create a content object". - "Submit content for publication":SubmitContentForPublication o Brief discussion of workflow with references back to workflow use cases. o Submit for the default workflow * Ensure that information is up-to-date and accurate. - "View personally authored content":ViewMyContent - "Update existing content":ChangeContent - "Remove unneeded content":RemoveContent - "Undo changes to content":UndoChanges * Improve content quality over time using end user feedback. - **XXX** "Make a content object discussable":EnableDiscussion *Note: this is not a per-object option in stock CMF.* * Collaborate with other content creators. - "Give local roles to other users":ManageLocalRoles * Modify content organization to improve maintainability or navigation. - "Add content folders":AddContentFolders - "Move / copy content between folders":MoveCopyContent - "Rename content object":RenameContent CMF-1.3/CMFDefault/help/Actor_MembershipManager.stx0100644000076500007650000000100407360424232022016 0ustar tseavertseaverMembership Manager Goals * Empower many users to collaborate on content production - **XXX** "Add a new member to the site using a standard user folder":AddMemberToUserFolder * Delegate responsibilities to site members - **XXX** "Change member information and abilities":ChangeMemberInformation * Ensure that only appropriate users have access to the site - **XXX** "Browse member roster":BrowseMemberRoster - **XXX** "Remove a member from the site":RemoveMemberFromSite CMF-1.3/CMFDefault/help/Actor_Reviewer.stx0100644000076500007650000000120407360416547020234 0ustar tseavertseaverReviewer Goals * Collaborate with content creators to ensure the quality and timeliness of site content. - "Browse content submitted for review and publication":BrowseSubmittedForReview o Brief description of the Action box, with pointers back to where this is set up in the skin. o Pending lists - "Approve content for publication":ApproveForPublication This should mainly be a pointer back into "Browse content submitted for review and publication" * React quickly to resolve issues with published content. - "Remove content from public site":UnpublishContent CMF-1.3/CMFDefault/help/Actor_SiteDesigner.stx0100644000076500007650000000276507360424232021034 0ustar tseavertseaverSite Designer Goals * Provide an integrated look and feel for site content. - **XXX** "Create new skin for the site":CreateNewSkin o portal_skins Properties tab. Discussion of Layers and their order of precedence. o Creating a new layer. Difference between file system layers vs TTW layers. Don't document actually creating a file system layer, this should likely be a separate use case that refers back to this one. o The theory behind customizing methods, but don't refer to specific layers or specific methods/images. - **XXX** "Modify skin appearance":ChangeSkinLookAndFeel o Brief description of customizing methods. Refer back to "Create new skin for the site". o Document each layer that deals with appearance with a description of each constituent object (method, image, ...) * Give end users an effective way to navigate the site. - **XXX** "Change skin behavior":ChangeSkinBehavior o Brief description of customizing methods. Refer back to "Create new skin for the site description". o Document each layer that deals with behavior with a description of each method. * Keep the site fresh and interesting for end users. - **XXX** "Change the default skin for the site":ChangeDefaultSiteSkin o Brief description of skins. Refer back to other skin use cases. o portal_skin Properties tab, bottom half. Very simple form. CMF-1.3/CMFDefault/help/Actor_SiteManager.stx0100644000076500007650000000211207360424232020630 0ustar tseavertseaverSite Manager Goals * Provide an online collaboration environment for an organization or community. - "Create a CMF site":CreateCMFSite - "Configure CMF site":ConfigureCMFSite * Maintain an overall site structure and organization. - "Create CMF Folder":CreateCMFFolder - "Create CMF Topic":CreateCMFTopic - **XXX** "Configure CMF Topic":ConfigureCMFTopic - **XXX** "Configure what types of content can be created by members":ConfigureAllowedContentTypes * Implement security policies for the site. - **XXX** "Configure security for a content object or folder":ConfigureObjectSecurity * Implement workflow policies for the site. - **XXX** "Associate a workflow with a content type":AssociateWorkflowWithContentType * Implement metadata policies for the site. - **XXX** "Configure required metadata for a content type":ConfigureRequiredMetadata * Implement syndication policies for the site. - **XXX** "Configure what content is released via syndication":ConfigureContentSyndication CMF-1.3/CMFDefault/help/Actor_SiteVisitor.stx0100644000076500007650000000152707360424232020726 0ustar tseavertseaverSite Vistor Goals * Participate in the community or operations of the site - "Become a member of the site":BecomeAMember - "Log into the site":LoginAsMember - "Browse community news":BrowseNewsItems - **XXX** "Submit a news item":SubmitNewsItem o Not a stock use case for anonymous visitors; see "Create Content":CreateNewContent and "Submit Content":SubmitContentForPublication for Content Creator's take. * Quickly find content that is important to him - "Browse the site homepage":BrowseCMFSiteHomepage - **XXX** "Browse a portal topic":BrowsePortalTopic - "Search site for information":SearchCMFSite * Improve relevance and usability of site information - "Configure personalization options":ConfigurePersonalization - "Provide feedback on content":ProvideFeedback CMF-1.3/CMFDefault/help/AddContentFolders.stx0100644000076500007650000000316507360416547020666 0ustar tseavertseaverUse Case: Add content folders Actor - Content Creator Overview As with directories on a filesystem, foldersin a CMF Site allow Content Creators to partition their content into manageable groups. Assumptions - Content Creator has logged into the CMF (see "Login to the Site":LoginAsMember). Procedure 1. Navigate to the folder in which you would like to create sub-folders. 2. In the "Folder contents" view of the folder, select the "New" button. 3. From the list of addable portal types, select "Folder" by clicking the adjacent radio button. Supply an ID [1] for the new folder in the input field at the bottom of the page, and click the "Add" button. 4. The system will create the new folder using the ID you supplied, and present you with a form for editing the folder's properties. 5. Supply appropriate values as follows: **Title** -- a "human-readable" title for the folder. **Description** -- a brief paragraph summarizing the use to which the folder is put. 6. Click the "Change" button. The system will update the folder's metadata using the values you supply. Notes ..[1] Don't confuse the folder's ID with the its Title. ID's cannot contain special characters (e.g., comma, asterisk, brackets, parentheses, etc.) A good practise is not to use spaces in an ID either. The ID is used in the url to reach the folder's content, so any character which is not allowed in a URI is not allowed in the id (see: "URI RFC", http://www.ietf.org/rfc/rfc2396.txt). CMF-1.3/CMFDefault/help/ApproveForPublication.stx0100644000076500007650000000174307360416547021601 0ustar tseavertseaverUse Case: Approve content for publication Actor - Reviewer Overview The Reviewer's job is to enforce the site's policies with respect to the quality and appropriateness of content published by Content Creators. Assumptions - Reviewer has logged into the CMF (see "Login to the Site":LoginAsMember). - Content has been submitted for review (see "Submit content for publication":SubmitContentForPublication). - Reviewer has completed the Use Case: "Browse for content submitted for review and publication":BrowseSubmittedForReview. Procedure 1. Select the item from the list of content pending review. 2. From the actions box, select the 'Publish' link. 3. Enter appropriate comments. 4. Select the Publish this item button. 5. The item has been published. 6. You can now "Browse for content submitted for review and publication":BrowseSubmittedForReview to repeat this process. CMF-1.3/CMFDefault/help/BecomeAMember.stx0100644000076500007650000000335007360416547017743 0ustar tseavertseaverUse Case: Become A Site Member Actor - Site Visitor Overview Becoming a member of a site allows you to access the additional services of the site. Often this includes a personal online work area (your "desktop"), the ability create and submit your own content for publication and the ability to personalize the look and behavior of the site to better meet your needs. Note that different sites have different purposes, and the specific services available to site members depends on the choices of the site administrators. By default, a CMF site provides members with a private "desktop", the ability to create certain types of basic content and the ability to select the visual style of the site that they see. Procedure 1. To become a member of a site, visit the site homepage and click on the "Join" link in the menu located on the left side of the page. 2. Clicking the "Join" link will take you to a form. Complete the fields on the form and click the "Register" button to become a registered member of the site. 3. The exact information required by the registration form will vary from site to site. The form for a default CMF site requires visitors to provide at least a login name, a password and a valid email address to become a member. The default form also gives you an option to have the password you provided at registration time emailed to you for future reference. 4. After submitting the member registration form, you should see a page informing you that you have successfully been registered as a site member. This page also provides a link that you can use to log into the site immediately. CMF-1.3/CMFDefault/help/BrowseCMFSiteHomepage.stx0100644000076500007650000000110707360416547021400 0ustar tseavertseaverUse Case: Browse the site homepage Actor - Site Visitor Assumptions - Site Visitor has already navigated to a page within the CMF site[1]. Procedure 1. Selecting the 'home' link or the site logo from the top navigation bar. 2. Browse the content published there (e.g. 10 most recent News Announcements, etc.) Notes ..[1] Site Visitors may get to the site initially through several mechanisms: - Links on another site, such as a search engine - Mailed URLs - Typing the URL directly CMF-1.3/CMFDefault/help/BrowseNewsItems.stx0100644000076500007650000000163707360416547020426 0ustar tseavertseaverUse Case: Browse community news Actor - Site Visitor Overview Visitors to a CMF Site will typically return to the site after an initial visit only if the site's content was interesting or valuable to them. They will return often to the site only if they perceive that this interesting and valuable content is being frequently updated. This use case deals with the most common "dynamic" feature of a CMF Site: its list of published News Items. Procedure 1. Select the 'news' link from the navigation bar. The system will display the ten most recent News Items, sorted in descending date order. If there are more than ten published News Items, the system will display a link to the next batch at the bottom of the page. 2. Navigate between batches of News Items by clicking the "10 older artcles" and "10 newer articles" links. CMF-1.3/CMFDefault/help/BrowseSubmittedForReview.stx0100644000076500007650000000121707360416547022273 0ustar tseavertseaverUse Case: Browse for content submitted for review and publication Actor - Reviewer Overview The Reviewer's job is to enforce the site's policies with respect to the quality and appropriateness of content published by Content Creators. Assumptions - Reviewer has logged into the CMF (see "Login to the Site":LoginAsMember). - Content has been submitted for review (see "Submit content for publication":SubmitContentForPublication). Procedure 1. From the actions box, select the 'Pending review (x)' link. 2. Browse the list of content which has been submitted for Publication. CMF-1.3/CMFDefault/help/ChangeContent.stx0100644000076500007650000000241107360416547020035 0ustar tseavertseaverUse Case: Update Existing Content Actor - Content Creator Assumptions - Content Creator has logged into the CMF (see "Login to the Site":LoginAsMember). Procedure 1. Navigate to a piece of content you have the permissions to modify. To retrieve a list of the content you have authored, see "View personally authored content":ViewMyContent 2. After selecting the piece of content you wish to update, select 'Edit' from the actions box. 3. The 'Edit' link will take you to the edit form for your particular piece of content you wish to edit: - 'Body Textarea/File Upload' edit form (Documents) - 'Lead-in/Body' edit form (News Item) - 'File Upload' edit form (Files, Images) - 'Simple' edit form (Link, Favorite, Event) Enter the changes you wish to make to the content in the form or browse to changed version of the content on your filesystem as appropriate. 4. Click the change button. The system will save your changes, and display a change notification on the edit form confirming that the change has taken place. 5. To view your changes, click the 'View' link from the actions box and view your content with the changes in place. CMF-1.3/CMFDefault/help/ConfigureCMFSite.stx0100644000076500007650000000655507360416547020426 0ustar tseavertseaverUse Case: Configuring a CMF site Actor - Site Manager Overview The "site configuration form" of a CMF Site object provides a simple way to set and change the sitewide configuration options and policies for a CMF site. Theses options include some of the information that was provided when the CMF Site was created (such as site title and description), as well as other options that were given defaults when the CMF site was created. Assumptions - Site Manager has logged into the CMF site using a user ID wtih the "Change configuration" permission (see "Login to the Site":LoginAsMember). Procedure 1. Site Managers see a "Reconfigure site" link in the actions box. Click the "Reconfigure site" link to bring up the site configuration form. The configuration options available from the site configuration form are: **Site 'From' Name** -- The name to be used as the (apparent) sender when the site generates email. The site may generate email to provide information to new members, or to notify members of various events. The default value for this name is 'Site Administrator'. A value for this field is required in order to send mail from the site. **Site 'From' Address** -- The email address used as the (apparent) return address when the site generates email. The default value for the from address is 'postmaster@localhost'. A value for this field is required in order to send mail from the site. **SMTP Server** -- The address of the SMTP (outgoing mail) server to be used when the site generates email. The default value for the SMTP server address is 'localhost', which presumes that you have an SMTP server running on the same machine as the Zope software. A valid SMTP server address is required in order to send mail from the site. **Site Title** -- The title of the site that appears at the top of all site pages (when using the default site skins). Providing a title is optional, but recommended. **Site Description** -- A short description of the site. This description may be made available with syndicated content and may be used by some of the default user interface elements of the site. Providing a description is optional, but recommended. **Password Policy** -- The password policy configuration option allows you to choose the way that the site handles passwords when members register with the site. If you select "Generate an email member's initial password" the site will randomly generate an initial password that members must use to log into the site and email that password to the address provided by the member. This option may be preferred if you want to verify a prospective member's email address before granting membership to the site. If you select "Allow members to select their initial password" (the default), the site will allow new members to enter their own password at registration time. After making changes to the site configuration options, click the "Change" button to save the changes. CMF-1.3/CMFDefault/help/ConfigurePersonalization.stx0100644000076500007650000000267307360416547022352 0ustar tseavertseaverUse Case: Configure personalization options Actor - Site Visitor Overview One of the benefits of becoming a member of a CMF Site is the ability to create persistent personalizations of the way the site is displayed: in particular, this personalization allows the member to select from among the skins which have been configured by the Site Manager. Assumptions - Site Visitor has logged into the CMF (see "Login to the Site":LoginAsMember). Procedure 1. Click the 'Preferences' link in your top user actions bar. 2. On the personalization form, you have the option of modifying the following: **E-mail address** -- your contact email address, used to send you your password if you forget it. The site administrator may also use this email to contact you when necessary. **Listing status (off/on)** -- determines if you're login name is visible to other members when they select the 'members' link from the navigation bar. **Skin** -- The 'look and feel' skin which is applied around the content of the site. The skin affects your 'view' while navigating the site. 3. Update these preferences as desired and select the change button. 4. The system will save your preferences and redisplay the personalization form with the a message stating that your preferences have been changed. CMF-1.3/CMFDefault/help/CreateCMFFolder.stx0100644000076500007650000000232507360416547020206 0ustar tseavertseaverUse Case: Creating a CMF Folder Actor - Site Manager Overview Folders may used in CMF to help organize content. Folders may contain any kind of content object, including subfolders. Assumptions - Foo Procedure To create a new Folder at a given place in the site heirarchy, navigate to the place where you want to add the new Folder, then click the "Folder Contents" link in the actions box. This will bring up the "desktop" view of the current Folder, listing the content objects and subfolders. In the desktop view, click the "New..." button. You will now see the "Add Content" form. This form provides a list of the kinds of objects you can add at this location (based on the permissions you have), and descriptions of the available objects. Select "Folder" from the listing, enter an id for the new folder in the "id" field located below the listing of available object, and click the "Add" button to add the new Folder. After submitting the add form, you will be taken to the "desktop" view of the newly created Folder. Notes - This use case is not specific to a Site Manager; it is properly a Content Creator use case. CMF-1.3/CMFDefault/help/CreateCMFSite.stx0100644000076500007650000000630407360416547017700 0ustar tseavertseaverUse Case: Create a CMF Site Actor - Site Manager Overview The top-level concept in the CMF is the idea of a "CMF Site". A CMF site is a content-oriented Web site with specific business goals, workflows, collaborations and audiences (content consumers). The "CMF Site" object is used in Zope to represent and manage a CMF Web site. The CMF Site object acts as a container for site components and content, and provides interfaces for configuring the functionality of the site. Assumptions - Site Manager is logged into the Zope Management Interface (ZMI) with a user ID having the "Add CMF Sites" permission at the desired location. Procedure 1. From the ZMI, select "CMF Site" from the add list and click the "Add" button. This will bring up the "Add CMF Site" Web form. The elements on the add form are: **Id** -- the id to be used for the new CMF Site object. This id will appear in URLs to the site and its subobjects. The id field is a required field. **Title** -- the title to be used for the new CMF Site object. The title provides a more human-friendly label for the site object. Providing a title is optional, but recommended. **Membership Source** -- the source of member information to be used by the new CMF Site. The default for this field is "Create a new user folder in the CMF Site". This option will create a new User Folder in the CMF Site to be used as the source of member data. You may also select "I have an existing user folder and want to use it instead". In this case, the CMF Site will draw its member information from a User Folder that already exists in the Zope object heirarchy above the new CMF Site. **Description** -- a short description of the site. This description may be made available with syndicated content and may be used by some of the default user interface elements of the site. Providing a description is optional, but recommended. After completing the Web form, click the "Add" button to create the new CMF Site object. 2. After submitting the form, the right frame of the ZMI should contain an administrative "welcome" page of the new CMF site. The welcome page provides links to: **The site configuration form** -- This form allows you manage sitewide policies and configuration options. This should be your first stop after creating a CMF Site object. **The management interface** -- The Zope management interface (ZMI) for CMF Site objects provides management-level access to the individual components of the site and provides for more advanced configuration options. **The site home page** -- The default homepage of the new CMF site. This is what visitors and members of the site will initially see when they access the site through the Web. 3. Now that the basic CMF Site object has been created, you should visit the site configuration form to continue setting up the new site. CMF-1.3/CMFDefault/help/CreateCMFTopic.stx0100644000076500007650000001043507360416547020052 0ustar tseavertseaverUse Case: Create CMF Topic Actor - Site Manager[1] Overview One of the ways you manage the structure of a CMF site is through the use of CMF Topics. Often a site is comprised of a large amount of content through which visitors are able to navigate. A Topic allows you to create a dynamic view onto the available content enabling visitors to drill down into that content. Within each Topic can be configured a set of Criteria which constrain the list of content that appear when viewing the Topic. Topic Criteria can be based upon any of the data or meta-data that comprise your content. Note that one useful meta-datam on which to base a Topic Criteria is "Subject," which is generally configured to allow a set of categories to be chosen when creating new content. These categories can then be used in a Topic Criteria to enable visitors to view categorized content. A standard pattern is to create a number of Topics which each correspond to a category. Another example of a useful Topic is one which constrains your content by creation or modification date in order to display all recently changed content. Assumptions - Site Manager has logged into the CMF (see "Login to the Site":LoginAsMember) Procedure 1. Select "Folder Contents" from the actions box and navigate to the folder which will contain the Topic. 2. Click the "New..." button, which brings up the "Add Content" form allowing you to choose among the various content types that you are allowed to create. Select "Topic," type in an ID which will be used to identify the Topic in the future, and click "Add." 3. The system will create the topic and present you with its "Edit Topic" form, which allows you to supply metadata about the topic. **Title** -- the name which will be displayed to visitors. **Description** -- a brief paragraph describing the intended purpose of the Topic. The description will be used to annotate the topic object when it is included in another display (e.g., the view of its parent folder). **Acquire criteria from parent** -- when creating topic hierarchies[2], allows sub-topics to refine the search criteria they acquire from their parents. Click "Change" to save the changes you've made. The system will then show the default view of your new Topic, including the list of content which match the Topic's criteria and the list of the Topic's Criteria. Note that since you have not yet set up any criteria, the Topic will match all content objects in the catalog. 4. To constrain the Topic's matches, select "Criteria" in the actions box. Create a new Criterion by filling out the "Add Criteria" form, which has the following fields: **Field id** -- a drop-down list containing the names of all indexed attributes. Select the value corresponding to the field to be searched by the criterion. **Criteria type** -- the kind of search to apply. The standard types include: *String Criterion* -- matches all content objects for which the specified field in the content contains the supplied value *Integer Criterion* -- matches ranges or exact values for fields which are represented as whole numbers *List Criterion* -- matches content objects for which the specified field contains one of a set of string values. *Friendly Date Criterion* -- applies a range search to a date field, relative to the current time. Click "Add" to create the criterion and add it to the Topic. 5. Fill in the value of the "Criterion value" and click "Save changes." You may continue to add criteria which each further constrain the content matched by the Topic. 6. To view the content matching the current set of criteria, select "View" from the actions box. Notes ..[1] Like "Create Folder", this is not solely the prerogative of Site Managers; Content Creators build topics. ..[2] See "Add a Subtopic":AddSubtopic for an explanation of topic hierarchies. CMF-1.3/CMFDefault/help/CreateNewContent.stx0100644000076500007650000000652407360416547020536 0ustar tseavertseaverUse Case: Create Content Object Actor - Content Creator Assumptions - Content Creator has logged into the CMF (see "Login to the Site":LoginAsMember). Procedure 1. Navigate to a location within CMF where you have rights to add content. For example, select 'My Stuff' from your navigation bar to create the content in your member folder. 2. If needed, select the 'Folder contents' link from the action box. 3. Click the "New" button. The system will display the "Add Content" page. From the list of available content types[1], select the radio button corresponding to the type of content which you wish to create. Enter an appropriate ID[2] for the new piece of content, and click the "Add" button. 4. The system will create a new, empty content object of the type you selected, and display the "Standard Resource Metadata" edit form. This form, common across all of the default content types which come stock with the CMF, allows you to enter specific metadata about your new content: **Title** -- A string used to identify your content. **Description** -- A short summary of the content. **Subject** -- A set of keywords, used for cataloging your content. The form provides three submit buttons, each of which saves your content: **Change** -- commits your changes and return to the metadata form. **Change and Edit** -- commits your changes and redirects to the edit form, which will allow you to enter the "body" of your content. **Change and View** -- commit your changes and proceed to viewing your new piece of content. 5. Select "Change and Edit", and supply the initial content for your object as follows: **Document** -- Enter the text for your object, as either Structured Text[3] or HTML[4]. You may either type or paste the text into the textarea, or upload it from your computer. **News Item** -- Fill out the "Lead-in" and "Body" text areas. **File / Image** -- Upload the content from your computer. **Link / Favorite / Event** -- Fill out the form with appropriate values. Fill out the form and select the "Change" button to save your content. 6. You may wish to continue with one of the workflow use cases: - "Submit Content for Review":SubmitContentForReview - "Publish Content":PublishContent Notes ..[1] see "Default CMF Content Types",DefaultContentTypes.stx. ..[2] Don't confuse the content's ID with the its Title. ID's cannot contain special characters (e.g., comma, asterisk, brackets, parentheses, etc.) A good practise is not to use spaces in an ID either. The ID is used in the URL to reach the folder's content, so any character which is not allowed in a URI is not allowed in the id (see "URI RFC", http://www.ietf.org/rfc/rfc2396.txt). ..[3] See "Structured Text Introduction":StructuredTextIntro ..[4] The HTML you enter will have everything outside the BODY tag stripped off; the TITLE and META tags will be used, if present, to update the content's metadata. CMF-1.3/CMFDefault/help/LoginAsMember.stx0100644000076500007650000000167707360416547020016 0ustar tseavertseaverUse Case: Login To The Site Actor - Site Visitor Overview Visitors to a site who have registered as members must login to the site to use member-only services. Procedure 1. Visit the homepage of the site and click the "Log in" link on the menu (located at the left of the page in a default CMF site). The "Log in" link will take you to a form where you may enter your username and password for the site. You may also select the "remember my name" checkbox and the site will fill in your username on the form for you the next time you login. 2. Once you have entered your name and password, click the "Login" button to login to the site. You should then see a message letting you know that you have been successfully logged in. If you did not type your username or password correctly, you will see a page telling you that the login did not succeed. CMF-1.3/CMFDefault/help/ManageLocalRoles.stx0100644000076500007650000000264407360416547020475 0ustar tseavertseaverUse Case: Give local roles to other users Actor - Content Creator Overview In order to collaborate on a set of content, each contributor must have the appropriate permissions to create and revise content. Assumptions - Content Creator has logged into the CMF (see "Login to the Site":LoginAsMember). Procedure 1. Navigate to a folder where you wish to give local roles to other users so you can collaborate on content. 2. Select 'Folder contents' from the actions box. 3. Select 'Set local roles' from the actions box. 4. Fill out the search form as follows: **Search Term** -- Enter the name or email address of the user to whom you wish to assign local roles. **Search By** -- Selected the appropriate drop down which corresponds to the kind of search term you have entered (e.g. 'Email Address' or 'User ID') 5. Click the Search button. The system will redisplay the form, showing a list of users matching your criterion. 6. Select users by checking the corresponding checkbox. Select the role to assign to the selected user(s) from the drop down menu labled **Role to Assign**. Click the "Assign Roles" button. The system will assign the selected role to the selected users, and redisplay the local roles form with a message indicating the change. CMF-1.3/CMFDefault/help/MoveCopyContent.stx0100644000076500007650000000247107360416547020417 0ustar tseavertseaverUse Case: Move / copy content between folders Actor - Content Creator Assumptions - Content Creator has logged into the CMF (see "Login to the Site":LoginAsMember). Procedure 1. Navigate to the folder containing the piece of content which you would like to move (or copy) to another folder. To retrieve a list of the content you have authored, see "View personally authored content":ViewMyContent 2. In the "Folder contents" view of the folder, check the box next to the content object(s) which you would like to move or copy. 3. Click the "Cut" button (or the "Copy" button, if you wish to create a copy in the new location rather than moving the content). 4. The system will set a cookie on your browser (you must have cookies enabled) representing the items you cut or copied. 5. Navigate to the folder into which you wish to move or copy the content (which might be the same folder). 6. Click the "Paste" button. The system will move or copy the content into the folder; new content whose IDs would conflict with exising content in that folder will receive an auto-generated name, typically 'copy_of_' plus the original ID. 7. To undo this action, see "Undo changes to content":UndoChanges. CMF-1.3/CMFDefault/help/ProvideFeedback.stx0100644000076500007650000000272107360416547020336 0ustar tseavertseaverUse Case: Provide feedback on content Actor - Site Visitor Assumptions - The Site Manager has configured the permissions of the site to allow anonymous users to discuss published content **or** the Site Visitor has logged into the CMF (see "Login to the Site":LoginAsMember). - The Site Manager has enabled discussion for the content type of the item for which the Site Visitor wishes to provide feedback. Procedure 1. Navigate to the content on which you wish to provide feedback 2. Select 'Reply' from the actions box. The system will display the reply form for the object. **Reply Title** -- an input field for the title of your feedback; initially, the field will have the title of the object to which you are replying. **Reply** -- a textarea for entering feedback. **Preview** -- a submit button which permits you to preview your reply before submitting it. **Reply** -- a submit button for creating your reply. 3. Update the title, if desired, and enter your comment into the textarea. 4. Click the "Preview" button to check that your reply will be formatted as you desire. If not, return to the reply form using the "Edit" button. 5. Click the "Reply" button. The system will append your comment to the thread of replies on the target object. CMF-1.3/CMFDefault/help/RemoveContent.stx0100644000076500007650000000146607360416547020116 0ustar tseavertseaverUse Case: Remove unneeded content Actor - Content Creator Assumptions - Content Creator has logged into the CMF (see "Login to the Site":LoginAsMember). Procedure 1. Navigate to the content to be deleted. To retrieve a list of the content you have authored, see Use Case: "View personally authored content":ViewMyContent 2. Select 'Folder contents' from the actions box. 3. Select the content to be delete from by checking the corresponding checkbox. Click the "Delete" button. 4. The system will delete the content from the folder and redirect to the folder contents view, adding a notification message which indicates that the selected content has been deleted. 5. To undo this action, see "Undo changes to content":UndoChanges CMF-1.3/CMFDefault/help/RenameContent.stx0100644000076500007650000000213107360416547020056 0ustar tseavertseaverUse Case: Rename Content Actor - Content Creator Overview The ID which an author assigns to a piece of content at creation time[1] may laterturn out to be inappropriate. Assumptions - Content Creator has logged into the CMF (see "Login to the Site":LoginAsMember). Procedure 1. Navigate to the folder containing the piece of content which you would like to rename. To retrieve a list of the content you have authored, see "View personally authored content":ViewMyContent. 2. In the "Folder contents" view of the folder, check the box next to the content object(s) which you would like to rename. 3. Click the "Rename" button. 4. The system will display a form listing each selected content object, with input fields for the new ID for each. Supply new IDs, and click the "OK" button. 5. The content has now been renamed; you will be redirected to the folder contents listing with a notification message to that effect. 6. To undo this action, see "Undo changes to content":UndoChanges. CMF-1.3/CMFDefault/help/SearchCMFSite.stx0100644000076500007650000000324307360416547017701 0ustar tseavertseaverUse Case: Search site for information Actor - Site Visitor Overview Procedure 1. Select the "Search" link from the masthead of any page. The system will display a form allowing you to specify the criteria for your search: **Full text search** -- enter one or more words you expect to find in the "searchable text" of your content (searchable text normally consists of the textual content, if any, plus the title and description). **Title** -- Enter one or more words you expecte to find in the Title of the content. **Subject** -- Select one or more keywords from the available seletion box. **Description** -- Enter one or more words you expecte to find in the Description of the content. **Find new items since...** -- Select one of the options which corrsponds to the range of time in which objects were created. **Item Type** -- Select one or more content types from the seletion box. **Creator** -- Enter the user ID of the user who created the content. Enter your search criteria and select the search button. 2. The system will return a results page listing the first twenty matching objects. For each matching item, the results page displays the Title, the Description, and the "last modified" date; the Title is also a link to the object. Alternatives 1. Enter one or more "full text" search words in the "quick search" form in the masthead of any page. Click the "go" button. Continue with step #3 above. CMF-1.3/CMFDefault/help/SubmitContentForPublication.stx0100644000076500007650000000210207360416547022751 0ustar tseavertseaverUse Case: Submit Content for Publication Actor - Content Creator Overview Non-privileged content creators site need to get review of their content before it can be published to all Site Visitors. Assumptions - Content Creator has logged into the CMF (see "Login to the Site":LoginAsMember). - Content Creator has created a piece of content which she wishes to publish (see "Create Content Object":CreateNewContent). Procedure 1. Navigate to the piece of content you wish to submit for publication. To retrieve a list of the content you have authored, see "View personally authored content":ViewMyContent 2. Select 'Submit' from the actions box. The system will display the "Submit Content" form: **Comment** -- A textarea for including an optional comment to the reviewer. 3. The system will mark your content as "pending review". Reviewers will be notified your content is pending review (see "Approve content for publication":ApproveForPublication). CMF-1.3/CMFDefault/help/Syndication-Tool_Overview.stx0100644000076500007650000000426407301312113022365 0ustar tseavertseaverSyndicationTool - Overview: CMF Syndication Overview. Description The SyndicationTool allows for sitewide syndication of content in folders (or folder-like objects which support the synContentValues interface). Currently on the SyndicationTool the following features are present: 1. Enable/disable sitewide syndication. 2. Override my Syndicaiton Element defaults on the Properties management form. Once sitewide syndication has been enabled, the Syndication action on folders is enabled, allowing syndication for a specific folder to be enabled. This is to protect calling the RSS dtml method on folder contents one wishes to remain non-syndicated. A 'syndication_information' object is set on the folder which acts as the 'propertysheet' for over- riding sitewide defaults for each particular syndication instance. In the next revision of the SyndicationTool, the following features are being planned: 1. Reimplementation of the manner properties are called on the SyndicationTool class and instance, as well as on the 'syndication- information' object. A getElementProperty method will handle generic grabing of properties. 2. Adding the ability to addElementProperties, to allow for easily enabling additional XML namespaces to be incorporated on an instance without requiring reimplementation of the SyndicationTool. 3. Default sitewide properties for the dublin core module support. 4. Sitewide enabling/disabling override switches for the supported XML namespace module default values. 5. Sitewide/Folder level content filtering of content returned back in the itemRSS DTML method to allow for selective content returned for the syndication. 6. Sort Order setting. Allow the setting of how the content is sorted in the syndication. 7. Add switch to disallow acquisition to disable sub-folder syndication within an existing syndicated folder. 8. Other features are possible as users give feedback on the Syndication implementation. CMF-1.3/CMFDefault/help/Syndication-Tool_Policies.stx0100644000076500007650000000071007301312113022316 0ustar tseavertseaverSyndicationTool - Policies: Manage the policies of a Site's Syndication Instance. Description View and manage the prolicies of a sitewide syndication instance. Controls 'Allow-Override' -- Each of the properties configureable from the Properties management form will have an override switch on the policies management form. This is currently unavailable and will be incorporated into the 1.1 release of the SyndicationTool. CMF-1.3/CMFDefault/help/Syndication-Tool_Properties.stx0100644000076500007650000000415307301312113022710 0ustar tseavertseaverSyndicationTool - Properties: Manage the properties of a Site's Syndication Instance. Description View and manage the properties of a sitewide syndication instance. Controls 'Enable/Disable Syndication' -- Turns enables/disables sitewide syndication. All methods check whether site syndication is enabled to ensure a sitewide policy is enforceable. 'Syndication Module' -- The RSS sy XMLNS is supported in this tool by default. The following properties are editable on the tool for sitewide configuring of each of the elements of the syndication module. In this release, they are over-rideable; these will be configurable in version 1.1 to enable a sitewide policy. 'UpdatePeriod' -- Describes the period over which the channel format is updated. Acceptable values are: hourly, daily, weekly, monthly, yearly. If omitted, daily is assumed. 'UpdateFrequency' -- Used to describe the frequency of updates in relation to the update period. A positive integer indicates how many times in that period the channel is updated. For example, an updatePeriod of daily, and an updateFrequency of 2 indicates the channel format is updated twice daily. If omitted a value of 1 is assumed 'UpdateBase' -- Defines a base date to be used in concert with updatePeriod and updateFrequency to calculate the publishing schedule. By default the sitewide date is the DateTime of the tool initialization. The UpdateBase in the RSS XML takes this DateTime object and sringify's it through DateTime.HTML4() The date format takes the form: yyyy-mm-ddThh:mm 'Max Items' -- Defines the max number of items which are included in the syndication. The RSS Specification recommends this not exceed 15, which is the default. 'DublinCore Module' -- The RSS dc XMLNS is supported in this tool by default. The sitewide properties will be editable on the tool for sitewide configuring of each of the elements of the dublin core module. In this release, they are over-rideable; these will be configurable in version 1.1 to enable a sitewide policy. CMF-1.3/CMFDefault/help/Syndication-Tool_Reporting.stx0100644000076500007650000000103507301312113022521 0ustar tseavertseaverSyndicationTool - Overview: CMF Syndication Reporting Facility. Description The SyndicationTool Reporting Facility is planned to be added to the next release of the Syndication services for the CMF. I hope to have generated enough feedback from users who might have definate ideas which would make a good foundation as a default suite of reports. The plan is not to be all inclusive, but to provide a mechanism to generate interesting SiteAdmin data regarding the use of Syndication within their sites. CMF-1.3/CMFDefault/help/TODO.stx0100644000076500007650000000257307360424232016061 0ustar tseavertseaverMissing Use Cases "ContentCreator":Actor_ContentCreator - "Define content metadata":ChangeMetadata - "Make a content object discussable":EnableDiscussion "MembershipManager":Actor_MembershipManager - "Add a new member to the site using a standard user folder":AddMemberToUserFolder - "Change member information and abilities":ChangeMemberInformation - "Browse member roster":BrowseMemberRoster - "Remove a member from the site":RemoveMemberFromSite "SiteDesigner":Actor_SiteDesigner - "Create new skin for the site":CreateNewSkin - "Modify skin appearance":ChangeSkinLookAndFeel - "Change skin behavior":ChangeSkinBehavior - "Change the default skin for the site":ChangeDefaultSiteSkin "SiteManager":Actor_SiteManager - "Configure CMF Topic":ConfigureCMFTopic - "Configure what types of content can be created by users":ConfigureAllowedContentTypes - "Configure security for a content object or folder":ConfigureObjectSecurity - "Associate a workflow with a content type":AssociateWorkflowWithContentType - "Configure required metadata for a content type":ConfigureRequiredMetadata - "Configure what content is released via syndication":ConfigureContentSyndiation "SiteVisitor":Actor_SiteVisitor - "Submit a news item":SubmitNewsItem - "Browse a portal topic":BrowsePortalTopic CMF-1.3/CMFDefault/help/UndoChanges.stx0100644000076500007650000000227107360416547017517 0ustar tseavertseaverUse Case: Undo Changes to Content Actor - Content Creator Assumptions - Content Creator has logged into the CMF (see "Login to the Site":LoginAsMember). - Content Creator has made changes to content which she wishes to undo (see "Update existing content":ChangeContent). - The changes are "undoable"[1]. Procedure 1. Select 'Undo' from the actions box. The system will present a list of the transactions which you have permission to undo. 2. Select the checkbox next to the transaction which you wish to undo and click the undo button. The system will undo that transaction and redisplay the list of transactions. Notes ..[1] Transactions which involve changing content remain undoable until one or more objects modified by the transaction are modified by a subsequent transaction. Normally, this means that only the latest transaction to an object is undoable, unless the later transactions are also undone. It is also possible to have transactions above the transaction in the list which do not effect the ability to undo a change a user wishes to undo. CMF-1.3/CMFDefault/help/UnpublishContent.stx0100644000076500007650000000130407360416547020621 0ustar tseavertseaverUse Case: Remove content from public site Actor - Reviewer Assumptions - Reviewer has logged into the CMF (see "Login to the Site":LoginAsMember). - Content has been published which now needs to be unpublished (see "Approve content for publication":ApproveForPublication). Procedure 1. Navigate to the content item you wish to remove from the publically visible site. 2. Select 'Reject' from the actions box. 3. Enter comments explaining why the object is being removed from the public site. 4. Select the "Reject" button. The system moves the item to the "private" state, making it no longer visible on your public site. CMF-1.3/CMFDefault/help/ViewMyContent.stx0100644000076500007650000000156407360416547020100 0ustar tseavertseaverUse Case: View personally authored content Actor - Content Creator Assumptions - Content Creator has logged into the CMF (see "Login to the Site":LoginAsMember). - Content Creator has created content (see "Create Content Object":CreateNewContent). Procedure 1. Click the 'search' link in the top navigation bar. The system displays the advanced search page. 2. Enter your user ID in the form field labled *Creator*. Make sure you spell it exactly as you type it in to login to the CMF. 3. Click the search button at the bottom of the page. The system then displayes a list of all content you've personally created using the standard search results page[1]. Notes ..[1] For a more detailed description of the options for advanced searching, see "Search CMF Site":SearchCMFSite. CMF-1.3/CMFDefault/images/0040755000076500007650000000000007524010066015063 5ustar tseavertseaverCMF-1.3/CMFDefault/images/workflow.gif0100644000076500007650000000014107263717577017442 0ustar tseavertseaverGIF89a! ,2 ' WB64y\fAkz2L1=@Nưy ;CMF-1.3/CMFDefault/interfaces/0040755000076500007650000000000007524010066015741 5ustar tseavertseaverCMF-1.3/CMFDefault/interfaces/Syndicatable.py0100644000076500007650000000242507401232662020717 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """\ Declare interface for synContentValues """ import Interface class Syndicatable(Interface.Base): """\ Returns back a list of objects which implements the DublinCore. """ def synContentValues(self): """ Returns a list of results which is to be Syndicated. For example, the normal call contentValues (on PortalFolders) returns a list of subObjects of the current object (i.e. objectValues with filtering applied). For the case of a Topic, one would return a sequence of objects from a catalog query, not the subObjects of the Topic. What is returned must implement the DublinCore. """ CMF-1.3/CMFDefault/interfaces/__init__.py0100644000076500007650000000132407401232662020051 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """\ Loads interface names into the package. """ from Syndicatable import Syndicatable CMF-1.3/CMFDefault/scripts/0040755000076500007650000000000007524010066015305 5ustar tseavertseaverCMF-1.3/CMFDefault/scripts/addImagesToSkinPaths.pys0100644000076500007650000000234707253517162022063 0ustar tseavertseaver## Script (Python) "addImagesToSkinPaths" ##bind container=container ##bind context=context ##bind namespace= ##bind script=script ##bind subpath=traverse_subpath ##parameters= ##title=Add the name 'Images' to all skin paths. ## # The Folder 'Images' in portal_skins is no longer directly referenced, # and should now be part of all skin paths. This script adds 'Images' to # all skins defined in the portal. # # Use this by creating a PythonScript object by the name # 'addImagesToSkinPaths' in your Portal, with this file as initial # content. Run it by selecting the 'Test' tab. You will need to have the # 'Manage portal' permission in order to be able to execute this method. import string ps = context.portal_skins skins = ps.getSkinSelections() for skin in skins: path = ps.getSkinPath(skin) path = string.split(path, ',') path = map(string.strip, path) if 'Images' not in path: path.append('Images') path = string.join(path, ', ') # addSkinSelection will replace existing skins as well. ps.addSkinSelection(skin, path) print "Added 'Images' folder to %s skin." % `skin` else: print "Skipping %s skin, already has 'Images' in it's path." % `skin` return printedCMF-1.3/CMFDefault/scripts/convertCatalogGetIconColumn.pys0100644000076500007650000000316607264414661023461 0ustar tseavertseaver## Script (Python) "convertCatalogGetIconColumn" ##bind container=container ##bind context=context ##bind namespace= ##bind script=script ##bind subpath=traverse_subpath ##parameters= ##title=Convert portal_catalog to use 'getIcon' instead of 'icon'. ## # Convert the 'icon' metadata column in the portal catalog to a 'getIcon' # column. Unfortunately this means we have to drop the 'icon' column, and add # the 'getIcon' column, then do a full reindex. # # To use this script, create it inside the Portal Root Folder and run it. # You will need the Manager Role to fully assure correct execution. catalog = context.portal_catalog needReindex = 0 columns = catalog.schema() if 'icon' in columns: catalog.manage_delColumns(('icon',)) needReindex = 1 if 'getIcon' not in columns: catalog.manage_addColumn('getIcon') needReindex = 1 if needReindex == 1: # We re-create the reindex code here; because we don't want to have to deal # with the redirect that's unavoidable if we call manage_catalogReindex. # Get all catalogued data, and extract the paths paths = [] add = paths.append for obj in catalog(): add(obj.getPath()) # Clear the catalog catalog.manage_catalogClear() # Index all paths again for p in paths: # I cannot use resolve_path, no access from Python Scripts. # Having Manager access should be enough though. obj = catalog.resolve_url(p, context.REQUEST) if obj is not None: catalog.catalog_object(obj, p) return "Updated the catalog, now using 'getIcon' instead of 'icon'." return 'Nothing done, catalog already tracks getIcon.' CMF-1.3/CMFDefault/skins/0040755000076500007650000000000007524010067014746 5ustar tseavertseaverCMF-1.3/CMFDefault/skins/Images/0040755000076500007650000000000007524010066016152 5ustar tseavertseaverCMF-1.3/CMFDefault/skins/Images/UpFolder_icon.gif0100644000076500007650000000162307245471210021372 0ustar tseavertseaverGIF89aPPP@@@@@!,@pH@, @Åŋ-JD#D 08($qb… W~(`͛8m)`$Ƀ{ y2DxeʇK*D1QNXu'֯N;CMF-1.3/CMFDefault/skins/Images/Zope_logo.gif0100644000076500007650000000140307403764774020613 0ustar tseavertseaverGIF89aW&3fLyU̦of?orY]yٌ@pf̿!#,W&@@pH,Ȥrl2;$q(vEb2cbJJBQH. #6C!084B ]_ "e!tLkDIF"iF]J RTVXE!eDmCIEE!k" W]LZESxᗄ')ed-eE4R˂ȹsvp4l2LߛiՎt8בS~| e.j /,O# «Xjʵׯ`Su۷eW0  &tB:S+Unvч`Xb E!P( 0pnDLjnI7J!eiH4 O@t@Cd6]@a*0" AJ0NP!(YD~b=s˞e[2"$h0W\rqaC./o[,0ȥa! PP |ZV`ֹn:>g A{!( .gbd @4+U]M@0E !w5@1>03Z|!@@FHXI6*10 8M"aA;LkkZ`i%Hp3DW =Hd"ڐ7wg1&NckhWHz6 Ex7I_ ;ާ[ 8`UL^&3lK9T_8'YJ3].u#Hk 32tQ} (0LtBR#UDX%| )e"b4e ԏ47h87 `m*ua'!GR:m@ dnW Z _7җ.0 Ў)o1Tu)b0Bf NG75;E$m&Itғ I8<9bLp8@w ޯNDPrd?< 0UX?-1}8 4 J8r0!Q.U4ٕ$nH-E \dHF2$hi$6u~4.4I6H9Bo"5(eo`&4dZo\B MJWҖ0LgJӚ) ;CMF-1.3/CMFDefault/skins/Images/logo.png0100644000076500007650000000255607260127370017631 0ustar tseavertseaverPNG  IHDR5WgAMA afPLTEfct,7KzNf)6Qgilfb[eimw҄)tRNS@fbKGDH pHYs  #utIME !qIDATxm:MQ@d]3- 9Mwv537 !ZJ)>M2*SD+[ȄR^9_&b| ``BB1F2}V0O*.!>EHʗ8KPaH][ҹU_1R/>b>QT=8 PLӫ?TޠU*ڱXHI%ՙ դ\+T[Sxpf*)kN Wfe¦@J*sIG6w cm_|b^mۖ&faag53Y$W琨X* ])4wX꺳okq9PkVqIv wE>Ih:y(4vv [ִRkTZaŵ|Sz.)jn 1ܪ}` jg6k fa_gV^^>ǒZyD)/H,↲!Ʀڔ#H[EbqmJ)ϰJʃz~dab6/TVH Qab+dtq@Oj5:DRɎe6ֺZ ua|fѳ7;ܖUPl`TVs$3\ޏ2"PSV &`;X:JOdƔ/{4 0BG%Ej)6\5ck!%;`H|)\N>\jOd_ πdk]e)X+j [ F~CcȺzvS8G׌k#qx>v)"bf]UDƂu1V~L( {R31yIo|gC 5hIENDB`CMF-1.3/CMFDefault/skins/Images/spacer.gif0100644000076500007650000000005307245471210020113 0ustar tseavertseaverGIF89a!,Q;CMF-1.3/CMFDefault/skins/Images/tinyzope.jpg0100644000076500007650000000120007245471210020525 0ustar tseavertseaverJFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222"(!A1Q$3Caq!1Aq ?ϻ7|n =Jc N{6HbW<4U99c#Ծ γmTL *^TJCȻth\ 470rN%`t{//,:HXZhݎ ! NIkҧou4/0HW=FR1RqD{tټ*L۸_K@" 33k.KE)`0–|M4"{G kK}@K$3wsM4CMF-1.3/CMFDefault/skins/content/0040755000076500007650000000000007524010066016417 5ustar tseavertseaverCMF-1.3/CMFDefault/skins/content/aboveInThread.py0100644000076500007650000000064407313472631021513 0ustar tseavertseaver## Script (Python) "aboveInThread" ##parameters= ##title=Discussion parent breadcrumbs breadcrumbs = '' parents = context.parentsInThread() if parents: breadcrumbs = 'Above in thread: ' for parent in parents: p_str = '%s' % (parent.absolute_url(), parent.Title()) breadcrumbs = breadcrumbs + p_str + ':' breadcrumbs = breadcrumbs[:-1] + '

    ' return breadcrumbs CMF-1.3/CMFDefault/skins/content/content_hide_form.dtml0100644000076500007650000000316107511034313022762 0ustar tseavertseaver

    Hide Item

    &dtml-message;

    Use this form to hide a content item by setting its status to Private, thereby making it unavailable to other portal members and visitors.

    Status This item is currently in &dtml-review_state; status.
    Comments

    Reviewing history
    &dtml-action; (effective: ) by &dtml-actor;

    CMF-1.3/CMFDefault/skins/content/content_publish_form.dtml0100644000076500007650000000341607247020610023523 0ustar tseavertseaver

    Publish Item

    &dtml-message;

    A published item is available to the general member base and anonymous visitors.

    Another way to control the visibility of an item is with its effective date. An item is not publicly available before its effective date, even if its status is published.

    Status This item is currently in &dtml-review_state; status.
    Comments

    Reviewing history
    &dtml-action; (effective: ) by &dtml-actor;

    CMF-1.3/CMFDefault/skins/content/content_reject_form.dtml0100644000076500007650000000321307247020610023324 0ustar tseavertseaver

    Reject Item

    &dtml-message;

    Use this form to reject the publication of a content item and set its status to Private, thereby making it unavailable to other portal members and visitors.

    Status This item is currently in &dtml-review_state; status.
    Comments

    Reviewing history
    &dtml-action; (effective: ) by &dtml-actor;

    CMF-1.3/CMFDefault/skins/content/content_retract_form.dtml0100644000076500007650000000317607247020610023524 0ustar tseavertseaver

    Retract Item

    &dtml-message;

    Use this form to retract a content item by setting its status to Private, thereby making it unavailable to other portal members and visitors.

    Status This item is currently in &dtml-review_state; status.
    Comments

    Reviewing history
    &dtml-action; (effective: ) by &dtml-actor;

    CMF-1.3/CMFDefault/skins/content/content_show_form.dtml0100644000076500007650000000347607511034313023042 0ustar tseavertseaver

    Make Item Visible

    &dtml-message;

    A Visible item is available other portal members and visitors, however it won't show up in the list of published items.

    Another way to control the visibility of an item is with its effective date. An item is not publicly available before its effective date, even if its status is published.

    Status This item is currently in &dtml-review_state; status.
    Comments

    Reviewing history
    &dtml-action; (effective: ) by &dtml-actor;

    CMF-1.3/CMFDefault/skins/content/content_status_history.dtml0100644000076500007650000000312107247020610024127 0ustar tseavertseaver

    Content Item status history

    &dtml-message;

    An item's status (also called its review state) determines who can see it. A private item can only be viewed by its Owner and by the site management. Only published items are available to the general member base and anonymous visitors. To make an item published, it has to be reviewed by one of the site's Reviewers. You can request that an item be reviewed by setting its status to pending.

    Another way to control the visibility of an item is with its effective date. An item is not publicly available before its effective date, even if its status is published.

    Current state

    This item is in &dtml-review_state; state.

    Reviewing history

    &dtml-action; (effective: ) by &dtml-actor;

    This item has not had any status changes.

    CMF-1.3/CMFDefault/skins/content/content_status_modify.py0100644000076500007650000000106407354415571023426 0ustar tseavertseaver## Script (Python) "content_status_modify" ##parameters=workflow_action, comment='' ##title=Modify the status of a content object context.portal_workflow.doActionFor( context, workflow_action, comment=comment) if workflow_action == 'reject': redirect_url = context.portal_url() + '/search?review_state=pending' else: redirect_url = '%s/view?%s' % ( context.absolute_url() , 'portal_status_message=Status+changed.' ) context.REQUEST[ 'RESPONSE' ].redirect( redirect_url ) CMF-1.3/CMFDefault/skins/content/content_submit_form.dtml0100644000076500007650000000355107247020610023360 0ustar tseavertseaver

    Submit Item for Review

    &dtml-message;

    To make an item published, it has to be reviewed by one of the site's reviewers. A published item is available to the general member base and anonymous visitors.

    Another way to control the visibility of an item is with its effective date. An item is not publicly available before its effective date, even if its status is published.

    Status This item is currently in &dtml-review_state; status.
    Comments

    Reviewing history
    &dtml-action; (effective: ) by &dtml-actor;

    CMF-1.3/CMFDefault/skins/content/discussionitem_icon.gif0100644000076500007650000000017507312217102023153 0ustar tseavertseaverGIF89aɲ! ,Bx 0J'c@7:W-Wp i!]7R4͗x) A;adÅf$;CMF-1.3/CMFDefault/skins/content/discussionitem_view.dtml0100644000076500007650000000060707312217102023370 0ustar tseavertseaver

    &dtml-Title;

    CMF-1.3/CMFDefault/skins/content/document_edit.py0100644000076500007650000000230107516644456021626 0ustar tseavertseaver## Script (Python) "document_edit" ##parameters=text_format, text, file='', SafetyBelt='', choice=' Change ' ##title=Edit a document try: from Products.CMFDefault.utils import scrubHTML text = scrubHTML( text ) # Strip Javascript, etc. context.edit( text_format , text , file , safety_belt=SafetyBelt ) qst='portal_status_message=Document+changed.' if choice == ' Change and View ': target_action = context.getTypeInfo().getActionById( 'view' ) else: target_action = context.getTypeInfo().getActionById( 'edit' ) context.REQUEST.RESPONSE.redirect( '%s/%s?%s' % ( context.absolute_url() , target_action , qst ) ) except Exception, msg: target_action = context.getTypeInfo().getActionById( 'edit' ) context.REQUEST.RESPONSE.redirect( '%s/%s?portal_status_message=%s' % ( context.absolute_url() , target_action , msg ) ) CMF-1.3/CMFDefault/skins/content/document_edit_form.dtml0100644000076500007650000000317107452041366023155 0ustar tseavertseaver

    &dtml-message;


    Edit &dtml-getId;

    Title
    Description
    Format checked id="cb_structuredtext" /> checked id="cb_html" /> checked id="cb_html" />
    Upload
    Edit

    CMF-1.3/CMFDefault/skins/content/document_icon.gif0100644000076500007650000000023707247777655021765 0ustar tseavertseaverGIF89aUUUPPP!,@LBBp`gGg [Y' yk4<jRT TCRwDnG;CMF-1.3/CMFDefault/skins/content/document_view.dtml0100644000076500007650000000045107305733404022153 0ustar tseavertseaver
    CMF-1.3/CMFDefault/skins/content/favorite_view.dtml0100644000076500007650000000046707415457271022172 0ustar tseavertseaver
    CMF-1.3/CMFDefault/skins/content/file_edit.py0100644000076500007650000000117007357707207020727 0ustar tseavertseaver## Script (Python) "file_edit" ##parameters=precondition='', file='', choice=' Change ' ##title=Edit a file context.edit( precondition=precondition, file=file) qst='portal_status_message=File+changed.' if choice == ' Change and View ': target_action = context.getTypeInfo().getActionById( 'view' ) else: target_action = context.getTypeInfo().getActionById( 'edit' ) context.REQUEST.RESPONSE.redirect( '%s/%s?%s' % ( context.absolute_url() , target_action , qst ) ) CMF-1.3/CMFDefault/skins/content/file_edit_form.dtml0100644000076500007650000000143407403526101022245 0ustar tseavertseaver

    &dtml-message;


    Edit &dtml-getId;

    Title
    Description
    Content type &dtml-Format;
    Upload file

    CMF-1.3/CMFDefault/skins/content/file_icon.gif0100644000076500007650000000161507247777655021067 0ustar tseavertseaverGIF89aPPP@@@@@!,j@AxჅ*tpÄ)f8A= pa< dǓ A$r"L*iH1ʚ&{ӡO/G~s̙?RlTӫ-* ;CMF-1.3/CMFDefault/skins/content/file_view.dtml0100644000076500007650000000072407274321367021265 0ustar tseavertseaver

    Filename:
    Size:
    Content type:
    Description:

    Download &dtml-title;
    CMF-1.3/CMFDefault/skins/content/folder_edit.py0100644000076500007650000000117507357721003021256 0ustar tseavertseaver## Script (Python) "folder_edit" ##parameters=title, description, choice=' Change ' ##title=Edit a folder context.edit( title=title, description=description) qst='portal_status_message=Folder+changed.' if choice == ' Change and View ': target_action = context.getTypeInfo().getActionById( 'view' ) else: target_action = context.getTypeInfo().getActionById( 'edit' ) context.REQUEST.RESPONSE.redirect( '%s/%s?%s' % ( context.absolute_url() , target_action , qst ) ) CMF-1.3/CMFDefault/skins/content/folder_edit_form.dtml0100644000076500007650000000154207357711742022620 0ustar tseavertseaver

    &dtml-message;


    Edit &dtml-getId;

    Title
    Description

    CMF-1.3/CMFDefault/skins/content/folder_icon.gif0100644000076500007650000000160707247777655021424 0ustar tseavertseaverGIF89aPPP@@@@@!,@dH@,PpXŋ#"laD aG%&4FbʜD8G숳gN-YDEMpI6HJU*;CMF-1.3/CMFDefault/skins/content/folder_view.dtml0100644000076500007650000000224007314010410021567 0ustar tseavertseaver

    Documents, Images, and Files

    • &dtml-Description;

    Links

    • &dtml-Description;

    Folders

    • &dtml-Description;
    CMF-1.3/CMFDefault/skins/content/full_metadata_edit_form.dtml0100644000076500007650000000664707357711742024162 0ustar tseavertseaver

    &dtml-message;


    CMF-1.3/CMFDefault/skins/content/image_edit.py0100644000076500007650000000117407357707207021076 0ustar tseavertseaver## Script (Python) "image_edit" ##parameters=precondition='', file='', choice=' Change ' ##title=Edit an image context.edit( precondition=precondition, file=file) qst='portal_status_message=Image+changed.' if choice == ' Change and View ': target_action = context.getTypeInfo().getActionById( 'view' ) else: target_action = context.getTypeInfo().getActionById( 'edit' ) context.REQUEST.RESPONSE.redirect( '%s/%s?%s' % ( context.absolute_url() , target_action , qst ) ) CMF-1.3/CMFDefault/skins/content/image_edit_form.dtml0100644000076500007650000000143607357711742022431 0ustar tseavertseaver

    &dtml-message;


    Edit &dtml-getId;

    Title
    Description
    Content type &dtml-Format;
    Upload image

    CMF-1.3/CMFDefault/skins/content/image_icon.gif0100644000076500007650000000162307247777655021231 0ustar tseavertseaverGIF89aPPP@@@@@!,@p@AxCpa ظbƏBNZ!?> —8s$J i]$IC<6tɌ/iZ*q)B`;CMF-1.3/CMFDefault/skins/content/image_view.dtml0100644000076500007650000000031707245471211021416 0ustar tseavertseaver
    CMF-1.3/CMFDefault/skins/content/link_edit.py0100644000076500007650000000112007357707207020740 0ustar tseavertseaver## Script (Python) "link_edit" ##parameters=remote_url, choice=' Change ' ##title=Edit a link context.edit(remote_url=remote_url) qst='portal_status_message=Link+changed.' if choice == ' Change and View ': target_action = context.getTypeInfo().getActionById( 'view' ) else: target_action = context.getTypeInfo().getActionById( 'edit' ) context.REQUEST.RESPONSE.redirect( '%s/%s?%s' % ( context.absolute_url() , target_action , qst ) ) CMF-1.3/CMFDefault/skins/content/link_edit_form.dtml0100644000076500007650000000121507357711742022277 0ustar tseavertseaver

    &dtml-message;


    CMF-1.3/CMFDefault/skins/content/link_icon.gif0100644000076500007650000000016207247777655021101 0ustar tseavertseaverGIF89a@!,@6h$8iЩ`(ga[ڡf往mY`H,Ap8;;CMF-1.3/CMFDefault/skins/content/link_view.dtml0100644000076500007650000000047107245471211021272 0ustar tseavertseaver
    CMF-1.3/CMFDefault/skins/content/metadata_edit.py0100644000076500007650000000465407403217064021567 0ustar tseavertseaver## Script (Python) "metadata_edit" ##title=Update Content Metadata ##parameters=allowDiscussion=None,title=None,subject=None,description=None,contributors=None,effective_date=None,expiration_date=None,format=None,language=None,rights=None def tuplify( value ): if not same_type( value, () ): value = tuple( value ) temp = filter( None, value ) return tuple( temp ) if title is None: title = context.Title() if subject is None: subject = context.Subject() else: subject = tuplify( subject ) if description is None: description = context.Description() if contributors is None: contributors = context.Contributors() else: contributors = tuplify( contributors ) if effective_date is None: effective_date = context.EffectiveDate() if expiration_date is None: expiration_date = context.expires() if format is None: format = context.Format() if language is None: language = context.Language() if rights is None: rights = context.Rights() context.portal_discussion.overrideDiscussionFor(context, allowDiscussion) try: context.editMetadata( title=title , description=description , subject=subject , contributors=contributors , effective_date=effective_date , expiration_date=expiration_date , format=format , language=language , rights=rights ) if context.REQUEST.get( 'change_and_edit', 0 ): action_id = 'edit' elif context.REQUEST.get( 'change_and_view', 0 ): action_id = 'view' else: action_id = 'metadata' action_path = context.getTypeInfo().getActionById( action_id ) context.REQUEST['RESPONSE'].redirect( '%s/%s?portal_status_message=Metadata+changed.' % ( context.absolute_url(), action_path ) ) except Exception, msg: target_action = context.getTypeInfo().getActionById( 'metadata' ) context.REQUEST.RESPONSE.redirect('%s/%s?portal_status_message=%s' % ( context.absolute_url() , target_action , msg )) CMF-1.3/CMFDefault/skins/content/metadata_edit_form.dtml0100644000076500007650000000440707305266571023126 0ustar tseavertseaver

    &dtml-message;


    CMF-1.3/CMFDefault/skins/content/newsitem_edit.py0100644000076500007650000000227607516644456021656 0ustar tseavertseaver## Script (Python) "newsitem_edit" ##parameters=text, description, text_format=None, choice=' Change ' ##title=Edit a news item try: from Products.CMFDefault.utils import scrubHTML text = scrubHTML( text ) # Strip Javascript, etc. description = scrubHTML( description ) context.edit(text=text, description=description, text_format=text_format) qst='portal_status_message=News+Item+changed.' if choice == ' Change and View ': target_action = context.getTypeInfo().getActionById( 'view' ) else: target_action = context.getTypeInfo().getActionById( 'edit' ) context.REQUEST.RESPONSE.redirect( '%s/%s?%s' % ( context.absolute_url() , target_action , qst ) ) except Exception, msg: target_action = context.getTypeInfo().getActionById( 'edit' ) context.REQUEST.RESPONSE.redirect( '%s/%s?portal_status_message=%s' % ( context.absolute_url() , target_action , msg ) ) CMF-1.3/CMFDefault/skins/content/newsitem_edit_form.dtml0100644000076500007650000000277207452122046023173 0ustar tseavertseaver

    &dtml-message;


    Edit &dtml-getId;

    Title
    Format checked id="cb_structuredtext" /> checked id="cb_html" /> checked id="cb_html" />
    Lead-in
    Body

    CMF-1.3/CMFDefault/skins/content/newsitem_icon.gif0100644000076500007650000000162007247777655021777 0ustar tseavertseaverGIF89aPPP@@@@@!,mHp (\0> "ń8th"ƈ. @Ƃ R• 9Q͉,'t@N4mْ'΄Bk&%ZsQ"<ڱGʕ$;CMF-1.3/CMFDefault/skins/content/newsitem_view.dtml0100644000076500007650000000060007305733404022164 0ustar tseavertseaver
    CMF-1.3/CMFDefault/skins/control/0040755000076500007650000000000007524010066016425 5ustar tseavertseaverCMF-1.3/CMFDefault/skins/control/addtoFavorites.py0100644000076500007650000000124007507206242021753 0ustar tseavertseaver## Script (Python) "addtoFavorites" ##title=Add item to favourites ##parameters= portal = context.portal_url.getPortalObject() homeFolder = portal.portal_membership.getHomeFolder() if not hasattr(homeFolder, 'Favorites'): homeFolder.manage_addPortalFolder(id='Favorites', title='Favorites') targetFolder = getattr( homeFolder, 'Favorites' ) new_id='fav_' + str(int( context.ZopeTime())) myPath=context.portal_url.getRelativeUrl(context) targetFolder.invokeFactory( 'Favorite', id=new_id, title=context.TitleOrId(), remote_url=myPath) url = '%s/%s' % (context.absolute_url(), context.getTypeInfo().getActionById('view','')) return context.REQUEST.RESPONSE.redirect(url) CMF-1.3/CMFDefault/skins/control/change_password.py0100644000076500007650000000121207445403530022143 0ustar tseavertseaver## Script (Python) "change_password" ##title=Action to change password ##parameters=password, confirm, domains=None mt = context.portal_membership failMessage=context.portal_registration.testPasswordValidity(password, confirm) if failMessage: return context.password_form(context, context.REQUEST, error=failMessage) member = mt.getAuthenticatedMember() mt.setPassword(password, domains) mt.credentialsChanged(password) return context.personalize_form(context, context.REQUEST, portal_status_message='Password changed.') CMF-1.3/CMFDefault/skins/control/disableSyndication.py0100644000076500007650000000075307316401561022614 0ustar tseavertseaver## Script (Python) "disableSyndication" ##title=Disable Syndication for a resource ##parameters= if context.portal_syndication.isSyndicationAllowed(context): context.portal_syndication.disableSyndication(context) return context.REQUEST.RESPONSE.redirect(context.absolute_url() + '/synPropertiesForm?portal_status_message=Syndication+Disabled') else: return context.REQUEST.RESPONSE.redirect(context.absolute_url() + '/synPropertiesForm?portal_status_message=Syndication+Not+Allowed') CMF-1.3/CMFDefault/skins/control/editSynProperties.py0100644000076500007650000000066407316405354022505 0ustar tseavertseaver## Script (Python) "enableSyndication" ##title=Enable Syndication for a resource ##parameters= REQUEST=context.REQUEST pSyn = context.portal_syndication pSyn.editSyInformationProperties(context, REQUEST['updatePeriod'], REQUEST['updateFrequency'], REQUEST['updateBase'], REQUEST['max_items'], REQUEST) return REQUEST.RESPONSE.redirect(context.absolute_url() + '/synPropertiesForm?portal_status_message=Syndication+Properties+Updated.') CMF-1.3/CMFDefault/skins/control/enableSyndication.py0100644000076500007650000000074507316403040022432 0ustar tseavertseaver## Script (Python) "enableSyndication" ##title=Enable Syndication for a resource ##parameters= if context.portal_syndication.isSiteSyndicationAllowed(): context.portal_syndication.enableSyndication(context) return context.REQUEST.RESPONSE.redirect(context.absolute_url() + '/synPropertiesForm?portal_status_message=Syndication+Enabled') else: return context.REQUEST.RESPONSE.redirect(context.absolute_url() + '/synPropertiesForm?portal_status_message=Syndication+Not+Allowed') CMF-1.3/CMFDefault/skins/control/expireAuthCookie.py0100644000076500007650000000022407265143025022246 0ustar tseavertseaver## Script (Python) "expireAuthCookie" ##title=Expire Authentication Cookie ##parameters=resp, cookie_name resp.expireCookie( cookie_name, path='/') CMF-1.3/CMFDefault/skins/control/finish_portal_construction.dtml0100644000076500007650000000054307311501204024752 0ustar tseavertseaver

    Welcome to the CMF!

    The first thing you should do is visit the basic configuration form.
    Then visit the management interface and the home page.

    CMF-1.3/CMFDefault/skins/control/folder_copy.py0100644000076500007650000000075707316706123021317 0ustar tseavertseaver## Script (Python) "folder_copy" ##title=Copy object from a folder to the clipboard ##parameters= REQUEST=context.REQUEST if REQUEST.has_key('ids'): context.manage_copyObjects(REQUEST['ids'], REQUEST, REQUEST.RESPONSE) return REQUEST.RESPONSE.redirect(context.absolute_url() + '/folder_contents?portal_status_message=Item(s)+Copied.') else: return REQUEST.RESPONSE.redirect(context.absolute_url() + '/folder_contents?portal_status_message=Please+select+one+or+more+items+to+copy+first.') CMF-1.3/CMFDefault/skins/control/folder_cut.py0100644000076500007650000000073707316706123021136 0ustar tseavertseaver## Script (Python) "folder_cut" ##title=Cut objects from a folder and copy to the clipboard ##parameters= REQUEST=context.REQUEST if REQUEST.has_key('ids'): context.manage_cutObjects(REQUEST['ids'], REQUEST) return REQUEST.RESPONSE.redirect(context.absolute_url() + '/folder_contents?portal_status_message=Item(s)+Cut.') else: return REQUEST.RESPONSE.redirect(context.absolute_url() + '/folder_contents?portal_status_message=Please+select+one+or+more+items+to+cut+first.') CMF-1.3/CMFDefault/skins/control/folder_delete.py0100644000076500007650000000063607405431404021577 0ustar tseavertseaver## Script (Python) "folder_delete" ##title=Delete objects from a folder ##parameters= REQUEST=context.REQUEST ret_url = context.absolute_url() + '/folder_contents' if REQUEST.has_key( 'ids' ): context.manage_delObjects( REQUEST['ids'] ) qs = '?portal_status_message=Deleted.' else: qs = '?portal_status_message=Please+select+one+or+more+items+first.' return REQUEST.RESPONSE.redirect( ret_url + qs ) CMF-1.3/CMFDefault/skins/control/folder_localrole_edit.py0100644000076500007650000000117707510771066023327 0ustar tseavertseaver## Script (Python) "folder_localrole_edit" ##parameters=change_type ##title=Set local roles ## pm = context.portal_membership if change_type == 'add': pm.setLocalRoles( obj=context , member_ids=context.REQUEST.get('member_ids', ()) , member_role=context.REQUEST.get('member_role', '') ) else: pm.deleteLocalRoles( obj=context , member_ids=context.REQUEST.get('member_ids', ()) ) qst='?portal_status_message=Local+Roles+changed.' context.REQUEST.RESPONSE.redirect( context.absolute_url() + '/folder_localrole_form' + qst ) CMF-1.3/CMFDefault/skins/control/folder_paste.py0100644000076500007650000000072507316706123021454 0ustar tseavertseaver## Script (Python) "folder_paste" ##title=Paste objects to a folder from the clipboard ##parameters= REQUEST=context.REQUEST if context.cb_dataValid: context.manage_pasteObjects(REQUEST['__cp']) return REQUEST.RESPONSE.redirect(context.absolute_url() + '/folder_contents?portal_status_message=Item(s)+Pasted.') else: return REQUEST.RESPONSE.redirect(context.absolute_url() + '/folder_contents?portal_status_message=Copy+or+cut+one+or+more+items+to+paste+first.') CMF-1.3/CMFDefault/skins/control/folder_rename.py0100644000076500007650000000043607316706123021606 0ustar tseavertseaver## Script (Python) "folder_rename" ##title=Rename Object ##parameters= REQUEST=context.REQUEST context.manage_renameObjects(REQUEST['ids'], REQUEST['new_ids'], REQUEST) return REQUEST.RESPONSE.redirect(context.absolute_url() + '/folder_contents?portal_status_message=Item(s)+Renamed.') CMF-1.3/CMFDefault/skins/control/folder_rename_items.py0100644000076500007650000000050707316706123023006 0ustar tseavertseaver## Script (Python) "folder_rename_items" ##title=Objects for folder_rename_form ##parameters= ids = filter(lambda id,c=context.aq_explicit: hasattr(c,id), context.REQUEST.get('ids',[])) objects = map(lambda id,c=context: getattr(c,id),ids) return filter(lambda ob: ob.cb_isMoveable(), objects) CMF-1.3/CMFDefault/skins/control/isDiscussable.py0100644000076500007650000000031607403217064021574 0ustar tseavertseaver## Script (Python) "isDiscussable" ##title=Return whether the context is discussable or not. ##parameters= if hasattr(context, 'allow_discussion'): return context.allow_discussion else: return None CMF-1.3/CMFDefault/skins/control/logout.py0100644000076500007650000000042507316645436020324 0ustar tseavertseaver## Script (Python) "logout" ##title=Logout handler ##parameters= REQUEST = context.REQUEST if REQUEST.has_key('portal_skin'): context.portal_skins.clearSkinCookie() REQUEST.RESPONSE.expireCookie('__ac', path='/') return REQUEST.RESPONSE.redirect(REQUEST.URL1+'/logged_out') CMF-1.3/CMFDefault/skins/control/mail_password.py0100644000076500007650000000026407316645021021646 0ustar tseavertseaver## Script (Python) "mail_password" ##title=Mail a user's password ##parameters= REQUEST=context.REQUEST return context.portal_registration.mailPassword(REQUEST['userid'], REQUEST) CMF-1.3/CMFDefault/skins/control/personalize.py0100644000076500007650000000135007507345743021344 0ustar tseavertseaver## Script (Python) "personalize" ##title=Personalization Handler. ##parameters= REQUEST=context.REQUEST member = context.portal_membership.getAuthenticatedMember() failMessage = context.portal_registration.testPropertiesValidity(REQUEST, member) if failMessage: REQUEST.set('portal_status_message', failMessage) return context.personalize_form(context, REQUEST, portal_status_message=failMessage) member.setProperties(REQUEST) if REQUEST.has_key('portal_skin'): context.portal_skins.updateSkinCookie() qs = '/personalize_form?portal_status_message=Member+changed.' context.REQUEST.RESPONSE.redirect(context.portal_url() + qs) CMF-1.3/CMFDefault/skins/control/reconfig.py0100644000076500007650000000040707316717253020604 0ustar tseavertseaver## Script (Python) "reconfig" ##title=Reconfigure Portal ##parameters= REQUEST=context.REQUEST context.portal_properties.editProperties(REQUEST) return REQUEST.RESPONSE.redirect(context.portal_url() + '/reconfig_form?portal_status_message=CMF+Settings+changed.') CMF-1.3/CMFDefault/skins/control/register.py0100644000076500007650000000200607501374141020620 0ustar tseavertseaver## Script (Python) "register" ##title=Register a user ##parameters=password='password', confirm='confirm' REQUEST=context.REQUEST portal_properties = context.portal_properties portal_registration = context.portal_registration if not portal_properties.validate_email: failMessage = portal_registration.testPasswordValidity(password, confirm) if failMessage: REQUEST.set( 'error', failMessage ) return context.join_form( context, REQUEST, error=failMessage ) failMessage = portal_registration.testPropertiesValidity(REQUEST) if failMessage: REQUEST.set( 'error', failMessage ) return context.join_form( context, REQUEST, error=failMessage ) else: password=REQUEST.get('password') or portal_registration.generatePassword() portal_registration.addMember(REQUEST['username'], password, properties=REQUEST) if portal_properties.validate_email or REQUEST.get('mail_me', 0): portal_registration.registeredNotify(REQUEST['username']) return context.registered( context, REQUEST ) CMF-1.3/CMFDefault/skins/control/search_debug.dtml0100644000076500007650000000167407245471211021732 0ustar tseavertseaver
    &dtml-sequence-item; value="" >
    URL &dtml-sequence-item;
    ">
    CMF-1.3/CMFDefault/skins/control/setAuthCookie.py0100644000076500007650000000024707265143025021552 0ustar tseavertseaver## Script (Python) "setAuthCookie" ##title=Set Authentication Cookie ##parameters=resp, cookie_name, cookie_value resp.setCookie( cookie_name, cookie_value, path='/') CMF-1.3/CMFDefault/skins/control/synPropertiesForm.dtml0100644000076500007650000000756507301312113023022 0ustar tseavertseaver

    Default Syndication Properties

    Channel Properties
    Channel Title: &dtml-title;
    Channel Description: &dtml-description;
    Sy Module Properties
    Element Default Value
    UpdatePeriod
    UpdateFrequency &dtml-updateFrequency;" size="3">
    UpdateBase
    Max Syndicated Items &dtml-max_items;" size="3">

    Syndication is Disabled

    CMF-1.3/CMFDefault/skins/control/undo.py0100644000076500007650000000036507452444171017755 0ustar tseavertseaver## Script (Python) "undo" ##title=Undo transactions ##parameters=transaction_info context.portal_undo.undo(context, transaction_info) return context.REQUEST.RESPONSE.redirect( 'folder_contents?portal_status_message=Transaction(s)+undone' ) CMF-1.3/CMFDefault/skins/generic/0040755000076500007650000000000007524010067016362 5ustar tseavertseaverCMF-1.3/CMFDefault/skins/generic/RSS.dtml0100644000076500007650000000037607301312114017705 0ustar tseavertseaver Content-type: text/xml CMF-1.3/CMFDefault/skins/generic/TitleOrId.py0100644000076500007650000000037407501374141020575 0ustar tseavertseaver## Script (Python) "TitleOrId" ##parameters=dontCall=0 ##title=Return Title or getId if dontCall: title = context.Title id = context.id else: title = context.Title() id = context.getId() if title: return title else: return id CMF-1.3/CMFDefault/skins/generic/actions_box.dtml0100644000076500007650000000413707305577266021576 0ustar tseavertseaver
    User


    &dtml-typ;
    Status: &dtml-review_state;


    CMF-1.3/CMFDefault/skins/generic/clearCookie.py0100644000076500007650000000044207501374141021152 0ustar tseavertseaver## Script (Python) "clearCookie.py $Revision: 1.2 $" ##parameters= ##title=clear browser cookie ## REQUEST=context.REQUEST REQUEST.RESPONSE.expireCookie('folderfilter', path='/') REQUEST.RESPONSE.redirect( context.absolute_url() + '/folder_contents?portal_status_message=Filter+cleared.') CMF-1.3/CMFDefault/skins/generic/content_byline.dtml0100644000076500007650000000050407301043750022252 0ustar tseavertseaver

    Created by &dtml-creator;. &dtml-creator;. Last modified on

    CMF-1.3/CMFDefault/skins/generic/css_inline_or_link.py0100644000076500007650000000162007501374141022574 0ustar tseavertseaver## Script (Python) "css_inline_or_link" ##parameters= ##bind container=container ##bind context=context ##bind namespace=_ ##bind script=script ##bind subpath=traverse_subpath ##title=Browser detection for stylesheet handling import string stylesheet_code = '' if hasattr(context, 'stylesheet_properties'): ag = context.REQUEST.get('HTTP_USER_AGENT', '') do_inline_css = 1 sheet = context.stylesheet_properties.select_stylesheet_id if sheet: if ag[:9] == 'Mozilla/4' and string.find(ag, 'MSIE') < 0: s_obj = getattr(context, sheet) s_content = s_obj(None, _, do_inline_css=1) stylesheet_code = '' % s_content else: s_url = '%s/%s' % (context.portal_url(), sheet) stylesheet_code = '' % s_url return stylesheet_code CMF-1.3/CMFDefault/skins/generic/default_stylesheet.dtml0100644000076500007650000001421507245471211023143 0ustar tseavertseaver body { background-color: &dtml-bg_color;; color: &dtml-base_font_color;; font-family: &dtml-primary_font_family;; font-size: &dtml-base_font_size;; } a:link { color: &dtml-link_color;; text-decoration: none; } a:visited { color: &dtml-vlink_color;; text-decoration: none; } a:active { color: &dtml-alink_color; text-decoration: none; } a:hover { color: &dtml-hover_color;; } p { font-size: &dtml-base_font_size;; background-color: &dtml-bg_color;; color: &dtml-base_font_color;; font-family: &dtml-primary_font_family;; } h1 { font-size: 120%; font-weight: bold; color: &dtml-base_font_color;; } h2 { font-size: 115%; background-color: &dtml-bg_color;; color: &dtml-base_font_color;; } h3 { font-size: 110%; background-color: &dtml-bg_color;; color: &dtml-base_font_color;; } .invisible { font-size: 80%; color: &dtml-bg_color;; } .mild { color: #7f7f7f; } .DesktopStatusBar{ font-size: 80%; font-family: &dtml-secondary_font_family;; color: #ff0000; } table { width: 100%; border: 0; } table.Masthead { background-color: &dtml-primary_accent_color;; } tr.Masthead td.PortalLogo { background-color: &dtml-primary_accent_color;; text-align: left; vertical-align: top; } table.Masthead tr.Masthead td.PortalTitle { text-align: left; vertical-align: middle; } h1.PortalTitle { background-color: &dtml-primary_accent_color;; color: &dtml-primary_accent_font_color;; font-family: &dtml-secondary_font_family;; font-size: 120%; font-weight: bold; } table.Masthead tr.Navhead { background-color: &dtml-primary_accent_color;; } table.Masthead tr.Navhead td.NavBar { color: &dtml-secondary_accent_color;; font-size: 80%; font-weight: normal; text-align: right; vertical-align: bottom; } table.Masthead tr.Navhead td.NavBar a:link { color: &dtml-primary_accent_link_color;; } table.Masthead tr.Navhead td.NavBar a:visited { color: &dtml-primary_accent_vlink_color;; } table.Masthead tr.Navhead td.NavBar a:active { color: &dtml-primary_accent_alink_color;; } table.Masthead tr.Navhead td.NavBar a:hover { color: &dtml-hover_color;; } td.SideBar { width: 15%; vertical-align: top; } table.ActionBox { font-family: &dtml-primary_font_family;; background-color: &dtml-primary_accent_color;; } table.ActionBox tr td { font-size: 70%; } table.ActionBox tr td.ActionTitle { font-weight: bold } table.ActionBox tr.GuestActions { background-color: &dtml-guest_actions_color;; } table.ActionBox tr.GuestActions td a:link { color: &dtml-guest_actions_link_color;; text-decoration: none; } table.ActionBox tr.GuestActions td a:visited { color: &dtml-guest_actions_link_color;; text-decoration: none; } table.ActionBox tr.GuestActions td a:active { color: &dtml-guest_actions_link_color;; text-decoration: none; } table.ActionBox tr.GuestActions td a:hover { color: &dtml-hover_color;; text-decoration: none; } table.ActionBox tr.MemberActions { background-color: &dtml-secondary_accent_color;; } table.ActionBox tr.MemberActions td { color: &dtml-secondary_accent_font_color;; } table.ActionBox tr.MemberActions td a:link { color: &dtml-secondary_accent_link_color;; text-decoration: none; } table.ActionBox tr.MemberActions td a:visited { color: &dtml-secondary_accent_vlink_color;; text-decoration: none; } table.ActionBox tr.MemberActions td a:active{ color: &dtml-secondary_accent_alink_color;; text-decoration: none; } table.ActionBox tr.MemberActions td a:hover{ color: &dtml-hover_color;; text-decoration: none; } td.Desktop { vertical-align: top; } td.Desktop table tr { vertical-align: top; } div.Desktop p { font-size: 100%; margin-right:100pt; font-family: &dtml-primary_font_family;; } div.Desktop h1 { font-size: 120%; margin-right:100pt; font-family: &dtml-secondary_font_family;; } div.AuthWarning { text-align: center; font-style: italic; } div.AuthWarning table { border: 0; } div.AuthWarning tr.Hot { color: #FF0000; } div.Error { color: #FF0000; } p.DesktopStatusBar { font-size: 100%; font-family: &dtml-secondary_font_family;; font-style: italic; font-weight: bold; } table.FormLayout { width: 80%; } table.FormLayout tr { vertical-align: top; } table.FormLayout tr th.TextField { vertical-align: top; } table.FormLayout tr td.TextField { vertical-align: top; } table.FormLayout th { text-align: right; } table.FormLayout dl.FieldHelp dd { font-size: 70%; } table.ContentsList { } table.ContentsList tr td img { border: 0; } table.SearchResults { width: auto; } table.SearchResults tr th { text-align: left; } table.SearchResults tr td img { border: 0; } table.Wizard { width: auto; } table.Wizard tr { vertical-align: top; } table.Wizard tr th { text-align: right; } div.NewsBar { text-align: right; } table.NewsItems { border: 0; } td.NewsBorder { background-color: &dtml-primary_accent_color;; } td.NewsListing { background-color: &dtml-primary_accent_color;; color: &dtml-bg_color;; } .NewsLeadin { background-color: &dtml-bg_color;; color: &dtml-primary_accent_color;; } .NewsByLine { background-color: &dtml-primary_accent_color;; color: &dtml-bg_color;; } .NewsDateline { background-color: &dtml-primary_accent_color;; color: &dtml-bg_color;; } td.NewsTitle { background-color: &dtml-primary_accent_color;; color: &dtml-primary_accent_font_color;; text-align: center; font-size: 90%; font-weight: bold; } tr.NewsItemRow td { background-color: &dtml-highlight_color;; font-size: 70%; } p.NewsHeadline { background-color: &dtml-primary_accent_color;; } td.ListName { background-color: &dtml-primary_accent_color;; color: &dtml-primary_accent_font_color;; font-weight: bold; } td.ListDefinition { font-style: italic; } CMF-1.3/CMFDefault/skins/generic/discussion_reply.py0100644000076500007650000000065507507417767022360 0ustar tseavertseaver## Script (Python) "discussion_reply" ##parameters=title,text ##title=Reply to content Creator = context.portal_membership.getAuthenticatedMember().getUserName() replyID = context.createReply( title = title , text = text , Creator = Creator ) target = '%s/%s' % (context.absolute_url(), replyID) context.REQUEST.RESPONSE.redirect(target) CMF-1.3/CMFDefault/skins/generic/discussion_reply_form.dtml0100644000076500007650000000176307507417767023714 0ustar tseavertseaver
    ">
    Subject (Title)
    Reply body
    CMF-1.3/CMFDefault/skins/generic/discussion_reply_preview.dtml0100644000076500007650000000161107507417767024422 0ustar tseavertseaver

    "> "> "> "> ">
    CMF-1.3/CMFDefault/skins/generic/discussion_thread_view.dtml0100644000076500007650000000116407264414662024021 0ustar tseavertseaver

    &dtml-title;

    Above in thread: 5">... : &dtml-title; : &dtml-title;, by on CMF-1.3/CMFDefault/skins/generic/doFormSearch.py0100644000076500007650000000141507523015700021304 0ustar tseavertseaver## Script (Python) "doFormSearch" ##parameters=REQUEST ##title=Pre-process form variables, then return catalog query results. ## vars = REQUEST.form form_vars = {} skip_vars = [] select_vars = ( 'review_state' , 'Subject' , 'portal_type' ) date_vars = ('created', ) epoch = DateTime("2025/01/01 00:00:00 GMT") for k, v in vars.items(): if k in select_vars: if same_type( v, [] ): v = filter( None, v ) if not v: continue if k in date_vars: if v == epoch and vars.get(k+'_usage') == 'range:min': skip_vars.append(k+'_usage') continue form_vars[ k ] = v for k in skip_vars: del form_vars[k] return context.portal_catalog( form_vars ) CMF-1.3/CMFDefault/skins/generic/filterCookie.py0100644000076500007650000000101607501374141021347 0ustar tseavertseaver## Script (Python) "filterCookie.py $Revision: 1.2 $" ##parameters= ##title=Manage filter cookie ## REQUEST=context.REQUEST if REQUEST.get('clear_view_filter', 0): context.clearCookie() REQUEST.set('folderfilter', '') REQUEST.set('close_filter_form', '1') elif REQUEST.get('set_view_filter', 0): filter=context.encodeFolderFilter(REQUEST) REQUEST.RESPONSE.setCookie('folderfilter', filter, path='/', expires='Wed, 19 Feb 2025 14:28:00 GMT') REQUEST.set('folderfilter', '%s' % filter) CMF-1.3/CMFDefault/skins/generic/folder_add.dtml0100644000076500007650000000132407245471211021326 0ustar tseavertseaver

    Add Folder

    A Folder contains other objects. Use Folders to organize your web objects in to logical groups.

    Id
    Title



    CMF-1.3/CMFDefault/skins/generic/folder_contents.dtml0100644000076500007650000001145007364776514022454 0ustar tseavertseaver folderfilter cookie maintenance. Folder contents display.

    Desktop


    [Link] Up to Root
    &dtml-Type; &dtml-getId; (&dtml-title;)
    End of first column
    No batch
    End of first column
    Previous items
    Next items
    End of listing table
    CMF-1.3/CMFDefault/skins/generic/folder_factories.dtml0100644000076500007650000000205507245471211022557 0ustar tseavertseaver

    Add Content




    ID:
    CMF-1.3/CMFDefault/skins/generic/folder_filter_form.dtml0100644000076500007650000000447507511373501023117 0ustar tseavertseaver
    Contents View Filter
    Subject: " />
    Content Type:
    This is a filtered list.
    CMF-1.3/CMFDefault/skins/generic/folder_localrole_form.dtml0100644000076500007650000000653407325113131023576 0ustar tseavertseaver

    &dtml-message;


    Search results

    Select Member(s) and a role to assign:

      User ID Email address
    &dtml-username; &dtml-email;
     
    Role to assign:
     
     

    Assign local roles

    Search by
    Search Term


    Currently assigned local roles

    These users currently have local roles assigned in this folder:

      User Name Role(s)
      &dtml-sequence-key;
     

    CMF-1.3/CMFDefault/skins/generic/folder_rename_form.dtml0100644000076500007650000000173207364776514023113 0ustar tseavertseaver

    Rename Items

    to

    You must select renamable items to rename.

    CMF-1.3/CMFDefault/skins/generic/iconHTML.py0100644000076500007650000000123107501374141020344 0ustar tseavertseaver## Script (Python) "iconHTML" ##bind container=container ##bind context=context ##bind namespace=_ ##bind script=script ##bind subpath=traverse_subpath ##title=Returns the HTML for the current object's icon, if it is available ##parameters= # dont you just wish namespaces had a get(name,default) method?! ;-) try: iconURL=context.getIcon() except KeyError: try: iconURL=_['icon'] except: iconURL='' if iconURL: try: Type = context.Type() except: Type='' return '%s' % (iconURL, Type) return '' CMF-1.3/CMFDefault/skins/generic/index_html.dtml0100644000076500007650000000536607501374141021407 0ustar tseavertseaver

    Documents, Images, and Files

    Links

    Folders

    Topics

    CMF-1.3/CMFDefault/skins/generic/itemRSS.dtml0100644000076500007650000000065707301312114020566 0ustar tseavertseaver <dtml-var Title> CMF-1.3/CMFDefault/skins/generic/join_form.dtml0100644000076500007650000000575007356376605021252 0ustar tseavertseaver

    Become a member

    You are already a member. You may use the personalization form to change your membership information.

    Becoming a member gives you the ability to personalize the site and participate in the community.

    It does not cost any money to become a member and your email and other personal information will remain private.

    You must submit a valid email address. This address will be used to send you your randomly-generated password. Once you have logged in with this password, you may change it to anything you like.

    These items do not actually exist (yet?)
    Login Name
    Email Address
    Password
    Password (confirm)
    Mail Password?
    Full Name
    (Optional)
    Company
    (Optional)

    CMF-1.3/CMFDefault/skins/generic/logged_in.dtml0100644000076500007650000000550207445403530021175 0ustar tseavertseaver

    Login failure

    You are not currently logged in. Your username and or password may be incorrect. Your browser may also not be configured to accept HTTP cookies. If you need help please contact &dtml-email_from_address;.

    First login by this user. Display message and offer password changer form. Init login times to now

    Welcome!

    This is the first time that you've logged in to &dtml-title;. Before you start exploring you need to change your original password. This will ensure that the password we sent you via email cannot be used in a malicious manner.

    Please use the form below to change your password.

    Username &dtml-member;
    New password
    Confirm new password

    Login success

    Welcome. You are currently logged in. /Members//update_html">personalization page.-->

    CMF-1.3/CMFDefault/skins/generic/logged_out.dtml0100644000076500007650000000046107245471211021374 0ustar tseavertseaver

    You have been logged out. You are logged in outside the portal. You may need to log out of the Zope management interface.

    CMF-1.3/CMFDefault/skins/generic/login_form.dtml0100644000076500007650000000302107245471211021372 0ustar tseavertseaver

    Log in

    Name ">
    Password

    I forgot my password!

    Having trouble logging in? Make sure to enable cookies in your web browser.

    Don't forget to logout or exit your browser when you're done.

    Setting the 'Remember my name' option will set a cookie with your username, so that when you next log in, your user name will already be filled in for you.

    CMF-1.3/CMFDefault/skins/generic/mail_password_form.dtml0100644000076500007650000000120507365517155023143 0ustar tseavertseaver

    Don't panic!

    Just enter your username below, click Send, and your password will be mailed to you if you gave a valid email address when you signed on.

    If this will not work for you (for example, if you forget your member name or didn't enter your email address) send email to &dtml-email_from_address;.

    CMF-1.3/CMFDefault/skins/generic/mail_password_response.dtml0100644000076500007650000000022507260176000024017 0ustar tseavertseaver

    Your password has been mailed. It should arrive in your mailbox momentarily.

    CMF-1.3/CMFDefault/skins/generic/mail_password_template.dtml0100644000076500007650000000051107401270217023774 0ustar tseavertseaver From: "&dtml-email_from_name;" <&dtml-email_from_address;> To: Errors-to: <&dtml-email_from_address;> Subject: Membership reminder Your password: Request made by IP at CMF-1.3/CMFDefault/skins/generic/menu.dtml0100644000076500007650000000057307245471212020215 0ustar tseavertseaver
    CMF-1.3/CMFDefault/skins/generic/metadata_help.dtml0100644000076500007650000001247707245471212022047 0ustar tseavertseaver

    Dublin Core Metadata

    . . ). . . . .
    Title
      the standard Zope 'title' attribute; we should look at making it mandatory for all PortalContent derivatives.
    Creator
      where possible, this should be one or more full names, of either persons or organizations. The current implementation finds the first user in the list returned by 'get_local_roles' who has the 'Owner' role; userids are not considered appropriate for this field by the DCI.
    Subject
      this is supposed to be drawn from a controlled list of keywords (e.g., selected from a multi-select list used across the whole site)
    Description
      a short summary, an abstract, or a table-of-contents are all considered acceptable. We might look at making this required, as well, at least for some kinds of content.
    Publisher
      a site-wide property, should be done through acquisition (do I smell a 'portal_metadata' tool about to appear?) Again, this is supposed to be a formal name.
    Contributor
      used to convey others besides the Creator who have contributed to the document (the current implementation aliases 'Creator', which is not what DCI intends)
    Date
      this one has modifiers, of which the approved set is: 'Created', 'Valid', 'Available', 'Issued', and 'Modified'. I propose extending the interface to include CreationDate(), EffectiveDate(), ExpirationDate(), and ModificationDate(). The current Date() could just return the CreationDate(), while the DCI 'Valid' and 'Available' would be ranges derived from EffectiveDate() and ExpirationDate(
    Type
      like the Zope 'meta_type', this is the main conceptual classification; 'meta_type' is often spelled identically to the class, which makes it less appropriate for the DCI usage.
    Format
      the kind of physical representation, e.g., 'text/html'
    Identifier
      should be the fully-qualified URL of the document (the current implementation returns the object's id, which is only required to be unique within its container)
    Language
      'en-us', 'pt-br', 'de', etc. Should be set at creation, with an appropriate default (and a picklist of values)
    Source
      the original from which a piece of content is derived. I'd like to ignore this one.
    Relation
      more relationships to other documents. Again, I'd like to ignore it (ZopeStudio and other such tools need this, however, to build site maps)
    Coverage
      geographic/chronological/jurisdictional scope. Again, ignore.
    Rights
      copyright and other IP information related to the document. Most portals should care about this: witness the brouhaha on Slashdot over the compilation of the Hellmouth postings into a book.
    CMF-1.3/CMFDefault/skins/generic/news_box.dtml0100644000076500007650000000200107511373501021057 0ustar tseavertseaver
     News
    "> &dtml-Title;
    No news is no news.
    More...
    CMF-1.3/CMFDefault/skins/generic/password_form.dtml0100644000076500007650000000157507245471212022141 0ustar tseavertseaver

    Change your Password

    Username &dtml-member;
    New password
    Confirm new password
    Domains
    If you do not know what this field is for, leave it blank.

    CMF-1.3/CMFDefault/skins/generic/personalize_form.dtml0100644000076500007650000000470207361070616022627 0ustar tseavertseaver You must be logged in to view this resource.

    Member Preferences

    &dtml-msg;


    Click here to change your password.

    Email address ">
    Listed status
    You will show up on the public membership roster.
    You will not show up on the public membership roster. Your Member folder will still be publicly accessible unless you change its security settings.
    Skin
    &dtml-sequence-item; ">
    CMF-1.3/CMFDefault/skins/generic/recent_news.dtml0100644000076500007650000000250107511373501021554 0ustar tseavertseaver

    Next &dtml-previous-sequence-size; more recent articles

    &dtml-Title;
    By &dtml-Creator;

    No news is good news!

    Next &dtml-next-sequence-size; older articles

    CMF-1.3/CMFDefault/skins/generic/reconfig_form.dtml0100644000076500007650000000471407245471212022071 0ustar tseavertseaver

    Configure the Portal

    This form is used to set the portal configuration options.

    Portal 'From' name
    When the portal generates mail, it uses this name as its (apparent) sender.
    Portal 'From' address
    When the portal generates mail, it uses this address as its (apparent) return address.
    SMTP server
    This is the address of your local SMTP (out-going mail) server.
    Portal title
    This is the title which appears at the top of every portal page.
    Portal description
    This description is made available via syndicated content and elsewhere. It should be fairly brief.
    Password policy Generate and email members' initial password
    Allow members to select their initial password
    CMF-1.3/CMFDefault/skins/generic/registered.dtml0100644000076500007650000000113307245471212021377 0ustar tseavertseaver

    Success!

    You have been registered as a member.

    You will receive an email shortly containing your password and instructions on how to activate your membership.

    You can log on immediately by clicking here.

    Return to homepage

    CMF-1.3/CMFDefault/skins/generic/registered_notify_template.dtml0100644000076500007650000000150107260176000024653 0ustar tseavertseaver To: From: "&dtml-email_from_name;" <&dtml-email_from_address;> Subject: Portal Membership Information You have been registered as a member of , which allows you to personalize your view of the Portal website and participate in the community. This describes the purpose of the portal: Visit us at &dtml-portal_url; Your login id and password are: Login ID: Password: You can use this URL to log on: &dtml.url-logged_in;?__ac_name=&dtml.url_quote-username;&__ac_password=&dtml.url_quote-password; Be aware that this URL might wrap over two lines. If your browser shows an error message when you try to access the URL please make sure that you put in the complete string. Portal Admin CMF-1.3/CMFDefault/skins/generic/roster.dtml0100644000076500007650000000423207260214525020562 0ustar tseavertseaver

    Portal Members

    We don't have a good interface for querying/setting roles We don't have a good interface for querying/setting roles We don't have a good interface for querying/setting roles

    Member Listed? Roles

    &dtml-id; &dtml-id; Yes No

    Previous Previous Next Next of &dtml-sequence-length; members.

    CMF-1.3/CMFDefault/skins/generic/rssBody.dtml0100644000076500007650000000314707301312114020662 0ustar tseavertseaver &dtml-Title; &dtml-portal_url; &dtml-Description; Start Items Elements End Items Elements Start Item Elements End Item Elements CMF-1.3/CMFDefault/skins/generic/rssDisabled.dtml0100644000076500007650000000014307301312114021465 0ustar tseavertseaver

    CMF-1.3/CMFDefault/skins/generic/search.dtml0100644000076500007650000000331107415453453020515 0ustar tseavertseaver

    Search Results

    Found items matching "&dtml-SearchableText;".


    Title Type Date
    [&dtml.missing-Type;] (No title) &dtml.missing-Type; &dtml-Date;
      (No description)

    There are no items matching your specified criteria.

    Next &dtml-nextSize; items

    CMF-1.3/CMFDefault/skins/generic/search_form.dtml0100644000076500007650000000745107511373501021541 0ustar tseavertseaver

    Search portal

    Review Status
    As a reviewer, you may search for items based on their review state. If you wish to constrain results to items in certain states, select them from this list.
    Full Text
    For a simple text search, enter your search term here. Multiple words may be found by combining them with AND and OR. This will find text in items' contents, title and description.
    Title
    Subject
    Description
    You may also search the items' descriptions and titles specifically. Multiple words may be found by combining them with AND and OR.
    Find new items since...
    You may find only recent items by selecting a time-frame.
    Item type
    You may limit your results to particular kinds of items by selecting them above. To find all kinds of items, do not select anything.
    Creator
    To find items by a particular user only, enter their username above. Note that you must enter their username exactly.

    CMF-1.3/CMFDefault/skins/generic/showThreads.dtml0100644000076500007650000000110507313201442021524 0ustar tseavertseaver showThreads.dtml Generate a simple tree view of message threads to include on a page. The following lamosity brought to you courtesy of the tree tag, which has to use the 'URL' value from the MD. &dtml-Title;, by on CMF-1.3/CMFDefault/skins/generic/simple_metadata.dtml0100644000076500007650000000440307245471212022376 0ustar tseavertseaver

    &dtml-message;


    CMF-1.3/CMFDefault/skins/generic/standard_html_footer.dtml0100644000076500007650000000015107245471212023443 0ustar tseavertseaver CMF-1.3/CMFDefault/skins/generic/standard_html_header.dtml0100644000076500007650000000356307312205142023377 0ustar tseavertseaver <dtml-with portal_properties>&dtml-title;</dtml-with ><dtml-if name="Title">: &dtml-Title;</dtml-if>

     
    Warning!
    You are presently logged in as a user from outside this portal. Many parts of the portal will not work! You may have to shut down and relaunch your browser to log out, depending on how you originally logged in.

    &dtml-portal_status_message;

    CMF-1.3/CMFDefault/skins/generic/standard_top_bar.dtml0100644000076500007650000000257207472536214022566 0ustar tseavertseaver

    &dtml-title;: &dtml-Title;

    CMF-1.3/CMFDefault/skins/generic/stylesheet_properties.props0100644000076500007650000000174707311501204024112 0ustar tseavertseaveralink_color:string=#FFFFFF base_font_color:string=#000000 base_font_size:string=8pt bg_color:string=#FFFFFF guest_actions_color:string=#888888 guest_actions_link_color:string=#660000 highlight_color:string=#AAAAAA highlight_font_color:string=#000000 hover_color:string=#000066 link_color:string=#0000AA primary_accent_alink_color:string=#FFFFFF primary_accent_color:string=#6699CC primary_accent_font_color:string=#FFFFFF primary_accent_link_color:string=#FFFFFF primary_accent_text_color:string=#000066 primary_accent_vlink_color:string=#FFFFFF primary_font_family:string=Verdana, Arial, Helvetica, sans-serif secondary_accent_alink_color:string=#FFFFFF secondary_accent_color:string=#6699CC secondary_accent_font_color:string=#000000 secondary_accent_link_color:string=#FFFFFF secondary_accent_vlink_color:string=#FFFFFF secondary_font_family:string=Arial, Verdana, Helvetica, sans-serif select_stylesheet_id:string=default_stylesheet title:string=Classic CMF Style vlink_color:string=DarkMagenta CMF-1.3/CMFDefault/skins/generic/truncID.py0100644000076500007650000000027107501374141020302 0ustar tseavertseaver## Script (Python) "truncID.py $Revision: 1.2 $" ##parameters=objID, size ##title=return truncated objID ## if len(objID) > size: return objID[:size] + '...' else: return objID CMF-1.3/CMFDefault/skins/generic/undo_form.dtml0100644000076500007650000000461707245471212021244 0ustar tseavertseaver

    Undo Transactions

    This application's transactional feature allows you to easily undo changes made to the application's settings or data. You can revert the application to a "snapshot" of it's state at a previous point in time.

    Select one or more transactions below and then click on the "Undo" button to undo the transactions. Note that even though a transaction is shown below, you will not be able to undo it if later transactions modified objects that were modified by the transaction.

    &last_transaction:int=&PrincipiaUndoBatchSize:int="> Later Transactions
    by Zope on at
    &PrincipiaUndoBatchSize:int="> Earlier Transactions
    There are no transactions that can be undone.
    CMF-1.3/CMFDefault/skins/generic/viewThreadsAtBottom.dtml0100644000076500007650000000064507323144474023214 0ustar tseavertseaver viewThreadsAtBottom.dtml Display the message threads with a simple header. The header text really should be defined by an acquired property or some such...

    Comments:

    CMF-1.3/CMFDefault/skins/no_css/0040755000076500007650000000000007524010067016232 5ustar tseavertseaverCMF-1.3/CMFDefault/skins/no_css/stylesheet_properties.props0100644000076500007650000000007407312205142023755 0ustar tseavertseaverselect_stylesheet_id:string= base_font_color:string=#000000 CMF-1.3/CMFDefault/skins/nouvelle/0040755000076500007650000000000007524010067016577 5ustar tseavertseaverCMF-1.3/CMFDefault/skins/nouvelle/nouvelle_stylesheet.dtml0100644000076500007650000001401507245471212023564 0ustar tseavertseaver body { background-color: &dtml-bg_color;; color: &dtml-base_font_color;; font-family: &dtml-primary_font_family;; font-size: &dtml-base_font_size;; } a:link { color: &dtml-link_color;; text-decoration: none; } a:visited { color: &dtml-vlink_color;; text-decoration: none; } a:active { color: &dtml-alink_color; text-decoration: none; } a:hover { color: &dtml-hover_color;; } p { font-size: &dtml-base_font_size;; background-color: &dtml-bg_color;; color: &dtml-base_font_color;; font-family: &dtml-primary_font_family;; } h1 { font-size: 120%; font-weight: bold; color: &dtml-secondary_accent_color;; } h2 { font-size: 115%; background-color: &dtml-bg_color;; color: &dtml-base_font_color;; } h3 { font-size: 110%; background-color: &dtml-bg_color;; color: &dtml-base_font_color;; } .invisible { font-size: 80%; color: &dtml-bg_color;; } .mild { color: #7f7f7f; } .DesktopStatusBar{ font-size: 80%; font-family: &dtml-secondary_font_family;; color: #ff0000; } table { width: 100%; border: 0; } table.Masthead { background-color: &dtml-primary_accent_color;; } tr.Masthead td.PortalLogo { background-color: &dtml-primary_accent_color;; text-align: left; vertical-align: top; } table.Masthead tr.Masthead td.PortalTitle { text-align: left; vertical-align: middle; } table.Masthead tr.Masthead td.PortalTitle h1 { background-color: &dtml-primary_accent_color;; color: &dtml-primary_accent_font_color;; font-family: &dtml-secondary_font_family;; font-size: 120%; font-weight: bold; } table.Masthead tr.Navhead { background-color: &dtml-primary_accent_color;; } table.Masthead tr.Navhead td.NavBar { color: &dtml-secondary_accent_color;; font-size: 80%; font-weight: normal; text-align: right; vertical-align: bottom; } table.Masthead tr.Navhead td.NavBar a:link { color: &dtml-primary_accent_link_color;; } table.Masthead tr.Navhead td.NavBar a:visited { color: &dtml-primary_accent_vlink_color;; } table.Masthead tr.Navhead td.NavBar a:active { color: &dtml-primary_accent_alink_color;; } table.Masthead tr.Navhead td.NavBar a:hover { color: &dtml-hover_color;; } td.SideBar { width: 10%; vertical-align: top; } table.ActionBox { font-family: &dtml-primary_font_family;; background-color: &dtml-primary_accent_color;; } table.ActionBox tr td { font-size: 70%; } table.ActionBox tr.GuestActions { background-color: &dtml-guest_actions_color;; } table.ActionBox tr.GuestActions td a:link { color: &dtml-guest_actions_link_color;; text-decoration: none; } table.ActionBox tr.GuestActions td a:visited { color: &dtml-guest_actions_link_color;; text-decoration: none; } table.ActionBox tr.GuestActions td a:active { color: &dtml-guest_actions_link_color;; text-decoration: none; } table.ActionBox tr.GuestActions td a:hover { color: &dtml-hover_color;; text-decoration: none; } table.ActionBox tr.MemberActions { background-color: &dtml-secondary_accent_color;; } table.ActionBox tr.MemberActions td { color: &dtml-secondary_accent_font_color;; } table.ActionBox tr td.ActionTitle { font-weight: bold } table.ActionBox tr.MemberActions td a:link { color: &dtml-secondary_accent_link_color;; text-decoration: none; } table.ActionBox tr.MemberActions td a:visited { color: &dtml-secondary_accent_vlink_color;; text-decoration: none; } table.ActionBox tr.MemberActions td a:active{ color: &dtml-secondary_accent_alink_color;; text-decoration: none; } table.ActionBox tr.MemberActions td a:hover{ color: &dtml-hover_color;; text-decoration: none; } td.Desktop { vertical-align: top; } td.Desktop table tr { vertical-align: top; } div.Desktop p { font-size: 100%; margin-right:100pt; font-family: &dtml-primary_font_family;; } div.Desktop h1 { font-size: 120%; margin-right:100pt; font-family: &dtml-secondary_font_family;; } div.AuthWarning { text-align: center; font-style: italic; } div.AuthWarning table { border: 0; } div.AuthWarning tr.Hot { color: #FF0000; } div.Error { color: #FF0000; } p.DesktopStatusBar { font-size: 100%; font-family: &dtml-secondary_font_family;; font-style: italic; font-weight: bold; } table.FormLayout { width: auto } table.FormLayout tr { vertical-align: top; } table.FormLayout th { text-align: right; } table.FormLayout dl.FieldHelp dd { font-size: 70%; } table.ContentsList { } table.ContentsList tr td img { border: 0; } table.SearchResults { width: auto; } table.SearchResults tr th { text-align: left; } table.SearchResults tr td img { border: 0; } table.Wizard { width: auto; } table.Wizard tr { vertical-align: top; } table.Wizard tr th { text-align: right; } table.NewsItems { border: 0; } td.NewsBorder { background-color: &dtml-primary_accent_color;; } td.NewsListing { background-color: &dtml-primary_accent_color;; color: &dtml-bg_color;; } .NewsLeadin { background-color: &dtml-bg_color;; color: &dtml-primary_accent_color;; } .NewsByLine { background-color: &dtml-primary_accent_color;; color: &dtml-bg_color;; } .NewsDateline { background-color: &dtml-primary_accent_color;; color: &dtml-bg_color;; } td.NewsTitle { background-color: &dtml-primary_accent_color;; color: &dtml-primary_accent_font_color;; text-align: center; font-size: 90%; font-weight: bold; } tr.NewsItemRow td { background-color: &dtml-highlight_color;; font-size: 70%; } p.NewsHeadline { background-color: &dtml-primary_accent_color;; } td.ListName { background-color: &dtml-primary_accent_color;; color: &dtml-primary_accent_font_color;; font-weight: bold; } td.ListDefinition { font-style: italic; } CMF-1.3/CMFDefault/skins/nouvelle/stylesheet_properties.props0100644000076500007650000000173407245471212024336 0ustar tseavertseaveralink_color:string=#FF0000 base_font_color:string=#000000 base_font_size:string=8pt bg_color:string=#FFFFFF guest_actions_color:string=#FFEEEE guest_actions_link_color:string=#660000 highlight_color:string=#888888 highlight_font_color:string=#000000 hover_color:string=#FF0000 link_color:string=#c4bd58 primary_accent_alink_color:string=#FF0000 primary_accent_color:string=#000000 primary_accent_font_color:string=#A28144 primary_accent_link_color:string=#A28144 primary_accent_text_color:string=#A28144 primary_accent_vlink_color:string=#A28144 primary_font_family:string=Arial, Verdana, Helvetica, sans-serif secondary_accent_alink_color:string=#FF0000 secondary_accent_color:string=#A28144 secondary_accent_font_color:string=#000000 secondary_accent_link_color:string=#FFFFFF secondary_accent_vlink_color:string=#FFFFFF secondary_font_family:string=Verdana, Arial, Helvetica, sans-serif select_stylesheet_id:string=nouvelle_stylesheet title:string=New Style vlink_color:string=#c4bd58 CMF-1.3/CMFDefault/skins/zpt_content/0040755000076500007650000000000007524010067017315 5ustar tseavertseaverCMF-1.3/CMFDefault/skins/zpt_content/content_hide_form.pt0100644000076500007650000000372607511034314023352 0ustar tseavertseaver

    Hide Me

    Use this form to hide a content item by setting its status to Private, thereby making it unavailable to other portal members and visitors.

    Status This item is currently in Private status.
    Comments

    Reviewing history

    (effective: ) by Actor
    Comments
    CMF-1.3/CMFDefault/skins/zpt_content/content_publish_form.pt0100644000076500007650000000416207452444172024115 0ustar tseavertseaver

    Publish Item

    A published item is available to the general member base and anonymous visitors.

    Another way to control the visibility of an item is with its effective date. An item is not publicly available before its effective date, even if its status is published.

    Status This item is currently in Private status.
    Comments

    Reviewing history

    (effective: ) by Actor
    Comments
    CMF-1.3/CMFDefault/skins/zpt_content/content_reject_form.pt0100644000076500007650000000372107452444172023723 0ustar tseavertseaver

    Reject Me

    Use this form to reject the publication of a content item and set its status to Private, thereby making it unavailable to other portal members and visitors.

    Status This item is currently in Private status.
    Comments

    Reviewing history

    (effective: ) by Actor
    Comments
    CMF-1.3/CMFDefault/skins/zpt_content/content_retract_form.pt0100644000076500007650000000374307452444172024117 0ustar tseavertseaver

    Retract Me

    Use this form to retract a content item by setting its status to Private, thereby making it unavailable to other portal members and visitors.

    Status This item is currently in Private status.
    Comments

    Reviewing history

    (effective: ) by Actor
    Comments
    CMF-1.3/CMFDefault/skins/zpt_content/content_show_form.pt0100644000076500007650000000424207511034314023413 0ustar tseavertseaver

    Make Visible Item

    A Visible item is available other portal members and visitors, however it won't show up in the list of published items.

    Another way to control the visibility of an item is with its effective date. An item is not publicly available before its effective date, even if its status is published.

    Status This item is currently in Private status.
    Comments

    Reviewing history

    (effective: ) by Actor
    Comments
    CMF-1.3/CMFDefault/skins/zpt_content/content_status_history.pt0100644000076500007650000000357607403772236024541 0ustar tseavertseaver

    Content Item status history

    An item's status (also called its review state) determines who can see it. A private item can only be viewed by its Owner and by the site management. Only published items are available to the general member base and anonymous visitors. To make an item published, it has to be reviewed by one of the site's Reviewers. You can request that an item be reviewed by setting its status to pending.
    Another way to control the visibility of an item is with its effective date. An item is not publicly available before its effective date, even if its status is published.

    Current state

    This item is in Private state.

    Reviewing history

    (effective: ) by Actor
    Comments
    This item has not had any status changes.
    CMF-1.3/CMFDefault/skins/zpt_content/content_submit_form.pt0100644000076500007650000000450707452444172023755 0ustar tseavertseaver

    Submit Me for Review


    To make an item published, it has to be reviewed by one of the site's reviewers. A published item is available to the general member base and anonymous visitors.

    Another way to control the visibility of an item is with its effective date. An item is not publicly available before its effective date, even if its status is published.

    Status This item is currently in Private status.
    Comments

    Reviewing history

    (effective: ) by Actor
    Comments
    CMF-1.3/CMFDefault/skins/zpt_content/discussionitem_view.pt0100644000076500007650000000175707403772236023776 0ustar tseavertseaver

    Document Title

    Document Description goes here.
    By Me
    Body
    Dicussions
    CMF-1.3/CMFDefault/skins/zpt_content/document_edit_form.pt0100644000076500007650000000364107452444172023541 0ustar tseavertseaver

    Edit ID

    Title Title
    Description Description
    Format
    Upload
    Edit

    CMF-1.3/CMFDefault/skins/zpt_content/document_view.pt0100644000076500007650000000160507403772236022542 0ustar tseavertseaver

    Document Title

    Document Description goes here.
    By Me
    Cooked Body
    CMF-1.3/CMFDefault/skins/zpt_content/favorite_view.pt0100644000076500007650000000174507415457271022552 0ustar tseavertseaver

    Document Title

    Document Description goes here.
    By Me

    Link: /index_html

    CMF-1.3/CMFDefault/skins/zpt_content/file_edit_form.pt0100644000076500007650000000217307452444172022641 0ustar tseavertseaver

    Edit My ID

    Title My Title
    Description My Description
    Content type html/text
    Upload file

    CMF-1.3/CMFDefault/skins/zpt_content/file_view.pt0100644000076500007650000000271507403772236021646 0ustar tseavertseaver

    Document Title

    Document Description goes here.
    By Me

    File Properties

    Filename My ID
    Size 1024K
    Content-type application/gzip

    Download File

    Download File

    CMF-1.3/CMFDefault/skins/zpt_content/folder_edit_form.pt0100644000076500007650000000210707452444172023172 0ustar tseavertseaver

    Edit: My ID

    Title
    Description

    CMF-1.3/CMFDefault/skins/zpt_content/folder_view.pt0100644000076500007650000000077707520005160022171 0ustar tseavertseaver
    CMF-1.3/CMFDefault/skins/zpt_content/full_metadata_edit_form.pt0100644000076500007650000000752607521252112024517 0ustar tseavertseaver

    CMF-1.3/CMFDefault/skins/zpt_content/image_edit_form.pt0100644000076500007650000000225107452444172023001 0ustar tseavertseaver

    Edit My ID

    Title My Title
    Description My Description
    Content type text/html
    Upload image

    CMF-1.3/CMFDefault/skins/zpt_content/image_view.pt0100644000076500007650000000157407403772236022013 0ustar tseavertseaver

    Document Title

    Document Description goes here.
    By Me
    Tag
    CMF-1.3/CMFDefault/skins/zpt_content/link_edit_form.pt0100644000076500007650000000174507452444172022663 0ustar tseavertseaver

    CMF-1.3/CMFDefault/skins/zpt_content/link_view.pt0100644000076500007650000000175407403772236021666 0ustar tseavertseaver

    Document Title

    Document Description goes here.
    By Me
    CMF-1.3/CMFDefault/skins/zpt_content/metadata_edit_form.pt0100644000076500007650000000552407507426226023506 0ustar tseavertseaver

    CMF-1.3/CMFDefault/skins/zpt_content/newsitem_edit_form.pt0100644000076500007650000000362007452444172023553 0ustar tseavertseaver

    Edit My ID

    Title My Title
    Format
    Lead-in
    Body

    CMF-1.3/CMFDefault/skins/zpt_content/newsitem_view.pt0100644000076500007650000000160607403772236022560 0ustar tseavertseaver

    Document Title

    Document Description goes here.
    By Me
    Body
    CMF-1.3/CMFDefault/skins/zpt_content/subjectsList.py0100644000076500007650000000037407403772236022357 0ustar tseavertseaver## Script (Python) "subjectsList" ##title=List Subjects for Metadata Editing allowedSubjects=container.portal_metadata.listAllowedSubjects(context) item=[] for i in context.Subject(): if not i in allowedSubjects: item.append(i) return item CMF-1.3/CMFDefault/skins/zpt_control/0040755000076500007650000000000007524010067017323 5ustar tseavertseaverCMF-1.3/CMFDefault/skins/zpt_control/finish_portal_construction.pt0100644000076500007650000000126107514377106025351 0ustar tseavertseaver

    Welcome to the CMF!

    The first thing you should do is visit the basic configuration form.
    Then visit the management interface and the home page.

    CMF-1.3/CMFDefault/skins/zpt_control/reverseList.py0100644000076500007650000000025707501374141022206 0ustar tseavertseaver## Script (Python) "reverseList.py $Revision: 1.3 $" ##parameters=aList ##title=Reverse A List or Tuple and Return it ## myList=list(aList)[:] myList.reverse() return myList CMF-1.3/CMFDefault/skins/zpt_control/synPropertiesForm.pt0100644000076500007650000001002207452444172023402 0ustar tseavertseaver

    Default Syndication Properties

    Channel Properties
    Channel Title: Title
    Channel Description: Description
    Sy Module Properties
    Element Default Value
    UpdatePeriod
    UpdateFrequency
    UpdateBase
    Max Syndicated Items

    Syndication is Disabled

    CMF-1.3/CMFDefault/skins/zpt_generic/0040755000076500007650000000000007524010070017251 5ustar tseavertseaverCMF-1.3/CMFDefault/skins/zpt_generic/actions_box.pt0100644000076500007650000000451507516662723022153 0ustar tseavertseaver
    Type ObjectID
    Status: Private
    Action
    Action
    Action
    CMF-1.3/CMFDefault/skins/zpt_generic/breadcrumbs.py0100644000076500007650000000155307501374141022124 0ustar tseavertseaver## Script (Python) "breadcrumbs.py $Revision: 1.3 $" ##parameters=include_root=1 ##title=Return breadcrumbs ## from string import join result = [] portal_url = context.portal_url() if include_root: result.append( { 'id' : 'root' , 'title' : context.portal_properties.title() , 'url' : portal_url } ) relative = context.portal_url.getRelativeContentPath( context ) portal = context.portal_url.getPortalObject() for i in range( len( relative ) ): now = relative[ :i+1 ] obj = portal.restrictedTraverse( now ) if not now[ -1 ] == 'talkback': result.append( { 'id' : now[ -1 ] , 'title' : obj.Title() , 'url' : portal_url + '/' + join( now, '/' ) } ) return result CMF-1.3/CMFDefault/skins/zpt_generic/content_byline.pt0100644000076500007650000000115407403772237022650 0ustar tseavertseaver

    Created by Creator Creator. Last modified Today.

    CMF-1.3/CMFDefault/skins/zpt_generic/discussion_reply_form.pt0100644000076500007650000000243607507417767024272 0ustar tseavertseaver
    Subject (Title)
    Reply body
    CMF-1.3/CMFDefault/skins/zpt_generic/discussion_reply_preview.pt0100644000076500007650000000214107507417767025001 0ustar tseavertseaver

    Text Body
    CMF-1.3/CMFDefault/skins/zpt_generic/expanded_title.py0100644000076500007650000000043207501374142022620 0ustar tseavertseaver## Script (Python) "expanded_title" ##parameters= ##title=Build title which includes site title ## site_title = context.portal_url.getPortalObject().Title() page_title = context.Title() if page_title != site_title: page_title = site_title + ": " + page_title return page_title CMF-1.3/CMFDefault/skins/zpt_generic/folder_add.pt0100644000076500007650000000172107452444172021715 0ustar tseavertseaver

    Add Folder

    A Folder contains other objects. Use Folders to organize your web objects in to logical groups.

    Id
    Title



    CMF-1.3/CMFDefault/skins/zpt_generic/folder_contents.pt0100644000076500007650000001720307514377106023025 0ustar tseavertseaver

    Desktop

    [Link]    Up to Up ID Root
    ID (Title)
     
    ID (Title)
    Previous Items   Next Items
    Filter Form Here
    CMF-1.3/CMFDefault/skins/zpt_generic/folder_factories.pt0100644000076500007650000000276107511373501023142 0ustar tseavertseaver

    Add Content



    ID:
    CMF-1.3/CMFDefault/skins/zpt_generic/folder_filter_form.pt0100644000076500007650000000564407523030745023501 0ustar tseavertseaver
    Contents View Filter
    Subject:
    Content Type:
    CMF-1.3/CMFDefault/skins/zpt_generic/folder_localrole_form.pt0100644000076500007650000001134707452444172024171 0ustar tseavertseaver

    Assign local roles: Search Members

    Search by
    Search Term

    Assign local roles: Search Results

    Select Member(s) and a role to assign:


    User Email address
    Username 1 Email 1
    Username 2 Email 2

    Role to assign:


    Sorry, no members matched your search.


    Currently assigned local roles

    These users currently have local roles assigned in this folder:


    User Role(s)

    Username 1 Role1, Role2
    Username 2 Role3

    Auth username Role1, Role2, Role3


    CMF-1.3/CMFDefault/skins/zpt_generic/folder_rename_form.pt0100644000076500007650000000311307473160131023445 0ustar tseavertseaver

    Rename Items

    Type Image ID to

    You must select one or more items to rename.

    CMF-1.3/CMFDefault/skins/zpt_generic/index_html.pt0100644000076500007650000000233207520007644021757 0ustar tseavertseaver
    'local_pt' header goes here.

    'local_pt' body goes here.
    CMF-1.3/CMFDefault/skins/zpt_generic/index_html_utils.html0100644000076500007650000001263407520005160023515 0ustar tseavertseaver

    Document Title

    Document Description goes here.

    Content

    Related Resources

    Folders

     News

    Date
    No news is no news.
    More...
    CMF-1.3/CMFDefault/skins/zpt_generic/join_form.pt0100644000076500007650000000575407465043034021622 0ustar tseavertseaver

    Become a member

    You are already a member. You may use the personalization form to change your membership information.

    Becoming a member gives you the ability to personalize the site and participate in the community.

    It does not cost any money to become a member and your email and other personal information will remain private.

    You must submit a valid email address. This address will be used to send you a randomly-generated password. Once you have logged in with this password, you may change it to anything you like.


    Login Name
    Email Address
    Password
    Password (confirm)
    Mail Password?

    CMF-1.3/CMFDefault/skins/zpt_generic/logged_in.pt0100644000076500007650000001015707514377106021565 0ustar tseavertseaver

    Login failure

    You are not currently logged in. Your username and or password may be incorrect. Your browser may also not be configured to accept HTTP cookies. If you need help please contact Email Admin.

    Welcome!

    This is the first time that you've logged in to Here. Before you start exploring you need to change your original password. This will ensure that the password we sent you via email cannot be used in a malicious manner.

    Please use the form below to change your password.

    Username You
    New password
    Confirm new password

    Login success

    Welcome. You are currently logged in.

    CMF-1.3/CMFDefault/skins/zpt_generic/logged_out.pt0100644000076500007650000000105707403772237021766 0ustar tseavertseaver

    You are logged in outside the portal. You may need to log out of the Zope management interface.

    CMF-1.3/CMFDefault/skins/zpt_generic/login_form.pt0100644000076500007650000000356007514377106021771 0ustar tseavertseaver

    Log in

    Name
    Password

    I forgot my password!

    Having trouble logging in? Make sure to enable cookies in your web browser.

    Don't forget to logout or exit your browser when you're done.

    Setting the 'Remember my name' option will set a cookie with your username, so that when you next log in, your user name will already be filled in for you.

    CMF-1.3/CMFDefault/skins/zpt_generic/mail_password_form.pt0100644000076500007650000000205207452444172023517 0ustar tseavertseaver

    Don't panic!

    Just enter your username below, click Send, and your password will be mailed to you if you gave a valid email address when you signed on.

    If this will not work for you (for example, if you forget your member name or didn't enter your email address) send email to me@here.com.

    CMF-1.3/CMFDefault/skins/zpt_generic/mail_password_response.pt0100644000076500007650000000053007403772237024413 0ustar tseavertseaver

    Your password has been mailed.
    It should arrive in your mailbox momentarily.

    CMF-1.3/CMFDefault/skins/zpt_generic/main_template.pt0100644000076500007650000001702007514377106022451 0ustar tseavertseaver Title goes here
    Site Title
    Guest
    [X]   ID

    Status message.

    Page Title

    Description of the resource goes here, perhaps even wrapping lines; this is to make it long enough to test.

    Section Header

    The content of the object is rendered hre. Lorem ipsum dolorem. Nihil obstat imprimatur. Semper ubi sub ubi. Non illegitimi carborundum. In vino veritas. E pluribus unam.

    CMF-1.3/CMFDefault/skins/zpt_generic/metadata_help.pt0100644000076500007650000001274207403772237022431 0ustar tseavertseaver

    Dublin Core Metadata

    . . ). . . . .
    Title
      the standard Zope 'title' attribute; we should look at making it mandatory for all PortalContent derivatives.
    Creator
      where possible, this should be one or more full names, of either persons or organizations. The current implementation finds the first user in the list returned by 'get_local_roles' who has the 'Owner' role; userids are not considered appropriate for this field by the DCI.
    Subject
      this is supposed to be drawn from a controlled list of keywords (e.g., selected from a multi-select list used across the whole site)
    Description
      a short summary, an abstract, or a table-of-contents are all considered acceptable. We might look at making this required, as well, at least for some kinds of content.
    Publisher
      a site-wide property, should be done through acquisition (do I smell a 'portal_metadata' tool about to appear?) Again, this is supposed to be a formal name.
    Contributor
      used to convey others besides the Creator who have contributed to the document (the current implementation aliases 'Creator', which is not what DCI intends)
    Date
      this one has modifiers, of which the approved set is: 'Created', 'Valid', 'Available', 'Issued', and 'Modified'. I propose extending the interface to include CreationDate(), EffectiveDate(), ExpirationDate(), and ModificationDate(). The current Date() could just return the CreationDate(), while the DCI 'Valid' and 'Available' would be ranges derived from EffectiveDate() and ExpirationDate(
    Type
      like the Zope 'meta_type', this is the main conceptual classification; 'meta_type' is often spelled identically to the class, which makes it less appropriate for the DCI usage.
    Format
      the kind of physical representation, e.g., 'text/html'
    Identifier
      should be the fully-qualified URL of the document (the current implementation returns the object's id, which is only required to be unique within its container)
    Language
      'en-us', 'pt-br', 'de', etc. Should be set at creation, with an appropriate default (and a picklist of values)
    Source
      the original from which a piece of content is derived. I'd like to ignore this one.
    Relation
      more relationships to other documents. Again, I'd like to ignore it (ZopeStudio and other such tools need this, however, to build site maps)
    Coverage
      geographic/chronological/jurisdictional scope. Again, ignore.
    Rights
      copyright and other IP information related to the document. Most portals should care about this: witness the brouhaha on Slashdot over the compilation of the Hellmouth postings into a book.
    CMF-1.3/CMFDefault/skins/zpt_generic/news_box.pt0100644000076500007650000000207607511373501021453 0ustar tseavertseaver
     News

    Date
    No news is no news.
    More...
    CMF-1.3/CMFDefault/skins/zpt_generic/password_form.pt0100644000076500007650000000227707452444172022526 0ustar tseavertseaver

    Change your Password


    Username You
    New password
    Confirm new password
    Domains
    If you do not know what this field is for, leave it blank.

    CMF-1.3/CMFDefault/skins/zpt_generic/personalize_form.pt0100644000076500007650000000612107517266104023206 0ustar tseavertseaver

    Member Preferences

    Click here to change your password.

    Email address
    Listed status
    You will show up on the public membership roster.
    You will not show up on the public membership roster. Your Member folder will still be publicly accessible unless you change its security settings.
    Skin

    CMF-1.3/CMFDefault/skins/zpt_generic/publishItems.py0100644000076500007650000000127307507657364022323 0ustar tseavertseaver## Script (Python) "publishItems" ##parameters=items=None, comment='' ##title= ## wf_tool = context.portal_workflow if items is None: items = [] for obj in context.contentValues(): if ( wf_tool.getInfoFor( obj, 'review_state', '' ) in ( 'private', 'pending' ) ): items.append( obj.getId() ) for path in items: object = context.restrictedTraverse( path ) wf_tool.doActionFor( object, 'publish', comment=comment ) context.REQUEST[ 'RESPONSE' ].redirect( '%s/review?%s' % ( context.portal_url() , 'portal_status_message=%d+items+published.' % len( items ) ) ) CMF-1.3/CMFDefault/skins/zpt_generic/recent_news.pt0100644000076500007650000000355507511373501022146 0ustar tseavertseaver

    n more recent articles

    Title Date
    By Creator
    Description

    No news is good news!

    n older articles

    CMF-1.3/CMFDefault/skins/zpt_generic/reconfig_form.pt0100644000076500007650000000537407452444172022461 0ustar tseavertseaver

    Configure the Portal

    This form is used to set the portal configuration options.

    Portal 'From' name
    When the portal generates mail, it uses this name as its (apparent) sender.
    Portal 'From' address
    When the portal generates mail, it uses this address as its (apparent) return address.
    SMTP server
    This is the address of your local SMTP (out-going mail) server.
    Portal title
    This is the title which appears at the top of every portal page.
    Portal description
    This description is made available via syndicated content and elsewhere. It should be fairly brief.
    Password policy Generate and email members' initial password
    Allow members to select their initial password
    CMF-1.3/CMFDefault/skins/zpt_generic/registered.pt0100644000076500007650000000217407514377106021773 0ustar tseavertseaver

    Success!

    You have been registered as a member.

    You will receive an email shortly containing your password and instructions on how to activate your membership.

    You can log on immediately by clicking here.

    Return to homepage

    CMF-1.3/CMFDefault/skins/zpt_generic/rejectItems.py0100644000076500007650000000106707517327214022117 0ustar tseavertseaver## Script (Python) "rejectItems" ##bind container=container ##bind context=context ##bind namespace= ##bind script=script ##bind subpath=traverse_subpath ##parameters=items, comment='' ##title= ## wf_tool = context.portal_workflow # XXX getToolByName for path in items: object = context.restrictedTraverse( path ) wf_tool.doActionFor( object, 'reject', comment=comment ) context.REQUEST[ 'RESPONSE' ].redirect( '%s/review?%s' % ( context.portal_url() , 'portal_status_message=Items+rejected.' ) ) CMF-1.3/CMFDefault/skins/zpt_generic/review.pt0100644000076500007650000000464707514377106021146 0ustar tseavertseaver

    Items pending review



    Title Type Date
    Type Title
      Description


    Comment:

    There are no items matching your specified criteria.

    CMF-1.3/CMFDefault/skins/zpt_generic/roster.pt0100644000076500007650000000363207403772237021155 0ustar tseavertseaver

    Portal Members


    Member Listed?

    ID ID Yes...Or No

    Previous p Members Next n Members
    CMF-1.3/CMFDefault/skins/zpt_generic/rssDisabled.pt0100644000076500007650000000066707403772237022103 0ustar tseavertseaver

    Document Title

    CMF-1.3/CMFDefault/skins/zpt_generic/search.pt0100644000076500007650000000536707415453453021112 0ustar tseavertseaver

    Search Results

    Found 100 items .


    Title Type Date
    Title Type Date
      Description

    Previous n items    Next n items

    CMF-1.3/CMFDefault/skins/zpt_generic/search_form.pt0100644000076500007650000001025507511373502022116 0ustar tseavertseaver

    Search

    Review Status
    As a reviewer, you may search for items based on their review state. If you wish to constrain results to items in certain states, select them from this list.
    Full Text
    For a simple text search, enter your search term here. Multiple words may be found by combining them with AND and OR. This will find text in items' contents, title and description.
    Title
    Subject
    Description
    You may also search the items' descriptions and titles specifically. Multiple words may be found by combining them with AND and OR.
    Find new items since...
    You may find only recent items by selecting a time-frame.
    Item type
    You may limit your results to particular kinds of items by selecting them above. To find all kinds of items, do not select anything.
    Creator
    To find items by a particular user only, enter their username above. Note that you must enter their username exactly.

    CMF-1.3/CMFDefault/skins/zpt_generic/setup_talkback_tree.py0100644000076500007650000000052607501374142023646 0ustar tseavertseaver## Script (Python) "setup_talkback_tree" ##parameters=tree_root ##title=Standard Tree ## from ZTUtils import SimpleTreeMaker tm = SimpleTreeMaker('tb_tree') def getKids(object): return object.talkback.getReplies() tm.setChildAccess(function=getKids) tree, rows = tm.cookieTree(tree_root) rows.pop(0) return {'root': tree, 'rows': rows} CMF-1.3/CMFDefault/skins/zpt_generic/standard_error_message.pt0100644000076500007650000000317607522311361024344 0ustar tseavertseaver

    Site Error

    An error was encountered while publishing this resource.

    Error Type:
    Error Value:


    Troubleshooting Suggestions

    • This resource may be trying to reference a nonexistent object or variable .
    • The URL may be incorrect.
    • The parameters passed to this resource may be incorrect.
    • A resource that this resource relies on may be encountering an error.

    For more detailed information about the error, please refer to the HTML source for this page.

    If the error persists please contact the site maintainer. Thank you for your patience.

    CMF-1.3/CMFDefault/skins/zpt_generic/stxmethod_view.pt0100644000076500007650000000051007403772237022700 0ustar tseavertseaver

    STX goes here.

    CMF-1.3/CMFDefault/skins/zpt_generic/talkback_tree.pt0100644000076500007650000000217507522251500022416 0ustar tseavertseaver
    Title, by Me on Today
    CMF-1.3/CMFDefault/skins/zpt_generic/unauthRedirect.py0100644000076500007650000000026207501374142022616 0ustar tseavertseaver## Script (Python) "unauthRedirect.py $Revision: 1.3 $" ##parameters= ##title=clear browser cookie ## REQUEST=context.REQUEST REQUEST.RESPONSE.redirect( context.absolute_url()) CMF-1.3/CMFDefault/skins/zpt_generic/undo_form.pt0100644000076500007650000000702207516537743021632 0ustar tseavertseaver

    Undo Transactions

    This application's transactional feature allows you to easily undo changes made to the application's settings or data. You can revert the application to a "snapshot" of its state at a previous point in time.

    Select one or more transactions below and then click on the "Undo" button to undo the transactions. Note that even though a transaction is shown below, you will not be able to undo it if later transactions modified objects that were modified by the transaction.


    Later Transactions
    by Zope on at
    Earlier Transactions

    There are no transactions that can be undone.
    CMF-1.3/CMFDefault/skins/zpt_generic/viewThreadsAtBottom.pt0100644000076500007650000000033307522251500023554 0ustar tseavertseaver

    Comments:

    CMF-1.3/CMFDefault/skins/zpt_generic/zpt_stylesheet.css0100644000076500007650000001221407505666540023070 0ustar tseavertseaverbody { background-color: White; color: Black; margin-top: 0; margin-left:2pt; margin-right:2pt; margin-bottom:0; } p { font-family: serif; } h1 { font-family: sans-serif; font-size: 120%; font-weight: bold; } h2 { font-family: sans-serif; font-size: 115%; } h3 { font-family: sans-serif; font-size: 110%; } h4 { font-family: sans-serif; font-size: 110%; font-style: italic; } a:link { text-decoration: none; color: #336699; } a:visited { text-decoration: none; color: #336699; } a:active { color: #336699; text-decoration: none; } a:hover { text-decoration: none; color: Blue; } table { border: 0; } td { vertical-align: top; } #Masthead { background-color: #336699; border: 1pt; border-color: #336699; padding: none; cell-spacing: none; width: 100%; } #PortalLogo { background-color: #336699; vertical-align: middle; width: 15%; height: 32px; } #PortalTitle { background-color: #336699; color: White; text-align: left; vertical-align: middle; font-family: sans-serif; font-size: 120%; font-weight: bold; width: 25%; height: 32px; } #NavBar { background-color: #336699; color: #FFFFFF; text-align: right; vertical-align: middle; font-family: sans-serif; font-size: 80%; width: 60%; height: 32px; } #NavBar a:link { color: #FFFFFF; text-decoration: none; } #NavBar a:visited { color: #FFFFFF; text-decoration: none; } #NavBar a:active { color: #FFFFFF; text-decoration: none; } #NavBar a:hover { color: #5599CC; text-decoration: none; } #MemberActionsBox { color: White; background-color: #5599CC; border-top: solid #AAAAAA; border-top-width: thin; } #ContextActionsBox { border-color: #AAAAAA; background-color: #AAAAAA; padding: none; } .ContextActionsRow { color: White; background-color: #AAAAAA; } .ActionTitle { color: Black; font-family: sans-serif; font-weight: bold; font-size: 90%; } .ActionLink { color: Black; font-family: sans-serif; font-size: 90%; } .ActionLink a:link { color: White; text-decoration: none; font-weight: bold; } .ActionLink a:visited { color: White; text-decoration: none; font-weight: bold; } .ActionLink a:active { color: White; text-decoration: none; font-weight: bold; } .ActionLink a:hover { color: #336699; text-decoration: none; font-weight: bold; } .ObjectStatus { font-style: italic; } #ForematterCell { margin-top: 20px; } #DesktopStatusBar { color: Red; background-color: White; font-family: Arial, Verdana, Helvetica, sans-serif; font-style: italic; font-weight: bold; margin-top: 8pt; } #DesktopTitle { margin-top: 8pt; } #DesktopDescription { font-style: italic; margin-left: 20pt; margin-right: 8pt; } #Desktop { margin-left: 8pt; margin-right: 8pt; } #ContentByline { font-size: 90%; font-style: italic; } #DiscussionAbove { margin-left: 16pt; margin-right: 8pt; margin-top: 8pt; margin-bottom: 8pt; font-style: italic; } table.FormLayout { width: 80%; } table.FormLayout tr { vertical-align: top; } table.FormLayout tr th.TextField { vertical-align: top; color: #000000; } table.FormLayout tr td.TextField { vertical-align: top; color: #000000; } table.FormLayout th { text-align: right; font-size: 80%; font-weight: bold; } table.FormLayout dl.FieldHelp dd { font-size: 80%; color: #000000; } table.FormLayout td.ListName { background-color: #336699; color: #FFFFFF; font-weight: bold; vertical-align: middle; } table.FormLayout td.ListDefinition { font-style: italic; color: #000000; } table.ContentsList tr td { font-size: 100%; } table.ContentsList tr td img { border: 0; } table.SearchResults { width: auto; } table.SearchResults tr th { text-align: left; } table.SearchResults tr td img { border: 0; } table.Wizard { width: auto; } table.Wizard tr { vertical-align: top; } table.Wizard tr th { text-align: right; } div.NewsBar { text-align: right; } table.NewsItems { border: 0; } td.NewsBorder { background-color: #336699; } td.NewsListing { background-color: #336699; color: #FFFFFF; font-size: 80%; font-weight: bold; } a.NewsListing{ background-color: #336699; color: #FFFFFF; font-weight: bold; } td.NewsByLine { background-color: #CCCCCC; color: #336699; font-size: 80%; text-align: left; vertical-align: top; } .NewsLeadin { background-color: #FFFFFF; color: #336699; } .NewsByLine { background-color: #336699; color: #FFFFFF; } .NewsDateline { background-color: #336699; color: #FFFFFF; } td.NewsTitle { background-color: #336699; color: #FFFFFF; text-align: center; font-size: 90%; font-weight: bold; } .NewsItemRow { background-color: #AAAAAA; font-size: 70%; } p.NewsHeadline { background-color: #336699; } CMF-1.3/CMFDefault/tests/0040755000076500007650000000000007524010070014753 5ustar tseavertseaverCMF-1.3/CMFDefault/tests/TestImage.jpg0100644000076500007650000000520607302216235017344 0ustar tseavertseaverJFIFHHPhotoshop 3.08BIMxHH(FG(HH(d'`8BIMHH8BIM8BIM 8BIM' 8BIMH/fflff/ff2Z5-8BIMp8BIM@@8BIM JFIFHH'File written by Adobe Photoshop 4.0Adobed            "?   3!1AQa"q2B#$Rb34rC%Scs5&DTdE£t6UeuF'Vfv7GWgw5!1AQaq"2B#R3$brCScs4%&5DTdEU6teuFVfv'7GWgw ?I%}jI$̿y)$e$I)I$JRI$I$$I)I%}jI$̿y)$e$I)I$JRI$I$$I)I%}jI$̿y)$e$I)I$JRI$I$$I)I%}jI$̿y)$e$I)I$JRI$I$$I)I%}jI$̿y)$e$I)I$JRI$I$$I)I%}jI$̿y)$e$I)I$JRI$I$$I)I%}jI$̿y)$e$I)I$JRI$I$$I)I%}jI$̿y)$e$I)I$JRI$I$$I)8BIM'File written by Adobe Photoshop 4.0Adobed@       FF   s!1AQa"q2B#R3b$r%C4Scs5D'6Tdt& EFVU(eufv7GWgw8HXhx)9IYiy*:JZjzm!1AQa"q2#BRbr3$4CS%cs5DT &6E'dtU7()󄔤euFVfvGWgw8HXhx9IYiy*:JZjz ?kثu/y?g=kC/r[w723[Wb]kثu/y?g=kC/r[w723[Wb]kثu/y?g=kC/r[w723[Wb]kثu/y?g=kC/r[w723[Wb]kثu/y?g=kC/r[w723[Wb]kثu/y?g=kC/r[w723[Wb]kثu/y?g=kC/r[w723[Wb]kثu/y?g=kC/r[w723[Wb]kثu/y?g=kC/r[w723[Wb]CMF-1.3/CMFDefault/tests/__init__.py0100644000076500007650000000022507306064550017072 0ustar tseavertseaver"""\ Unit test package for CMFDefault. As test suites are added, they should be added to the mega-test-suite in Products.CMFDefault.test_all.py """ CMF-1.3/CMFDefault/tests/test_Discussions.py0100644000076500007650000001707607523330772020717 0ustar tseavertseaverimport Zope from unittest import TestSuite, makeSuite, main from Products.CMFCore.tests.base.testcase import \ SecurityTest from Products.CMFCore.tests.base.utils import \ has_path from Products.CMFCore.tests.base.dummy import DummyFTI from Products.CMFCore.tests.base.dummy import DummyContent from Products.CMFCore.CatalogTool import CatalogTool from Products.CMFCore.TypesTool import TypesTool from Products.CMFCore.WorkflowTool import WorkflowTool from Products.CMFDefault.DiscussionTool import \ DiscussionTool, DiscussionNotAllowed from Products.CMFDefault.URLTool import URLTool class DiscussionTests( SecurityTest ): def setUp( self ): SecurityTest.setUp(self) root = self.root root._setObject( 'portal_discussion', DiscussionTool() ) self.discussion_tool = root.portal_discussion root._setObject( 'portal_catalog', CatalogTool() ) self.catalog_tool = root.portal_catalog root._setObject( 'portal_url', URLTool() ) self.url_tool = root.portal_url root._setObject( 'portal_workflow', WorkflowTool() ) self.workflow_tool = root.portal_workflow root._setObject( 'portal_types', TypesTool() ) types_tool = self.types_tool = root.portal_types try: root._delObject('test') except AttributeError: pass root._setObject( 'test', DummyContent( 'test', catalog=1 ) ) def test_policy( self ): test = self.root.test self.assertRaises( DiscussionNotAllowed , self.discussion_tool.getDiscussionFor , test ) assert getattr( test, 'talkback', None ) is None test.allow_discussion = 1 assert self.discussion_tool.getDiscussionFor( test ) assert test.talkback del test.talkback del test.allow_discussion self.types_tool._setObject( 'Dummy Content', DummyFTI ) self.assertRaises( DiscussionNotAllowed , self.discussion_tool.getDiscussionFor , test ) assert getattr( test, 'talkback', None ) is None ti = getattr(self.types_tool, 'Dummy Content') ti.allow_discussion = 1 assert self.discussion_tool.getDiscussionFor( test ) assert test.talkback del test.talkback ti.allow_discussion = 0 self.assertRaises( DiscussionNotAllowed , self.discussion_tool.getDiscussionFor , test ) assert getattr( test, 'talkback', None ) is None test.allow_discussion = 1 assert self.discussion_tool.getDiscussionFor( test ) assert test.talkback def test_nestedReplies( self ): test = self.root.test test.allow_discussion = 1 talkback = self.discussion_tool.getDiscussionFor( test ) assert talkback._getDiscussable() == test assert talkback._getDiscussable( outer=1 ) == test assert not talkback.hasReplies( test ) assert len( talkback.getReplies() ) == 0 reply_id = talkback.createReply( title='test', text='blah' ) assert talkback.hasReplies( test ) assert len( talkback.getReplies() ) == 1 assert talkback.getReply( reply_id ) reply1 = talkback.getReplies()[0] items = talkback._container.items() assert items[0][0] == reply1.getId() assert reply1.inReplyTo() == test parents = reply1.parentsInThread() assert len( parents ) == 1 assert test in parents talkback1 = self.discussion_tool.getDiscussionFor( reply1 ) assert talkback == talkback1 assert len( talkback1.getReplies() ) == 0 assert len( talkback.getReplies() ) == 1 talkback1.createReply( title='test2' , text='blah2' ) assert len( talkback._container ) == 2 assert talkback1.hasReplies( reply1 ) assert len( talkback1.getReplies() ) == 1 assert len( talkback.getReplies() ) == 1 reply2 = talkback1.getReplies()[0] assert reply2.inReplyTo() == reply1 parents = reply2.parentsInThread() assert len( parents ) == 2 assert parents[ 0 ] == test assert parents[ 1 ] == reply1 parents = reply2.parentsInThread( 1 ) assert len( parents ) == 1 assert parents[ 0 ] == reply1 def test_itemCataloguing( self ): test = self.root.test catalog = self.catalog_tool._catalog test.allow_discussion = 1 assert len( self.catalog_tool ) == 1 assert has_path( catalog, test.getPhysicalPath() ) talkback = self.discussion_tool.getDiscussionFor( test ) assert talkback.getPhysicalPath() == ( '', 'test', 'talkback' ), \ talkback.getPhysicalPath() talkback.createReply( title='test' , text='blah' ) assert len( self.catalog_tool ) == 2 for reply in talkback.getReplies(): assert has_path( catalog, reply.getPhysicalPath() ) assert has_path( catalog , '/test/talkback/%s' % reply.getId() ) reply1 = talkback.getReplies()[0] talkback1 = self.discussion_tool.getDiscussionFor( reply1 ) talkback1.createReply( title='test2' , text='blah2' ) for reply in talkback.getReplies(): assert has_path( catalog, reply.getPhysicalPath() ) assert has_path( catalog , '/test/talkback/%s' % reply.getId() ) for reply in talkback1.getReplies(): assert has_path( catalog, reply.getPhysicalPath() ) assert has_path( catalog , '/test/talkback/%s' % reply.getId() ) def test_deletePropagation( self ): test = self.root.test test.allow_discussion = 1 talkback = self.discussion_tool.getDiscussionFor( test ) talkback.createReply( title='test' , text='blah' ) self.root._delObject( 'test' ) assert len( self.catalog_tool ) == 0 def test_deleteReplies(self): test = self.root.test test.allow_discussion = 1 talkback = self.discussion_tool.getDiscussionFor(test) id1 = talkback.createReply(title='test1', text='blah') reply1 = talkback.getReply(id1) talkback1 = self.discussion_tool.getDiscussionFor(reply1) id2 = talkback1.createReply(title='test2', text='blah') reply2 = talkback1.getReply(id2) talkback2 = self.discussion_tool.getDiscussionFor(reply2) id3 = talkback2.createReply(title='test3', text='blah') reply3 = talkback.getReply(id3) talkback3 = self.discussion_tool.getDiscussionFor(reply3) self.assertEqual(len(talkback.getReplies()), 1) self.assertEqual(len(talkback1.getReplies()), 1) self.assertEqual(len(talkback2.getReplies()), 1) self.assertEqual(len(talkback3.getReplies()), 0) talkback.deleteReply(id2) self.assertEqual(len(talkback.getReplies()), 1) reply1 = talkback.getReply(id1) talkback1 = self.discussion_tool.getDiscussionFor(reply1) self.assertEqual(len(talkback.getReplies()), 1) self.assertEqual(len(talkback1.getReplies()), 0) def test_suite(): return TestSuite(( makeSuite( DiscussionTests ), )) if __name__ == '__main__': main(defaultTest='test_suite') CMF-1.3/CMFDefault/tests/test_Document.py0100644000076500007650000003453407500233370020154 0ustar tseavertseaverfrom unittest import TestSuite, makeSuite, main from StringIO import StringIO from re import compile from Products.CMFCore.tests.base.testcase import RequestTest from Products.CMFCore.tests.base.content import DOCTYPE from Products.CMFCore.tests.base.content import HTML_TEMPLATE from Products.CMFCore.tests.base.content import BASIC_HTML from Products.CMFCore.tests.base.content import FAUX_HTML_LEADING_TEXT from Products.CMFCore.tests.base.content import ENTITY_IN_TITLE from Products.CMFCore.tests.base.content import BASIC_STRUCTUREDTEXT from Products.CMFCore.tests.base.content import STX_WITH_HTML from Products.CMFCore.tests.base.content import STX_NO_HEADERS from Products.CMFCore.tests.base.content import STX_NO_HEADERS_BUT_COLON from Products.CMFCore.tests.base.content import SIMPLE_STRUCTUREDTEXT from Products.CMFCore.tests.base.content import SIMPLE_HTML from Products.CMFDefault.Document import Document class DocumentTests(RequestTest): def setUp(self): RequestTest.setUp(self) self.d = Document('foo') def test_Empty(self): d = Document('foo', text_format='structured-text') self.assertEqual( d.title, '' ) self.assertEqual( d.description, '' ) self.assertEqual( d.text, '' ) self.assertEqual( d.text_format, 'structured-text' ) self.assertEqual( d._stx_level, 1 ) def test_BasicHtmlPUT(self): self.REQUEST['BODY'] = BASIC_HTML d = self.d d.PUT(self.REQUEST, self.RESPONSE) self.assertEqual( d.Format(), 'text/html' ) self.assertEqual( d.title, 'Title in tag' ) self.assertEqual( d.text.find(''), -1 ) self.assertEqual( d.Description(), 'Describe me' ) self.assertEqual( len(d.Contributors()), 3 ) self.assertEqual( d.Contributors()[-1], 'Benotz, Larry J (larry@benotz.stuff)' ) # Since the format is html, the STX level operands should # have no effect. ct = d.CookedBody(stx_level=3, setlevel=1) self.assertEqual( d._stx_level, 1 ) subj = list(d.Subject()) self.assertEqual( len(subj), 4 ) subj.sort() self.assertEqual( subj, [ 'content management' , 'framework' , 'unit tests' , 'zope' ] ) def test_UpperedHtml(self): self.REQUEST['BODY'] = BASIC_HTML.upper() d = self.d d.PUT(self.REQUEST, self.RESPONSE) self.assertEqual( d.Format(), 'text/html' ) self.assertEqual( d.title, 'TITLE IN TAG' ) self.assertEqual( d.text.find('Not a lot here\n ') def test_EditPlainDocumentWithEmbeddedHTML(self): d = self.d d.edit('structured-text', FAUX_HTML_LEADING_TEXT) fully_edited = d.cooked_text d._edit(FAUX_HTML_LEADING_TEXT, 'structured-text') partly_edited = d.cooked_text self.assertEquals(fully_edited, partly_edited) def test_BigHtml(self): d = self.d s = [] looper = '
  • number %s
  • ' for i in range(12000): s.append(looper % i) body = '
      \n%s\n
    ' % '\n'.join(s) self.REQUEST['BODY'] = HTML_TEMPLATE % {'title': 'big document', 'body': body} d.PUT(self.REQUEST, self.RESPONSE) self.assertEqual( d.CookedBody(), body ) def test_BigHtml_via_upload(self): d = self.d s = [] looper = '
  • number %s
  • ' for i in range(12000): s.append(looper % i) body = '
      \n%s\n
    ' % '\n'.join(s) html = HTML_TEMPLATE % {'title': 'big document', 'body': body} file = StringIO( html ) d.edit(text_format='html', text='', file=file) self.assertEqual( d.CookedBody(), body ) def test_plain_text(self): """test that plain text forrmat works""" d = self.d d.edit(text_format='plain', text='*some plain text*\nwith a newline') self.assertEqual( d.CookedBody(), '*some plain text*
    with a newline') def test_EditStructuredTextWithHTML(self): d = self.d d.edit(text_format='structured-text', text=STX_WITH_HTML) self.assertEqual( d.Format(), 'text/plain' ) def test_StructuredText(self): self.REQUEST['BODY'] = BASIC_STRUCTUREDTEXT d = self.d d.PUT(self.REQUEST, self.RESPONSE) self.failUnless( hasattr(d, 'cooked_text') ) self.assertEqual( d.Format(), 'text/plain' ) self.assertEqual( d.Title(), 'My Document' ) self.assertEqual( d.Description(), 'A document by me' ) self.assertEqual( len(d.Contributors()), 3 ) self.failUnless( d.cooked_text.find('

    ') >= 0 ) self.failUnless( d.CookedBody().find('= 0 ) # Make sure extra HTML is NOT found self.failUnless( d.cooked_text.find('') < 0 ) self.failUnless( d.cooked_text.find('<body>') < 0 ) # test subject/keyword headers subj = list(d.Subject()) self.assertEqual( len(subj), 4 ) subj.sort() self.assertEqual( subj, [ 'content management' , 'framework' , 'unit tests' , 'zope' ] ) def test_STX_Levels(self): d = self.d d.edit(text_format='structured-text', text=BASIC_STRUCTUREDTEXT) self.assertEqual( d._stx_level, 1 ) ct = d.CookedBody() self.failUnless( d.CookedBody().find('<h1') >= 0 ) self.assertEqual( d._stx_level, 1 ) ct = d.CookedBody(stx_level=2) self.failIf( ct.find('<h1') >= 0 ) self.failUnless( ct.find('<h2') >= 0 ) self.assertEqual( d._stx_level, 1 ) ct = d.CookedBody(stx_level=2, setlevel=1) self.failIf( ct.find('<h1') >= 0 ) self.failUnless( ct.find('<h2') >= 0 ) self.assertEqual( d._stx_level, 2 ) ct = d.CookedBody() self.assertEqual( d._stx_level, 2 ) self.failIf( d.CookedBody().find('<h1') >= 0 ) self.failUnless( d.CookedBody().find('<h2') >= 0 ) def test_Init(self): self.REQUEST['BODY']=BASIC_STRUCTUREDTEXT d = self.d d.PUT(self.REQUEST, self.RESPONSE) self.assertEqual( d.Format(), 'text/plain' ) self.assertEqual( d.Title(), 'My Document' ) self.assertEqual( d.Description(), 'A document by me' ) self.assertEqual( len(d.Contributors()), 3 ) self.failUnless( d.cooked_text.find('<p>') >= 0 ) d = Document('foo', text='') self.REQUEST['BODY']=BASIC_HTML d.PUT(self.REQUEST, self.RESPONSE) self.assertEqual( d.Format(), 'text/html' ) self.assertEqual( d.Title(), 'Title in tag' ) self.assertEqual( len(d.Contributors()), 3 ) d = Document('foo', text_format='structured-text', title='Foodoc') self.assertEqual( d.text, '' ) self.failIf( d.CookedBody() ) self.assertEqual( d.title, 'Foodoc' ) self.assertEqual( d.Format(), 'text/plain' ) # Tracker issue 435: initial text is not cooked. d = Document('foo', text_format='structured-text', text=STX_NO_HEADERS) self.assertEqual( d.EditableBody(), STX_NO_HEADERS ) self.failUnless( d.CookedBody() ) self.assertEqual( d.Format(), 'text/plain' ) def test_STX_NoHeaders( self ): self.REQUEST['BODY']=STX_NO_HEADERS d = self.d d.editMetadata( title="Plain STX" , description="Look, Ma, no headers!" , subject=( "plain", "STX" ) ) self.assertEqual( d.Format(), 'text/html' ) self.assertEqual( d.Title(), 'Plain STX' ) self.assertEqual( d.Description(), 'Look, Ma, no headers!' ) self.assertEqual( len( d.Subject() ), 2 ) self.failUnless( 'plain' in d.Subject() ) self.failUnless( 'STX' in d.Subject() ) d.PUT(self.REQUEST, self.RESPONSE) self.assertEqual( d.Format(), 'text/plain' ) self.assertEqual( d.Title(), 'Plain STX' ) self.assertEqual( d.Description(), 'Look, Ma, no headers!' ) self.assertEqual( len( d.Subject() ), 2 ) self.failUnless( 'plain' in d.Subject() ) self.failUnless( 'STX' in d.Subject() ) def test_STX_NoHeaders_but_colon( self ): d = self.d d.editMetadata( title="Plain STX" , description="Look, Ma, no headers!" , subject=( "plain", "STX" ) ) d.edit(text_format='structured-text', text=STX_NO_HEADERS_BUT_COLON) self.assertEqual( d.EditableBody(), STX_NO_HEADERS_BUT_COLON ) def test_ZMI_edit( self ): d = self.d d.editMetadata( title="Plain STX" , description="Look, Ma, no headers!" , subject=( "plain", "STX" ) ) d.manage_editDocument( text_format='structured-text' , text=STX_NO_HEADERS_BUT_COLON) self.assertEqual( d.EditableBody(), STX_NO_HEADERS_BUT_COLON ) class TestFTPGet( RequestTest ): def testHTML( self ): self.REQUEST['BODY']=BASIC_HTML d = Document( 'foo' ) d.PUT(self.REQUEST, self.RESPONSE) rnlinesplit = compile( r'\r?\n?' ) simple_lines = rnlinesplit.split( BASIC_HTML ) get_lines = rnlinesplit.split( d.manage_FTPget() ) # strip off headers meta_pattern = compile( r'meta name="([a-z]*)" ' + r'content="([a-z]*)"' ) title_pattern = compile( r'<title>(.*)' ) simple_headers = [] while simple_lines and simple_lines[0] != '': header = simple_lines[0].strip().lower() match = meta_pattern.search( header ) if match: simple_headers.append( match.groups() ) else: match = title_pattern.search( header ) if match: simple_headers.append( ( 'title', match.group(1) ) ) simple_lines = simple_lines[1:] get_headers = [] while get_lines and get_lines[0] != '': header = get_lines[0].strip().lower() match = meta_pattern.search( header ) if match: get_headers.append( match.groups() ) else: match = title_pattern.search( header ) if match: get_headers.append( ( 'title', match.group(1) ) ) get_lines = get_lines[1:] self.assertEqual( get_lines, simple_lines ) self.failUnless( get_headers ) self.failUnless( simple_headers ) self.failUnless( len( get_headers ) >= len( simple_headers ) ) for header in simple_headers: self.failUnless( header in get_headers ) def testSTX( self ): self.REQUEST['BODY']=SIMPLE_STRUCTUREDTEXT d = Document( 'foo' ) d.PUT(self.REQUEST, self.RESPONSE) rnlinesplit = compile( r'\r?\n?' ) get_text = d.manage_FTPget() simple_lines = rnlinesplit.split( SIMPLE_STRUCTUREDTEXT ) get_lines = rnlinesplit.split( get_text ) # strip off headers simple_headers = [] while simple_lines and simple_lines[0]: simple_headers.append( simple_lines[0] ) simple_lines = simple_lines[1:] get_headers = [] while get_lines and get_lines[0]: get_headers.append( get_lines[0] ) get_lines = get_lines[1:] self.assertEqual( get_lines, simple_lines ) for header in simple_headers: self.failUnless( header in get_headers ) class TestDocumentPUT(RequestTest): def setUp(self): RequestTest.setUp(self) self.d = Document('foo') def test_PutStructuredTextWithHTML(self): self.REQUEST['BODY'] = STX_WITH_HTML r = self.d.PUT(self.REQUEST, self.RESPONSE) self.assertEqual( self.d.Format(), 'text/plain' ) self.assertEqual( r.status, 204 ) def test_PutStructuredText(self): self.REQUEST['BODY'] = BASIC_STRUCTUREDTEXT r = self.d.PUT(self.REQUEST, self.RESPONSE) self.assertEqual( self.d.Format(), 'text/plain' ) self.assertEqual( r.status, 204 ) def test_PutHtmlWithDoctype(self): html = '%s\n\n \n %s' % (DOCTYPE, BASIC_HTML) self.REQUEST['BODY'] = html r = self.d.PUT(self.REQUEST, self.RESPONSE) self.assertEqual( self.d.Format(), 'text/html' ) self.assertEqual( self.d.Description(), 'Describe me' ) self.assertEqual( r.status, 204 ) def test_PutHtml(self): self.REQUEST['BODY'] = BASIC_HTML r = self.d.PUT(self.REQUEST, self.RESPONSE) self.assertEqual( self.d.Format(), 'text/html' ) self.assertEqual( self.d.Description(), 'Describe me' ) self.assertEqual( r.status, 204 ) def test_suite(): return TestSuite(( makeSuite(DocumentTests), makeSuite(TestFTPGet), makeSuite(TestDocumentPUT), )) if __name__ == '__main__': main(defaultTest='test_suite') CMF-1.3/CMFDefault/tests/test_Favorite.py0100644000076500007650000000600207523027526020153 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Unit tests for Favorites. $Id: test_Favorite.py,v 1.4.6.2 2025/08/03 19:30:30 efge Exp $ """ from unittest import TestCase, TestSuite, makeSuite, main import Zope from Products.CMFCore.tests.base.dummy import \ DummyTool as DummyURLTool, \ DummyObject as DummySite from Products.CMFDefault.Favorite import Favorite class FavoriteTests( TestCase ): def setUp( self ): self.tool = DummyURLTool() self.site = DummySite( portal_url=self.tool ) def _makeOne( self, *args, **kw ): f = apply( Favorite, args, kw ) return f.__of__( self.site ) def test_Empty( self ): f = self._makeOne( 'foo' ) self.assertEqual( f.getId(), 'foo' ) self.assertEqual( f.Title(), '' ) self.assertEqual( f.Description(), '' ) self.assertEqual( f.getRemoteUrl(), self.tool.root ) self.assertEqual( f.getObject(), self.site ) self.assertEqual( f.getIcon(), self.site.getIcon() ) self.assertEqual( f.getIcon(1), self.site.getIcon(1) ) def test_CtorArgs( self ): self.assertEqual( self._makeOne( 'foo' , title='Title' ).Title(), 'Title' ) self.assertEqual( self._makeOne( 'bar' , description='Description' ).Description(), 'Description' ) baz = self._makeOne( 'baz', remote_url='portal_url' ) self.assertEqual( baz.getObject(), self.tool ) self.assertEqual( baz.getRemoteUrl() , '%s/portal_url' % self.tool.root ) self.assertEqual( baz.getIcon(), self.tool.getIcon() ) def test_edit( self ): f = self._makeOne( 'foo' ) f.edit( 'portal_url' ) self.assertEqual( f.getObject(), self.tool ) self.assertEqual( f.getRemoteUrl() , '%s/portal_url' % self.tool.root ) self.assertEqual( f.getIcon(), self.tool.getIcon() ) def test_editEmpty( self ): f = self._makeOne( 'gnnn' ) f.edit( '' ) self.assertEqual( f.getObject(), self.site ) self.assertEqual( f.getRemoteUrl(), self.tool.root ) self.assertEqual( f.getIcon(), self.site.getIcon() ) def test_suite(): return TestSuite(( makeSuite( FavoriteTests ), )) if __name__ == '__main__': main(defaultTest='test_suite') CMF-1.3/CMFDefault/tests/test_Image.py0100644000076500007650000000173007433262536017423 0ustar tseavertseaverimport Zope from unittest import TestCase, TestSuite, makeSuite, main import os, cStringIO from Products.CMFDefault.Image import Image from Products.CMFDefault import tests TESTS_HOME = tests.__path__[0] TEST_JPG = os.path.join(TESTS_HOME, 'TestImage.jpg') class TestImageElement(TestCase): def test_EditWithEmptyFile(self): """ Test handling of empty file uploads """ image = Image('testimage') testfile = open(TEST_JPG, 'rb') image.edit(file=testfile) testfile.seek(0,2) testfilesize = testfile.tell() testfile.close() assert image.get_size() == testfilesize emptyfile = cStringIO.StringIO() image.edit(file=emptyfile) assert image.get_size() > 0 assert image.get_size() == testfilesize def test_suite(): return TestSuite(( makeSuite(TestImageElement), )) return suite if __name__ == '__main__': main(defaultTest='test_suite') CMF-1.3/CMFDefault/tests/test_Link.py0100644000076500007650000000713707510101065017266 0ustar tseavertseaverimport Zope from unittest import TestCase, TestSuite, makeSuite, main from re import compile from Products.CMFDefault.Link import Link BASIC_STRUCTUREDTEXT = '''\ Title: Zope Community Description: Link to the Zope Community website. Subject: open source; Zope; community http://www.zope.org ''' STX_W_CONTINUATION = '''\ Title: Zope Community Description: Link to the Zope Community website, including hundreds of contributed Zope products. Subject: open source; Zope; community http://www.zope.org ''' class LinkTests(TestCase): def canonTest(self, table): for orig, wanted in table.items(): # test with constructor d = Link('foo', remote_url=orig) self.assertEqual(d.getRemoteUrl(), wanted) # test with edit method too d = Link('bar') d.edit(orig) self.assertEqual(d.getRemoteUrl(), wanted) def test_Empty( self ): d = Link( 'foo' ) self.assertEqual( d.Title(), '' ) self.assertEqual( d.Description(), '' ) self.assertEqual( d.getRemoteUrl(), '' ) self.assertEqual( d.format, 'text/url' ) self.assertEqual( d.URL_FORMAT, 'text/url') d = Link('foo', remote_url='bar') d.edit('') self.assertEqual(d.getRemoteUrl(), '') d = Link('foo', remote_url='http://') self.assertEqual(d.getRemoteUrl(), '') d = Link('foo', remote_url='http:') self.assertEqual(d.getRemoteUrl(), '') def test_StructuredText( self ): d = Link('foo') d._writeFromPUT( body=BASIC_STRUCTUREDTEXT ) self.assertEqual( d.Title(), 'Zope Community' ) self.assertEqual( d.Description() , 'Link to the Zope Community website.' ) self.assertEqual( len(d.Subject()), 3 ) self.assertEqual( d.getRemoteUrl(), 'http://www.zope.org' ) def test_StructuredText_w_Continuation( self ): d = Link('foo') d._writeFromPUT( body=STX_W_CONTINUATION ) rnlinesplit = compile( r'\r?\n?' ) desc_lines = rnlinesplit.split( d.Description() ) self.assertEqual( d.Title(), 'Zope Community' ) self.assertEqual( desc_lines[0] , 'Link to the Zope Community website,' ) self.assertEqual( desc_lines[1] , 'including hundreds of contributed Zope products.' ) self.assertEqual( len(d.Subject()), 3 ) self.assertEqual( d.getRemoteUrl(), 'http://www.zope.org' ) def test_fixupMissingScheme(self): table = { 'http://foo.com': 'http://foo.com', '//bar.com': 'http://bar.com', } self.canonTest(table) def test_keepRelativeUrl(self): table = { 'baz.com': 'baz.com', 'baz2.com/index.html': 'baz2.com/index.html', '/huh/zoinx.html': '/huh/zoinx.html', 'hmmm.com/lol.txt': 'hmmm.com/lol.txt', } self.canonTest(table) def test_trailingSlash(self): table = { 'http://foo.com/bar/': 'http://foo.com/bar/', 'baz.com/': 'baz.com/', '/baz.org/zoinx/': '/baz.org/zoinx/', } self.canonTest(table) def test_otherScheme(self): table = { 'mailto:user@foo.com': 'mailto:user@foo.com', 'https://bank.com/account': 'https://bank.com/account', } self.canonTest(table) def test_suite(): return TestSuite(( makeSuite(LinkTests), )) if __name__ == '__main__': main(defaultTest='test_suite') CMF-1.3/CMFDefault/tests/test_MetadataTool.py0100644000076500007650000003031707511373502020753 0ustar tseavertseaverimport Zope from Acquisition import aq_base from unittest import TestCase, TestSuite, makeSuite, main from Products.CMFDefault.MetadataTool import \ MetadataElementPolicy, MetadataTool, ElementSpec, \ DEFAULT_ELEMENT_SPECS, MetadataError from Products.CMFDefault.DublinCore import DefaultDublinCoreImpl class TestMetadataElementPolicy( TestCase ): def setUp( self ): self.sv_policy = MetadataElementPolicy( 0 ) self.mv_policy = MetadataElementPolicy( 1 ) def tearDown( self ): del self.sv_policy del self.mv_policy def test_emptySV( self ): assert not self.sv_policy.isMultiValued() assert not self.sv_policy.isRequired() assert not self.sv_policy.supplyDefault() assert not self.sv_policy.defaultValue() assert not self.sv_policy.enforceVocabulary() assert not self.sv_policy.allowedVocabulary() def test_editSV( self ): self.sv_policy.edit( 1, 1, 'xxx', 0, '' ) assert not self.sv_policy.isMultiValued() assert self.sv_policy.isRequired() assert self.sv_policy.supplyDefault() assert self.sv_policy.defaultValue() == 'xxx' assert not self.sv_policy.enforceVocabulary() assert not self.sv_policy.allowedVocabulary() def test_emptyMV( self ): assert self.mv_policy.isMultiValued() assert not self.mv_policy.isRequired() assert not self.mv_policy.supplyDefault() assert not self.mv_policy.defaultValue() assert not self.mv_policy.enforceVocabulary() assert not self.mv_policy.allowedVocabulary() def test_editMV( self ): self.mv_policy.edit( 1, 1, 'xxx', 1, ( 'xxx', 'yyy' ) ) assert self.mv_policy.isMultiValued() assert self.mv_policy.isRequired() assert self.mv_policy.supplyDefault() assert self.mv_policy.defaultValue() == 'xxx' assert self.mv_policy.enforceVocabulary() assert len( self.mv_policy.allowedVocabulary() ) == 2 assert 'xxx' in self.mv_policy.allowedVocabulary() assert 'yyy' in self.mv_policy.allowedVocabulary() class TestElementSpec( TestCase ): def setUp( self ): self.sv_spec = ElementSpec( 0 ) self.mv_spec = ElementSpec( 1 ) def tearDown( self ): del self.sv_spec del self.mv_spec def test_empty( self ): assert not self.sv_spec.isMultiValued() assert self.sv_spec.getPolicy() == self.sv_spec.getPolicy( 'XYZ' ) policies = self.sv_spec.listPolicies() assert len( policies ) == 1 assert policies[0][0] is None assert self.mv_spec.isMultiValued() assert self.mv_spec.getPolicy() == self.mv_spec.getPolicy( 'XYZ' ) policies = self.mv_spec.listPolicies() assert len( policies ) == 1 assert policies[0][0] is None class Foo( DefaultDublinCoreImpl ): description = title = language = format = rights = '' subject = () def __init__( self ): pass # skip DDCI's default values def getPortalTypeName( self ): return 'Foo' class Bar( Foo ): def getPortalTypeName( self ): return 'Bar' class TestMetadataTool( TestCase ): def setUp( self ): self.tool = MetadataTool() def tearDown( self ): del self.tool def test_empty( self ): assert not self.tool.getPublisher() assert self.tool.getFullName( 'foo' ) == 'foo' specs = list( self.tool.listElementSpecs() ) defaults = list( DEFAULT_ELEMENT_SPECS ) specs.sort(); defaults.sort() assert len( specs ) == len( defaults ) for i in range( len( specs ) ): assert specs[i][0] == defaults[i][0] assert specs[i][1].isMultiValued() == defaults[i][1] policies = specs[i][1].listPolicies() assert len( policies ) == 1 assert policies[0][0] is None assert not self.tool.getElementSpec( 'Title' ).isMultiValued() assert not self.tool.getElementSpec( 'Description' ).isMultiValued() assert self.tool.getElementSpec( 'Subject' ).isMultiValued() assert not self.tool.getElementSpec( 'Format' ).isMultiValued() assert not self.tool.getElementSpec( 'Language' ).isMultiValued() assert not self.tool.getElementSpec( 'Rights' ).isMultiValued() try: dummy = self.tool.getElementSpec( 'Foo' ) except KeyError: pass else: assert 0, "Expected KeyError" assert not self.tool.listAllowedSubjects() assert not self.tool.listAllowedFormats() assert not self.tool.listAllowedLanguages() assert not self.tool.listAllowedRights() def test_add( self ): self.tool.addElementSpec( 'Rating', 1 ) assert len( self.tool.listElementSpecs() ) \ == len( DEFAULT_ELEMENT_SPECS ) + 1 rating = self.tool.getElementSpec( 'Rating' ) assert rating.isMultiValued() def test_remove( self ): self.tool.removeElementSpec( 'Rights' ) assert len( self.tool.listElementSpecs() ) \ == len( DEFAULT_ELEMENT_SPECS ) - 1 try: dummy = self.tool.getElementSpec( 'Rights' ) except KeyError: pass else: assert 0, "Expected KeyError" try: self.tool.removeElementSpec( 'Foo' ) except KeyError: pass else: assert 0, "Expected KeyError" def test_simplePolicies( self ): tSpec = self.tool.getElementSpec( 'Title' ) # Fetch default policy. tDef = tSpec.getPolicy() assert not tDef.isRequired() assert not tDef.supplyDefault() assert not tDef.defaultValue() # Fetch (default) policy for a type. tDoc = tSpec.getPolicy( 'Document' ) self.assertEqual(aq_base(tDoc), aq_base(tDef)) # Changing default changes policies found from there. tDef.edit( 1, 1, 'xyz', 0, () ) assert tDef.isRequired() assert tDef.supplyDefault() assert tDef.defaultValue() == 'xyz' assert tDoc.isRequired() assert tDoc.supplyDefault() assert tDoc.defaultValue() == 'xyz' tSpec.addPolicy( 'Document' ) assert len( tSpec.listPolicies() ) == 2 tDoc = tSpec.getPolicy( 'Document' ) self.assertNotEqual(aq_base(tDoc), aq_base(tDef)) assert not tDoc.isRequired() assert not tDoc.supplyDefault() assert not tDoc.defaultValue() tSpec.removePolicy( 'Document' ) tDoc = tSpec.getPolicy( 'Document' ) self.assertEqual(aq_base(tDoc), aq_base(tDef)) assert tDoc.isRequired() assert tDoc.supplyDefault() assert tDoc.defaultValue() == 'xyz' def test_multiValuedPolicies( self ): sSpec = self.tool.getElementSpec( 'Subject' ) # Fetch default policy. sDef = sSpec.getPolicy() assert not sDef.isRequired() assert not sDef.supplyDefault() assert not sDef.defaultValue() assert not sDef.enforceVocabulary() assert not sDef.allowedVocabulary() # Fetch (default) policy for a type. sDoc = sSpec.getPolicy( 'Document' ) self.assertEqual(aq_base(sDoc), aq_base(sDef)) # Changing default changes policies found from there. sDef.edit( 1, 1, 'xyz', 1, ( 'foo', 'bar' ) ) assert sDef.isRequired() assert sDef.supplyDefault() assert sDef.defaultValue() == 'xyz' assert sDoc.isRequired() assert sDoc.supplyDefault() assert sDoc.defaultValue() == 'xyz' assert sDef.enforceVocabulary() assert len( sDef.allowedVocabulary() ) == 2 assert 'foo' in sDef.allowedVocabulary() assert 'bar' in sDef.allowedVocabulary() assert sDoc.enforceVocabulary() assert len( sDoc.allowedVocabulary() ) == 2 assert 'foo' in sDoc.allowedVocabulary() assert 'bar' in sDoc.allowedVocabulary() sSpec.addPolicy( 'Document' ) assert len( sSpec.listPolicies() ) == 2 sDoc = sSpec.getPolicy( 'Document' ) self.assertNotEqual(aq_base(sDoc), aq_base(sDef)) assert not sDoc.isRequired() assert not sDoc.supplyDefault() assert not sDoc.defaultValue() assert not sDoc.enforceVocabulary() assert not sDoc.allowedVocabulary() sSpec.removePolicy( 'Document' ) sDoc = sSpec.getPolicy( 'Document' ) self.assertEqual(aq_base(sDoc), aq_base(sDef)) assert sDoc.isRequired() assert sDoc.supplyDefault() assert sDoc.defaultValue() == 'xyz' assert sDoc.enforceVocabulary() assert len( sDoc.allowedVocabulary() ) == 2 assert 'foo' in sDoc.allowedVocabulary() assert 'bar' in sDoc.allowedVocabulary() def test_vocabularies( self ): fSpec = self.tool.getElementSpec( 'Format' ) fDef = fSpec.getPolicy() formats = ( 'text/plain', 'text/html' ) fDef.edit( 0, 0, '', 0, ( 'text/plain', 'text/html' ) ) assert self.tool.listAllowedFormats() == formats foo = Foo() assert self.tool.listAllowedFormats( foo ) == formats fSpec.addPolicy( 'Foo' ) assert not self.tool.listAllowedFormats( foo ) foo_formats = ( 'image/jpeg', 'image/gif', 'image/png' ) fFoo = fSpec.getPolicy( 'Foo' ) fFoo.edit( 0, 0, '', 0, foo_formats ) assert self.tool.listAllowedFormats( foo ) == foo_formats def test_initialValues( self ): foo = Foo() assert not foo.Title() assert not foo.Description() assert not foo.Subject() assert not foo.Format(), foo.Format() assert not foo.Language() assert not foo.Rights() self.tool.setInitialMetadata( foo ) assert not foo.Title() assert not foo.Description() assert not foo.Subject() assert not foo.Format() assert not foo.Language() assert not foo.Rights() # Test default policy. foo = Foo() fSpec = self.tool.getElementSpec( 'Format' ) fPolicy = fSpec.getPolicy() fPolicy.edit( 0, 1, 'text/plain', 0, () ) self.tool.setInitialMetadata( foo ) assert not foo.Title() assert not foo.Description() assert not foo.Subject() assert foo.Format() == 'text/plain' assert not foo.Language() assert not foo.Rights() # Test type-specific policy. foo = Foo() tSpec = self.tool.getElementSpec( 'Title' ) tSpec.addPolicy( 'Foo' ) tPolicy = tSpec.getPolicy( foo.getPortalTypeName() ) tPolicy.edit( 1, 0, '', 0, () ) try: self.tool.setInitialMetadata( foo ) except MetadataError: pass else: assert 0, "Expected MetadataError" foo.setTitle( 'Foo title' ) self.tool.setInitialMetadata( foo ) assert foo.Title() == 'Foo title' assert not foo.Description() assert not foo.Subject() assert foo.Format() == 'text/plain' assert not foo.Language() assert not foo.Rights() # Ensure Foo's policy doesn't interfere with other types. bar = Bar() self.tool.setInitialMetadata( bar ) assert not bar.Title() assert not bar.Description() assert not bar.Subject() assert bar.Format() == 'text/plain' assert not bar.Language() assert not bar.Rights() def test_validation( self ): foo = Foo() self.tool.setInitialMetadata( foo ) self.tool.validateMetadata( foo ) tSpec = self.tool.getElementSpec( 'Title' ) tSpec.addPolicy( 'Foo' ) tPolicy = tSpec.getPolicy( foo.getPortalTypeName() ) tPolicy.edit( 1, 0, '', 0, () ) try: self.tool.validateMetadata( foo ) except MetadataError: pass else: assert 0, "Expected MetadataError" foo.setTitle( 'Foo title' ) self.tool.validateMetadata( foo ) def test_suite(): return TestSuite(( makeSuite(TestMetadataElementPolicy), makeSuite(TestElementSpec), makeSuite(TestMetadataTool), )) if __name__ == '__main__': main(defaultTest='test_suite') CMF-1.3/CMFDefault/tests/test_NewsItem.py0100644000076500007650000001062107452155411020125 0ustar tseavertseaverfrom unittest import makeSuite, main from Products.CMFDefault.NewsItem import NewsItem from Products.CMFCore.tests.base.testcase import RequestTest from Products.CMFCore.tests.base.content import DOCTYPE from Products.CMFCore.tests.base.content import BASIC_HTML from Products.CMFCore.tests.base.content import ENTITY_IN_TITLE from Products.CMFCore.tests.base.content import BASIC_STRUCTUREDTEXT class NewsItemTests(RequestTest): def test_Empty_html(self): d = NewsItem( 'empty', text_format='html' ) self.assertEqual( d.Title(), '' ) self.assertEqual( d.Description(), '' ) self.assertEqual( d.Format(), 'text/html' ) self.assertEqual( d.text_format, 'html' ) self.assertEqual( d.text, '' ) def test_Empty_stx(self): d = NewsItem('foo', text_format='structured-text') self.assertEqual( d.Title(), '' ) self.assertEqual( d.Description(), '' ) self.assertEqual( d.Format(), 'text/plain' ) self.assertEqual( d.text_format, 'structured-text' ) self.assertEqual( d.text, '' ) def test_PUT_basic_html(self): self.REQUEST['BODY']=BASIC_HTML d = NewsItem('foo') d.PUT(self.REQUEST, self.RESPONSE) self.assertEqual( d.Title(), 'Title in tag' ) self.assertEqual( d.Description(), 'Describe me' ) self.assertEqual( d.Format(), 'text/html' ) self.assertEqual( d.text_format, 'html' ) self.assertEqual( d.text.find(''), -1 ) self.assertEqual( len(d.Contributors()), 3 ) def test_PUT_uppered_html(self): self.REQUEST['BODY'] = BASIC_HTML.upper() d = NewsItem('foo') d.PUT(self.REQUEST, self.RESPONSE) self.assertEqual( d.Title(), 'TITLE IN TAG' ) self.assertEqual( d.Description(), 'DESCRIBE ME' ) self.assertEqual( d.Format(), 'text/html' ) self.assertEqual( d.text_format, 'html' ) self.assertEqual( d.text.find(''), -1 ) self.assertEqual( len(d.Contributors()), 3 ) def test_PUT_structured_text(self): self.REQUEST['BODY'] = BASIC_STRUCTUREDTEXT d = NewsItem('foo') d.PUT( self.REQUEST, self.RESPONSE ) self.assertEqual( d.Title(), 'My Document') self.assertEqual( d.Description(), 'A document by me') self.assertEqual( d.Format(), 'text/plain' ) self.assertEqual( d.text_format, 'structured-text' ) self.assertEqual( len(d.Contributors()), 3 ) self.failUnless( d.cooked_text.find('

    ') >= 0 ) def test_Init(self): self.REQUEST['BODY'] = BASIC_STRUCTUREDTEXT d = NewsItem('foo', text='') d.PUT(self.REQUEST, self.RESPONSE) self.assertEqual( d.Title(), 'My Document' ) self.assertEqual( d.Description(), 'A document by me' ) self.assertEqual( d.Format(), 'text/plain' ) self.assertEqual( d.text_format, 'structured-text' ) self.assertEqual( len(d.Contributors()), 3 ) self.failUnless( d.cooked_text.find('

    ') >= 0 ) def test_Init_with_stx( self ): d = NewsItem('foo', text_format='structured-text', title='Foodoc') self.assertEqual( d.Title(), 'Foodoc' ) self.assertEqual( d.Description(), '' ) self.assertEqual( d.Format(), 'text/plain' ) self.assertEqual( d.text_format, 'structured-text' ) self.assertEqual( d.text, '' ) def test_suite(): return makeSuite(NewsItemTests) if __name__=='__main__': main(defaultTest='test_suite') CMF-1.3/CMFDefault/tests/test_Portal.py0100644000076500007650000001305107517321004017626 0ustar tseavertseaver############################################################################## # # Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Unit / functional tests for a CMFSite. $Id: test_Portal.py,v 1.1.2.2 2025/07/23 18:35:48 tseaver Exp $ """ import unittest import Zope # product initialization from Products.CMFCore.tests.base.testcase import SecurityRequestTest from Acquisition import aq_base class CMFSiteTests( SecurityRequestTest ): def _makeSite( self, id='testsite' ): from Products.CMFDefault.Portal import manage_addCMFSite manage_addCMFSite( self.root, id ) return getattr( self.root, id ) def _makeContent( self, site, portal_type, id='document', **kw ): site.invokeFactory( type_name=portal_type, id=id ) content = getattr( site, id ) if getattr( aq_base( content ), 'editMetadata', None ) is not None: content.editMetadata( **kw ) return content def test_new( self ): site = self._makeSite() self.assertEqual( len( site.portal_catalog ), 0 ) def test_MetadataCataloguing( self ): site = self._makeSite() catalog = site.portal_catalog site.portal_membership.memberareaCreationFlag = 0 portal_types = [ x for x in site.portal_types.listContentTypes() if x not in ( 'Discussion Item' , 'Folder' , 'Topic' ) ] self.assertEqual( len( catalog ), 0 ) for portal_type in portal_types: doc = self._makeContent( site , portal_type=portal_type , title='Foo' ) self.assertEqual( len( catalog ), 1 ) rid = catalog._catalog.paths.keys()[0] self.assertEqual( _getMetadata( catalog, rid ), 'Foo' ) doc.editMetadata( title='Bar' ) self.assertEqual( _getMetadata( catalog, rid ), 'Bar' ) site._delObject( doc.getId() ) self.assertEqual( len( catalog ), 0 ) def test_DocumentEditCataloguing( self ): site = self._makeSite() catalog = site.portal_catalog doc = self._makeContent( site , portal_type='Document' , title='Foo' ) rid = catalog._catalog.paths.keys()[0] doc.setTitle( 'Bar' ) # doesn't reindex self.assertEqual( _getMetadata( catalog, rid ), 'Foo' ) doc.edit( text_format='structured-text' , text='Some Text Goes Here\n\n A paragraph\n for you.' ) self.assertEqual( _getMetadata( catalog, rid ), 'Bar' ) def test_ImageEditCataloguing( self ): site = self._makeSite() catalog = site.portal_catalog doc = self._makeContent( site , portal_type='Image' , title='Foo' ) rid = catalog._catalog.paths.keys()[0] doc.setTitle( 'Bar' ) # doesn't reindex self.assertEqual( _getMetadata( catalog, rid ), 'Foo' ) doc.edit( 'GIF89a' ) self.assertEqual( _getMetadata( catalog, rid ), 'Bar' ) def test_FileEditCataloguing( self ): site = self._makeSite() catalog = site.portal_catalog doc = self._makeContent( site , portal_type='File' , title='Foo' ) rid = catalog._catalog.paths.keys()[0] doc.setTitle( 'Bar' ) # doesn't reindex self.assertEqual( _getMetadata( catalog, rid ), 'Foo' ) doc.edit( '%PDF-1.2\r' ) self.assertEqual( _getMetadata( catalog, rid ), 'Bar' ) def test_LinkEditCataloguing( self ): site = self._makeSite() catalog = site.portal_catalog doc = self._makeContent( site , portal_type='Link' , title='Foo' ) rid = catalog._catalog.paths.keys()[0] doc.setTitle( 'Bar' ) # doesn't reindex self.assertEqual( _getMetadata( catalog, rid ), 'Foo' ) doc.edit( 'http://www.example.com' ) self.assertEqual( _getMetadata( catalog, rid ), 'Bar' ) def test_NewsItemEditCataloguing( self ): site = self._makeSite() catalog = site.portal_catalog doc = self._makeContent( site , portal_type='News Item' , title='Foo' ) rid = catalog._catalog.paths.keys()[0] doc.setTitle( 'Bar' ) # doesn't reindex self.assertEqual( _getMetadata( catalog, rid ), 'Foo' ) doc.edit( '

    Extra!

    ' ) self.assertEqual( _getMetadata( catalog, rid ), 'Bar' ) def _getMetadata( catalog, rid, field='Title' ): md = catalog.getMetadataForRID( rid ) return md[ field ] def test_suite(): suite = unittest.TestSuite() suite.addTest( unittest.makeSuite( CMFSiteTests ) ) return suite if __name__ == '__main__': unittest.main( defaultTest = 'test_suite' ) CMF-1.3/CMFDefault/tests/test_all.py0100644000076500007650000000075607512744330017153 0ustar tseavertseaverimport Zope from unittest import main from Products.CMFCore.tests.base.utils import build_test_suite def test_suite(): return build_test_suite('Products.CMFDefault.tests',[ 'test_Discussions', 'test_Document', 'test_NewsItem', 'test_Link', 'test_Favorite', 'test_Image', 'test_MetadataTool', 'test_utils', 'test_join', 'test_Portal', ]) if __name__ == '__main__': main(defaultTest='test_suite') CMF-1.3/CMFDefault/tests/test_join.py0100644000076500007650000000372607517321004017334 0ustar tseavertseaverimport Zope from unittest import TestSuite, makeSuite, main from Products.CMFCore.tests.base.testcase import \ TransactionalTest class MembershipTests( TransactionalTest ): def test_join( self ): self.root.manage_addProduct[ 'CMFDefault' ].manage_addCMFSite( 'site' ) site = self.root.site site.portal_membership.memberareaCreationFlag = 0 member_id = 'test_user' site.portal_registration.addMember( member_id , 'zzyyzz' , properties={ 'username': member_id , 'email' : 'foo@bar.com' } ) u = site.acl_users.getUser(member_id) self.failUnless(u) self.assertRaises(AttributeError, getattr, site.Members, member_id) # test that wrapUser correctly creates member area site.portal_membership.setMemberareaCreationFlag() site.portal_membership.wrapUser(u) memberfolder = getattr(site.Members, member_id) homepage = memberfolder.index_html self.assertEqual( memberfolder.Title(), "test_user's Home" ) tool = site.portal_workflow self.assertEqual( tool.getInfoFor( homepage, 'review_state' ) , "private" ) def test_join_without_email( self ): self.root.manage_addProduct[ 'CMFDefault' ].manage_addCMFSite( 'site' ) site = self.root.site self.assertRaises(ValueError, site.portal_registration.addMember, 'test_user', 'zzyyzz', properties={'username':'test_user', 'email': ''} ) def test_suite(): return TestSuite(( makeSuite(MembershipTests), )) if __name__ == '__main__': main(defaultTest='test_suite') CMF-1.3/CMFDefault/tests/test_utils.py0100644000076500007650000000636207443642626017552 0ustar tseavertseaverfrom unittest import TestCase,makeSuite,main import Zope from Products.CMFDefault.utils import parseHeadersBody, tuplize, comma_split from Products.CMFDefault.utils import seq_strip class DefaultUtilsTests(TestCase): COMMON_HEADERS = '''Author: Tres Seaver Title: Test Products.PTKDemo.utils.parseHeadersBody''' MULTILINE_DESCRIPTION = '''Description: this description spans multiple lines.''' TEST_BODY = '''Body goes here, and can span multiple lines. It can even include "headerish" lines, like: Header: value ''' def test_NoBody( self ): headers, body = parseHeadersBody( '%s\n\n' % self.COMMON_HEADERS ) assert( len( headers ) == 2, '%d!' % len( headers ) ) assert( 'Author' in headers.keys() ) assert( headers[ 'Author' ] == 'Tres Seaver' ) assert( 'Title' in headers.keys() ) assert( len( body ) == 0, '%d!' % len( body ) ) def test_Continuation( self ): headers, body = parseHeadersBody( '%s\n%s\n\n' % ( self.COMMON_HEADERS , self.MULTILINE_DESCRIPTION ) ) assert( len( headers ) == 3, '%d!' % len( headers ) ) assert( 'Description' in headers.keys() ) desc_len = len( headers[ 'Description' ].split('\n') ) assert( desc_len == 2, '%d!' % desc_len ) assert( len( body ) == 0, '%d!' % len( body ) ) def test_Body( self ): headers, body = parseHeadersBody( '%s\n\n%s' % ( self.COMMON_HEADERS , self.TEST_BODY ) ) assert( len( headers ) == 2, '%d!' % len( headers ) ) assert( body == self.TEST_BODY ) def test_Preload( self ): preloaded = { 'Author' : 'xxx', 'text_format' : 'structured_text' } headers, body = parseHeadersBody( '%s\n%s\n\n%s' % ( self.COMMON_HEADERS , self.MULTILINE_DESCRIPTION , self.TEST_BODY ) , preloaded ) assert( len( headers ) == 3, '%d!' % len( headers ) ) assert( preloaded[ 'Author' ] != headers[ 'Author' ] ) assert( preloaded[ 'text_format' ] == headers[ 'text_format' ] ) def test_suite(): return makeSuite(DefaultUtilsTests) if __name__ == '__main__': main(defaultTest='test_suite') def test_tuplize( self ): assert( tuplize('string', 'one two three') == ('one','two','three')) assert( tuplize('string', 'one,two,three', comma_split) == ('one','two','three')) assert( tuplize('list', ['one',' two','three ']) == ('one',' two','three ')) assert( tuplize('tuple', ('one','two','three')) == ('one','two','three')) def test_seq_strip( self ): assert( seq_strip(['one ', ' two', ' three ']) == ['one','two','three']) assert( seq_strip(('one ', ' two', ' three ')) == ('one','two','three')) CMF-1.3/CMFTopic/0040755000076500007650000000000007524010071013304 5ustar tseavertseaverCMF-1.3/CMFTopic/Extensions/0040755000076500007650000000000007524010070015442 5ustar tseavertseaverCMF-1.3/CMFTopic/Extensions/Update.py0100644000076500007650000000534707401232663017254 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from Products.CMFCore.utils import getToolByName from cStringIO import StringIO import sys def update(self): """\ Calls into UpdateTopic to perform updates on CMF Topic installations. """ out = StringIO() Updater = UpdateTopic(out) out.write('Updating types tool configuration 1.0 to 1.1...\n') Updater.update_TypesToolConfiguration_10_11(target=self) return out.getvalue() class UpdateTopic: """\ A suite of methods for applying updates to CMF Topic installations, used by external method(s) or other upgrade scenarios. """ def __init__(self, stream): """\ stream is expected to be some writable file object, like a StringIO, that output will be sent to. """ self.stream = stream def update_TypesToolConfiguration_10_11(self, target): """\ This updates the types tool configuration to reflect the name changes from 'topic_edit' to 'topic_edit_form' (etc), and sets the immediate_view to 'topic_edit_form'. """ typestool = getToolByName(target, 'portal_types') write = self.stream.write for ti in typestool.listTypeInfo(): if ti.content_meta_type != 'Portal Topic': continue acts = list(ti.getActions()) for action in acts: ta = action['action'] if ta in ('topic_edit', 'topic_criteria', 'topic_subtopics',): write(" Changed '%s' in %s to '%s_form'\n" % (ta, ti.id, ta, ) ) ta = '%s_form' % ta action['action'] = ta ti._actions = tuple(acts) initial = getattr(ti, 'immediate_view', None) if initial == 'topic_edit': s="Changed the immediate view in %s to topic_edit_form" % ti.id write(" %s\n" % s) ti.immediate_view = 'topic_edit_form' CMF-1.3/CMFTopic/AbstractCriterion.py0100644000076500007650000000665707522303415017320 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Home of the abstract Criterion base class. $Id: AbstractCriterion.py,v 1.5.20.1 2025/08/01 19:07:57 tseaver Exp $ """ from Products.CMFTopic.TopicPermissions import ChangeTopics from Products.CMFCore.CMFCorePermissions import AccessContentsInformation from Acquisition import Implicit from AccessControl import ClassSecurityInfo from Persistence import Persistent from Globals import InitializeClass from OFS.SimpleItem import Item import string, operator class AbstractCriterion( Persistent, Item, Implicit ): """ Abstract base class for Criterion objects. """ security = ClassSecurityInfo() security.declareProtected(ChangeTopics, 'apply') def apply( self, command ): """ Apply 'command', which is expected to be a dictionary, to 'self.edit' (makes using Python Scripts easier). """ apply( self.edit, (), command ) security.declareProtected( ChangeTopics, 'editableAttributes' ) def editableAttributes( self ): """ Return a list of editable attributes, used by topics to build commands to send to the 'edit' command of each criterion, which may vary. Requires concrete subclasses to implement the attribute '_editableAttributes' which is a tuple of attributes that can be edited, for example: _editableAttributes = ( 'value', 'direction' ) """ return self._editableAttributes security.declareProtected( AccessContentsInformation, 'Type' ) def Type( self ): """ Return the Type of Criterion this object is. This method can be overriden in subclasses, or those concrete subclasses must define the 'meta_type' attribute. """ return self.meta_type security.declareProtected( AccessContentsInformation, 'Field' ) def Field( self ): """ Return the field that this criterion searches on. The concrete subclasses can override this method, or have the 'field' attribute. """ return self.field security.declareProtected( AccessContentsInformation, 'Description' ) def Description( self ): """ Return a brief but helpful description of the Criterion type, preferably based on the classes __doc__ string. """ strip = string.strip split = string.split return string.join( # Sew a string together after we: filter(operator.truth, # Filter out empty lines map(strip, # strip whitespace off each line split(self.__doc__, '\n') # from the classes doc string ) ) ) InitializeClass( AbstractCriterion ) CMF-1.3/CMFTopic/DateCriteria.py0100644000076500007650000001127607522303415016227 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Various date criteria $Id: DateCriteria.py,v 1.5.4.1 2025/08/01 19:07:57 tseaver Exp $ """ from Products.CMFTopic.AbstractCriterion import AbstractCriterion from Products.CMFTopic.interfaces import Criterion from Products.CMFTopic.Topic import Topic from Products.CMFTopic.Topic import TopicPermissions from Products.CMFCore import CMFCorePermissions from AccessControl import ClassSecurityInfo from DateTime.DateTime import DateTime import Globals import string, operator class FriendlyDateCriterion( AbstractCriterion ): """ Put a friendly interface on date range searches, like 'where effective date is less than 5 days old'. """ __implements__ = ( Criterion, ) meta_type = 'Friendly Date Criterion' security = ClassSecurityInfo() _editableAttributes = ( 'value', 'operation', 'daterange' ) _defaultDateOptions = ( ( 0, 'Now' ) , ( 1, '1 Day' ) , ( 2, '2 Days' ) , ( 5, '5 Days' ) , ( 7, '1 Week' ) , ( 14, '2 Weeks' ) , ( 31, '1 Month' ) , ( 31*3, '3 Months' ) , ( 31*6, '6 Months' ) , ( 365, '1 Year' ) , ( 365*2, '2 years' ) ) def __init__( self, id, field ): self.id = id self.field = field self.value = None self.operation = 'min' self.daterange = 'old' security.declarePublic( 'defaultDateOptions' ) def defaultDateOptions( self ): """ Return a list of default values and labels for date options. """ return self._defaultDateOptions security.declareProtected( TopicPermissions.ChangeTopics, 'getEditForm' ) def getEditForm( self ): """ Return the name of the skin method used by Topic to edit criteria of this type. """ return 'friendlydatec_editform' security.declareProtected( TopicPermissions.ChangeTopics, 'edit' ) def edit( self , value=None , operation='min' , daterange='old' ): """ Update the values to match against. """ if value in ( None, '' ): self.value = None else: try: self.value = int( value ) except: raise ValueError, 'Supplied value should be an int' if operation in ( 'min', 'max', 'within_day' ): self.operation = operation else: raise ValueError, 'Operation type not in set {min,max,within_day}' if daterange in ( 'old', 'ahead' ): self.daterange = daterange else: raise ValueError, 'Date range not in set {old,ahead}' security.declareProtected( CMFCorePermissions.View, 'getCriteriaItems' ) def getCriteriaItems( self ): """ Return a sequence of items to be used to build the catalog query. """ result = [] if self.value is not None: field = self.Field() value = self.value # Negate the value for 'old' days if self.daterange == 'old': value = -value date = DateTime() + value operation = self.operation if operation == 'within_day': range = ( date.earliestTime(), date.latestTime() ) result.append( ( field, range ) ) result.append( ( '%s_usage' % field , 'range:min:max' ) ) else: result.append( ( field, date ) ) result.append( ( '%s_usage' % field , 'range:%s' % self.operation ) ) return result Globals.InitializeClass( FriendlyDateCriterion ) # Register as a criteria type with the Topic class Topic._criteriaTypes.append( FriendlyDateCriterion ) CMF-1.3/CMFTopic/ListCriterion.py0100644000076500007650000000631607522303415016460 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ List Criterion: A criterion that is a list $Id: ListCriterion.py,v 1.9.10.2 2025/08/01 19:07:57 tseaver Exp $ """ from Products.CMFTopic.AbstractCriterion import AbstractCriterion from Products.CMFTopic.interfaces import Criterion from Products.CMFTopic.Topic import Topic from Products.CMFTopic import TopicPermissions from Products.CMFCore import CMFCorePermissions from Globals import InitializeClass from AccessControl import ClassSecurityInfo import string class ListCriterion( AbstractCriterion ): """ Represent a criterion which is a list of values (for an 'OR' search). """ __implements__ = ( Criterion, ) meta_type = 'List Criterion' operator = None value = ( '', ) security = ClassSecurityInfo() _editableAttributes = ( 'value', 'operator' ) def __init__( self, id, field ): self.id = id self.field = field self._clear() security.declarePrivate( '_clear' ) def _clear( self ): """ Restore to original value. """ self.value = ( '', ) # *Not* '()', which won't do at all! self.operator = None security.declareProtected( TopicPermissions.ChangeTopics, 'getEditForm' ) def getEditForm( self ): """ Return the name of skin method which renders the form used to edit this kind of criterion. """ return "listc_edit" security.declareProtected( TopicPermissions.ChangeTopics, 'edit' ) def edit( self, value=None, operator=None ): """ Update the value we match against. """ if value is None: self._clear() else: if type( value ) == type( '' ): value = string.split( value, '\n' ) self.value = tuple( value ) if not operator: operator = None self.operator = operator security.declareProtected( CMFCorePermissions.View, 'getCriteriaItems' ) def getCriteriaItems( self ): """ Return a tuple of query elements to be passed to the catalog (used by 'Topic.buildQuery()'). """ # filter out empty strings result = [] value = tuple( filter( None, self.value ) ) if not value: return () result.append( ( self.field, self.value ), ) if self.operator is not None: result.append( ( '%s_operator' % self.field, self.operator ) ) return tuple( result ) InitializeClass( ListCriterion ) # Register as a criteria type with the Topic class Topic._criteriaTypes.append( ListCriterion ) CMF-1.3/CMFTopic/README.txt0100644000076500007650000000166307303457427015024 0ustar tseavertseaverUpdating CMF Topic in a CMF Site Since default settings may change from time to time in CMF Topic, you may need to update your Topic types tool (and other) settings. This is done similarly to installing by adding an External Method to your CMF Site instance with the following configuration:: **id** -- 'update_topic' **title** -- *Update Topic* **module name** -- 'CMFTopic.Update' **function name** -- 'update' Go to the management screen for the newly added external method and click the 'Try it' tab. The update function will execute and give information about the steps it took to register and update CMF Topic site information. *Note: This update script should **only** change values that are still at their default, such as changing an action from 'topic_edit' to 'topic_edit_form'. If you changed that action to 'mytopic_edit', the script should pass that by and not change your settings.* CMF-1.3/CMFTopic/SimpleIntCriterion.py0100644000076500007650000001002407522303415017440 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Simple int-matching criterion $Id: SimpleIntCriterion.py,v 1.8.20.1 2025/08/01 19:07:57 tseaver Exp $ """ from Products.CMFTopic import TopicPermissions from Products.CMFTopic.AbstractCriterion import AbstractCriterion from Products.CMFTopic.Topic import Topic from Products.CMFTopic.interfaces import Criterion from Products.CMFCore import CMFCorePermissions from Globals import InitializeClass from AccessControl import ClassSecurityInfo class SimpleIntCriterion( AbstractCriterion ): """ Represent a simple field-match for an integer value, including catalog range searches. """ __implements__ = ( Criterion, ) meta_type = 'Integer Criterion' security = ClassSecurityInfo() _editableAttributes = ( 'value', 'direction' ) MINIMUM = 'min' MAXIMUM = 'max' MINMAX = 'min:max' def __init__(self, id, field): self.id = id self.field = field self.value = self.direction = None security.declareProtected( TopicPermissions.ChangeTopics, 'getEditForm' ) def getEditForm( self ): """ Return the name of skin method which renders the form used to edit this kind of criterion. """ return 'sic_edit' security.declareProtected( TopicPermissions.ChangeTopics, 'getValueString' ) def getValueString( self ): """ Return a string representation of the value for which this criterion filters. """ if self.value is None: return '' if self.direction == self.MINMAX: value = self.value if type( value ) is not type( () ): value = ( value, value ) return '%s %s' % value return str( self.value ) security.declareProtected( TopicPermissions.ChangeTopics, 'edit' ) def edit( self, value, direction=None ): """ Update the value to be filtered, and the "direction" qualifier. """ from string import strip, split # XXX: WAAAA! 2.3 compatibility if type( value ) == type( '' ): value = strip( value ) if not value: # An empty string was passed in, which evals to None self.value = self.direction = None elif direction: if direction == self.MINMAX: if type( value ) == type( '' ): minimum, maximum = split( value, ' ' ) else: minimum, maximum = value self.value = ( int( minimum ), int( maximum ) ) else: self.value = int( value ) self.direction = direction else: self.value = int( value ) self.direction = None security.declareProtected( CMFCorePermissions.View, 'getCriteriaItems' ) def getCriteriaItems( self ): """ Return a tuple of query elements to be passed to the catalog (used by 'Topic.buildQuery()'). """ if self.value is None: return () result = [ ( self.Field(), self.value ) ] if self.direction is not None: result.append( ( '%s_usage' % self.Field() , 'range:%s' % self.direction ) ) return tuple( result ) InitializeClass( SimpleIntCriterion ) # Register as a criteria type with the Topic class Topic._criteriaTypes.append( SimpleIntCriterion ) CMF-1.3/CMFTopic/SimpleStringCriterion.py0100644000076500007650000000456707522303415020173 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Simple string-matching criterion class $Id: SimpleStringCriterion.py,v 1.8.20.1 2025/08/01 19:07:57 tseaver Exp $ """ from Products.CMFTopic import TopicPermissions from Products.CMFTopic.AbstractCriterion import AbstractCriterion from Products.CMFTopic.Topic import Topic from Products.CMFTopic.interfaces import Criterion from Products.CMFCore import CMFCorePermissions from Globals import InitializeClass from AccessControl import ClassSecurityInfo class SimpleStringCriterion( AbstractCriterion ): """ Represent a simple field-match for a string value. """ __implements__ = ( Criterion, ) meta_type = 'String Criterion' security = ClassSecurityInfo() _editableAttributes = ( 'value', ) def __init__(self, id, field): self.id = id self.field = field self.value = '' security.declareProtected( TopicPermissions.ChangeTopics, 'getEditForm' ) def getEditForm( self ): """ Return the skinned name of the edit form. """ return 'ssc_edit' security.declareProtected( TopicPermissions.ChangeTopics, 'edit' ) def edit( self, value ): """ Update the value we are to match up against. """ self.value = str( value ) security.declareProtected( CMFCorePermissions.View, 'getCriteriaItems' ) def getCriteriaItems( self ): """ Return a sequence of criteria items, used by Topic.buildQuery. """ result = [] if self.value is not '': result.append( ( self.field, self.value ) ) return tuple( result ) InitializeClass( SimpleStringCriterion ) # Register as a criteria type with the Topic class Topic._criteriaTypes.append( SimpleStringCriterion ) CMF-1.3/CMFTopic/SortCriterion.py0100644000076500007650000000535007522303415016471 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Allow topic to specify sorting. $Id: SortCriterion.py,v 1.4.20.1 2025/08/01 19:07:57 tseaver Exp $ """ from Products.CMFTopic.AbstractCriterion import AbstractCriterion from Products.CMFTopic.Topic import Topic from Products.CMFTopic.interfaces import Criterion from Products.CMFTopic import TopicPermissions from Products.CMFCore import CMFCorePermissions from Globals import InitializeClass from AccessControl import ClassSecurityInfo class SortCriterion( AbstractCriterion ): """ Represent a mock criterion, to allow spelling the sort order and reversal items in a catalog query. """ __implements__ = ( Criterion, ) meta_type = 'Sort Criterion' security = ClassSecurityInfo() field = None # Don't prevent use of field in other criteria _editableAttributes = ( 'reversed', ) def __init__( self, id, index ): self.id = id self.index = index self.reversed = 0 # inherit permissions def Field( self ): """ Map the stock Criterion interface. """ return self.index security.declareProtected( TopicPermissions.ChangeTopics, 'getEditForm' ) def getEditForm( self ): """ Return the name of skin method which renders the form used to edit this kind of criterion. """ return 'sort_edit' security.declareProtected( TopicPermissions.ChangeTopics, 'edit' ) def edit( self, reversed ): """ Update the value we are to match up against. """ self.reversed = not not reversed security.declareProtected( CMFCorePermissions.View, 'getCriteriaItems' ) def getCriteriaItems( self ): """ Return a tuple of query elements to be passed to the catalog (used by 'Topic.buildQuery()'). """ result = [ ( 'sort_on', self.index ) ] if self.reversed: result.append( ( 'sort_order', 'reverse' ) ) return tuple( result ) InitializeClass( SortCriterion ) # Register as a criteria type with the Topic class Topic._criteriaTypes.append( SortCriterion ) CMF-1.3/CMFTopic/Topic.py0100644000076500007650000002244307522303415014743 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Topic: Canned catalog queries $Id: Topic.py,v 1.28.20.2 2025/08/01 19:07:57 tseaver Exp $ """ from Products.CMFTopic import TopicPermissions from Products.CMFCore import CMFCorePermissions from Products.CMFCore.utils import _checkPermission, _getViewFor,getToolByName from Products.CMFCore.PortalFolder import PortalFolder from Globals import HTMLFile, package_home, InitializeClass from AccessControl import ClassSecurityInfo from Acquisition import aq_parent, aq_inner, aq_base from ComputedAttribute import ComputedAttribute import os # Factory type information -- makes Topic objects play nicely # with the Types Tool (portal_types ) factory_type_information = \ ( { 'id' : 'Topic' , 'content_icon' : 'topic_icon.gif' , 'meta_type' : 'Portal Topic' , 'description' : 'Topics are canned queries for organizing content ' 'with up to date queries into the catalog.' , 'product' : 'CMFTopic' , 'factory' : 'addTopic' , 'immediate_view' : 'topic_edit_form' , 'actions' : ( { 'id' : 'view' , 'name' : 'View' , 'action' : 'topic_view' , 'permissions' : (CMFCorePermissions.View, ) } , { 'id' : 'edit' , 'name' : 'Edit' , 'action' : 'topic_edit_form' , 'permissions' : (TopicPermissions.ChangeTopics, ) } , { 'id' : 'criteria' , 'name' : 'Criteria' , 'action' : 'topic_criteria_form' , 'permissions' : (TopicPermissions.ChangeTopics, ) } , { 'id' : 'subtopics' , 'name' : 'Subtopics' , 'action' : 'topic_subtopics_form' , 'permissions' : (TopicPermissions.ChangeTopics, ) } ) } , ) def addTopic( self, id, title='', REQUEST=None ): """ Create an empty topic. """ topic = Topic( id ) topic.id = id topic.title = title self._setObject( id, topic ) if REQUEST is not None: REQUEST['RESPONSE'].redirect( 'manage_main' ) class Topic( PortalFolder ): """ Topics are 'canned queries', which hold a set of zero or more Criteria objects specifying the query. """ meta_type='Portal Topic' security = ClassSecurityInfo() security.declareObjectProtected( CMFCorePermissions.View ) acquireCriteria = 1 _criteriaTypes = [] # Contentish interface methods # ---------------------------- security.declareProtected( CMFCorePermissions.View, 'icon' ) def icon( self ): """ For the ZMI. """ return self.getIcon() security.declarePrivate( '_verifyActionPermissions' ) def _verifyActionPermissions( self, action ): pp = action.get( 'permissions', () ) if not pp: return 1 for p in pp: if _checkPermission( p, self ): return 1 return 0 def __call__( self ): """ Invoke the default action. """ view = _getViewFor( self ) if getattr( aq_base( view ), 'isDocTemp', 0 ): return apply( view, ( self, self.REQUEST ) ) else: return view() index_html = None # This special value informs ZPublisher to use __call__ security.declareProtected( CMFCorePermissions.View, 'view' ) def view( self ): """ Return the default view even if index_html is overridden. """ return self() security.declarePrivate( '_criteria_metatype_ids' ) def _criteria_metatype_ids( self ): result = [] for mt in self._criteriaTypes: result.append( mt.meta_type ) return tuple( result ) security.declareProtected( TopicPermissions.ChangeTopics, 'listCriteria' ) def listCriteria( self ): """ Return a list of our criteria objects. """ return self.objectValues( self._criteria_metatype_ids() ) security.declareProtected( TopicPermissions.ChangeTopics , 'listCriteriaTypes' ) def listCriteriaTypes( self ): out = [] for ct in self._criteriaTypes: out.append( { 'name': ct.meta_type, } ) return out security.declareProtected( TopicPermissions.ChangeTopics , 'listAvailableFields' ) def listAvailableFields( self ): """ Return a list of available fields for new criteria. """ portal_catalog = getToolByName( self, 'portal_catalog' ) currentfields = map( lambda x: x.Field(), self.listCriteria() ) availfields = filter( lambda field, cf=currentfields: field not in cf, portal_catalog.indexes() ) return availfields security.declareProtected( TopicPermissions.ChangeTopics, 'listSubtopics' ) def listSubtopics( self ): """ Return a list of our subtopics. """ return self.objectValues( self.meta_type ) security.declareProtected( TopicPermissions.ChangeTopics, 'edit' ) def edit( self, acquireCriteria, title=None, description=None ): """ Set the flag which indicates whether to acquire criteria from parent topics; update other meta data about the Topic. """ self.acquireCriteria = acquireCriteria if title is not None: self.title = title self.description = description security.declareProtected( CMFCorePermissions.View, 'buildQuery' ) def buildQuery( self ): """ Construct a catalog query using our criterion objects. """ result = {} if self.acquireCriteria: try: # Tracker 290 asks to allow combinations, like this: # parent = aq_parent( self ) parent = aq_parent( aq_inner( self ) ) result.update( parent.buildQuery() ) except: # oh well, can't find parent, or it isn't a Topic. pass for criterion in self.listCriteria(): for key, value in criterion.getCriteriaItems(): result[ key ] = value return result security.declareProtected( CMFCorePermissions.View, 'queryCatalog' ) def queryCatalog( self, REQUEST=None, **kw ): """ Invoke the catalog using our criteria to augment any passed in query before calling the catalog. """ kw.update( self.buildQuery() ) portal_catalog = getToolByName( self, 'portal_catalog' ) return apply( portal_catalog.searchResults, ( REQUEST, ), kw ) ### Criteria adding/editing/deleting security.declareProtected( TopicPermissions.ChangeTopics, 'addCriterion' ) def addCriterion( self, field, criterion_type ): """ Add a new search criterion. """ crit = None newid = 'crit__%s' % field for ct in self._criteriaTypes: if criterion_type == ct.meta_type: crit = ct( newid, field ) if crit is None: # No criteria type matched passed in value raise NameError, 'Unknown Criterion Type: %s' % criterion_type self._setObject( newid, crit ) # Backwards compatibility (deprecated) security.declareProtected( TopicPermissions.ChangeTopics, 'addCriteria' ) addCriteria = addCriterion security.declareProtected( TopicPermissions.ChangeTopics , 'deleteCriterion' ) def deleteCriterion( self, criterion_id ): """ Delete selected criterion. """ if type( criterion_id ) is type( '' ): self._delObject( criterion_id ) elif type( criterion_id ) in ( type( () ), type( [] ) ): for cid in criterion_id: self._delObject( cid ) security.declareProtected( CMFCorePermissions.View, 'getCriterion' ) def getCriterion( self, criterion_id ): """ Get the criterion object. """ try: return self._getOb( 'crit__%s' % criterion_id ) except AttributeError: return self._getOb( criterion_id ) security.declareProtected( TopicPermissions.AddTopics, 'addSubtopic' ) def addSubtopic( self, id ): """ Add a new subtopic. """ try: tool = getToolByName( self, 'portal_types' ) except: self._setOb( id, Topic( id ) ) else: topictype = tool.getTypeInfo( self ) topictype.constructInstance( self, id ) return self._getOb( id ) # Intialize the Topic class, setting up security. InitializeClass( Topic ) CMF-1.3/CMFTopic/TopicPermissions.py0100644000076500007650000000210507522303415017170 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ TopicPermissions: Permissions used throughout CMFTopic. $Id: TopicPermissions.py,v 1.4.20.1 2025/08/01 19:07:57 tseaver Exp $ """ from Products.CMFCore.CMFCorePermissions import setDefaultRoles # Gathering Topic Related Permissions into one place AddTopics = 'Add portal topics' ChangeTopics = 'Change portal topics' # Set up default roles for permissions setDefaultRoles(AddTopics, ('Manager',)) setDefaultRoles(ChangeTopics, ('Manager', 'Owner',)) CMF-1.3/CMFTopic/__init__.py0100644000076500007650000000355207522303415015424 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Topic: Canned catalog queries $Id: __init__.py,v 1.12.20.1 2025/08/01 19:07:57 tseaver Exp $ """ import TopicPermissions import Topic import SimpleStringCriterion import SimpleIntCriterion import ListCriterion import DateCriteria import SortCriterion from Products.CMFCore.utils import ContentInit from Products.CMFCore.DirectoryView import registerDirectory from ZClasses import createZClassForBase bases = ( Topic.Topic, ) import sys this_module = sys.modules[ __name__ ] for base in bases: createZClassForBase( base, this_module ) # This is used by a script (external method) that can be run # to set up Topics in an existing CMF Site instance. topic_globals =globals() # Make the skins available as DirectoryViews registerDirectory( 'skins', globals() ) registerDirectory( 'skins/topic', globals() ) def initialize( context ): context.registerHelpTitle( 'CMF Topic Help' ) context.registerHelp( directory='help' ) # CMF Initializers ContentInit( 'CMF Topic Objects' , content_types = (Topic.Topic,) , permission = TopicPermissions.AddTopics , extra_constructors = (Topic.addTopic,) , fti = Topic.factory_type_information ).initialize( context ) CMF-1.3/CMFTopic/version.txt0100644000076500007650000000000407523373537015543 0ustar tseavertseaver1.3 CMF-1.3/CMFTopic/help/0040755000076500007650000000000007524010070014233 5ustar tseavertseaverCMF-1.3/CMFTopic/help/Overview.stx0100644000076500007650000000764207264373216016626 0ustar tseavertseaverPortalTopic Overview *Note: this help file is old and being updated (ney, replaced) as **CMF Topic Overview**.* PortalTopics present collections of portal items according to catalog searches formulated by the PortalTopic creator/configurer. Visitors to a portal topic see a brief description of the topic, its criteria, available subtopics, and the batch-browsable results. (Eventually the visitor will be able to sort and filter the results to their liking. Initially, we will be providing rudimentary batched browsing.) Visitors will also see links to subtopics which refine the PortalTopic configurers (the creator and anyone generally enabled to configure a portal topic, according to site policy) can toggle the browsing view to adjust the topic query criteria, adding, deleting, and modifying textual, numeric, and list criteria against the site's standard content metadata fields. PortalTopic configurers will also be able to add new topic objects to the topic that will act as subtopics, with their queries refining the results of the containing topic query. Subtopic nesting, and the cumulative refinement, is unlimited. Use Cases **Topic Visitor** browses topic on PortalTopic page -- PortalTopics visitors see a (possibly empty) description of the topic, its (possibly) empty collectino of subtopics, and batch-browsable links to the topic contents. Visitor visits topic -- ... sees topic description, subtopics, and first batch of topic contents links. Visitor browses topic collection -- The visitor can follow a result link to the target contents, advance bakwards and forward in the results batch (if it's more than a single screenful), *(Not in v1.0.) Visitor twiddles filtering and sorting parameters* -- *to adjust their view of the results.* Visitor navigates to subtopic -- ... by following subtopic link. Visitor gets help about PortalTopics purpose and navigation -- ... by hitting help button. *(Just this design doc, in v1.0.)* * (Not in v1.0.) Eventually, when returning to a topic, the visitor's view resumes with batch, sort, and filter state as they last left it. For now, they return to start.* **Topic configurer** configures topic Configurers can toggle the view of the topic to reveal controls for adjusting the topic description, subtopics, and topic query criteria. Topic configurer adjusts topic criteria -- Configurer hits a button that opens the configuration form, showing a view of the same topic, with: - A text area for the filling in the topic description - An add/delete/rename list of subtopics, for managing their containment. - A section for changing the topic criteria. The top of the section is a table with columns for string, integer, and list criteria entries. The bottom is a row of buttons: "Submit Changes", "Delete Checked", and "Reset" Table entries for already set criteria will consist of a checkbox, the criterion field name, and an input box for the value. The checkbox indicates entries to be deleted. The bottom of each column will have a "blank" entry, for adding a new criterion. It will be like the existing entries but it will not have the checkbox, and its initial value will be empty. - *(Not in v1.0.) A control for designating whether or not to apply the topic query. (The topic may only be for collecting and specifying the common aspects of a query for it's subtopics).* The qeury results will display as they would for a regular visitor. Topic configurer gets help about configuring PortalTopics -- ... by hitting help button. *(Just this design doc, in v1.0.)* CMF-1.3/CMFTopic/help/Topics.stx0100644000076500007650000000157307264373247016262 0ustar tseavertseaverCMF Topic Overview CMF Topics present a way of defining a *canned catalog query*. They help organize a site into dynamically executed searches according to a set of static criteria defined by the person who created or configured the Topic. Visitors to a particular Topic will see its results, and also links to any subtopics, which may use their parent topics criteria to further refine a search. Clicking on any particular result will lead to the item. Of course, being skinnable, this behavior may be altered by the site designer as needed. The configurer (the creator and anyone generally enabled to configure CMF Topics according to site policy) configures the Topic by adding criterion and setting the values. The current set of criteria include simple String, Integer, and List, as well as some Date criterion. Subtopics may be nested for cumulative refinement.CMF-1.3/CMFTopic/interfaces/0040755000076500007650000000000007524010071015427 5ustar tseavertseaverCMF-1.3/CMFTopic/interfaces/Criterion.py0100644000076500007650000000533207401232663017746 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """\ Declare interface for search criterion classes, as used by Topic instances to build their queries. """ import Interface class Criterion(Interface.Base): """\ A Topic is composed of Criterion objects which specify the query used for the Topic. By supplying some basic information, the Criterion objects can be plugged into Topics without the Topic having to be too aware of the Criteria types. """ def Type(self): """\ Return the type of criterion object this is (ie - 'List Criterion') """ def Field(self): """\ Return the field this criterion object searches on. """ def Description(self): """\ Return a brief description of the criteria type. """ def editableAttributes(self): """\ Returns a tuble of editable attributes. The values of this are used by the topic to build commands to send to the 'edit' method based on each criterion's setup. """ def getEditForm(self): """\ Return the name of a DTML component used to edit criterion. Editforms should be specific to their type of criteria. """ def apply(self, command): """\ To make it easier to apply values from the rather dynamic Criterion edit form using Python Scripts, apply takes a mapping object as a default and applies itself to self.edit. It's basically a nice and protected wrapper around apply(self.edit, (), command). """ def edit(self, **kw): """\ The signature of this method should be specific to the criterion. Using the values in the attribute '_editableAttributes', the Topic can apply the right commands to each criteria object as its being edited without having to know too much about the structure. """ def criteriaItems(self): """\ Return a sequence of key-value tuples, each representing a value to be injected into the query dictionary (and, therefore, tailored to work with the catalog). """ CMF-1.3/CMFTopic/interfaces/__init__.py0100644000076500007650000000131607401232663017545 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """\ Loads interface names into the package. """ from Criterion import Criterion CMF-1.3/CMFTopic/skins/0040755000076500007650000000000007524010071014433 5ustar tseavertseaverCMF-1.3/CMFTopic/skins/topic/0040755000076500007650000000000007524010071015551 5ustar tseavertseaverCMF-1.3/CMFTopic/skins/topic/friendlydatec_editform.dtml0100644000076500007650000000266407264134576023170 0ustar tseavertseaver
     
    CMF-1.3/CMFTopic/skins/topic/listc_edit.dtml0100644000076500007650000000213407416556754020602 0ustar tseavertseaver
      Value:

    Operator:
    CMF-1.3/CMFTopic/skins/topic/sic_edit.dtml0100644000076500007650000000272307410451323020222 0ustar tseavertseaver
      Value:
      Direction:
    CMF-1.3/CMFTopic/skins/topic/sort_edit.dtml0100644000076500007650000000137007340252567020443 0ustar tseavertseaver
      Reversed?
    CMF-1.3/CMFTopic/skins/topic/ssc_edit.dtml0100644000076500007650000000114607247477732020256 0ustar tseavertseaver
      Value:
    CMF-1.3/CMFTopic/skins/topic/topic_addCriterion.py0100644000076500007650000000055307374601306021742 0ustar tseavertseaver## Script (Python) "topic_addCriterion" ##bind container=container ##bind context=context ##bind namespace= ##bind script=script ##bind subpath=traverse_subpath ##parameters=REQUEST, RESPONSE, field, criterion_type ##title= ## context.addCriterion(field=field, criterion_type=criterion_type) RESPONSE.redirect('%s/topic_criteria_form' % context.absolute_url()) CMF-1.3/CMFTopic/skins/topic/topic_addSubtopic.py0100644000076500007650000000072407265122345021574 0ustar tseavertseaver## Script (Python) "topic_addSubtopic" ##bind container=container ##bind context=context ##bind namespace= ##bind script=script ##bind subpath=traverse_subpath ##parameters=REQUEST, RESPONSE, new_id ##title= ## topictype = context.getTypeInfo() context.addSubtopic(new_id) action = topictype.getActionById('subtopics') url = '%s/%s?portal_status_message=%s' % ( context.absolute_url(), action, "Subtopic+'%s'+added" % new_id ) RESPONSE.redirect(url) CMF-1.3/CMFTopic/skins/topic/topic_criteria_form.dtml0100644000076500007650000000234207401607421022461 0ustar tseavertseaver

    Topic Criteria: &dtml-getId;

      

    Add New Topic Criteria:

    Field id:
    Criterion type:
     
    CMF-1.3/CMFTopic/skins/topic/topic_deleteCriteria.py0100644000076500007650000000067107265122345022261 0ustar tseavertseaver## Script (Python) "topic_deleteCriteria" ##bind container=container ##bind context=context ##bind namespace= ##bind script=script ##bind subpath=traverse_subpath ##parameters=REQUEST, RESPONSE, criterion_ids ##title= ## for cid in criterion_ids: context.deleteCriterion(cid) message = 'Criteria+deleted.' RESPONSE.redirect('%s/topic_criteria_form?portal_status_message=%s' % ( context.absolute_url(), message) ) CMF-1.3/CMFTopic/skins/topic/topic_editCriteria.py0100644000076500007650000000225707265122345021746 0ustar tseavertseaver## Script (Python) "topic_editCriteria" ##bind container=container ##bind context=context ##bind namespace= ##bind script=script ##bind subpath=traverse_subpath ##parameters=REQUEST, RESPONSE, criteria ##title= ## """\ Save changes to the list of criteria. This is done by going over the submitted criteria records and comparing them against the criteria object's editable attributes. A 'command' object is built to send to the Criterion objects 'apply' method, which in turn applies the command to the Criterion objects 'edit' method. """ for rec in criteria: crit = context.getCriterion(rec.id) command = {} for attr in crit.editableAttributes(): tmp = getattr(rec, attr, None) # Due to having multiple radio buttons on the same page # with the same name but belonging to different records, # they needed to be associated with different records with ids if tmp is None: tmp = getattr(rec, '%s__%s' % (attr, rec.id), None) command[attr] = tmp crit.apply(command) message='Changes+saved.' RESPONSE.redirect('%s/topic_criteria_form?portal_status_message=%s' % ( context.absolute_url(), message) ) CMF-1.3/CMFTopic/skins/topic/topic_editTopic.py0100644000076500007650000000064407265122345021260 0ustar tseavertseaver## Script (Python) "topic_editTopic" ##bind container=container ##bind context=context ##bind namespace= ##bind script=script ##bind subpath=traverse_subpath ##parameters=REQUEST, RESPONSE, acquireCriteria, title=None, description=None ##title= ## context.edit(acquireCriteria=acquireCriteria, title=title, description=description) RESPONSE.redirect('%s/topic_view' % context.absolute_url()) CMF-1.3/CMFTopic/skins/topic/topic_edit_form.dtml0100644000076500007650000000204107311716162021603 0ustar tseavertseaver

    Edit Topic:

    Title:
    Description:
    Acquire Criteria
    from Parent:

    CMF-1.3/CMFTopic/skins/topic/topic_icon.gif0100644000076500007650000000011507256722663020403 0ustar tseavertseaverGIF89a! ,y/*0JI}ߺ2;CMF-1.3/CMFTopic/skins/topic/topic_subtopics_form.dtml0100644000076500007650000000244507265122345022703 0ustar tseavertseaver

    Topic Subtopics:

    &dtml-getId; ( : , )

    Add subtopic:

    Id:

    CMF-1.3/CMFTopic/skins/topic/topic_view.dtml0100644000076500007650000000266507406726310020622 0ustar tseavertseaver

    Topic:

    Subtopics:

    Topic matches:

    Previous items Next items

    Query Parameters

    • :
    CMF-1.3/CMFTopic/skins/zpt_topic/0040755000076500007650000000000007524010071016446 5ustar tseavertseaverCMF-1.3/CMFTopic/skins/zpt_topic/friendlydatec_editform.pt0100644000076500007650000000340507510431524023525 0ustar tseavertseaver
     
    CMF-1.3/CMFTopic/skins/zpt_topic/listc_edit.pt0100644000076500007650000000235607416556754021170 0ustar tseavertseaver
      Value:

    Operator:
    CMF-1.3/CMFTopic/skins/zpt_topic/sic_edit.pt0100644000076500007650000000410407410451323020575 0ustar tseavertseaver
      Value:
      Direction:
    CMF-1.3/CMFTopic/skins/zpt_topic/sort_edit.pt0100644000076500007650000000154707351733520021024 0ustar tseavertseaver
      Reversed?
    CMF-1.3/CMFTopic/skins/zpt_topic/ssc_edit.pt0100644000076500007650000000165107343175100020614 0ustar tseavertseaver
      Value:
    CMF-1.3/CMFTopic/skins/zpt_topic/topic_criteria_form.pt0100644000076500007650000000341207401607421023040 0ustar tseavertseaver

    Topic Criteria:

      

    Add New Topic Criteria:

    Field id:
    Criteria type:
     
    CMF-1.3/CMFTopic/skins/zpt_topic/topic_edit_form.pt0100644000076500007650000000242007507431664022174 0ustar tseavertseaver

    Edit Topic:

    Title:
    Description:
    Acquire Criteria
    from Parent:

    CMF-1.3/CMFTopic/skins/zpt_topic/topic_subtopics_form.pt0100644000076500007650000000342607347221671023266 0ustar tseavertseaver

    Topic Subtopics: Id

    Id ( )

    Add subtopic:

    Id:

    CMF-1.3/CMFTopic/skins/zpt_topic/topic_view.pt0100644000076500007650000000412407406726310021172 0ustar tseavertseaver

    title

    Subtopics:

    Topic Matches:

    Previous n items    Next n items

    Query Parameters

    • item
    CMF-1.3/CMFTopic/tests/0040755000076500007650000000000007524010071014446 5ustar tseavertseaverCMF-1.3/CMFTopic/tests/__init__.py0100644000076500007650000000012107260136566016565 0ustar tseavertseaver""" This package contains the unit tests for Topic and its criteria objects. """ CMF-1.3/CMFTopic/tests/test_DateC.py0100644000076500007650000001347007522303415017046 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Unit tests for DateCriterion module. $Id: test_DateC.py,v 1.8.4.1 2025/08/01 19:07:57 tseaver Exp $ """ import unittest from DateTime.DateTime import DateTime class FriendlyDateCriterionTests( unittest.TestCase ): lessThanFiveDaysOld = { 'value': 4 , 'operation': 'min' , 'daterange': 'old' } lessThanOneMonthAhead = { 'value': 30 , 'operation': 'max' , 'daterange': 'ahead' } today = { 'value': 0 , 'operation': 'within_day' , 'daterange': 'ahead' } def test_Interface( self ): from Products.CMFTopic.interfaces import Criterion from Products.CMFTopic.DateCriteria import FriendlyDateCriterion self.failUnless( Criterion.isImplementedByInstancesOf( FriendlyDateCriterion ) ) def test_Empty( self ): from Products.CMFTopic.DateCriteria import FriendlyDateCriterion friendly = FriendlyDateCriterion( 'foo', 'foofield' ) self.assertEqual( friendly.getId(), 'foo' ) self.assertEqual( friendly.field, 'foofield' ) self.assertEqual( friendly.value, None ) self.assertEqual( friendly.operation, 'min' ) self.assertEqual( friendly.daterange, 'old' ) self.assertEqual( len( friendly.getCriteriaItems() ), 0 ) def test_ListOfDefaultDates( self ): from Products.CMFTopic.DateCriteria import FriendlyDateCriterion friendly = FriendlyDateCriterion( 'foo', 'foofield' ) d = friendly.defaultDateOptions() self.assertEqual( d[0][0], 0 ) self.assertEqual( d[1][0], 1 ) self.assertEqual( d[2][0], 2 ) def test_Clear( self ): from Products.CMFTopic.DateCriteria import FriendlyDateCriterion friendly = FriendlyDateCriterion( 'foo', 'foofield' ) friendly.edit( value=None ) self.assertEqual( friendly.value, None ) self.assertEqual( friendly.operation, 'min' ) self.assertEqual( friendly.daterange, 'old' ) def test_Basic( self ): from Products.CMFTopic.DateCriteria import FriendlyDateCriterion friendly = FriendlyDateCriterion( 'foo', 'foofield' ) friendly.apply( self.lessThanFiveDaysOld ) self.assertEqual( friendly.value, 4 ) self.assertEqual( friendly.operation, 'min' ) self.assertEqual( friendly.daterange, 'old' ) def test_BadInput( self ): from Products.CMFTopic.DateCriteria import FriendlyDateCriterion friendly = FriendlyDateCriterion( 'foo', 'foofield' ) # Bogus value self.assertRaises( ValueError, friendly.edit, 'blah' ) # Bogus operation self.assertRaises( ValueError, friendly.edit, 4, 'min:max', 'old' ) # Bogus daterange self.assertRaises( ValueError, friendly.edit, 4, 'max', 'new' ) def test_StringAsValue( self ): from Products.CMFTopic.DateCriteria import FriendlyDateCriterion friendly = FriendlyDateCriterion( 'foo', 'foofield' ) friendly.edit( '4' ) self.assertEqual( friendly.value, 4 ) friendly.edit( '-4' ) self.assertEqual( friendly.value, -4 ) friendly.edit( '' ) self.assertEqual( friendly.value, None ) def test_Today( self ): from Products.CMFTopic.DateCriteria import FriendlyDateCriterion friendly = FriendlyDateCriterion( 'foo', 'foofield' ) friendly.apply( self.today ) self.assertEqual( friendly.daterange, 'ahead' ) now = DateTime() result = friendly.getCriteriaItems() self.assertEqual( len( result ), 2 ) self.assertEqual( result[0][0], 'foofield' ) self.assertEqual( result[0][1], ( now.earliestTime() , now.latestTime() ) ) self.assertEqual( result[1][0], 'foofield_usage' ) self.assertEqual( result[1][1], 'range:min:max' ) def test_FiveDaysOld( self ): from Products.CMFTopic.DateCriteria import FriendlyDateCriterion friendly = FriendlyDateCriterion( 'foo', 'foofield' ) friendly.apply( self.lessThanFiveDaysOld ) self.assertEqual( friendly.daterange, 'old' ) result = friendly.getCriteriaItems() self.assertEqual( len( result ), 2 ) self.assertEqual( result[0][0], 'foofield' ) self.assertEqual( result[0][1].Date(), ( DateTime() - 4 ).Date() ) self.assertEqual( result[1][0], 'foofield_usage' ) self.assertEqual( result[1][1], 'range:min' ) def test_OneMonthAhead( self ): from Products.CMFTopic.DateCriteria import FriendlyDateCriterion friendly = FriendlyDateCriterion( 'foo', 'foofield' ) friendly.apply( self.lessThanOneMonthAhead ) self.assertEqual( friendly.daterange, 'ahead' ) result = friendly.getCriteriaItems() self.assertEqual( result[0][1].Date(), ( DateTime() + 30 ).Date() ) self.assertEqual( result[1][1], 'range:max' ) def test_suite(): return unittest.makeSuite( FriendlyDateCriterionTests ) def main(): unittest.TextTestRunner().run( test_suite() ) if __name__ == '__main__': main() CMF-1.3/CMFTopic/tests/test_ListC.py0100644000076500007650000000626407522303415017107 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Unit tests for ListCriterion module. $Id: test_ListC.py,v 1.7.12.1 2025/08/01 19:07:57 tseaver Exp $ """ import unittest class ListCriterionTests( unittest.TestCase ): def test_Interface( self ): from Products.CMFTopic.interfaces import Criterion from Products.CMFTopic.ListCriterion import ListCriterion self.failUnless( Criterion.isImplementedByInstancesOf( ListCriterion ) ) def test_Empty( self ): from Products.CMFTopic.ListCriterion import ListCriterion listc = ListCriterion('foo', 'foofield') self.assertEqual( listc.getId(), 'foo' ) self.assertEqual( listc.field, 'foofield' ) self.assertEqual( listc.value, ('',) ) self.assertEqual( len(listc.getCriteriaItems()), 0 ) def test_Edit_withString( self ): from Products.CMFTopic.ListCriterion import ListCriterion listc = ListCriterion( 'foo', 'foofield' ) listc.edit('bar\nbaz') self.assertEqual( listc.getId(), 'foo' ) self.assertEqual( listc.field, 'foofield' ) self.assertEqual( listc.value, ( 'bar', 'baz' ) ) items = listc.getCriteriaItems() self.assertEqual( len( items ), 1 ) self.assertEqual( len( items[0] ), 2 ) self.assertEqual( items[0][0], 'foofield' ) self.assertEqual( items[0][1], ( 'bar', 'baz' ) ) def test_Edit_withList( self ): from Products.CMFTopic.ListCriterion import ListCriterion listc = ListCriterion( 'foo', 'foofield' ) abc = [ 'a', 'b', 'c' ] listc.edit( abc ) items = listc.getCriteriaItems() self.failUnless( 'foofield' in map( lambda x: x[0], items ) ) self.failUnless( tuple( abc ) in map( lambda x: x[1], items ) ) def test_operator( self ): from Products.CMFTopic.ListCriterion import ListCriterion listc = ListCriterion( 'foo', 'foofield' ) abc = [ 'a', 'b', 'c' ] listc.edit( abc ) items = listc.getCriteriaItems() self.assertEqual( len( items ), 1 ) listc.edit( abc, 'or' ) items = listc.getCriteriaItems() self.assertEqual( len( items ), 2 ) self.failUnless( ( 'foofield_operator', 'or' ) in items ) listc.edit( abc, 'and' ) items = listc.getCriteriaItems() self.assertEqual( len( items ), 2 ) self.failUnless( ( 'foofield_operator', 'and' ) in items ) def test_suite(): return unittest.makeSuite( ListCriterionTests ) def main(): unittest.TextTestRunner().run( test_suite() ) if __name__ == '__main__': main() CMF-1.3/CMFTopic/tests/test_SIC.py0100644000076500007650000001543007522303415016502 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Unit tests for SimpleIntCriterion module. $Id: test_SIC.py,v 1.6.22.1 2025/08/01 19:07:57 tseaver Exp $ """ import unittest class SimpleIntCriterionTests( unittest.TestCase ): def test_Interface( self ): from Products.CMFTopic.interfaces import Criterion from Products.CMFTopic.SimpleIntCriterion import SimpleIntCriterion self.failUnless( Criterion.isImplementedByInstancesOf( SimpleIntCriterion ) ) def test_Empty( self ): from Products.CMFTopic.SimpleIntCriterion import SimpleIntCriterion sic = SimpleIntCriterion( 'foo', 'foofield' ) self.assertEqual( sic.getId(), 'foo' ) self.assertEqual( sic.field, 'foofield' ) self.assertEqual( sic.value, None ) self.assertEqual( sic.getValueString(), '' ) self.assertEqual( len(sic.getCriteriaItems() ), 0 ) def test_EditWithString( self ): from Products.CMFTopic.SimpleIntCriterion import SimpleIntCriterion sic = SimpleIntCriterion('foo', 'foofield') sic.edit('0') self.assertEqual( sic.value, 0 ) self.assertEqual( sic.getValueString(), '0' ) items = sic.getCriteriaItems() self.assertEqual( len( items ), 1 ) self.assertEqual( len( items[0] ), 2 ) self.assertEqual( items[0][0], 'foofield' ) self.assertEqual( items[0][1], 0 ) def test_EditWithInt( self ): from Products.CMFTopic.SimpleIntCriterion import SimpleIntCriterion sic = SimpleIntCriterion( 'foo', 'foofield' ) sic.edit( 32 ) self.assertEqual( sic.value, 32 ) self.assertEqual( sic.getValueString(), '32' ) items = sic.getCriteriaItems() self.assertEqual( len(items), 1 ) self.assertEqual( len(items[0]), 2 ) self.assertEqual( items[0][1], 32 ) def test_RangeMin( self ): from Products.CMFTopic.SimpleIntCriterion import SimpleIntCriterion sic = SimpleIntCriterion( 'foo', 'foofield' ) sic.edit( '32', SimpleIntCriterion.MINIMUM ) self.assertEqual( sic.value, 32 ) self.assertEqual( sic.getValueString(), '32' ) items = sic.getCriteriaItems() self.assertEqual( len( items ), 2 ) self.assertEqual( len( items[0] ), 2 ) self.assertEqual( len( items[1] ), 2 ) self.assertEqual( items[0][0], 'foofield' ) self.assertEqual( items[0][1], 32 ) self.assertEqual( items[1][0], 'foofield_usage' ) self.assertEqual( items[1][1], 'range:min' ) def test_RangeMin_withInt( self ): from Products.CMFTopic.SimpleIntCriterion import SimpleIntCriterion sic = SimpleIntCriterion( 'foo', 'foofield' ) sic.edit( 32, SimpleIntCriterion.MINIMUM ) self.assertEqual( sic.value, 32 ) self.assertEqual( sic.getValueString(), '32' ) items = sic.getCriteriaItems() self.assertEqual( len( items ), 2 ) self.assertEqual( len( items[0] ), 2 ) self.assertEqual( len( items[1] ), 2 ) self.assertEqual( items[0][0], 'foofield' ) self.assertEqual( items[0][1], 32 ) self.assertEqual( items[1][0], 'foofield_usage' ) self.assertEqual( items[1][1], 'range:min' ) def test_RangeMax( self ): from Products.CMFTopic.SimpleIntCriterion import SimpleIntCriterion sic = SimpleIntCriterion( 'foo', 'foofield' ) sic.edit( '32', SimpleIntCriterion.MAXIMUM ) self.assertEqual( sic.value, 32 ) self.assertEqual( sic.getValueString(), '32' ) items = sic.getCriteriaItems() self.assertEqual( len( items ), 2 ) self.assertEqual( len( items[0] ), 2 ) self.assertEqual( len( items[1] ), 2 ) self.assertEqual( items[0][0], 'foofield' ) self.assertEqual( items[0][1], 32 ) self.assertEqual( items[1][0], 'foofield_usage' ) self.assertEqual( items[1][1], 'range:max' ) def test_RangeMax_withInt( self ): from Products.CMFTopic.SimpleIntCriterion import SimpleIntCriterion sic = SimpleIntCriterion( 'foo', 'foofield' ) sic.edit( 32, SimpleIntCriterion.MAXIMUM ) self.assertEqual( sic.value, 32 ) self.assertEqual( sic.getValueString(), '32' ) items = sic.getCriteriaItems() self.assertEqual( len( items ), 2 ) self.assertEqual( len( items[0] ), 2 ) self.assertEqual( len( items[1] ), 2 ) self.assertEqual( items[0][0], 'foofield' ) self.assertEqual( items[0][1], 32 ) self.assertEqual( items[1][0], 'foofield_usage' ) self.assertEqual( items[1][1], 'range:max' ) def test_RangeMinMax( self ): from Products.CMFTopic.SimpleIntCriterion import SimpleIntCriterion sic = SimpleIntCriterion( 'foo', 'foofield' ) sic.edit( '32 34', SimpleIntCriterion.MINMAX ) self.assertEqual( sic.value, ( 32, 34 ) ) self.assertEqual( sic.getValueString(), '32 34' ) items = sic.getCriteriaItems() self.assertEqual( len( items ), 2 ) self.assertEqual( len( items[0] ), 2 ) self.assertEqual( len( items[1] ), 2 ) self.assertEqual( items[0][0], 'foofield' ) self.assertEqual( items[0][1], ( 32, 34 ) ) self.assertEqual( items[1][0], 'foofield_usage' ) self.assertEqual( items[1][1], 'range:min:max' ) def test_RangeMinMax_withTuple( self ): from Products.CMFTopic.SimpleIntCriterion import SimpleIntCriterion sic = SimpleIntCriterion( 'foo', 'foofield' ) sic.edit( ( 32, 34 ), SimpleIntCriterion.MINMAX ) self.assertEqual( sic.value, ( 32, 34 ) ) self.assertEqual( sic.getValueString(), '32 34' ) items = sic.getCriteriaItems() self.assertEqual( len( items ), 2 ) self.assertEqual( len( items[0] ), 2 ) self.assertEqual( len( items[1] ), 2 ) self.assertEqual( items[0][0], 'foofield' ) self.assertEqual( items[0][1], ( 32, 34 ) ) self.assertEqual( items[1][0], 'foofield_usage' ) self.assertEqual( items[1][1], 'range:min:max' ) def test_suite(): return unittest.makeSuite( SimpleIntCriterionTests ) def main(): unittest.TextTestRunner().run( test_suite() ) if __name__ == '__main__': main() CMF-1.3/CMFTopic/tests/test_SSC.py0100644000076500007650000000435007522303415016513 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Unit tests for SimpleStringCriterion module. $Id: test_SSC.py,v 1.6.22.1 2025/08/01 19:07:57 tseaver Exp $ """ import unittest class SimpleStringCriterionTests( unittest.TestCase ): def test_Interface( self ): from Products.CMFTopic.interfaces import Criterion from Products.CMFTopic.SimpleStringCriterion \ import SimpleStringCriterion self.failUnless( Criterion.isImplementedByInstancesOf( SimpleStringCriterion ) ) def test_Empty( self ): from Products.CMFTopic.SimpleStringCriterion \ import SimpleStringCriterion ssc = SimpleStringCriterion( 'foo', 'foofield' ) self.assertEqual( ssc.getId(), 'foo' ) self.assertEqual( ssc.field, 'foofield' ) self.assertEqual( ssc.value, '' ) self.assertEqual( len( ssc.getCriteriaItems() ), 0 ) def test_Nonempty( self ): from Products.CMFTopic.SimpleStringCriterion \ import SimpleStringCriterion ssc = SimpleStringCriterion( 'foo', 'foofield' ) ssc.edit( 'bar' ) self.assertEqual( ssc.getId(), 'foo' ) self.assertEqual( ssc.field, 'foofield' ) self.assertEqual( ssc.value, 'bar' ) items = ssc.getCriteriaItems() self.assertEqual( len( items ), 1 ) self.assertEqual( len( items[0] ), 2 ) self.assertEqual( items[0][0], 'foofield' ) self.assertEqual( items[0][1], 'bar' ) def test_suite(): return unittest.makeSuite( SimpleStringCriterionTests ) def main(): unittest.TextTestRunner().run( test_suite() ) if __name__ == '__main__': main() CMF-1.3/CMFTopic/tests/test_SortC.py0100644000076500007650000000472707522303415017125 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Unit tests for SortCriterion module. $Id: test_SortC.py,v 1.2.22.1 2025/08/01 19:07:57 tseaver Exp $ """ import unittest class SortCriterionTests( unittest.TestCase ): def test_Interface( self ): from Products.CMFTopic.interfaces import Criterion from Products.CMFTopic.SortCriterion import SortCriterion self.failUnless( Criterion.isImplementedByInstancesOf( SortCriterion ) ) def test_Empty( self ): from Products.CMFTopic.SortCriterion import SortCriterion ssc = SortCriterion( 'foo', 'foofield' ) self.assertEqual( ssc.getId(), 'foo' ) self.assertEqual( ssc.field, None ) self.assertEqual( ssc.index, 'foofield' ) self.assertEqual( ssc.Field(), 'foofield' ) self.assertEqual( ssc.reversed, 0 ) items = ssc.getCriteriaItems() self.assertEqual( len( items ), 1 ) self.assertEqual( items[0][0], 'sort_on' ) self.assertEqual( items[0][1], 'foofield' ) def test_Nonempty( self ): from Products.CMFTopic.SortCriterion import SortCriterion ssc = SortCriterion( 'foo', 'foofield' ) ssc.edit( 1 ) self.assertEqual( ssc.getId(), 'foo' ) self.assertEqual( ssc.field, None ) self.assertEqual( ssc.index, 'foofield' ) self.assertEqual( ssc.Field(), 'foofield' ) self.assertEqual( ssc.reversed, 1 ) items = ssc.getCriteriaItems() self.assertEqual( len( items ), 2 ) self.assertEqual( items[0][0], 'sort_on' ) self.assertEqual( items[0][1], 'foofield' ) self.assertEqual( items[1][0], 'sort_order' ) self.assertEqual( items[1][1], 'reverse' ) def test_suite(): return unittest.makeSuite( SortCriterionTests ) def main(): unittest.TextTestRunner().run( test_suite() ) if __name__ == '__main__': main() CMF-1.3/CMFTopic/tests/test_Topic.py0100644000076500007650000000342107410451323017135 0ustar tseavertseaverimport unittest class TestTopic(unittest.TestCase): """ Test all the general Topic cases """ def test_Empty( self ): from Products.CMFTopic.Topic import Topic topic = Topic('top') query = topic.buildQuery() self.assertEqual( len( query ), 0 ) def test_Simple( self ): from Products.CMFTopic.Topic import Topic topic = Topic('top') topic.addCriterion( 'foo', 'String Criterion' ) topic.getCriterion( 'foo' ).edit( 'bar' ) query = topic.buildQuery() self.assertEqual( len(query), 1 ) self.assertEqual( query['foo'], 'bar' ) topic.addCriterion( 'baz', 'Integer Criterion' ) topic.getCriterion( 'baz' ).edit( 43 ) query = topic.buildQuery() self.assertEqual( len( query ), 2 ) self.assertEqual( query[ 'foo' ], 'bar' ) self.assertEqual( query[ 'baz' ], 43 ) def test_Nested( self ): from Products.CMFTopic.Topic import Topic topic = Topic('top') topic.addCriterion( 'foo', 'String Criterion' ) topic.getCriterion( 'foo' ).edit( 'bar' ) topic.addSubtopic( 'qux' ) subtopic = topic.qux subtopic.addCriterion( 'baz', 'String Criterion' ) subtopic.getCriterion( 'baz' ).edit( 'bam' ) query = subtopic.buildQuery() self.assertEqual( len( query ), 2 ) self.assertEqual( query['foo'], 'bar' ) self.assertEqual( query['baz'], 'bam' ) subtopic.acquireCriteria = 0 query = subtopic.buildQuery() self.assertEqual( len( query ), 1 ) self.assertEqual( query['baz'], 'bam' ) def test_suite(): return unittest.makeSuite(TestTopic) if __name__ == '__main__': unittest.TextTestRunner().run(test_suite()) CMF-1.3/CMFTopic/tests/test_all.py0100644000076500007650000000054507433262536016646 0ustar tseavertseaverimport Zope from unittest import main from Products.CMFCore.tests.base.utils import build_test_suite def test_suite(): return build_test_suite('Products.CMFTopic.tests',[ 'test_Topic', 'test_DateC', 'test_ListC', 'test_SIC', 'test_SSC', ]) if __name__ == '__main__': main(defaultTest='test_suite') CMF-1.3/docs/0040755000076500007650000000000007524010072012631 5ustar tseavertseaverCMF-1.3/docs/imgs/0040755000076500007650000000000007524010073013571 5ustar tseavertseaverCMF-1.3/docs/imgs/category.png0100644000076500007650000004724107374605754016144 0ustar tseavertseaverPNG  IHDRPLTENNN33fffB"""7<ff3ff,)6Q7KzNfct3f333ffߙff[gilЄֹwPPP@9-M IDATx c۶!_ueE3ngkM%muW\@,$HBsdk$>܋-O++pA pA C4i)SyTn}mKfUv[n\d|> y7D꾾uFA徰ӼUҵنf |X6XO*~+3\-}K<)56 Ko8 ,G}cĽƀSo侪X྾.p V9[@1poB^ޡ۬,Muq7)=W}aׅ؆j ˽#A@ ~u='o@[`0ȃ9}c~ef| -#7 9`~o>hHM{C^v%qRAмpA pA|!C!%W-LG<~~Z!hyܯ6j$j-V!vOV׸RS?yvUT_KW#jQ߇$ ˤMKl: qz6]`t9Kwn;n'bj2#!#2{Q׻oVʏ'/__D:_EKIw7hU9@Ept6u/ISN^aosw앃H>"Ou' ~MJ}CRاK{ZA{^ ,J.?/V~~|ݶիR>I~;<]2ا?O߷ Z]fӳH~e&yzg{q~?Va~'7-V־9Qu 6+n%- INf~r%Mn:_La YͰd1{}_kﲝh⧔$_F]n"KN{),(s\(#?)mU_nט? :u*Ͽ徧SH"q_a{V>E^o?I^^ORqY RSOB/+)~޿boχ6}my}ۯײiw}^n*e6ڭnvա= '$_c\u˨ENTK{Ȫ׶EZ_^Js}*:v2ooo3L`׷9gnҗT[S_:a_,)Wy2SzRݪ [GQҦ[@?)Ԗ{i%Y"1*v{tԍ}ԯEn"-VB_g?m.y~ ~}$i\zMdת^fwb+V^Sq*{ϗ5{Y)?;tE:+|Z/u!vm{}{oϖlbiQ7KazD2JSïMe_o65tuIt?3ez v}ܗW3싾>t /:r/}^o+=ɨOOݤ+HEQt='Ϥoeӳ ~+Eˆ&+ږ}DfO/B4Koooo6g_efx"OV^Dm%E,Kͽg٨6o}e_xU6>1}uޯ W7&M v~7`??)ͦ%k}kLN$no說QR_DjoM%ϓ$B=?+a/}j>fi{*Xg׫޵:%o{{lyiWAJK%Ne;Zw.!/Q,vbR^uڂ$y!EDԺfi(Q6u/o2__o$9f.Afn8u^5tp (eJR[$~/"e y_D~TZgzIf}_tlo fӢ>K7Ye5FfS%7IREU ?iZ~}q/%晀tWm|uu lIe wԵON[َ6!hVARņJ>wN6;=|$)lE2_{4'3‹cr>&A5h?}y)(~WX7mҫ5_5Ks|Cn4< 1OH-W+h`~>ɩ|[.PdxF=!'ƶ/ >nӹ\?_Xܞ`.q_i&+b^B՝{=U,Yi_͈zƅ/l=1Y=-* /A;#` s2I32#eU=AYЙ{-Vk"E8 uV`Zw5:_:s_H2n>vNYHO]()#uu%Xe50Ӻ7 |l"_Rxf+JILy/.T%n2t>Ǩ:jQr j}S#֏mypJLW|oܷW~~O]z!ܛ62|JH72vƶ9aCCUuc7`8>%MH)7jP̽s)rߧ|%Q~5UU }V;+}-\s}>Efɣv@l"Vn8 v\}>_q9Uy)JIG ڋ{pK(ʗXU_y^P*VB}p H,|++{}b,}IrpQ qC_{͛Aː hA=rnhrgo&= kdwy{N)?)HRvnR^#4z/ڟCk7oq/ zފhSzU!h{_^CmG`;q(nXr0w&ԏ8@k=q}<=xf+j;RHGs\s e؋|ln>W^tX❡˽o|pXkѩGV^]%N d.nyޘ1~ 6Uvuݵ^Y:Y x&?ytɲvNNk.yEqh+{_H*l)ȇ}?M0-E~Vܗ:n Xs0J:h-ّ}u0]g\jKQmYmŘ[v@~"y~Y{{=;z54gjw$jf&!O|-ds2 =^.w󓤆{ܹ {TC7@ƆfsKH Vhsr^ ykg)^wc2 y\εNk3!|}g_|v=-Z]Tʏ WopN:ޓ?W63dw_5pOILrWW(q/#'<)E݄yEB;;8Lc|}6U4S94O\}}zg5iI 4O6z)޷5?fd&z<͍В5xkC0ւHAa`{/;~~mUb̒+~7mۘX7g"Is~O*5kE}SgL;L|xM5>{?u?&pAܫq~CBVZC|pA~AԞϏAP/7?AA~TGNl[t=PMc 8Fj˹0Ku3t8D ѯoylGp=sezng D ѯoylGp<83126.E3\ B!WFL! %,Lz=3l,4ՆF܊v30,KCMG6!*lj=u8p 9ՅBbՓgH/*n E~c6ܳ_/|>w"L{}>}b15af ɘ}±fQ!〻:JuR<.(c4N螳5rūA1G:W3#rI$b2'KlJdVCo]c„^_'U9o ߯KG[CD:^gV}E(5sTbLa>h}Sn:nSiA=ϙaif뫏.BP݈qa5dO׷^b4bozW@uYq3yB@7Px77uS!E+7iu:gLC<鑍UY!/dYkO8?7 ុ"ѧPp:$ՀP7n*FUm1V(bM$Q]%sލg\Gqo [rB]^z+w&_s0O P0{ b|A9&ɻ [eW %Ά*\SX-?]ܳ]/' r[_5; s^!Ay)a@ۖ.[iylPmzO+?soF>YzGۍ|~v|aSՆP:j"P{ECZ>(WoYp1ݓ5I :g&3\'!=-:OB6Yh]̏AP{>?A<u~>- pAм|p!=-{\Z;A>!7BO=A3>뤗Q:R|L"/g==,{p`Mњ_r~\OԵׯߤzR{܏.}}z$"{ܳث\g^?cf cJ_~Zv|ܿO{M>}7wo}{J,gn~*{~e韝?uz_Keg^CgtlՕWpM.!}e|&bo=4}a凉_ͷߤ| ~8Yc}}{]?p'8py{YokWMOzKxp?}n#Oާn)s R싒7Z~X4ĸMܟ)ߤ_J}jKO}ez}k) C/܃s?e{/[J?Kd#֍}V 0? ý6/~Q-2X~+a*e|ӷ:w 7M۷?ьCَ.{K+OBuo*>= ߩ~c\q_,>VTQXnDd&^}z_ݞ;g%͗ؿ֍l{ ~m]N|xT,#"UA9̱fǪࠕ bN͸5gClTEǗ?޻W<굗nue=܏ʄV]K[_}uf~r< g 7z~m=ouZxzuϖsݛ=4a}Q|b_kO/? 罖%.>XO?(RV *?{hpopzCP nm(Q$T7XJ/Eނ1B4 n^|+sk-K<(C3Ԗ{ ូkW+/s-8sO&.Gz{kksPf~&vտro9M]=A{r;NLllq״ UVs0V(֟ ?6c_O\G㎕ꅾ\ojږFD<>ܿ{l Aq7s wF`݀ pA C!{= ܃{=Yp*@/4y"pA3q? C칗~/k}AĹZe86:{?e~S 4zjSn}YY}=M{gʊF #n@8?<'J?X=M{dWn6yZ|&b6˳+.{=x= p!܃{=sL!hYľ{RT;omк#mƝ6 masܵj Yb$^I=V=[ý H;i'o[qr4%0z~PUvl?++('u#)Y4?iV_yL=Q(ږN"{'k! {s/j<{(,z,F3ϲ$=geemj沥q/=#;1.r q?z:{ovܻдnŽ"A-͒0+ LoEsVoOX/, |f _f6rrM?tqo7-{݈ު[;&W_X+LDA7͠/Fk>2eޱ\۹$7qMظ(gTbͽPXsVVPG <~ߓy?*UZ7>枸|rg]6pLV}f5n$rƝ19;dϊ9E!NVPyŪSs^k `ɚC,7 gHOfqO^}=NK4+nLno{cIs_eo!orm!hܛ&}ܓ4CZBI+O{C¹Wx^Tߍ%_AՓ{pZf\_}lO}=1SATS{|3JZÿY{hyM{Aa|8)1O~υtHlܟ#}^WjI=tPUx7&6'$M/ -^:!f#T΁= 06I2D'L#5:;Hl{?!?&^'"zft/vŇFw<+j'6>TMx#ɇ`\`lƳf4M6#c7rWο{'vV%<{?[p$:ioBmZ^O 7yZ@;8h!x ''Gs#GH]Yɀ6Ζ-V?=4Dzqf<=Gޠqp7?==Z Jo!h 7{.}`8F}՛,/$k~O{!L{$}#ʬKǃ Uu2CUݱ2d={5=͇x #`!hv5M*pAs^7ns%q4_:V[6ށ{:UݽFQo;*V#)rux?{Z]|wpA0$aLwjU!hYK4{^A4A;R4GsksƉ?_Vjƺ43Q?JQ.¹? z+\滕{hܧޏXzVV|pާO>3 {>睄l3O :dglv$yx<3:> \5,t*UКEgԧv">C?Žշ.½Fy?](!/}C7cBW\WI;I/G7f9:#n^y\5NuqwWr,;\{PZd'PK}k lUd@ۀuÁC%mCriW<|5? o`/:2;MfhGH5韤}%n)`o%V3Hb;qӏV0צH62sM~>uf?A'x\$K3{!@ub@fݳ ;לaedNsug_:I|tR^hy]MWdϝ{Y+v!OH[H{ǒַJdd BŽ^ s`LVqϞqb~-Yכ{O~^\9 )Hqe%O0ܓ7'0cf/wm-nL{{k9C}h{d΢/|pOc㾧Tcឯ׳sOLR(pe`k=gA1=0=.˽`7(Gh́_ K3sb߯|$MYzIh?6nl`ډ@ffE6qGHj ׺rle}sp;~[KZDETB >„~[g~yi.{pw`sO e={{p-sc\?n{y{Ɵ%uN%Qv {G:~f7]s쵿{ ئiKv ݰ]|#)Rv?}O8Xl}Kྗ7 fz\/noO{_MvMfX>}ZջG!o<3ֹ<)֋ @6on$fU| 7@{liH3_GZy p-AaC0#Ϟ4e E5vHxUsn~V}ZB?<>-vg;YPl};›pϧ7?OMp,5l 67-c^fK{;L^zj]Ƚn3T c=V=Or1OԶ;&E$rF'cמ.}ыfKxkzp@{d,bpogqo2o @Jѿ_3&~t:<3bO+Cƫ=3Yrm'n5nƚ+k@jɛ5NqGcG!8 iF{wد > IpCBYCn8\Fi?i'U jcV3U9_{7o[W(dcAC _; ؎r[ ޏ{$Z\s^~~g+ ~/9AA3=WZ}rd]i4ld W[2=HGn!⩁][ {8&#9 oJ Q-@U_ʟ/Ͻ=(}}9Bs2DpWk˼=ōݜ|Kqd w={B|un)`ޛ7i{v)QX5l,*~."@Ԙt i:3>7~rQj3Q3A}>$0~gJU}hm,|7Z`og{2 hzcp-|a{t=81~>>===>=8Q{a{Sa2B{}?{O{tlpAÃcj n={:}eM\t6{*!-'PSdTSgSeTpSjTs8a~pA[׆Ѳ#njSj? pA]Ou{,j7>|pA~O-w'r}]' m.pA_ M (29sQ&E^A}{<˽9CP$+@j#}` _&m pAgi1ɯKɬR!<_y<̓A=]{{p-}y=E{hKدJa|=p-{-IJ}_=MZSɺ4}jŽbȃ{ tW}i=M 4a2l]_UP?X৴V֚{(~B?HKssliԺ>pAW E^Ѩw4R3%gkOEyQ4J3K;/"a?JQ__ߡgOsOћ=E¾0.)s__u$kv/;a~0]{$_AՔ=N.zHGxM^--0e#^-g7g.U傠*OT~]9k=4ie:B~v$߃*U{qs/r/b՚+_ 8g=-PʽS;4j|>9r?p7 C!{=A]?%WFk=M&=͞c=GpAz{pO'%+nDJq_0>3G dߴW>K Vr?c/Ƃ׹gC# o9rot$ds/}/}GUPu櫆$8ޞ;ONs}h =ӽGo/Nއϛ8c\LOosp ~ćiܫY]~ѴZ"ki=Nƌ(W_jCmji1>M{b$Q5q7ݷ$`nK ܟ!vwxvCVc>?U {IDATr"\91:w~ܛ'rk ysb.>]{zy?6z,UugWg^P2j`= @\*xgc<ڞES^o1qfT.Fm;pct$^ϝ3^㑀{SG~H=p?wԓq_VqugrOmׯ}i}휴< |at߇qars?Κ`=_ZrK~y//9_X ܃{ `޷lڷsbt{g|p?RnpqLO܇mjǽ%'pya`O^UzkN' ܟ{{({=1Ib;mcw ~ aP=Ԃ{.{?x-O~(aVa礗5m//NG^o;٭% m;Dӝܟ{{p{{: ;C{'rrH4/$1/8 T/{{S9}ۋd7iЃ{p?[3ث勷%LgJ`L?VZ'W^?j ?sgѴ#_|'z9܃`OŽFi_;O|l?5[/l_4U>S^^$5p=^/K \ҼЫ=&*X {V d(^/k_{_>Ows3n*0ۍ܃ɀ)^"[ W{vH0{{p?~;}(g=e3]$]wO K=Xxw1_Ǿ^t3Z (7 Y625pK-x`m.ggs3{,r=7y>ރ{p_F:.k(gjCޤcA<}F=wɟ/G}>s?Ҭɇ~{?_XyXsH p!C!4a=8q ===_K==3܏B9s;*_~Ys{ٻg==3ͧ&nVڡ1sg=鬖>/~rYs_^h 263~ܓMj֤{ipgͽdy]tLx4Hx'UmP =5!]l.16s"SϚ{7 ~ˮskUu<~ SPvϚ{o^m-u )rg=SPnUQυ{?k>˭ۭ߯4ޮͳ+{_w2,6pyjcϚ{0ۍ6<2*V;_m) ܏{?k^=-~ܷz/w$ p?fx;ྼ#=Eb Ҹ褸.=!p=={pA C!{=A pA C!x!hK!p==͓{ CР܏Q`%qyk;=$/d{{{p-{{p-{|N]ɲ= =鰖>/l H=VW!hH!UD={&YV>/(.I9A^'{_RP/ g|''(׷7t=^#4/ݜW{a =]{za¶u>z!{osͱE!Cgʍ7ǥ׊&E~@CХU}/+ˎA.ǽ[gV14eEhiu$=]{~M_k}nz3ý!ܷG:ш4$m~ !hܷDq48]u{{q=?A hKO=AsAr+Ӆ%qkAAer(_7:=!OϹs⹝g;b@::M_{}$CPEJwm%DsBΊfy+Js5ܯrӴX߉m=FgHD†.Ȕl P bG/-Ni.%}{g h`-vss{{&k>-rk_g!b݋}^f'6Ϥ%O~s/~r9n'gr"pAʻf}po>Ϟt~]ƽ6r IzrΐN^>2UԒoR3}'${fezzRlu;2 pA]wz)o6I7^ RV^a3c, {=[>ܭ79[mZ{L_孷y>K.ߧ*wwr/^fܯnZ>{,koq/5W-OP}Yo{AQg7rmbd1׹_'~ѹ/<}i=ܯ6/ viIb݌gUOa$DF[,60Ylb⅊c/Ht"ob2dKXDZl,+u)ųXlQNVFqRHLXT&#y$Y-n]^m4Y&pLIXThj!_(|\ |0w,2qLM2,zϢLY\T}1Ա-9@*DUL)kj9Sz )e cI4;0^*uJ+9lR`Srt}#2MĤ+T[EyVs^ίY rvYޮg#昷vl4O2[I{ QBۥx6c&*"Ϣz勌dc }^Bqlbb@GF6"]__2V_g]z͟]fcs(O!wl4/zǹR-R7c77!-n. $qlXV5JvKg?9CKkӣ&MXVx2U0D"Hf}A+Fgחw+s|,RLBjޔn DrЙ&-BDZ1 c0_űmE02)iXʣp,L|9t)3=N%+m߁yEc)a'S:!DUhŔcMǞ+~UqlŔhؗ_|KXII]7 ZڱbF m+91Ƕ>sǍcKaJTEn+ylCj~♎cڪ-cd:kڊ*􎗧cکK岔L9Om+Z%S]xֳъe~ocC$ڰ||6/ ƴc[~[q7mAUq* ح(r-&m߶&b4h.2X+QlYﶢڑsX%ZK+a%5qL°|ql\%8-i)Oؐv,?{42+F3[{%hUh빉cfzf~ۢ%+V(6J]~(d3)Y_~->汖,J*L^m1]cVECx!3^[q.V*_1LϻPS|"Iv$&֞Q&]ybLP]gAkM"fH#eRi.V|{rLMmE3ksFKȄِ"bkRL="[.U01'RE8Yh+vL]%bYՐQ/Kh+EͰ*e1ڊcY#2F1s `F[`ڴ}]]4u҆tѲ}zT9z}|!{)7'-ӤcmE:s1kc;&7THwFl\d;\+/Nu[d/[L~*.iacwx];fAz(CosV= cOdV.dvXHYuG)zwOF1gX[[ޒGTWËvǪڄюYk{frU?lpPW_WkMRdho,o vc-mE/ݸ,zϬi?15X aײnj\h> s;ܕ鞥@,DQ].qNl+v,{ԬdU;ecf5 eWѱAY)gϸXRtl6cn9+džgۊ1vj+u$h+3-EKv,[D]T9;֝W );f9֞6nU1׶hjr[Ƕ]c'elֶPFu8fD܇<+ m&e-(g\Pk#ByE]^qCcVJ]1g'+frϦ#9AǦ>_,G1rV=ȄBqO\+a?? Gxȋ:OBMgW9_Ƕ]!t߶QqVصc31Su8G۶;Ʒ$ #s\&>ah[Ǣ$9t ͱYtcЗ)55ΖK.W1jk:MLsLmE/dIW**Uquc-mE1\LĉeMh&c(b q,/W//|hjt-"Yu:~pю+w]?] p^?W鲡|1zJwe\}Dt,=O2Ǭj=kyP2TmYZf,]ż7SMft5~Tl/+l2~l(Y:+,Uܩclͬ/6z%WeQ59Li1X,&2XpL՘geK&k*$5 =Qb[A"yc&2K2Wz_Qc?'..ʶbrr+Xcxhցbi,e}$YzX.+m2"L6Tű_1Bfdc5ft[rm,E"[ NJULNhg,ZVYV7k/ {Z9[qJ N<` 8󯓫V{}7VfENS7/#UM\Bm.,_ZY_ gD5iEF[yh4"B{IAC5cf9:dJǂ<,vW^ѹ\k^Qg9MDN*rߨ0irGcRQL٠,wO|<߱*I汉cO*i_QEL2vuJpZ,VgZlj\'̖<*RıH=r";PLVq%9Q9B1<gO8h&<N6wG*8?W\xxߟL$)vcqO{,\*n܊ۑ9vm(Ϳ_;"@8p0=! kh+춭cuǻ8vN}{wp<G };6?j͏m>>jZ Opq5т)g0G'(ur<`6k*9&(g0G'(ur~̔39G6nVw>A`YRޣ _`*6Xl-M^3{sm,VC]K ۊyrcrc΂GԎoJ֮^Ԗ BΛMذDN6;6ejq,Vtkz"u1p&Z+-+Wx#%cտ¥Tհ]*ʹLFA}jLZwtJ*kg6VΆn?4`&ҿCuJ,>hj+IpFgxG{dž1W5pbNzԪtn=PJvuUoMUapst5%˅딡i[ns&)4sjXHHYx+ҩeOY*mK ,*BC*/cܸmC@O3g5jOpu(n+?~ DZ`3ױ@9#ERdE4]8Vl&yEiwdlV[dSlOXUgv(%.w 715)Ϡ#8!W3^}M/ ZZp{[+Z*Ι-,<3쨨 ]שJoti@ftW9ݎM&ߣ {_שJoni|ڿ_[-Ouӱv{t|r:U--W\ŇT w{tnVv5g0=;vF8zh+1):u[n Ĭ` p plHX=WJ$q#plcpfxxxx;f/}[c_A[vw Jo8F ޱWw&x㽲!,[@+ayHZpUQLYvPV,K=Zp ѱ\-eBkvJh1ȇG>w|1"wpN"wV>|86^X .k56̱u(Yc|pt}%6o@ K[ww::VD=1:ýXiXnmRտtlǜy< ѱ;P=ׯcU܇WYmCᴇH1 ž3 VŰ{_ 2Ui'j)?C?pl(:f<2)d?j)~FԎfs8v:hnk؛7߽]qLMkۙB|pP oD_xsCrkAq'%XƿqVb:sㇻ>|jl~pnӧ?ss$pl8/?t.coqX~Xطʘ۲j6AN{QRw)*#msIϱ;y1UsBW!pXʷқܱLߦq6u ϱsU1ç[{2Ʊ{cE{3)FeN\ԯ5eT]O?j#߿8M|±'ᘮt?U?@1N:V쭩(uF,;*>"6NM@[. /GvꊩvVQIfNoj+<޿*_wjU75;jƱ,_/?XXkH||w?}S9c뜢o:옙kp ǎ7R:v9R;}wob?H!Bqksڱʨ7󣱵\p ǎ6l_VYiF;vWy>g5o ~CQS明c&JC?nrQ;{:gr;i^ʜS>-糤ҽ;ϵ*v[~iſO˄ c#ڊ'U3d ǠuKcr.ۻj93Cv W]<~cc[ZE}߸+{RJ&w‡ŻkQ_.|^[Pӏc:?DΞҒoB|#k+~}[8O+Cc[#'x@ʉfQeT3->:h-^gto!=~g ϿW8wǔei):Vu[k+>!uNUwjtv_닱bw`j{Z_c·2wg_DW|>?:{{?F|rmT1߱d/[Eq]u }ڱ54 cm pl7po4 퇏opMW!Ǽ8cc8JVX=ke@] +=*ݛ-}Qa=c~Vt3~SVIDAT۬KVI4zq ̱_ۋ<|68v>,;ck?ʰmEj5u+->ǠpQ#Ϛ"H1)Ncpb1#p p p p p p p p p p p p c91KVi 84*]fOfWQplc]昮uL q#ce')$ɖ69&{:F NݱuA9&GO$D88-qR%{xe{#G pdžhs<Ѫj!OV3! RI_#=<)^R17(9UH'YD#w,s<1qh18Fǂ¸ޚiI'Q&q 1+9n8&**:ۊ8X=KdR2;cpG䘥y#c:uǬ?q;ss(9]vyǠݳ;8 '/V8$Vv?&:,D18Jǜc{p p `Rr8cc8cX!<96"tRp `"!h+ұ&uL7vcZLy/rnXLN{rʛ)1ױ*bMGYiib)Vp q逕iX=X;?ʱc8-ctV\{p XrN+_!9Vf)a*H qp p p p `cα1.68K680ұoziCn;F+sSu,3_h/:=PIG+YsIc_֏x7^"9HUC>yدcSMy4N;f0,L8,=񱑎Rqv1Q< ; (f@cѲ*H ݱn1118u6MQMHl;p u$X?zSXDz ՝:0klp ceMM<ܬ^o6c8OԱ}mƦ \q c˱Ft1qerYlIzʱ~ 8fz vqlm;+O*80ı\*hҴ( [[1ގq N!`ooF\1~`v;yxQ&VPi{c9Jg}rLH'lHXE' O^ocwpf#t;{g~c8>?=L6}Ƕ?f96V؉;6Zގ=8fj8;y<8v& ; azmܡWR~ v,p|(>Ƒnփ8vf[^Aat!zLTqkuLL$oQv1VPqݱma{11118bRo8ic;Vzp c)бu+u˻fńt$cücVϦ9ڎYL<ncV)LXg<Nl:XFv;}ʹ6t:?y*^1cM suN3\OױZ JCgկF  옝ZU=-G9pގ';pcñ"8[,+<#qގc΁uy?0p]2v;<-&8qc<ckؙcin[Q2qcwFrЕ愚cyYlؽbOˁ3BTUt_t cc8pV%IE;&'U]݋DUU qqyy_}T5;,X.e 돈nR,dzg,Wj!ˎr t)$r^*xlZ5إlqL,dcpJ8FY,_a,~iњ`$J2.u,rӅnjb-EKe  bX%e/ELG(eːb1 Echr,wyԊۊYT9)q ޱP[F pU,J<{LV44kwLϡ튢cK⥭X" *D[cpԎܶb|ύ\eG/`>*Jı8*Qlyp(`4(*y 'V-EV+B'v%˅Kyqz[1L+fd 9sc DUDqz**X1"K \Dz\j)~LI[Ⱥc⅁8جWoQUŶba%y(Xd,+r18ayX'J1ǒ8vhb۱<fANJ8,~q+ݗq D rBű*щx;Y7VװTؗ8B91uLk218 fJyuQ IqG}}/W: cwl_t<88cgXx= س$Ng`q(1];8Gd-IENDB`CMF-1.3/docs/imgs/enternewsitem.png0100644000076500007650000005056307374605754017221 0ustar tseavertseaverPNG  IHDRPLTENNN33fffB"""7<ff3ff,)6Q7KzNfct3f333ffߙff[gilЄֹwPPP@x1 IDATx 㶑.nelۻg$vbKǞ1Y $HBU HAJA< Zn={=A pAФLgQQݷ-^:vJ}+z o}}ae+[-Kf'2l9`puTVF#Wo ˔_ʽLL`X}r/vt#[[@Ђg :VJU {o勑-{S&+ bQYV{C~%}S]ew|$haܯdTpW: h y0ҹo-߯A^jpr}"X[ iiwȫ1]!hI܇C4C C4_{{f A {Zk %qZ'eqZUl-.z3|]6Z {AֆiW7U}B'VCl^}pvI$1]m o~&1nW;~4mJ/RĶxM*{u׆uJ.{ >yf,>HO/}bg}]zj%}L;라TC~T pztx 6_!nV/&+r!Ur3gRm?K_Q}*]g7 D+GWR{꿂/E]$ ua@[* O>Ǯ?F7g5 "Fٸ>EptWTޓm%_~Q121}1H,ϱ}w͟d_SK2{T!4W8>%^S`֒ ,Q*vÓ~[u֯^k%8j$ YT(jRw{ cr"}߆O$kYwO"5Qיg^+ uϱeHYvPOrB6#p-muѼU gx1m&ˌQ?P%&P?avpRD~]5zvI]쉦qܧnu}[czbKR%[zv*?z8Sր(k6ʗ9 ?Vҕ>kO!}}_Suj:9yE'yy!C?}KiHA?=j Ўu'U}>@kfϻ~5L*VV8zݯzd49̵\wj_4`t^L5鿶1ϼzm[uU;[ݗjJ{B?: `+>?[u{c ՕƗ^)~`"K~z}wLbԻr4Qnct u^[yn}]u#q_8k[uJ~;)wEwv"k ?ǾĿcK]tuc/;~pϔD]ܡu^V=uѨ _֋z9~;]{}2 *i'{m7C/d3u>~ӭnV¢"q_weا܇_޿Ll¯kjwOu>뼓rfxf˪=C+^Y./g}}:>}^^BM{ߩWzQbd }I_ UE~,(^|}'-{U8[}. _7d~P7Y~ն} 34w b*OSn={7̬OʫH|_S_ī,/2k`MՆ˗/S{U'f~.un~I߮7ُbf76~t_~[aSީ}Dr5JHm:EDg}78wcƸ'iu?\{xoS^[ϖ~[⬬}/PB] NbE~S4?*n!_T \ ^[$/:wl>Mߖu4FռMۍ?G]??L 3E>9 Mn3&?56帿J_I }$ݯE$6a?OcܑZB/l>m~!m:Bu&l zJ&N52I}6]uC4 joo34<ЮAT--dM0I|,?nIIw}~>͊{=SP)fWq?$|J&zLe~>IŘ AdD-Zχr8{vd ߍ?-Gڹxװq}RߐMσF)R 0~?}>F|-W^f(2U+ԝpc; 7 nn^?J-r{ ǫ-_۸mqx Uy X`]b_I͈z&v枊t= QPx˗ӝQm0R tfU9ɤ{x2,=U~VbQpz7Nuepoڤɬm[*!ȬqAm̽p+†ƀr^/g.~o(;77!5tjި8C7ΥrG%vVUrSX>_%zxZU"$|VrRJIG ڋ{rK(_*_bUQJ]꽊⿯NI?ʊ!/=!1|HIrpQ 2χ%r!77e!A?O{t3L{֠)~$S~CSܥ=Fhq_|q?' o/^7#^gUѸBиp^'/?}~j5 ~۫oq_6yW>㞡ff$piv9;{kg}zovI ¸ľwSMP)}#tz?̎NgRE 5,C(F;fU}>wC^nܿZ;ǽ;C{n5zvۉ7ΣS#[-GH CU#zc>9$"r)5&-0KǪ??n5o^BfҞɚiJoG,kKXsA+C[{rkȴhQabs2 ʏӉҐSu[nB&UHG%g]l oŅGF_:d A7cҐۇ6|>?}~=.jbrF7['dfΟtk|]{دZ'; b c󚹷S#'T^]g?,r 1c4 㞔k6U4Sg'>sc^wEFyZ. !uͦ{ ]w=ߌ]M<6qrwk3ѱhJa5yGO-NYR?%OsCM~w!,7ud7ixIM~xޓ]ͽ : u/D'%sOmAS?vfg7iԸ~݉!{3WfS7|CadT=㾞| +\ eޭDz}UmJśTU_fTNV=_(nbI0⠄uDAYuAVua[z?KסM<2߷IϜ-<>?AݹxzAt5oWkEvT܎.qnern#R"8N8/C+z[t#zdy 8J˹2=}3lp"^Ww"L{>?XlmMـ}yh2f$gpYEq ]>)yI1gqItٚO#W3#JI$b 'KlJdVCƇ{aBHЪ 7{akMžqoۻ_Bi8V(V+ FSJ1t:]L;x -sݧbB/ [V/&¬֐=]{ӊ]'N g% y&m JnBrr޸6Mbܤ#n1 GG1V=gs{=ؗޘ/{0O Ho"P/{U<ڼc1oQ%H/f=KR ̽ϖVC޺`腬7iVJ+M"Aa7@(!x+ȍzO}%sUCSv%#d ݷʮS K-UZ~o0+g^rO5&|jwAB%?@cS{WÀv-]L*" 8pW"p?*lns}>_YrRwv#_NT>{yZm Es'5߽WԚ>Ԭ`U Z.{&Ald>CɓwNGr~.R}~ӕoTh2Ϊޛ~>UzRx`ƍ͙ ]6Bl<:L猐X{{) }.X^6Yh9#$/ەmr2D5VMg;ZhtuŒu8D ѯoyeَ7xu~Aԝσ APC_{͛{̇ A Cҹepg-COpAysԪ C4SNzJQjto ZZ$E% ִM(_{pD]W\)A~|~m7o>W>|׿_)eMz "URK }%{93Rg9%o_Fv|ܿ~L{˛|p&{o`߉t]T!p`7KͽfV~_{S_rZsͥ>/D+pMib_{m_Sw_ ~8Ys }c7\ߦ} 'xpOӕ(75埿 >|OC;~lW׸ ͜P}1s꼠Z(ތ{<Ǘ_KWm >+gn 7597u-/?wOOO޽We[J3?߂qqhZn5կ(92;";? ,nROǝP { c_4g7rϔǪ?˯];O:/xѠoGހ{{sy.?M=_﹇a6jddTǎ,}~;Oxs+J(ۻǷeOÿQFwع[k(=C]`C ~>N?ݔWS]Q/_e fկGp/'|o&P9KQy,SVkz%林s';?|{@ '}}N]N0UP4؁I(>u~*ݬƽ*Դs{k p/ _;Vv:8yf]9|ycxOu X2'{e܍!6}k?w/[^7tsp?bkc{dfC~؝so2Nfю\6vjv?|o櫚{Y s-|swo%5{ߤ0z 'KR?dySӏJȲ_{+!5RʿRr*߃{OGއnۇ6퍾yzfEP+C5tvx?#|?YwV+CE߰{pC5^9;}x|r!%{qvT=to/A%{Ӆ5c9`3/_h{/}V5o=tIMCns7uȆGFG޿釟~:쁿P IDAT{OhP7r?~N{ g>~P6Ye$~O?UxgMѻo;V@xdjoý ~If;–^mٛ[ՖMǐ5^Ǜ7pAχ^=k_1̽mC=r_|k4ZЃ{c7 p!܃{==C܃{܃{= p!܃{=A pA C!{=A pA C!{=A=A pA hf_ylʤ{tڈ{Ct91p'* <'pM:hfܗ8'"{=\DzU7[\~Y+orp7 h<8X_ʚsks_[vۿܟUP^xFϽQ*嬬>B=ٺ>w}34N?ɳ"pASjI7Z|UMlW.{=x= p!܃{=sL!hY.}U}kٲڡyj6QZA4(ej<<;ltTK+e- :g97qE՗<&k{k b}5@AE#M|Poq]ܓ=yA;^sz!8ܳ{̽}&ށԏS3@8UYJW?1Zr3?EDb#=X<˃?5s}Nu,doHubO &zۊ{2v2E3vhYoU12܍+ gdg*"v Qv~<r~V,KN|7^aPlT}Ͽ9p_Qs|^r'"#ɀ^0fYG[ 3\vDFX 8[Dڹ&"ӿ8T[:K0O{4~cD$]n<Ü[kh]2󃺲tgedn$I' >7+#qbV9?{PٖN*{3ĽW מ=?}%(`Yd第Wf*{e_E L{e4Ǹ>*&`]ޛ˛콑qC2*Փ{ Mr/43Qfe鍋{Jn$?޸ʽj[q?f6rrM<۝}?2Fp-\,ucrr?^9dz&;7j^5s/|wܟ2eޱ\ۻ$7ɻqMٸ(gTb ŽPpV$lCz'pX@OV(XQ&6$O^˝ wm3m;X3nFIJlR<+M:}aIٵy׳{c~XIrHN>Lչ?ƕڨc M (|5YronϘ!hܟ=͎C3'pAsO^}۞e%~  E7&콱CФ粷7g[{M{eO$1}4pԲ=M oێV̀.H0y)zOj!乊ll6ig*IE rz[ρZ/ѨO`Y4l`l aI|QP`o@r*M<'"~ft7}Np`N`j[\{φKV_{%J7.͗mU7s_~sr/QӅ'] 3OQ ߛUIr] S%7ujB 1Đz=_;5=G/+"^>.I[_wUq]D#&}zkqq|6+2vkAl=;O;5)5DR|rۧ8xalY>ow?X]GY"r5f<^Tlxw#r5=IBZC ܃{hqطp<K(W(o+!h ]_p,= 7 ,/ x5@3po2m?`G*~cE4E3{}+pA⾏!h.}lpK!{=- zIo=`Z&}CA(~Nr,eśm A3 *å}_%Iweh=9)Cu4' Xo{h>,lv2K'n!wRgNx縒 {ѼS»{\7p%{Щq2ʽEPj'= S +_*[nU=o'CujBeo_|'lߡ,tz4gBfL}}Q?rߘI茸yz) jIږڎvE؁,pΞWW?H_=Bp?n̽f^̏f`p;Oۂ{ptnp}GMVCe,=b$-Hì' ؊p6ƌH+BW׃x4ϹهWHׁoDHF2;MfhGS?k'b_IZ*$l@A@/+Ƿ)fG r^ƦΗڇ|HztF2>|K}L`;h^0~R5Ě./َ?]10RC̽D75lpGE^ʼ>6i=9 yE➕/i(DFHvȌC_ =ypY9NGϖ{ Z{Cj! U1[Qg]S[}cГ&kx]38q(ĉ_$n{jAy+a,3:){pw/asߧ?ȓΨ۹`sAcC \q9a.{w>]h|?yB #Yu/ǽW8?u8~f7]s?]{O_԰yb Iu;xkְi[z5w?avɧј=v6&öa?lYͅ aw/}=5fX>wt()}-1-}'8^lʥGѾi(R`aғyr@>ǭ:7 'fŁZC}36pS^J~~TmO۵`'-=Q3tNV£гh+`* ~pbn{U) 7=X޴ ˀctK> b'xJE f='sn{χbop 7=90)V Zk۹o{3x*[mʽ]1p}Ehޚ^}~!Kao!յaBk4sƭj״mp-զ,v^jt:sޗ˃L~TG@)k|;e4А.-^^ڰ+[0(Vؠ]qc5e5U{r䡛p|ziWzoz+6[+V)sƳ[qc\IxhM bc5Lz~aCÅf ~o{!hރXGb\F{p?1ZϿ/W}{6}k܋$j!V%lQ.$RԿP̚+HָnL CC, _[-J3s{.]Ƣ}}Iixt{P{Go%U`|}m"g8Ϻm5#몯H6aa~~@i,d1l{؃{uwSֻo,U~}s=f:nUo K=C_|n3ŲC;lyƇ88OӃ&p#EuƑ`c+%p|HJlF>~jܑ Džƾ}q ýX?J . =]{( &f_Oqr/Zo>S6ƕ=$Tpu{ᮺ0D2X,pl˽4:rAi2iaIЎ'[e|c)a*G }bΉk2. .ߧ, ܃{h&J|;xt-#{d&Q.@{3^F|~_χ'sՉv\ƁߓO߹{xہ?v{:c_*9{>Z ZZ ?C{hރ{hqރ{hg^=E~7G4F9F/]pAg~ w@.Å?#jQ6a7rP,y9"a?|oFղ0#k" ck$r?pA\OudF >Hn'ǖܓ[rg- 7Go½/ APd#s炣Nm?!(2󃸷x'{9CP$쇷W m9,8u"?y1 cMu{Y-TZh` ?!ރ{h_GCP$1 ¾?ii"+ sVG{*Yן}^_X?7ս|_|pAS>ML;* (SDCP,S +_}(W~pA״/=sl~4j]XWW@ ڋQ!hgkܟZ=!hg's_Q=E>cѣjc'pAG|g9"a_{*s__wkv/;awbH"xՈ=9-ك{ܷ\!eR5WzYZa"G$&[Ro p/r]A#Uz_v;;>SpSoasgnܗФ/Rp; |Z7aIs_Wo3ek'=4iU} (矪gTɇ&=*Z aIs;h-g}NGB=higϝ{MG`M{uWkܣ^s| n-@׳+Nn@+=͑ w=A pA CԇXBɾ j^ !hB< !h?{4G-=2@ȖT_q{g)kq. όcQ:7qtg7޺~XVcA\܋w{wBjwzr/m~C&py{NK:)^->8Q/BsOUnXKWPn9q*G\ i*׷YU(Q.;ޟ{Øw ps[RBm?# R>ۤ.n/R=Ђ{O 72q{Oer_srUW8褹/kt5=V 0ZΫDs",aDjǣ?2fC^wbj犌1L7qĿd>af9' <x3Z?c%ŵ쾷2`?WY4.p,{_u$̿Ͽ*AO{'Xyr"̟tCKYd?|7q9~]Bosn)w}}5+^+#/n-`wݼדޘ{0 0m'ci1>M{E"Rngy(KܓČ]S +iM 7r0/s ';EG9$tlI bLT_N~cʩsɽYq{νyzpO6ӳ%|=OUud'̽]'L^{[[Ǹk2ڞEBb;TFtrr7I3#<M^Ϟ ~}V0x C9`{?@S>~_ߝX}i@p{(OhkcW[C}puay{pY/%wptCJ*0SJ<)K~2^){o`wlgx[ͽ0|Ck2Cܻl?Y塕~PjbU|P(l-S;aۏϷZP7]O?͌{5Ir8P0^Ub{0jzn>=}=1Ib;6D { ~Bw<{U2C3eK 'Y#+ot:p4.{uwF9F~Zܟ|{(S.޹q5'286|2 ]ܟ|{/٪KƴXhV_g lw,uܟYރp?~3Yr#c^nojѸTlUs91+ws&36p]a}X{/q{yI${ْCulM>CF6 }T] 2}0 ـ_{>} =|kӹܳwy`LM{_B u)IjC$޲u=sr񑺴X ޴dy ߌɢjp?edHp.]nҳp/ZTK}tb_x=x;U>˭ ܏Vgu2}YӇ|p?~s'WR~ Kxw}UNC~Yc?=,=/E?rOXCe)O/{.og~)._qOjO8/^o^-5S7519l Ş^ec͇_Rec@6e\>?{ܛt|,ȟϨG9sχ}NGU= !p^q^v? A^Ky }u.;!܃{{=M?/x$p-E<= p0삹_j?*pU/WПuA_n߀1q6Iރ{`˭ۭ߯DKޮͳ+{_wb@Qoj/{2ۍ6<2*ygp?*zc;r}׻m/Wq})N /{`49tQ1b ¹ꤸbOCC=A pA C!{=A pA CP#pAJ>yp{pACЌ=]`=`?] sYR=]{{p-{{p-{|^]Teg{4a-}_H@{5ס!ܓC={)dy|^P\\/32 rs2$^'=]{/)P/ (|''({ {7 ~z7 Ō~C5{+Ȭ $fpl#pAW[gͽ6'SU7/ ǽP/Pn9.=+ K)pA~㾦+W=]{&ά'}iމeEX?)pAW^_wgoϛ~pAWkܫG&}K6$擻⋆Wn>)%SNd|T+= W\4jvd/Q ba 2u(dm*Rk/j)wd} z_PF~H_?^Es<ZDڬr׻4o;{p{O>d/Sϴ%O\~s~J9arak?? {p͊{H|nD g)_~~vqͽB^ܯ3k3ERPu-݃{hfܯL?ӆw'7(o?,kԓb߬K쵵=ܗl_ដ=p-{߬׹߯^fzzY`dղ {{/g^& 9q/{O6Mw_W>+uym^q2R/?)ܫW^{ʭ<]NW[Km|/ ؗv~_V{hY ]KO}٘sz_o8'q/C؎Wܣ5j> :K5SU ܿJp[or-CP_WOY>{ط׸[|2]lɽts}=-{y͛s@Π`W'pAcB4ڰAIENDB`CMF-1.3/docs/imgs/newnewsitem.png0100644000076500007650000004151207374605754016667 0ustar tseavertseaverPNG  IHDR]PLTENNN33fffB"""7<ff3ff̻UY' IDATx흉bȲmp4+" ڮbrV̐CmO;ж=BBp{ЪR{/&P9V?w\|~+ғeBpr_鸕m{ˀ` ),dX|;nQ)zsR@hC7=*qJMw+I3☜m{ŀA5X_sFp 0SkR% B &HrP ,ˍpYv}_c6}t1wqnHn]0ֹ!^oɹuCHO{.Bl"%B==Bp{ {#}_#y>#=pЖa ôI/qwh~T_ݑy1;G1_G뿩֕u:'T>,w}q(w _jy~_>Jvv,B$t4{o jW?,Oϟlxq'ӳɕw>%?/~>Ky"t++|Wsݡ;{m5/ﰟ ~}ϧQy'gzD?ɱ?T$.C_8)}co_N֟#;tޟ 6ک\"|L8P^Sp?ߟ?SȢ SiA|Kp_Jڱ>6*ӟJxŵTGYyξN`?[%% p> @__ܻ<[$Qs?euzSb|v}:ӥRL]"t;b'w,-ýR9u~<앫2듪r[o1rB~rN +TQ'av\\kH~I}ϱ?P~=.e,Ϲ6s8 oޟ❜S;rZ.Sپ~}~n}|Ucz}ag_jҳ྅YS_gZ Xԥ8_h)s?sm9ܗZse4+/ l'^ߏUE#О+߿s{?Y'~l_OS_(z/n7Bչ8ZޭixoA.$"22\agT9Z^~n7߿ +ԋ Pz}?w%SLvq:Nqe9#?ׅr x b#%H}Jszx{}JJK_ СF+ϝu[rMo݈y>Bhz{+h{+/Tb3G)پ< q:SHwQN=|&L9 kdN-z&[/y߬#L:g%j"V#|6}.228R<緽6h/zOh\ᾳ-&t!761P /tK]]zdDR_IŖ;+C{l0wo0M[4 "nHl}a^V5T@Lpo*!tFO&?q$M*kk84uߗiI'T)Ed.W|W_*1"K_Sx@\easS"׹Wyhc⾼G[Ϳj8Ҏ4I@A5|֣A,nWcw~<7vk׏AG>_u|{0qFn}xMe!_??5g' v]8s-?ӥ{~2AS~ݿE_*pJ+ϗ{عģ*<q4Y /Ɋy}M{}l>ߵ8g􀏀ע^h/姼#:\sJp-~V~;\P#އ{m#x! 9<=#7Z9V=5Y~l?}{G~v^s-=F|K3G>iwo5`n1%oU}VQKq3wogW  x*ˋރ=zt~v|?s'G-}p]ew,?軏T9Z*8(uI&\|}ƹ~xn}ɽBmc$b;c^SbEy)}a\~^!} JWFGEGll}'vYM?[s Vc D"&z DIYM=-#BTE}HxF ,kNy'v6RRI :_h6H|Fqris/& mc0JOybboߔ7=qNԑ\}v ?El9w;k?O4?zyum9L$m!9JeƑ0 ,?H{_^3w(^=o1Kc_>Ob'Xkv4u3Ϸ3?g|r'e=^3~: ׺Lw*g69ؔg Kz1dXS'\~ow{Y]|wF?_vCمcM.:3|:s^ҙ 2q$_6ܣػ(,{;~s[%[ |ox<w97 nf/޹;Qs}}v ޻^ϴZ8QR;s_}"GqOp_ϯs3<' 9Yuڟ5=_< Bw羽/c0BV(mom?!4?!4{>BǸ?nSz{tb8Do}ۣ%DzSlIgfXEoxn޷[, /R-xi5[ Yo$@7mjeua;c;$Y*^"CZi</ai[4{k8*hқvḱe^ʸ>t-7R!}h y~K[p/7O֐ +P_*2taR3QCxwHd~>4hjbM}|o r]o6ك}oJ+xOiSx*]bIG}X,c:{Sm9(Nc]dPcF*+uopGWFSkI֖hw'0ڟ~iq ;LOBf9+Z:N=ϊҾ^ $]$abl]ƹ]U4Lg{nn*xդb{rɲ= i܅]kR'hdRMJBvGQ?t/2|'^I%sjjzclWb*H +kITg 'ZZ[duZ"~!qvs噜v̌eL:QR $(-R޷[, - 翽s2BҹͰxNOP^֥RoYnC[({;ߟYk ~fXE'(w/R޷[, - ?,̥2^&a!M znyܦ&hxܯxܯ9 3Gh!G=7<>z2u{ν{6 ;#޹]=BI//ҫ>=BpzNkOpІG=^Oq^h #5ۉ@Bp{#!!G=BBp{#!!G!G=B*[DmԶ|pܯ{Q3/AJ s/oc j{<"2{~23BOƽ4!z"JϽ|jgR}I~'EbdP_qMI@hS_~{u;["H($EjY@yw>9KkGܣMp/J>ŦKSN{[O}o~AhkW_w޲ -}t}ߌͫ dܷ~G^{3%n2{?8ܣmsѻ#pW81^đ^`f8ܫ{sľ_b+p\73}~k/ Srof?#BwqBBpZ{#!s_!{{G)A1Z=&Gh'w_b3G)#nw^z/͜٠y;ہqM8Nݛkq{βi-ڎ-lzx͂=No.W'{Ύ'9NsI7s=Wb_MpZoOuۓvƝ׼{#[O';_>{G !G=Bh?P:p7fq3 7?{(+Bp{̽1}VyW(Eg[澢lT/w;|}t/G支~W׊ƻso_*.|=?i{Rg\?{,zz2 =}x~/p~+}K #̽{9;|}p܏U7o74_.y}x^̟߄tq? ~c>T;xCb\99NۻSsB_mћ@[boݱ=/ V/t(oc -{Gqq=.1ߙ{ļ>ryCq+p8=__vYΫ@4k',c*tg*)phskJa"H躾V8mRMo/ަ0s?M{K{O<7|P> ՒH<8.afoHlJ76H8b+d]6Lyrb׾=8l7(UČvn MY62D +D!p%^DްkG~ܛzWxIBop7 IDATuCY7_,&#B4|WS) +\;uk<.ɫZ͔BGJԩtmD1oG}k֫~w<W>/Ti؛6,/02iDOi'\*s$l5ҷ(; S'ūi8'cǖp?ow{4|g^vCp>< ;_5" wZ(+^t#%9ڷ6's_DwKwZ#FfqPm/swGpPm~j=A5#gqC!#V$|QaUq٩_#^";3G_"Bp*M2G'Ggf;P%~}ü<}|ÏܛrYۯ x/N>e!SQ'r_"?Gp˽kv\ouӍ d|cW~}w}~z{}}{ ms=Aq6zj4Gsol!O=O=-ǹm-þaqdxqco}'nw_6*=C1bZe:doep@p{/=Bp?1ùq?~ qoG?y?|s_8f@}(g{Bp=#p=Bp=:_ ##_֋wGhܷ3\nr< @2Rk\HzZ/s]Qv[- CҬ!^72AѝOG{4coYv /NK߷3n/ͽ;,|s8QEW=Wͽ>WZR5ԏm]znjb6؈,; ^,F6%?-^r@U8V|W,1jm6:Bhe^=y|{_wӮ7t}뿪}/&uѽ$g3 k5¢}i3tma#lTuBhe_HpobL}jS.UH&*n^N񠻧ofޟDI:GLܧ^l]r~ɔMQ,as)JX;RXM긿k뎧sҠA2'ʱw!}l& bܳ"{-V}ח{5I=?5Lzh~o0n,QǕ-h![o"gժZ{˟w[M.8(V'?}W>9[{NB&.="a`wͥgÓLQmMk!G7u,^82BprgC5vԅ;ޭGoǻrEo{#uW3h{}-{gt[ pߚ #ߘ{*7{k:3=,/W_ Ǟ랗\^wsoo߂󇢾_ncPPVUs/^}G˽޿\X)p?P%~p =Bpz[_fW=헺+}B\$ tk#ﻑ|Jݪ6`^ƺܗ{k&w/ߊ{};m`8voeECEp w9__t:/y(_p~=ܷ8;ƽoP|GpV[/)AW#___y~_f͇{}j}Y}dp5{op.><]G^;x_o|_#rϒ !{{G!/ҨX~Ml?ݷ7l2$Bo og5hM>ڳz,{y`gmP@tQJ42ZW[:8Yܯ)@ qp>J:Kb<3ܧ"p`4_&p\dI6w>&ls0[^_]nԿ/ wMfroVԭIE;{n6Wp0_6C/YO`O%X\Wt%\PYꚪ#)t1]Db7*oOJ6S/[y VQ1I~LC$)[#=Y)hhxSu:ܯV~mwI}MT?{ϽùX?1wAv^|fG%m˦vDr$ځT/^ 5[bǙH#cW˽>Y%=p'jyo75fN+*s.0,1"zj\4$iAoJ2l"72ௗL }{o 7s ;HW%R\2auƹmd_/WW2nj~.G+Dvj/w'l&n{OWj4RM%ʪ.̓U-?I.]+ZD=7ԋlk wjUw,<\?7W~urɬѭ<=^~.gRgW{羾]m^g~ܷ77ןԾ{:7r/'%z܃=5/p~#ܷĹw  Gp{ͽKpW~ˋޅҽ^w~[wt[[={G!{{Gp!G=BBp{#!3$OveܤUjrDܦC/Xjtԙ5M sR&;;4c@7?}/ZvdoIߝG["ss/|bG%^={ '2co4سLNFfgrqi&Ek6j(CDl{P۴#ѹIZ7KHHb-h"/sg"nj` GiI/'0מjz&8%ko1'~y:nd cޛ0HOZ׹OѢ(iҦrFq91/=KjeG^4 %FG;x2>p(.9|w~"!y&Oľ9QNI:ǵS̟7w?:EMRJ|z`uj-jbb- * =Z#GOO=sG6{#!!(x.#p=Bpܷ,?vVSC?a3n$X}k~gp?uֿ{ .H{~㾢ҒܢV_AEw76@*4=?&xZ/}QǾsu+m_{G_=Jsou;#_?_aNE#_{}i_ݥs_|pp7[Ƌޛ\ԉ,a2Ip#]MFK~HXݜ 9,ʸw`_ğGs)I?b7k&,D'86󻽘O270Cs?"!o$/16\̔3>_{q_ uMi_@|Ӱ faӮ˨#*iek/hզh U&&ҵ- k16$"*.X䠩~K.mh@$WCk=;E>Z|gUNԨ'am^52_Ъ<ѶmoEm Aq'MoxP9@6KH0{D&*=8v)3^Ef"~&.,Dԅ-m{>1ZZ4q_'4~}`5yB+֫TXjgHI@C]dI$"bBX"W%Ɉ$:Ž^ NouEaE_Iכ&4crf(^=a duZ(:WtVJFɪލ>1By q{/;H&|"jmZ]27\t+ϰQ ֺNX>n&'mqyǞt{7Q<E$S^飵H!֎/_5z=,⭁ ψyi̕ ќZgCʼn$oa2}_"D%rߙ>sVޭDŽܯ警n{?&~Eܗ]=~Ls:ϫQlU4E4{z=^7{+W+s_"W/͛uv@=?{t~f/R- Xnu*`K]ED"*FH&M*&.aSgp Пo@l[%m0{ 0 EDTF*zjmfHE?$.zb7IXpt8 X!O7~"m 鳃_b.-7i:'h;33I8n AU$ʽ JP{_), Kl`CtҟF} {I픘 kPQPbjP+*Υ&%iCŹ7V~UhdqJSkog){x>6RK. $ƪ?bUQyuשڗ‰wao'_-\76I7gt^lo 8g|YC̓e YJ<=(G {#!=#ksE<{G! #@sBpRkSr/> p_!Jm_ @~;+_wvTܯ{Μ`ܯQ{߇|ܯ/>kEW # 5 {Gpܗkx?_}Gp^^ :u &~ܯ[gRmo{!{{G=#!!G=BBp{ϐA"]GK~=Bp<r?dXfܨ>0X7 3n} vR:`.HtM{ {_l:Xjb $(}A'>uES]NdǟnH:5/Hߥ."rߙ9;^.y;1\|ѩfrwaგ!N +b_v.XK{3,!\ Ԝoʻs>L'%ŸPI&>OKD,&&{GXa4%yg36Hm`}4(ZA bV&=' 6|O~j&n0#p{'9mf4z^\,ll[̋nJS"E0~{߽1!~}qh}^+p#!=#!G=BBp{#!h;R4Uח.~;;Gp #7^/S2{pO}cie0pOֲ{~ހ {}};iXz{d>=ܯ#_}{P pc1X{VwōbxO{h3KOH.[{y}7s{0/+ˤ.{/QMpo!Vm>fU;X{]_K&!9{==o{ϽO[9XW5=o}j}k7q^ʟ~ Ʒ"ܯ*>o|ދxީS;vim%p\D㟿4[@X#;7{~;O=:7:7;<=Ep)a}Q_q{m}=v/M!=́#!!G=BBp{#!!G=BBp=Bp{#!"y>B""9ۉ@h#m{6k~ !{Гr__#!yC=By "%i6=M!GhܿAwM+Ѡʌ^~:lvߧ_!{6}e½kt}s_쐈} 8I.}vrt#Uļ;m]e3O;3;b=eѴ5E*lov5]hwh9ý;8&ð{\ט>*"IDAT<9G}YYM-*{ߟ|m}|c#7 /ý=G[>ޟΑO!|V#Xs4<bl)e͇{LG>P{T>ܗkڗ?V򿿾?u؟>%).>OYqoOW:{>ڗ}}./?H}[{ܣmq )>`?h@.}c>kZ{?{CgN??¡8՟膄D%5냶b)$=BS߅s繸Fjݯ^([o4#pІk.wO>ݼ#j"==B{"5X!}IENDB`CMF-1.3/docs/imgs/publishreject.png0100644000076500007650000004054207374605754017167 0ustar tseavertseaverPNG  IHDRPLTENNN33fffB"""7<ff3ff,)6Q7KzNfct[gilЄֹw3f333ffffPPP@CfM IDATx흉b6@b]Y2$mZtڑfUKKDQ)%χc3{{{{8keF7\(ZVj۩6߷ 3GZWl+!#w/i*[MKf z) !-[VX̗-pզ,M\ +ԣ{B2c)\-+“eS}7Xy9`[+=o 0m-B[,p5 xo"9xBbeܨ,l{r_}S}'z ¼_:Pޣs 2^@.O4{t[ 3rWWR aU"EHٰºު}Дj}|wTxxf30GFrxpyG%\(=eyĢ_,Z-/Uq-X{Ѽ/>+յ,~۸ yb۵>\9:Uf]-g۴_?_\b*}śh+bU^"{g)FSki+Vgg2)_eB|GC>?C]گfcRě͘z%GE!>i/_}^!͏k%{~vv5LXSV+s$eD&^uoV!|/U(y~eõOIWR,O"MG^E_gfk~nn/n|M/{U{k?.?͊+%~~|ޯJ8:_i%77Uʴ_f1{Rn_|ϿҴ_g?z?$j[M$MxӞh2˸O]_cz|/S|L{n@QˬQWvg7Dy*_~rWg $#y_k?TT{uoֽ2Ty!ejJ}2ӷT~޿J DZSU%ʸD7WEY>Idݦe=OhsjWE|뚬pЇz.d )׵_eY˨,6EZTWgzmU˜+u/JqvNEkߛLL<lmc}K4/_/=/O￲=/M^Uv)nTY}%_5j>ޫT^%~uuGG"OLw3]Iſʴ~W/V~=/4KU)׽X~%^#d~@>_el{QVZo4(V_,ܷS/ӾJz_ϬWeTeuzTU'H׷2*S:z\v?iz^}J;_.Yަ8'+]gE3{}zq /z 5^wǙqv髇(%z/VzUwWj_Tjhjecxy~T^W]镮Ŀ^ޟ~%+yjJ$|G_Y_lWD|(^yg \VmxY>uzŋ4qXUÿ(~G|1ɢxZ"Ϗ{^Kj*T!\/{ςe~U6^BOW_+՗WWF&NO']=p\])ۯ2JU O>Av 4kg7F?^/ { SXuϝ|>PΧ/&*&3h1A p7q6,= ou\a}ǰq~T9x{X'h~Gc)g 8|^N-}w:"{^ߢUk.;lj}^d;y_-Tý׷v# 3cӋ7_8bKJVbn:3"; )5ek4YJ3aVgߛaQ&;!:WH{3ڶ E@208 |)_P/̒GcR:rV{vW/_Po+;=B}~~_^Sx?U8K,y^#]QW[%aC@ʣEQRoeŐ@#xx\~ paRp!{gp@\^>ܽ'MnO}6򌽧)(Gs@LEtvS\#L99O!6>{2,'潸*F\JL*xEMb`h|B7F۲Zl~wBhS>GWͫX0_5l<ɯ8:9b},WhG*K}rR_=S2oe]>׆\y]}}߬6UjyfwfJTt][\[?yGew븣uGYⷲg|~'!kk{R%;> yuT>1%8BS{3Z)jj8Nݍl=ޓt˿˳w\z)y_ yC>E"{am<eaϞUҾJ훼 wiʓ_rWqtt{kNw*悆ޝ2mK'rS6ihZ'YIH2!ri.Rmo#Jg^ У30}GI"…͕[t{OFtCDog!?^4s0ziy6?yuR%_Ş޽/3RHsl鮽y\;J!-JxB7X%Uz{_u, RnѪMN. ﯣ8n齴'if*K7nh^6'佴u6G2/0^t\֛~I]:\e7ҧez)ndg{7^_7a4sa|Q#u%-ܯӠl~\6{/;}w/lbTQGFhθQH`s=R7&zV@}^ߨ_UKz__QR<To^mۘ4 rSo2Q6wwכ&^oN]&s>L/k.2t|~e[4x|5h!w_WiZQ/);h z}|3UouZ¿Ve]S)z,*W5nݖҬR3giU9Y^j׶N]XQl 3OJe2ЎgFV(xߞ(7ZzDVNnjϗuaUD֡eùKz> +C ]Xn;۶G;/ojoϭ¹"걜f,ṜÚ0bp/tt4H#Yp*`oQa[ITm69f .x(Mo?!Z{_O9pO'O)&+О9ޟ{>{8U?|=y@}|<y_|hbquͺT8W۱czjjA\0J=i3a&B=ey x jz.jXŽΈa#zStG64q6l*<a=cˎmdp<"yGAMt D` l Mf;>+]Cmk}Htٕ.PGb:l{9{cwt۠Pds{(>xwJhg5Pwwޟ7st.&i[f6h_ZoJgpQE~JS|AC}1Za}Sno8;Ms#}myj+A2A@>0@i׷bjoX;ꆳ75(q -=޷sp[B`.Kk>OwאgmѻU/ J֞{Dtϓ1$_}GDKeW= T;"CZr6b8.?^4`7Sz}*;8[~'µV7$= gwsAވRv5&o~Ѷ5=%X4!۸Fڷ|`坞j,=9OݏD߅f|~s\/Y?ҹ?d(_?0ޟ80{=0.f=d%!seO=7|^?<<߿>wg?7w|mʛ7'T{oeé_o#Y"_fS_巬>F 5}p޿OSR$P^2; =5]l~R!jVDp+:oY)~K ~޷, p)޿V9C={CH|> w>k{/24z>1k:x/%5{p޿~Ծ0?C{}^O=HT^}QQV. #mz{ >MKO: ihww.Lo>+?vH⻇{-û}Po͛HlbKMZbPV$뷿/)OOEdO!}xF=zpg%,'51O׵rOߊO>~$iK}Ƿwz*/ha[s/yP1?3oS_,*/'~Pq']P( 0UȽ+BJ_}ly?ҍ޵0e^.~C4_E OV3~{}޿ѴȪU~~j)-\I;?h1yj{kWU)Y{CUOSswiҮϯI*{uQ<)%?{4/W7}x]ﭻ>~M{Z*]?Akq¯u>Ï?(8O׫Pw.|M^^~4?ZN|~bjDnǷߨN]њh{G*{g>fܓ8ǼÝ4ϼU*ܷr!wM-zvMU{@Rg^3W (ԍn߾vϟ4ͧŷOOO_ʆ~6?\nOa^=7;ԔOJwx={Uxy*Q9'4@e-O}㧏Z7o^ z{NxHk=+=lo)>Umz+ƽ}%e}l3X>y]$?.=b%5:{{䵛?7zZwO 5NOeݰ}7vM=LCW2Ўg cޟvh^/U?㟣x7>,cPi>[{L?~*u4Y IDAT7;N_+|+Iv)ǬZ,1x0'ZZϊo_x2|{G+| v{Vᶳf}ޫҽG["Jx=p?>;u>=?+J}ޫ;*O{m^(VT/V,VF齪W,S>Xx>{:ﵸ[3f0~av&okſ՛[a,\]}.YޫzTޟ{٧KRt2|jɭ5=ۗWV6iRZtaxTiyv'z%ޫvܸm"/ޜœmN6r|}N j,~h,/כ};k}|eo8Z4_|>݃%~[.{VYxc # g}<| y:7 ܮK>k;"f[W ~{X9=O?Dk͡p|d'bwEjIOI$~s'mއo(' ̼?[f7y[T5ᶬ.{o6{izﮆQ4@[$jOjݡv|?UI`'z:>]mG:1X xJxOzpKq/9kOzxxxxxxxx3Np1Sva|cޞ[6&O17 K?b_:ҷ&΄7?+^zƼ,.#} F#Ji$x?5332sY[)l?obCx/{`#~vl4=){iy/ls:&,ދY՛0'}*Yίˆ?VLR!mWj\Ud̾̀겫Y;^n>KxéQ:[bk통Y8:S+­60_8ki;L )uTZW6Sw3zǿ9zh/j AtX[kyg>.'#mzZ>},xkZn޻Ҿ!$ΣcUxJѭ'1zSe#Nh!_$\v2nȭyoc̄^vW_N{/ܸ4CO7V>e:9~=w| KmrƎ{^z{(-syuLݹZٽ/z>]ލּЌFF=8}P%gY=lp"|gnvU b}aRѽR7z/%^;#{Jќzey/|ܺ6ޛH6E`X.c{`!m Z轜G^s>3VN9|^oOBqwJ{*]c,KUj> "MvVI>?\M/;#wm9jt3ӍSl5.@U-}=YuBzz !~'D{nF#~/ESOGϺ{h#4?_:k7^fyf΁)y='CNc3X^/\QxϾO{Yy/'P+qOo/a޸W[l;s)toݕcޡ/1=:ɩd 0NuskxNyxV.O{j +-ںF=ͼ? u{]v;_m=/'_N+xl^ڹYqڶֆrq{?s-y;eA&aEo3oNlIoZrݥXwIط (*RDc*fXC)_XnҷIƯV%amsӰ*-Kh,]8kM (S{_wO[~`By*OXqske-[gXsJ92dI=/J-*ц#oY{f OIIQ=iKZ;۽@WUIZ ve<yΕ-NY*Vnœ3_ugXHj|]U ~F^ 5h|B}q +JlgsmK+uÚ^][O>3ԻE w ݼoe ߵmt*!m6Oqx'M|@ ܑf{>KKXof넽c~]Uy7 ~4x <*N뾝@()>YS}ZrwKx0mxpio`FWeV?o0psD f}!}N{{y0)FT Lg̓(vhH#Ogk37hĨ;] _{`h ߆hPm6SIzDyy15ƗLjۍv'ahjAދt#ɵ-_v>2#LA ȡ{}ak|Z^^jli{Y{^w"6ֻR}՞Wۆe=/=wϗ4ޯ7;V6ʨ L8sjס~\Ӿk5|F2_WJo$[6Ӝ'۷Ժ #Usva<`ݞU9p pqs.{2=Xy^rpZwho{N{y}'[ۭpn8K=q]m]p2}qٍAa#gs:N}'q5尝'o&}/"s@Iۍ77t>iIV@3{ >,>'`R~|7[BCY{ߚB>` miۭ!P8S zx:xp=exbzQ@.aE|r18ZVSK٫8{y_Q'wp\Z=^"x"}z)&ox>U*dJJRj{o>LlK3L{vﵴߛO 齯|,S0]EUx0Os}S'.rzS~m\zʙܟpvk2>q2 <I^dt$ }WwQ{yo3x0aGh}'0m]Y%G- ۍd'-hm}Jc3N}bgeatW9mt `ޛ^7 ܁`nVWŁH&^ 7rVJ[2 | z ~YN񼗝k-wv nzO>`bʺNZY|_oODaNnȓ-d{|?'`7Wq2&q8o'`ʻo}+]W"\?xp|wxpa'e~9aw?'W~U?Np\wZM=R]'-{Ef3DgOج8e%~+ {{{{{{/Zt89_D>=%x_wcx0gNSd*OR/{0{;4ˊ=1od{J|S~ݖi=4{3MlfxpoԽe6m d?['{~[ 78?﷕e = -5IھZk#L{#1+\S=y.Yه3>jIʲ|ݐ{l7*{k¨ >'xON@{m'#p2}⎉!מ}^|"g_I6R6zaW)^|b;SrCCU3ޮyW=l/{ ^.{n۹+~'Kid{{{{{{u`{{;4wx0Kqw;kAdZ~A{xۙin;pxpuw{`=\7sxpARf}V)= ngvk%'5N=O¼? Cz߳a^+Y"d={_ޛiڽ<L{]OwxpFvz[Zq9gM`:a=o3:rf7<ɻ]''F7[a~ȿ3{Iyeo;$%f|S^O|{عL{l=gp2v{8# {uv*i> #z7R|C{m6np< \ܧ pYB0d:p2{cr2}v;{{8{wFucxpnA|NɼO`[\=콷 ˏnG0{y? 8xxxxxaUX}pwx0ﻛ4z=3(w(=z_fԻy-es2}Ep5zodʇk5G=v8v9gvDOxpޏ 'o=L.-\[VYIu{vkݹsTK{ x6f}T .{D{ .{x~WB= O`{9G~p2Fwp8[z' y_u/{xsVuLOr0`mշ㛏h^kW.Y}=.Oq2VK}utOV+STfdλuRp{Fa[ϋnk~k{of}-Mvs6їVs`Bo o۽w'ۭ> N r¼ޛ|-5r -PjOj9m^v蕫5=D7q2=x]{sq2!d={{\XpDnN$>'{{zX'x0sxxxxxx0MO|'x'F\ܯx0;my7M{^,x3|8z~!Q?ގ (`R_wQLe׫XUUxq3c\~q}}_}L {XxHEt'b7_.J_D,boQa eGƬЦ_ZWx0BS㚾K͏/7~*Z/)v{}{{YM>-ri{A{?\siʽW~|1nC}{wwetY_~%?OgN֙VWK(>ʔWZ^>eJzz0˻4==%:OWN>,+bGzJUjx_zCw(Mtr)/43i|^)j"{7_"Q 콷>?Mכxy-swWn7Q;ge./Z >/g}*~}'] {/zs&VK]Y>gcyWE[>?)Kz;{Q/U:>M^>&Z92xX+U= ޓ4lbU\G˪Z3K=7WQBzpGwc%w5%^|Ώ">BIDATz{^=@pHzc=t F\ pi?XPIENDB`CMF-1.3/docs/imgs/search.png0100644000076500007650000004303407374605754015570 0ustar tseavertseaverPNG  IHDRPLTENNN33fffB"""7<ff3ff,)6Q7KzNfct[gilЄֹw3f333ffff- IDATx c6Hs($m+wJd"AăsP !<BGBp{#Κ{ɔT}+M}V!!{}aVږM"-',~*~+34*+#}KhHM{}@ތYВҞwQ=BB#!G͒'yZOpZ-(Z(eqԪ _㋋K:UuL]fc텎} _uT=iB9G^M p2{$6q$F1Y ܷa}yyLśEMoU꯵Dm}<)T{YV &_ }??W}q/Tn7%;TBZ>Nx"S) l~^=O^䮬HܫfSOK߇mM؟?ޫҿ]d ŋ޿IWQJ]xQav-ZE ІOwO|%ѝ2mcKϦ'n]NBd`q/N>X_%KrW"v??~t{HUtD.*OtnoZWLq)?j=tf-Πʽ] ~9I6?`?>_?}[u pU(\f-OOO.kFJZ>.O}p% 'WIO ᯒ;˼0+kqKEu`s'~Wo3dڬzIQsBOMvNEg?{6y uVӞ?R%&~6;ڱCO('>:$.wk퉦NW!"pQF^$7<~3U~ho$uU$~JccB}O|~-);N}G*QV]hDد6gYz$9%H`ۤ^So{VmR{Qp)+C-{Y-?;xS5*_4M}ea˸ #grCa_exX ߞi>~on֮L}=1>{yz=]{|; _۬YoWEʚ`ǔrbO%_]]د}r}jG 5}QqF}}]fzp_A(XRףx}4}rUݹߤג-?uVe-[m~}}Orz Uo_C!د2R<6^nD}SxK}l2?yb{$kޏ½Th}}NDK}'77WW͋ޘڣx"7?oS_Ժߘ)YmqIU1/'i(F.^ebKef_uz7g, P,m.zPWD`|I3SSR&pԿ =Lf2Q~[x(;Uͣu ~zpH7)2sY;=5dF#b]k/W@#mvѮĐ&&QSܑZQ֠g6*b}yqg_fg?-.=guz}i2zB}JE2mMO~>B!}M(vQYGhNe=hBo8I/@N}>k~x8=:uHԘr$xo(~x /321jHBea}_C,y''Op7}_XTt=Εm[F m1N> uyR{׫1/@>0#ś/}coɪ:IЂco3* ս&?Uc.F67Vޢ RYu98s,eV44Ƹc{"$No׹J"yv4bR"~QKCyo?߬GqNabanھC(YD*7*e{+1(cܮ^;^yoJw e Ѥr+#p)Os̍ѽV//ZQ#͎B2Pj1`w;2%UQ ~~NFifosq#ǽ( \ʟ]C'rޞ;It^)sMYwoBjĽ p`pO+V+pKvinonϗ eʎ$M"|2=k9&ǷXΊqy}A{qoZn EWջ*=r(*Z[)Ɉ =~L& 2qg|>܏ܟ%[x}SYC ǷQpAԸ_E5n"5ŧWCr=ZA{=ڭO{uYpLxꍚ{IR?}_^<+G-}./v*/&fEj:{]nmܾQr̾M~~Qz!y2?_sv}sx觱7v ]:zt=חͮj&3x{[p/^lpŶ./tW7`zү(Q[=7OO{E/^.3,k׹ϱ~QsrMNxn^:ܻV^M,Y,uc)Va#lSoZ^v=~*4e=E;3i rSGv(vw7{^s:W?QBAwc'oIeb\k{~L.?Tn5mRÿV Vಲvih q6/[B))zCclAD}yutx7߬1D>"8Gtp_ h~d=:Oo^'ˋ)Ns_W=sx?w?s#}5/e0B  -Q-χ{㭏z4OBpZ]>V.O*ӻcܢS "B\NJ+ӄ3a2LE zP-vOr.'Y~ؕML oa f˃mhD)< a3cKͶ$xedH*Ȼg5-LN7<+7hi M|?K̎q~ͭJ˘,@25[)7&^9y2/tJLʪ5Ӭ;+Xd`.gӧMD8Gh|NoL߯XKGEtؗ7>Sc!ڷ4\W[PNU1*U` AKcmꕘv2+l}jX+Ks"|7b0_^oLb+qZ7B օ|+yƆ"@[oiv嚃qmZ i\Yk5owys@~j`lPv{F^tIo,ׅ{_hF_r g@Vrc"6m %H;&fJR ̽ϖV-ĽuO[ a1@??lt;ҘV*+ -Nsn7_@WNni4९q%!w%;Fg~wߪJj\l9Nʵtk FG~eW-;r/k(M-6=%Z4vfo}jоNwz^ ##OSq\̽7|kVd=ev*BŇ@{W[B 5[Xļ-V@}ӽXNld}VZ)uk7FuCfY{%JN[ZK#O,}@AM7SzlyX!o{#`?NU-vMStxrNq5`9d3?vcw[8,&O:U͖;[׀֏T'& a=-:Lzly x`}5#s;f_Bܿyxktd{tVVn^~B_ n:r/C}ܣ߿gS0:Nɿ}q/C{ܣ3>翺ˌ|ٿKXMYfgb4 G=nӎEIp_NMbK'?V=^j\b!\S({s1C7\72 i?s?V-~n}Ͱ]qfCNMTH]t+_={g~>:}_\oros?T4}XC=Ggmچ`_߼5Mt>6 q鼧ܗe^$z-~~{;}&&0+_L}v}otc:FxCpq]?jw[Թ? t,/߃)7 o~F3e?~)1u }￿( ~½Wa}ӰЊǾN>gn~kاywokyߪnoo?|m'{$J }LwoЋ9Vܑ7m>}߃3L.qp_4<޿0E7~F~#&T-&.*P/7M˂tC@dF=콁~7omνޠwW4߼ 7nJ{ZT,WÿSJ"LJ%Gҿq[ʿ__v7TUpou~s ܣ^g7a5@POOISOizZwܣ}6'JڸOj55|v|?Y=<Z?pr!>+A8|iptާ=wR?g(e0h7ߤۢ7/Џwq}jﲾ$?䎃L7}AC^5~6۫?ۛS{7b?=kk2wis_WST#o w{q[jSz!w]݇|xt {3s̟{r懚}hf,t"2{M~ܑ;=F~oJGysާ^w7/a{ݫ_ܻ̚ӟ~5vgկ{4-ܳvP"A+ާxU M>N:wF>^\^}b= }sm|T흾5H{>_w޻[=~gs9Կ;{p)= ;~ӌ)&һ˕ͼ>7{?$E{~V}2K*?Q־g^ ?sw#̟{X]=;q~q/'ze+D?ol? [ 8s?'ͽŲ4@)/O\ξ6\uO5{|Tc?u^-ęR[FE/gh㍅sVq ̲?|]xNP7X =?k{?sc3/ӻQ{_YӀcuR46{Nw;P=pqgt IDAT69꾟CGϐ{#ܟGiv?|z{kggi6v&gP7O^?oWݗ2TF6{ZGuS\&12<4{ߤ&ʿW -чWa# ?{? &%)__)v{]|ﲥ4eͣW;|gB1p-ϑ{} ~&?1Δ]٦3bZpLgL\?[G&?{9ep!4.~k ?G{__1N+[.`e/MO #?+{" ޵}+'{!ߨ\#s͛{Q{qcBp{{GBp{#!!G=BBp{#!=#p#!4cB#!G=BBp{#!!G=BBp{#!G=BBp'9o)ɤGMiCCyBRe^ܗ{{(SW^-{)C %)p/p>H܋]<=c/6pT Uha{ӺOWߌ=4D/=y͕>h{e[e7q/lMٗG 5=LŁXl}=Z$9W>cdAWܛ{)G[/K]X-xʦsēMpЌGhi{f}>r*Ɓja cs_p\\c}~9v >e~** o:y }կW7ϵ#t~_[}B| > >c&G\jN;9|~=3j>-=B'#!!G=Bp!#,q/廹졅r0+˥rMG aܫFM04G篥p_Zx;Ͻν=؟-9g6GڻEBE,@:y+=ۻqWs_`Žn[? ܋`?FBW76=-v^,_=`9rTTS܋tiϗO ܃qwayǽKn !N{6̧7 ,4g<8݆6`,==?.p'chܛWYh,P.(){rPn;^8%;rqZί>^wi[y. K/2-ܛ+5 7;˼ h^K1`?!0w_ 󲹷 u)j ;soaFK'?{p/^Y_ͨk@hjmN?[܋!V9oZ:)6콱Vo3_?;eϓ{[j @OM<]ƸՐW5[kWb {dr_7}y:?G`\?k0GL+{ 5m!̽g?i8q{O l=jOM=&E^pԝzu#<P;G/~Ty!t Uo/{FG茸:ܛ:W+/xP[~cM\p:>Zmnǵް5!͇x O#4;4Mpu;Zp|[Հܲ:w {#{Α{GZqG<=B~Z:pЬG=BBp:#BpJJ81CTRGi{_zGh M|?m`:'xt?b̂-} Cp`LhvTkY$ w|\LL{4Z x9rt܋A}!FK58Sm( )J)*(Sf;q:\:1OG|@J*KGu2ի!z9pyAuީKAQ;F;+Ϲ8.B)ܩܷp/-țuK4{_<uǒI_{?_Wn{[`}h0~H^y;0V[Jn3ve9LirNeMƠxw"[hy/ ; NyAlˇaoRlFտZov+FJhvl;evu6[e6*F]m}8O(E?)`w6'ƽs٪h#iDur1{spn#봣7W+ghCyl8T܏7%K3?'j,E{ph.D'HT#ܟ%nC@=5D.5vi/gm} &Hǁ'rgnJ3%-@Ycz9%*CotNH%ɽ>p(g {Ճ{L~zdY̎1_!ܟ28ZgH83UY4U=fTYT3X)TSl~bwyqU:=p1m{nG,f {T/{ݚIojٮٝjE;g:*åq7 w:fQL|;T,f` I&=2K>U*cjOQYupuy4ĝaxJG'Bp<{Rf32{UT`9og(̽j|v?Õxp><7qfh^=ܣ+λ@L{t-%ipD:M pý+Q tv]w焇{GӃ/As1*kr+g8e9Y-Z!_=wR &#{?`~ T>7sJ?ۛ{̽yW-cܟw܃=yp_n=gt)+F{Gp? KrGp?Wuorg̽{=sI {~y܃=q/F!@ zޢ6`~Yi*=#_{v/%-{{<{Gi.j/J`p{#!!G=BBp{{#MR,am>B爽7#?W܋ !WC{\s/ڧje7`k֋`ŁXQ.!?bsiEz o^DwgB~q#?sKqx!>#s/pdk#r/%u%j3J=_=#{#Wu_[=#?rhŽ]6(Sqm5BT>cB߇{71cD`JjWZ _xGpl; grڻ0pĄF ?4W&ʩ[S|gYQ̽,`=3^~~kEDx*0ܟ8V2ZjEPV ~Թ?|/{Gp<|/{Gp<ir/XPܟ$sO{Z={{3^mCpܟ*bΞaM;=^esh`ϟ{kt9r(9r/r,|A{>sokkLk΍ g=#_`~y>q=#_`~y>qςp?ρq}{/{Gpܗ3*<<ןw{Ͻ{&Wo`χ{ӅQ܃=^1jr-35`4 i=po̦Ngǽ[/ڸN##_~y܃=q=#_~y܃=q=#_~y܃=q=#_~y܃=q=#_~y܃=q=#_~y܃=q=#_~y܃=q=#?4uBpbiBp{#!!G=Bp=BBp{#y|)^=ZjGZ/B{G-{{<f{GKY܃=qGp<#_`~yc/{Gp<=#_{/{=q={{~q3q>ܣqp==Z`hy>#=E-UiBp{#!!G=BBp=Bp{#~B!?m g}'z}: r ϝx|[ꦁa?.3_+i) gσX@kЭ;qp~W/{WGp?? \5p~v[{W$[S\ڤ|"}nCF%:S0SBb8>C(S^?n TVϿ902^z5%*Ƚ}Q~%x 8la?1pӘMu=j0Swpye2d[ Ng1ћz:*`ʽF<Ͻ۝h-s&!VRje0k6^nh4ԫAάfy=JH;.:#f{Vbe/eׯjWr.^n|k6e629C㘚: k P7U4]fՌX|++)2i;&F:7w&1m)V!:4-6h&|PM,fE`1 K;%ږVs>;Wbgbtq{:="zѓ-{O!#۸*=EN"3#9q/b#:އqC4% E'jϽErZD6YK_Gd+a6yj1N0ZE/O5{{Hv=y~"q!~~}=FW\7Ľ8Fqlq/?֞/uՆMy՞_[rl=_ͽ50:Q'bОuq!H}w scIt&uZ~4fwԣ9ɔ]9U~9C'zǜ5Z@ O6c|1؝D/҉ݑYYb;rTLG&ϓ^,91'ϟQcƚəG` ]<#<=BpG! n&)z:W~TΰsկCe܏ǽ ~]K!+${tF܏!`q5Xi澕\y} Gý&c0k-"6֙MD= 39TY3wR, LQqĢskyBn8Qh2/qF#[\Xˑ.4{4g|_ٺi-w(;{h"Gg=tu˙%ب|t\5WYI IDAT˵p^f0ez[' q/І/h*GU pxw{h+ڍ{-[=oUI`r_۔s/KYYN{8L^Z/] |M{O{~ˬ[,Q.4O۞'myR9V&nfsޙ[l䲆̺eϟgj"ա$01({)1;q.;:GPzȹ,/%q6Cܣ(|"`qܣ(K5휹' Z0-Ļ_>!!4Os!G͔/BBs#?0N%qO 8I !,={GoGб΃39viP<DO(~љKجN>O?{Z qGQ ^_<}eo-^ĩ .^߯j8z%~aRm^wOܣqmfi]ڮȵR_꫍U ;*LCđS穢/CQKVǛܗ? {G k/3Sb9WWW[er"Ǿh8Mo{~~fvv[~Z>79ӠBoD^oS~~^yHW8S?pfŽϿNެG+iBW1fܫ}GJ|Gkձ׸H"U k̸_~:1*e/5[O ͍:}z\>kep/Eܣ%q(.~)On TV^a3c)Y~T㩸N6ќ'{:gww*{p鋶h7ܯLmZ\ՋLܯvwXhk[>>m/"__a^V@]$Κq*=ܿH^>-jRpPWKXޥctwoU9w/7]#ԟiGqkQf9y:xFV3)j"<=B䞹PZ? kT}IENDB`CMF-1.3/docs/imgs/searchpending.png0100644000076500007650000003756507374605754017151 0ustar tseavertseaverPNG  IHDRPLTENNN33fffB"""7<ff3ff,)6Q7KzNfct[gilЄֹw3f333ffffPPP@n IDATx c6hb*\V$m钖;?X)$J:lj׃I@8?fp^<{^樿Dشz3Mþ-G:gl+%_DSÿ}QL}OL?ia^^Œ_g}uWzl>l5QN`X ӓUSpWZ3yo8`7z4zx` 6y77x4튌= CUrpS[͔x,d_7R}ܯ~9YX:Gҫ l0XdDss~c~fg@ ,n\E)[\wZ k{3CьY pNwi t{#p?p>|8?Yxp^1e/ў?^ջ֩BvnoxEvThIF~J{} {R{\}Ki*I(dWKUF ]pIoK}Zd\ʞU-L[9EC^9ҘzVh\٪m{}4ƫ:}^zț^Z*H{R-DGhOv<Xr___ju). g_\P? 5e>AڗgF'YZާ_랧JK]\Rx/b٠(s[@?)뽎Z}}Y"c4-sWܨ;e>EOrEz]~1zߎnOu/~X\[_h_Q%Bt뎽)/&yfѼO@XqrN-~;39ŬOt|[KߞnQ7oSv=)G򾹕QkoxRy?w߯*/_}~2o[ɛ`K}>}P___}0{﷾+ԎJOsi }-([ɢ:މj{ۢ -y9[y~,~ s/Z|}k9_ZR޾Y!R>nD2}/zږ<S^{e6r~/xK{V}_Ty߾ dH |.ż\Hs9_\[d55ojOґIxJ{Mz#k ۪q#٫c_qLfI~~2ۺI.UTTWחy?sZ'gZWjS첬 y*X)_re3.NKRMf>JQ$e3E o{'s-~~8DU@/}ps񯇄l=crVcI=N*Y+1 y?_}5iH4h-e~!\{Ru~B?8]Y Ъ//uLW].g=p\^j//s5ZE O9Av} &>W9_.U>oMut9pRNb.+=w2I{v^>ϻ^6S8%Vcѽ_M8fr~>Na/\绑3wQDžtyo9 l}/ s_gދlPFnZZ,ڭrGܜgz@g7}K9>[`QSM\Gi|&X*<+CL`l7P:懽#)ސ]Ȉ0džFsi&]΍4e3P}0{^3,7޷Ckk 0_F6BF]twtjlZ*/`8TF o)Hi0i?iX2+* Q|e_~1UMs-tTQDyP߃SRnX}6s>#<Eva? 3Z~ >nGR٬7}ro9Ves9Ӭ)ދo)-<l,c2Ϛ~s{~D녝N1헐t `] {Zwφr4 4&n(ioooϗ#[Ι ~xp'jϗ۵wGQs7gfŊpH`|QU G{ߊԉ(~s>oF#o'~|j(9\r>9z?{Yu38!ίޓsygj>1e}0b4ګ?}W7/ʁUyR+Qd#b }$'봜0_|"w7by_hG*/Ky6M!NNKŻ9宗pBWko.ӫŪjH?jnKadHE9܅Ze!ޙYnZ.oiG[uGP_6me9YO|=x_P/Y&UE1O#4E6_X,A[imƖ1{K7E1&0\=ߣ[Ѯ7%1 Iz?{.,/uo>(DIw&O@9(_?A[ёQzYRp ZzwH?M/e,g2*J1] /VٓTiSb9/fעL*/*8y])=.;{'oҫK#;[O';]G{0ZtyE9ف^ZeQOmu^]{/FJҳҚPb=ed HÞGK$fh{Ą%H鯧 -AV/7J/^"sM{iv6'gqmBIIx/Ip"]ެf7͉y/sX>ecgY"Kpb}Cii%cnF&VZ }ix.ж^ha saBIңtEU*UOvex{?ԊwƭCGr;E{)cy~h7Wt!Q(p?MoϷ_.Vʿ6rӬ\(r6w߷{V^t.wKyBZXұ9˔'܋msUk^oS1rŏC[|b4¯ҥ}39Nbe@9gᄲ2*۶_|œ+Mۍi{}Лě_]cJ"JvSk݄}z!G}9osΕe`#tJMrֵ=__]XT?"Yvr\pnTq,~hc~l9OPéE{~~<'=&…"]걜jXͷ幜Hv9ޏg} yOؓ-/ j$ns78.cVJٯ4gb(H}y~|#~|D۟0N1h?v{>]~ļ?ps ?Ӌ^86}/y0@C+9B9xp~xcx`ޗx?]j.'6lrZcPs9#R=lFZ8錸Fߣ;_@@Vز.2eł˞a³.{G%|>\η73.?vlѾZFߔPqFS3p*FE+T%e Ynh IOu5=##NN;3rwnֽZ;Xdd.K3wc_oum,?24҉j_8w١Wهwg7He6%zJph{搧d0ڙnrmкaK3SX1]~X  Y#f]윍ۥ@ ΆB#yζMEFݘTh`W7lst]ۆBagSk6OpP`i1VOJ9(=b}@:N@N<\q皍`GG ᒲC+"lKlFI}6{熱V.-sL7fNǨd]r&cf=DP77]RnWbL`qJMbIsYKwf(q: ;uW١Lɹa>TeVP9%/3ŽK٩P՛٦=خ.IE5 -euh}wzns #XZ8uw狟޻ޞ/ܳ;?l2V264}Xki7 ܚ@u[&X} 66 l3MA6Gh}ki\ܖP{~۞}6{R%}etqfGIﺎ~O˝-Gr+lϿ<,ث mJguT{Zli]\`{jo`{S:m7G'(crgK뚰 WÌ9ւIg0!M#rgK+t@}|< pഽg<\~K}֬]{)xǙwL8s{{www{{ptvg{8E?ۭffӟ34hw&Θ;zxG[+*;&>W 7ʵ=Cg*>6W(TW`ޗ+u?/+;ޯM}r8,$-ɜnL୞\͆^;_}K^9ߗT.ˠ}g|otm~yWcu@}UoQXzw+=F\۲&,wߛW齿ߋNYSɵ_nj; xG7>n~>(c/޻FDlfZ3=N)˚۷F~Fw*oގ_v Iq] ?`]? ~T}i`Ďzi&I{x/F>w;^z})~ޅk5sIń~3﹎7u/ߖJw~xk:mg> C#o=>x|,3!u636{otVxk_* ρ50w_uRwǻ?? AG恓Gޛ ze~)xoB =Jv_m }ݺ_ܨ]YU?ߋFץx^t^䯿K ݽ?}{]]WΞ fM|yC,YUyzz:xkBo#D;{wTU_k(͋z{=5LJ&,qEr^/yhVO߿ku['.߬S1xD{KE{kvZ;}_|1C]矄~ GUT_^0 GWr/߉vw:o7=>&]y5/rQq潎>ǜ[t4+*Yy=Ckc;^Uߗ+3ʜO~o^7||N}G}ᎊx~]d}T)/ۃCHgr0skk㬱ײx{-uU%{ݮ/[7wָ3W Ϣ}zKZouz|/kNyܬ^e9ܯCQm/2 u 뀿6#-qE:=\^/^[Tuߝ{}rh=oߝp?/簿?J`:M^`MoN~Q);һ/{Cy>ܩ*k߉vnzU@0Ÿxq9쯣ޛ{.1'}y(_u%D?"P7=޺Uw#S~;.[Vެߟxco {M?ˇE'???GnszvxzwudK|~}ߍ+p%̙:>}7'~j Y׿O'ޯGgnuk}__>1gBv/ySf-㈎ߺ2pFo 9>y|Zn?~>}4*dy0-So58Wk=loޖ=kzpѫx޾3}7 ux>oԿ-E<8:c{dޛiO#ogTo~~AxoUVx"-;k7x/[G|?x__Ďyoek|#{c%<:bV#xeu€G'BmT^]gjiqex+Z LS?fxxxxxxxxxxxxxxxxxxp Wsiw]z_H7D<}Ex_97_G.N{qx)yx;=|8׾^e?xIw}#Ye(*ۖ%MnKҟș DdM۾oDv{$7yoLpޛWkײ:~`vepps $Լ|?E{o{o( yWO}Sc[+y{a^'i;ok X={FvH#>~*|CCRN{r? mryif1=8A-yoӭ|jno޹/dP_=e۽4צoGmM9yw+I+i|c2޹7./U/;s#pr 5 f[^$#ERGg}(8{~p0Jh{{{8 J*?{~R\}':=|s N{]ԯO;:c8ҹx|xLf}翭sZ4ުb}=yoe9P9c*篝X/5W G}缰/4/7m&T`=XӘ#qxz|aL#pw \Yy)&1&.}5a=r9[] [3~= c򕭬6VJ;?Ud^J#WV Z7μxxU=yx/zy_f!Xxo3_~b!>}}h:z~v?3{3>ۻxpXwu >z:p0{o9Hf`gYn8~7)8z6]p0}qݍa'[۽szY&=kZa7y&}ޗ=ڋ/ss~!ޯwJFq9+ִLx{=d#q@̞H|)xoޚo]{_g޵Е~jڟ'2~*p0 ѳaJG#ON=Gw}s^˪|s98R搉]{Y/>]Dqx{oW7瞡O{ 5萙 F+Cy牲:[摅Iz/m>?={\;:dib (k#H6ypN*Z퉲nN7m?洽z/s~ie?TpKLn7n{ M{ni^y'ےM{ZMDeZҨRYRpo8x YZކ3ZC8mχ#vh؇z7fqFKvyM>e}xohg N {{{{8 c>L;!͗09x??ʬ[2M1y6Y)e. ;|vaz;tZ L; Ac$ɤޱdo qk~r`S/$=,zDJI:P ڿ; nnv軓YkMr{{~+px;\Z&'}ے>ΈSBt޵7-+xn48嬜̞ՙmXeMi13XݰzəAť߭uf%=ν=yZUSy[P*]0G(p<nZ)"底NgDN>r^dC6VOZuzz\tHyo~k/P7joŘ5u?5r^{/+6KÓR{!.ߪ_ugr=,2{' ΢>w0 f^bnKβ$ψQ xhwxyo+b~^"%({E \KTއry/*Au?}Vߛ^Ў缑ゥ'}6r~>Vͷ>dJc=P `b޻%]{нxW {>o݉x/ 7wޘ#f$ƚh^``W-͘RC8ζ֛v{Z)[P[]J97f/N96=۽H{~0OrZמomX=?P{0m-yYi)pވ78 k.IXx?B/ii|KFm޽l_t792w`e@~YQ ps!333n:w!¢43#s~Ѕz[fM;S;ϲ尮ߋ)\ǛuJ lVp'ĽմŚ|xS?w޽6;.m{1?N?'bag79 n*~Lh(NtJ.g9,ca;Cd8{M}-kj{~騿Կ 4[ؚFy F!EH?j"]{ n*J#vfZ~^p#fH2 Q{8٨=ls0q`=ݏk8 38738QnYܹ}#ݩl>PZxpf[5 ol8)=YX '. ߠث0=Լ  yaW؟Xwm= znr>=x {k޺|}n7˵r{ixov܎o?cxm^g<ثCm+Lثy*׋V!{ z^k>s@uS{9 [7Se7x0!{Yfzo~%Peޏv9(gV4=4vf9ڼ+W|k}~.n`L{3=x=Lu޹8~p0v;.XGn$>`{~x{> x======4?; y=yxjzx'9<-x3=D>"~Ὓego zF GhM~xg}I=E9o\J瑹|⿿eTŽOB,i{YSpFϊHnR,7*4\kB" fTib}UR|[5l^,tJGvᴼ/5e~r| \hFz)l>}[ڷx,UZxx.++Jp):+__ByH!eyEAyR)^ipRއ7Jʛyߕd޻q>'\_=:ҫ EO=\Y8כ^XsӹMimo(1E.>pE==/xp? \\N}=%yi.(VQJƭhƎΉ؎x<R 8K1e?IvT'Ur7ΰ8SȘ?) O ?A>Hjy~:BKؤWOW/ɲ;Q,[߷mLIK1g!XK*}1E!o28O!~/.CWO)-_ˬWE}?Rm ?ueM)⡜de=ZZH_{y@bνE!~YߣͲW翢q~}s3?Is~Yt#ǣ/rt{{hmG~O}x3ﯳ 彬϶"'>/Ɏ̲sYTo~qWWş5O_8i<@?- ͋8y$_-Ϟ4+*RBIޝ7/O˜-m7ůgb!,3K҉xd].7zmO}&|.syYz=l U!~x{㢾x]h|..ŏz3jP﷬ $yj?VLl_.J$3.{BW0_dePD ly'EwEx6[o$+ūe=LM>?j)CXDWM4z@qP"f%ZVyMsy?EPtͫ+jx^~K5Wu}?A~Zܴ,˙ݔD=Ξ^e?9Ceu|3J̨$K{3[fbK璇*w~1_9J*zecoQhF݉X5~\қjBLYӛ.>ϯɖ<Ų:kQS.ݱW 5{$8?+ZŪ>.Enɼ" lXo{_m}sj˼O>)/ɰu~L_Od"[s3y$kV/_ Eѭ oUSt.Q {}q*/^>﷾+Ԭݕgڧʼn>O7},yQ|4ioV~oK~ ?Rje۲@)/ބ∴E^Zh-Wj ?/gARk3gxg_'Y#fgͲIh@l>B !q>YELZ]VpA/Yh77utC2U"5=wɢ8ȝLOG/2IJ0b, 'l "P;|}wv>)蕿'\oXЛKto>o$-[nSnNu<`އ|N/됫32*h2>Ł|\G.z'ܜ'O7}Gߜ-UeєCo'gM޷u\9hskR|Y~?T9 -6R1*qVs1Nyw,v|0{-W}dJ˂2ཱHgp"v66NE|{cULg8lGqDZ*Kӥ{`;r[i?Q qhݿboUq^%z=y{y \ .|ˋO{Jn8p''=58L_iz_}{8ԱyŇo*c 董zjKS>X'292լps5U?}/U yRy/0?޺yhǮ}gŋ1^5UŀWee8:)jq{L=zTM >e>DJ]zmp|d`:T|Ȉ(Fmax+x,/ھ^S- ;k3QdVbD2Om=*U0 cSl;u9g` %MV/U]Fׇ y/GBQq {95DItsZNs=t}ԱvkL5VkLц&il'C]KӿG`J֛t& vf<ʰshdȎ>?ίVlӖ!hܬ>/C]ȰܹIm-w^4LvTOy ޛp-18ߴ^O~vwڙh6ۏYwFh{X}֦?:v/' EPt7߯??06Et^Ug!ڻtlצh Fև< 4hgIȖuXצb_Y2$ mq1@닂97@ ֆDBSpAk:@='\ߛq2#[2Ŗ+!xis&HPשRi HXv!olvYڔf :U=-w .3ϟ͖w~۝ݛda=:>A{^Ju7ae0caNiҙp7LGwxv&ʚɞnC#>w.pT;q+pۡ m8yuICm|Oo}PX{/x'}q _Yzo~f.YMBpb޿|Ծ2Γ?۟w/Y" ˪{B5c$2Z~N7Yr_.#_[H[{8GO}VXUm]P{+|Yj'7vCr݌|1ۅ}QwZMϻ~%N纾ʙ& [뽷}ucR__9*i͏}vߵ3U+o}l:<>XcL&[ȾvZ٣ofҗ#/ZEkJyey?~>z>###+ ?Ǐҋy#wOWa.$tފ@wW/#{7}u03߲?߫?˂\t}W{C{G~{CگĿ{M?yиN}zWy_ A=#ܟ\c}_-9ՏERY  eJ_3|{8Etkwu}J{i+y?gMOԟ2@{[VAVOx>7t￲p EǼk?#\o]S_R-SֽR[Q*Z/6?1j^(o{iZwe3~fcӯ0/+}<|Ϸ?_2wrOr ~H}k_ٟ_r}կhߥLϟ?X@?)V$>Zć,xȽYF}~,J&?b>r7QsG{ܭƽǷ||S] \{Tq}^?x|_w ʔu4^77 x'gzz57j>=5sh_yw_E~D?j#|NVyo'?%Bg.ۃ}ڇO7mSKϣduku3}U;\ w9I}/;~;wxݙ+2ۼϣ)u&so߿.Ԝp~~n.aOM_LZ}9fs^$hu+n]Deg?>3߼/;Yg}{U ȁIDATr|{ǽ{{K&C;ݹM- Nedhm6,E}?};գt=Oyݻ>>%DxwJ~[g>99>'oyWMrn^ziS]N}W} Ecͽ2tz/'.3DO|Or}CK}ju1_=+TO܍U9j}qzhxF?o-:?~9pÞ q~_ޯ{qYܺ$p'Ҧ~x΍E;}EggQ߯&xV#}uAxO~|_}||CX_S7?Ѭ|Vw8l;3y] ~Uuᕵ}y׽|w?Ok F25˼|5so\xo_|V>jWիZC1?K޾3+C׆=tSl●9љwG>xۙK7/?љb$gɲ~eWxNOo/ݿ {xb79;ʺ?_KZCދo ЀU6%{>С0IGBc]knǸ2ݕk{^=(OY{{{{{{{{{{8cN~(MR޴;mPBxKKƨ_tHͼxk^ $σc^{}ưOѝ=|W囀{^yϋ?n6!s8{W6PmC|eR{;hwtgig8n۷otk5rzC'Mkm[M=Qd/{|<{e8DFxx/u_yo߃O77|8#k35n)[0IH9{#$W^+fWނ=ex^۷>Є2A_ yoߥsxm{~m^"fܻg轩gU[xEpvKo? sS}(KΚ u@3@o+k[wsk/xp޷5ܷM^Exp ^ַ_Zhۍ[﷼8]NJ8~/F>{Sdךn^3_N'za^N/}cpq_vc̣7^iWh:n}szߌOτAn,eQ?9y[ "{iB`-wjqkc뿚iIډb-ִ!ĘYxmfDj>{qmL.ޯCP^Ysv)-L6ieiMZD 꿑qksbJyBx~ENSε3콖XAj`&|Ŗ'̮ğ]{ޓ}Ux4JOEfD+ςkӂZK.{&>$nhfwemZɮ>𾴺|:x."_o$/ |.~xWMk٠҃Yq~35]0FB׳&fSZU]i)}3 b^Ľ 6f^ 풺2WD!Z\9sv ZB7{؅+W@ xZ޾h]&yk¯;u甽y85 m||_7{C||8-}7s,_k=x޻xpXwu >k1nXc2u8{1Kf8[e?8*W20?`WǷb~f4` 4Fn36'/_g[YxgN~q`v:|{p3UJ=K/:н]`/Z~z7*po`7H[ءC{G|^_y{z`ޛwן4"D/8ڋz=8/ P/;1Ӿ8|מynpLG`Ӵ;0nNYL9Pޠ|KǨbcE?bX/G`x/m>oƒ7W=b"Zxo y @Wgsb\yRVu^xnk~cL\mRzQu=>g\yy^ku{ k:`}Po!%!ST{_9hyCf&ZP]R׆EȽWʮwR_hq'x;N۫_+c}sCn}7Dn5uc{}T5vv{tEr'OIkyAHL eh-FJU 4}Dž^s_p@!x~絎}8Wx;^Ҽ״G|)X ?:,ɼ_=];~03sWG}V/G_7ny<ދ埭{N^Yժ!nɟvj|M3i7|/+Sq>=S+GT_+؟'۟vۙhg_wi_~=鸅?M<޷9 7OOoӟW=2{dWۤ=iM]Ec6Q2W׽zb;v@0{gYgFJNJM믺}t7/v^+{T\u5ݱKy6vzmGEǵuZ5k^(뵸ʡm!F>ٓ:8FX[7-oWޤҦyKR SKըooʓ'ɆFRZ?#k뙬r*jj4m]־k͛qv{:qACM۵}}SSUC7t0zֵ*bU;jjE{~bfKcl(/%鏩8׾8ߜIzʊyZ޷`]/KqwIc^{e=HV(L(8FU{@W_{z }{{7Y'ao?N(˔JzFk{_)v8U}+D]hMOHM!_|}5Ϳ-w#'EGs5}J~߳=^xׯ^[b=G Y`'Nqp=jֱ7y/&N뽵D}3rv+A/jۿ}nM \Sӕnj]XIx0L2tNa+fL?pބ7X*$顽?|/1{!kao_w/kmk{~oeyO~6)- K!0n:0w!7Ekt]% ymܵs1wiڧfe)=eq{t Á+ܓ4k<UMeCiOt27}49)`Ub e-bDQtz"\9qZ˙ή40JojLk{a0PY,n6t[<{OcywNv{]O3loMSG'EMF$eD$Jț/ӡ= Ξ5s6E>߳:w9&ϰ~nX3_TPr\G7u NS^#S=$tbZ56] {#!;+s?X/{f{w{(Q2yP_}wPyo>+88$|D}c"K {I_Yg[ғ?/ިa@(gϨmNarw_c~>|I/WfPUl]3yr vх{·=ʷ5-i ute^`u Z`{/z[ t[(qKmϯk*5`fQru3qW8硽_T !75vv:+TӢ1yqVXӔ}SKS$?wN*K(C\e3RE(X 沛iҾ!|('AAW_.qKg?S {N8+#g%qANsp짒{g (",=ezġ'ۥA<}ݟZ|{@4x79}W9ߤ߄WV8$꿿cHާ 4޿Rg~fȞTKZJ$Rf ɍhm/"!QT/ۦ! #ΌuwT|jܦKߒRߨ꛹JUM;S1YNu(kWZȳt~B׾MRaT 7ҲG}mf~|xVɋB\}孱2nzEiQj}JaZ֭HŜfLDWJ4=ދXS45y-Mg]^|/װW=q~|bJ5yf uEcٲ]Lb7 qf1V-1G"DS^$]&>oӧ߸q~|Qxnqz?B^>U<#KD{/Yg.$zBҭFO- <9O!cgZ`%\Þ8?ͪ[W O ?Y܉6}zZ͕>ώ{X1^sMUg&7bv{=y1Y3{'O⸬󥧅$/r,R+:>]sn/*{7ίϵ/ yv+h4{mYr{m#m7˽o㽛kأ^,6NJwr7˸6}՗ˎˢݾ_/n^=/8sm1o^ïb5y3펱z*;/7NH17[Ⱥ*Zz֥1K2[;cM?;^QϞG:ߋkث$\[dg}$q/q{_Ex%o+PQ2?{OY}\y~2-k-Zտ=?ϼN>/jrDҳz}e~Ҫ7Rߍ9]luٱ~/gwk,ߗ;xxYOP}`':\M.{ pi?C5k:IENDB`CMF-1.3/docs/imgs/submitnewsitem.png0100644000076500007650000004070507374605754017404 0ustar tseavertseaverPNG  IHDRPLTENNN33fffB"""7<ff3ff,)6Q7KzNfct[gilЄֹw3f333ffffPPP@CfM IDATx흉b6@dUYQ2$mZN;lj 8yuĖDc3{{{{8ieN7Z)ZWjiܷi>ȖлCŅlY4_ѻKi~ҲcrYoa0^UlpAWw~G~KuCJ;vX'$~aGسy9`;ooo 0]-B1r7]coZn4vNLp9/|-!7ywpa/z~9^U  o'=\8Vزq}L"lao>hJKfӞ=xxg30GFrxpyG.E$಼_Db/qbD+<#RWLUJݻG&%.SحuIMj?_~<.b?*I6/U3pb)e4}>y%rn%}~%2U.,ŗ?=_<"~="l_'ףo/Q1»Z}U2$ LJ ( 1^~Č2p{;l9ND3VODʈ\LV˟zHT᫬y~2\O6,2盫QXUdѝ(onuLըI^wkʊ`I/^:2O^"OT@zƽu.~ߣW_R{s=6?W$JiHJhynnn([2i}DBuQd$ޫD62{ͯ|W״j:CBIߔm\7m^])TPo'~UihxhQM*Yx5Qy\8OiߓvSurR~o49~fI2Q݉H%fu}Qȼ"s'>wj aɋ|ŰϵY^xO⽲;&*r]TU_F/zg~wgL}X3ݾY*+3兔+IQ_~eu 2_aǟ5^5RKb(/'u{>l'Βujmi/_5䕃!WbD@~|]U^Fe);֪mo>/=kGm֯ ޯԳT+Q4'C;Q~orsD`Yug{G1eW?/=o|NoMV^Uq)f1TE}ܟ$_ *FɐYOPU.Wy~?E:~Ãy_#QQHd/s_Uw=E)~}[,+RgsUj'~-V*_iS!š&QW-{y-?GejWh֛( >}bodx_KzUϴ=ev=)'yQiy48^~Rw2ᅩOyަ&WO_*ϵ/u,/x1{Tz*=ɭϴO}g?%e~/YTѣioQ~Eᫎq^-k+պ?|ѷJxx|<[)nDC/ed׋,{`q\A7˗/~iUjO`QYT*"LUoEr~vqq_P{LT̏QY_F*߯LI2A;?miCΪ5/&Q|~ػ≖Y^zV.;U+")_d ] o-H )LQ\(0Nբͣe/TW_QD eps1x#_T*I2bM)#1f xj$|W!gxg+ïrZdehP`T/ %HTQ1~Y~]u$*\uVOtkՏc{g)S^IJB=s'$^S8'q>4DUr>9 /{Cq5d,(\-|sOv>gXwmåZ}[|*GǷυu{}6ޛyYSηht#.jp/`=DZؾLw{˃ϋ {.;]yo~?{T>gQΗZZ;[gE}]ʜt{=_5tm=_מ78թԷ=A{~_^^Kx?u8.K,y^ױEYR!3xxO&> ny|+Bx=\8*r>%z{Y3 \ .ޓrfO}>q|SYS(INx86S=s1G}*'x}0/7y/y;^ɼY8nGQ"ՋrbhzB޷j#a⸬֪]P=ñk\1^5WE_e<:%b}ī#}?] E^SmG`)8͔~wj]66yGu^l|7,+Nl=e2v%uݻ%,a(ʖqi/n(˯QrE!O3472_}Z^[imGK7E1&qb ѭh;&! Cғ{leh5iҡk9ɽQzo_ޫ}٘Y]OZ/unW\MLSrjUVڷΣ#;ZtQUruqh^:I3G2?YRϊU ,C{~UJk_$JgY]2z3+7;7rBU?WҾ;)0ϒ~y&ʕo\;J7˄(^~Xj~Kwdkh0HԞvVBiH3K.I}ޗ+I.d-}yCk `F{o_Ek')=CNHohws} y5t 2| D,3 ;vt/_JD{/."=*J$K;}f)~so6isBK-"=wt{{lgHwto&a2xYu3R\3c {3= G{*3ozoSXEc+!1 8vkv½oYўzW?y;͔f3͓=_>󽎚duMVO{3{c7S9Gy& )Z%<*e{rMz/}7p㤿BUD(/ԯɪ#on,)r?͍lk7_6LlGGgi"g)j$9ˬҬ8D/{tۅ-o{!{/Ed. ɖ}U_gkGߧ~:5\_$C}-'ެVx#|/GrayHЅM^dY~GBd}Au(9̗)3Z}8=NÏ>6}8ʲ|F4xޯNO,_gͧ9̓p x;=OV.?ϑy ~/?r>08 |+=ypO}~<͏O}򩢵Ø,@41[)fszfD-dzV.=̺3C' ̵go9vOLD};Т*2](t%RH'}u}^&\{7 9ou pW Xd B@9ߍo"z[Our:7KW@uYf\ L`_;9׶&8 u]i=[~jdtI<SwFL_Έ[G(24U.cSؤ3rI٣"l lfI۽w1j߽ig8v J6t+zU.dh1}O(Ul5MF)0x o{\(hʽh %{FW~]}*{9;VZk[2]ݳ oB{){z~MpAq *0Ufo)@{L3M@U<שG].l.gRo1Z3+m&C*@ɇ@{~vksk&5߃gԚ>l`]5h_t lq2݃5MuEgh5} ε:hoTG?_{~ۙ_y,{vKsp6r>%z{p {^V럎;XͯWx=ཇ7wwwwwoFR޻?}.ۇ'T{yzo2~ ޛ?|IV$wx/M-e(-6E{.+*3$˯U^Jz!ԁ"\骂3O!~~xh{o%$xrlZ"5.7r_@/J:'yoާ2{wwwe_oaRCާf^磻e.7o>֗yGq1?ߞ.{Նi_߽{5ςDQ>?~pg}wYfy7> T^=åzkB|{㡼O[;ys(Tz,piߕwN~]= ӧ?ͯnsc<3{^xF;zp5g7[׵J߈ϟ?},~}眇wOz/hy`yZ/yp_62?7o2"**?UWig]|x;P`z_x7=Mwov? ۧ*UOKߊdޭ0wU^!~KYNnį T+t70*i7A},ԓcVu^Jw >{U:Rާ>?4?|3T^Jﳬ]&Oi}>FSJnjByrzP㛺>`=mKd>7ڿS~÷-_K~oRP.>||0e^zJo߽ iI/ⳕ@|2ߋw\(~Zg+ =_G*}To?Q{5NDC1N{QFyO~ʮ}SxpHF=o }i3N{2WoE9ݻo^3oߨ~:r>a\Ap#ꨱ32_>3_KwXd}{o2r|8=l{{yyc{gIG~^dYU[x:x=}=^p6jxAǻ{gR=L}q/οto'oyNWs捕ͯB M#V3Nw$:>/{z>7?">MN~@ZP=}*dm8wBpՍSjP? i>fGǢE7?{`/ད{s6-£=e1w_~)?y?o-\>sNhOVOϟ §{#m޼ zGNxJ{h5, IDAT=W+#loߕ-_}zѫx>}ꌒ1>,e{)z_ 3{ĥgZ`\ xxo{qsOV;)CɳYSYow{:ݒ/Gzx x_סip? Yߘk3 x/N.IO<"cR馗>;{? kcI_N`;ߤ;7J '}D'u !O?cK8x,#>ޣ=e~?-xշe{/G{^t_Yq>Ov=H){_v/_V>(Sx_Y1ߙ_VA"?\/{s'Wxx_-*#Z]V)خ8^UUvޭݗ?h-DOj?^5?+EݟS9j<{qzBk+fʐ6Fl͜wMZP/O+G7c8C;;vrjG5O|~hd`/[]m#>ӳw3wr:ϗp>JߡU?{}zo~^`gX^_ﹻ|Q7h -wޗs;kオc+OրݧU?mlysN~I?-=ɭO>G&w ͂vng}߶fX083^} }a]K9_[4}Y?={lp7݁q{{o Vk:yf}mwMA tsw#0x^Zeώ- lIMfyVc ~l.ouJ|bwT}v}:8[G;6^ {mm<6.=sx6{?ot6͓si{'">WeD"5{vx.ޟx;}}H}2ޟ'msޖ po,7}~n;txO~?!n|7ޛeN0ޓ{kTZixcZ ;Վo#_f~ksՖX Ձg/{{S^{Qޟxxxxxxxxxxxxxxxxxxp*mkxLd$p.= T~F{expދµ[!L X 8~e1y{[dӷ>/oVpmN{YS )D:-ӁܟW>Rm_ *m,^Ti >-ۦ(y|k}$ E]x(Sp_.齯ZjekJo5  797o5XE ҽ'I RV[}J)g^One-ٽݼ* po{/`_z㟕} T^XtUis> {{f[R{H0U䴒hiwVт[ \n\`T{)K27uv=9x/ e?o'b>\M_jg=yo.\ ]GExpާ>ųO}S4wxpޫ~UUhſtuP5+(p1ҹZ{g-l{ƥz !Ή{3;va@_3ܫқ{?{-;W]{*aThCd{dd2,9!|~ޟRwV'+ݱ0[9 )[Cx߉RoLrin{a|5ej{琍挩wOVoqgs&^G^6|_{ʾz6^BFS7W!ca']"Rg{h4Oju Nfz< {VP{>Юo]i?5n@]-}?YmBzz4!~'D{nF'/E[Ngzzjh]h/iYkx޼k3T qo!E'CG4N\qG?; 9oZuz1rJ/M ·گ(nӸN%D/߅O{{{=]5~t'}xeo{j-;-&=ͼr.1Ѫwz}Ǝ[ﱾk;+NrZrR:x8﷚vk۵/{cx'#"07x@>m#SAtx/7xH+mMS?;M{{0(a`7M{|)g`*aXk@)ߠXnf]c+sgI{ <4J{G 3a;k7-Z'zR;cyS{o܌ΗHDN,hѢv+D`)oo ˵W\=ZYkǦVCY>=7c\ ~;ߌ"EKRG ;r*=&䨞/陯E^|޳$`/Yks~;xwҬ%=}`Ǎ3rhʫo07rN*$C-v~ ޣv3t{h, ~8zv=Z;WxޛlgsoKk+uZ^C[O>w3ջU zxTy2Xe{TB۽D3OixO轛} ͠YC=cIװ {xoVh)s9 J# ,(rjo^6kk7vBU-3SY2ԞXhi7 U%̨k;'x3{XlLnoGgf;sIv̗Ÿ(oiW[q!n7 7CMDJ~[}ovvҁ}0;R?{y@> <=F]t{#8w{Z`nͧrC*+; S{9/Dw B֖O?=\ȹAtsbM>s`>ӭ51iw`ޜ"9r<.qx_NWҖWz}nߘW[TvV.oZm~۴iZN,k=x_)t=yռ7}f&HsRvzd?_y4ҞGiDpaˌp6fWa,3^'>4C{+?wX(ZmD;fj)ɀ$ϿLzv/z2r(~V.oJ;Rh/|_}1u1o3 `y[UݼއE#x/z/Gz_$ǖ];*M۽fV7ۃyښUZSTgVU]mUT}oVn "U7,vΈwK&qTĭHb, 3y݇Hg*l83^:)fiӜ_>}RL<S}9feAvinyfB 0:^h{- Vl5{,<㽧ߪyy[+[]Giy;]~/f/=1m tziYo gcmc4io\kF }NB{#^?rR,'Aeٍ鿟{#]Ta=޵M=߾{m=6).}W{lo7y ܱQރ|o#̫|!٦Ms쎩ц{>@:gJk`^V=^"x*}z-[Ze^UMxWWWR;{ȽoM.<}4_={{)EF3|{QW-%iǓty=c~m-v}T y>$dj7Cb =ȼx=8`ʻotW=Ex_t=¼O}U,.x_,+u}J?`^wZ:S[`N ˞Sogl3[OSb}ͧxp)ޗ=\)=;4陼=ݜp1^>8c~Vi/=/{Kl8{oऽ{b8!K뿹OU_iNZ[w_4Wy~K"g}oqAj5W};x72N/8ՁZnzO92ߚ}xpާ[- @Uםo:xpRޗ]F^ao}0ic=!VpΟzVNL/]S&ޙcԹxpW^[WΛc/_Gxp&ޗ%{$S$wxpyx;=? y;ȣ;\ :4?֯T?xxxxxmb;Vxpklﺽ>ϻj 5fndMxo^Mr=m;<6;{#`/ܝޠ,q=QywH{zMY{=_~~>u㵼=Qz:Ewg^,{\ ^= yoOai3h[f1_0zG7hK!==\Cb) ,=ez̡X6s7  Ԡ(<;|#fod|3{@$x/7=}W^%6YWWW}{۲Bf녽OBI#_yf"kooX[ZܮD"oQi eDƢP2db-_&+}qOdG7Wov΅V o_]__}d6{SBul`r~j#6z/k?W9yfk{Uh!W}oJ{o9ӗmyѪh_yY)?o3׍z/^~7MFDǺu|^my˾jH` {NV,6܌"A˖SL7Jd=?r+nxroS*6 %p-ܷJ&z#1 P׃7{*#o mLKV({Zr1X68.bCʢ/p_@Q!~eus o@a(9nJ6z5 @,#W`Apz^>0I^cVB-q3AвpA pAr!C!Z$wݘGn8~~?CqNtKܯۭ~v?;'{tf4uwܲbuiMBOܭN{7셛zMhVWܷal=cs]=XV#vOz벟zz~p{*~ߺ]0&;]\Ӈk+6Im&p[毓7ϓMrU6r!]r3){հ~o>. ޻ҿwh˗?EM畺Qgد {ТU1_mW>ߕ?IvG2 )J/'z%U#.qɿmmK |2ܷ9'/Frw ~SOiwT2(զ/ )efmA{Që_H?>y~I^ڦ؏!FJ_?ӓ}r\>=eO/$3EbG>mL!:^_:<>78?aCG+k?Q?c 6R;t9]'~ٮώ_na /_t8oacTIx3N]nJ#q&=oIv{='΂2Dž2G>w &ӷ/v2vGo_t; {d`=uf)x(ܧtg7ڬs+_[௢{WcIWl?d$k`þr w4m_O~%w JHA?9j}:H:.֙{a~L~6I^ևti_=:]_=T?}B)9̵\wPL`ӱ.'S? N̵ܿ.jJ{?€6ig5{`?X܏y#p8ܻUxg=$?rE(Xɶ}z}XwߧRӪ ;J]}?m}jSiޏ1ϰ?Fݑ/-:m/V$gدvf 9%XR'kZoR{spϔD]ܡӑz8bRuu07uVo1TwcaWU{{Hf(oo`vn6}}+c}}|&\{}w? oٰޡ&(O6^ܗϰ/>'|WJf'o$? uE~,Q|Yj;a}rVsIӉ&і=}~}*Ojz æ{YoWY)K"}q"_]dxQր%>~` Tsx 牽PĿ+3{?[O ~t?E¾3v$7ާFI}M{ھnGO:lnا0{ߥTcqMVjk}XtoxޯM/V9+k ԢfHǮW_)&ʒ 6ݐ/iy!5.el""|fiF9|}IQOI&@f}s>NfH~kl6=ql;Ta}(2'ڄ~oD쯢O?ZgzJ~?o3336Ц@=~ 6#POGd GW~>A}J}}]A\//dO`!膸~ݤ~L~>-4SP;Zo7{$'oЋ$uX{$2‹cWوZA7}o|g]݅sr75 oz7zItr.}':pLj. ̽ oՊ5u>$\ׅ"rAt n^?-Z{lǫ}l㾶-)T9s΂*b/Mde?=9YUz,:-|Eʸ2&?UcNs65]3A7,W22Ų p/Ntv""LhI Yd}/$.~:vN{z|Rƈ>s,WٿlȺ̂sxaM#Cf,Tz^&WV;Y;rGs_t﫫X)l[S]I~ғ,S:;O]z1K4~e̪:^籲 66/ ,?}gp}RsM&(r(CwWOl(p 謫k⮰i|'{qxZC&$|6r(m{q#ŵi$(܏}~|UU睻u*W&$],Vr|p?$G#*e~P,sФxC-r!Z6ww݆A7(t{~ܹG ^l=͌Gg $x׷vΏ>{hrS~};^#4y/^:-n߬_xL_4"Kq`jڻWC܋O+4~n~&adex$C#\-כ92~]oO}&6z&Ҕv9}::D~F򚬽{JM4Dw^Q mmsJTR?ņ /ޖM4.?N'ݪ\ilHdځe}HUBh&_o}qϬW|'0~$j#Ύɥowz-$ZICOnv>w+xua=9 xQ)6$6-G lE܇Ggm]' M_l[IesO|E5EMLXv]{Ivw??Q{_W*)|~wXĦut(MMҢö&?lIwMshu)4_$/Zu'vRC{k5uK *rpea{>3R|?dm8-N7uO;IuN_3`u]g?~:`9Lχ Ctq8h!FVEχ=s_}}<zp +ZٺT:;K\zjy?Ps9#RF:8X /y!V˳o£d鹜Qwfc;B:#%zKtGHlG8jx>;@qc!L"?֢.ػPL edTҴK4p 6adͤ15Vy?d[j;u)x$bN%Erouԅ{ͽ8pӊ9 5Br'5gH o5. vU܏Ypo&q&r^_ro>!L{]|,i3/MbM lIG*R J}R<+)gq%h.蟳ZOuPdjeDDqz:̺B+"kWeLlzmޏ Zo]3ϣa[H'}yW ]TΫ-)ܪ*xBxr d]NlڲjujX+&|y,n@ZgZr}`i^zW@ԉ鬬հٲ5ō[~~п_{AfC=QP-7I*xŦdW߷1€vGxPc͸N/\ ~~m'>X#FyɒW*vv!ru%k̭^TKT-*G&XH^U+Vx~Da'+I:gu Zf:\/Tlgn寧H)cLI ]6czjyuHΙ!s< 9jKqa ]Aݹxu~`Cԃ{tܯ={=Å A!ֹGy@mpT. Ap!Z6έ{}v^ CЍs_ܓ'&7o.v=F睺 te={spQ3>6Cb;:=pM2NW^D?!Z|=Č!\SHG cߝyKR6g}{BzD)+>: Qm}vG=:aC()o1ϼ+Y|aW>ޞLnƽ|hܿJ;J_>o ~u=xq{hfgwoj3߼~]8l57Iz߼~sUw}NF]8{hfܿz}E}Ak#%?&?Eכ@wcwߝ,N싞wo~ܠWIr_/=?Vlv"^p??;'׶M6o|~oܫvv!& ܃|1*}atD?#y|&tco8R?ケ]w?gM߿~[C_믿~Ç'h >}߇{[0}o'=ރs=z𙛟bt6MnU^???P|kE}!ӛw&$o%Xm}z su'2nӭ?{G8|bGO_y.Wƾo ů'.*;w|W~9m ~p_oާz^`sڽιzoׯƒvvKkT,~>o$s K.~WN7t=fლmr_{g]jكץ˰ΆFLOI۟6߽7۷ާA=t%SkSs㏵6g75oKzmܧ?WĚ?-JL;vNӬz9swuI8V=hu>S?$ z߾o]{_W<{IOP~7kߥ?͏_Usoӱ|&>:gcwߧhw#u׫No߽ sfM/@|oR'~ Uiw̝u~ou޼Kzއlۇټ<>&S{&KRqp}>k wOGjkqT*/61+i:e~{}:P;w{qKjSo'-BSt# 9Ͻ(񩦺X2<̅^FbqMkvgn ~~wo}Z|_J}mw09݉ƽg 6W'sڲϾo:|X&rAMkoy:q'yD4vF`/}./>|{yE&}o}}LJ;w > _ˏPmkDNc{?};E(,ޛ\Yob}4|_e t}~iGpC~ol{>rs_ܯ7}/iʂg_n~zއH:8pdW,ɨ?ZOlx.XW^?};{^=K~Wgt| \EmH-z 4z1 Dٽ/_|Q7$aN`P[.1e߰Ha3/ߘ}m~Ȇ<{MѬ3Gk6q IDAT'?z^Hdy [_[aNe3?#Gփ`MŒ(q74_MvЍd~]똅m>"oO?nS6,t{ Uo@付WSr'ux/}mÇܫ ?P"q8[ń_Ͽl~vïD?q.3S feQp7faԁc1[o9?{z_~_{Ax,Kϫcp߰u랍={ ~I~~0VwfoW\li=Tr;#r4=[> |}6:!xquƴ6Ŵ`g_=B`俹~"?g9 /^qamˣ?%"}~k^VZ.`_ƛfzǦ!p?+{?UD8{ͻjkk\IWz!\ `!hܓ^{{=A= p!C!{=A pA C!{=A= p!C!Z0-W=A pA C!{=A pA C!{{=Aq?P*V#:U؛hch;)}}CW3H23ZEЀFZ %A徍IpO^\5l5)&|xM7i s}=&=O@O||mKOUឬ@ԸTO{s.<c=EpOj289q__ݯwKȾ%w1rͩ~ťwy~|Fь9L*6ț eл}}Zu s?:A C4{=Asq== p!h,dh !h3-ē=-=|5?{Z<_;e0\'4%5\mC-;w;m:Ts/:ռ^/?B=eI\'/}ukr| |)C\j夝+U`<f}5^iyK>_A~*u pAq֥Ď-Kj%ZҲuһ]n*ԒQl{`Αr;+fjQJ,-d7tq=\_W^Tu<Ů}cx^Zӯ4 7b/UZҧ"p{E;,o˱}s0FpmkIZy&>éA&oD5[N߷t{)2pc;,[,=s ų lobuWZĽ@]p&GqY yݕ*^lK%7OE)ޜ.VF|GsއQ.? OF?5 ~~1GF˽ \!ܛ'l3}7n pOM½MymýlH~~; :sZVxlAM7zsp?YINՕ\PHnkLϯI8Ջ_Cn@3*g&Zy0,ħ4:J_}'%B!g޺!C4CЍp8_"Vo235K=w1Goݛufn= wQ)/~=7л[6sA5lszHU/{s{I=5poe6NBHHhcOάw1]zjٺFN_0 47VRTN?61:28/y'oTwWg"3;}ṔeLdB?u 6PeS2>6D\p]#tZD`* Oze>D.s ?Nd{?T؇Bk{'+Xeę\SP̏tt 3AV/;R4i"{b@*4pg*fېCܢHO=ťBquAHƭ);fH|"v+ubvݡc zpOqC:p?tZ&N6*^Us%ӓ{aw';xF#g/f;օ!o(810 f.ht|_zGrPO*W绦 ":!\|~D~ݢ|X_xp=KYjyS'A\spO G޿6Y,_<玠-w!ƣ!d,2!bO Mb߲WfXmeEzR DTp{g(Љ̙hܟ:~39l?o5y^pWḇ4:O1;k޿}Io DFQ>f$q$Cq-">M@tN(nx[֑8DFy8TτOF/0z[a#s:ˏoqXdMW k~ O T[ᾰAA.ulwCt>-xƽ ؘ> ~׳ -#g_#)qϗK) Ori4=5_̽O\5ӯg>' Lha%s?iD煕ʕdbEy{8qaH/]dƊr73qc^X)Xl<@*g$^ rO^PrX9. ? xU 5x5~[p{Ž|usQV4"+4Wy0㤸'/ +8Ux~"S8V ?4kn{qx TZaŌx7VAXQdrZs&C.!pS3~N!hW/YL-P-qn.6֧L-}W:]{}Z׹>]}C"?q-A^̫q :8[A pA C!7sb7\ ؇jKQElF^0+x3S6 Twb3׫c5bl}{}_G;ȽNU5%OgV#*NY~.צcwv9;sȏ F^09pԙK"fɸ>uk(L @k&v<يC 3ZY\}t}B7ƿ`s3n3땠?~fx&{?..{}㙀 r;Afu Ƚ7k%{y8{o;q=u޾~s~ϝ{7IN3:;"㾸rrK>q=;t2u ,fs=|C~QoJ7,H *kM{Lh#U8NP:o7VؽwgZSv'GNHO ~w+od9rc^ǝ ]{N/ks/vyS?]{\餞{ok C|p Ž(l>r' d{SϷ|\'x"LEXmn'&3M$5٨=<㺣]G;f#? ^}1B Ht}=1q?&X"$HܤNGGr9V%$BOfR 2I'ڗ{Z44Nɟ{>  No '˱glUrߋsi/Q.3枆rOCq?H3U'w[9c aH[#:teRi\.=ϯo,P|0MLJ?9@F4L(>b`./g lUp_5clmc6-7Q*8#"-M`;z^8L]c:44bTg lUsϟ5w'S#[#:8X"ِY}`Qэ $G eEܤK҅B8^#*/5bl|W E#<wFX3s~IĐbT0*0jp=q/'x聭*]{;+~PgӐ}ȟ=c8(YGv[5v #?{p24~`{q'~b8+NF so?M~qcx+p?8U+7h"Ag*Ӂ^RD=5rNޱ 4{>gcs/Mp5?[`XՇEu"/+Ěq+@(TW<=(3\sM5YbhpCޔzfft$#]v)ssۋ둈7w lEl9 Dv̴7fEt CգL˥/НˎܥN#ѵC`3r!bߵE"Lfv{pߢ-\NeGc9ջ&GpoLo\}l4]N~x6,=h~)kgɽg k{ur׽:k~5dI7#Yp_1܋=͕cr`i;7: {jr7c_٢q^$$@9^q m˒t2Ϙ( gĽ{ݪ.:l:iqoq#GRϑe>SʹN:Ct^t: @yԇ@s6q/mZgeX]ʏhe 0#YJ#lF7G`qOckǽ8P\kN70q]{GMLi8T価m r?Łn{*j AmF9,ɽU:QN#{;>٨!s/"Zy.YqD Sx#=^b{6 =4Hy '-$t7"ZM!3b;ɉoy">Yc{1uȉOÅO6ZH0pl^ r=Ns^,i/t껔=o!g޹KXC!܃{}֣/ޟX!hܧ ̫5D:Aո?{=Ep_ !6w{Zz;/0AK~u pA C!lu;e{19h}<纇 & C¸ױ[u@(_uKs_oW >ɃAt2l~5j:IϹ?*OO:AEI=x;>=Vm> h܋Ny!h2۸g[I?fiD=4jm\NS#l=|p}Oˍ|p߉󯙃ԅ;?Iq{,Й?r_Oj3pKUجcu{_o;pϯpYp?k=ԏT O{{h|Eǽi}Xǟ=}O{' ̫5r?++=3>h=F {prx58}\.Cpp}6gSx>ʽO՗yTpKqgAf=q{==o{{&&o{p!p{C=z=!{=A pA C!{=A pA C C!i(OTC;ks±HO˧IzyT[;}MvIDͲz}0wz9g)jb6gVycpvwVX>CiU\E*2+րW_Bs el=̷5v[a?dZxv;po!{r;B4Rϵ}6~ߩTuE˚~ieA3\^^Qb6{=qcNWgs+~+˲$2a7EIDATAk> /|G 8=3puv| G%dB Z6 !qr_~y;{{`Z\}kE{/{Kd%9jבSTEQP0]jJީ2n$Fc8ǚVwV^!A6J;=9d=# 06}́K[RΘ#='eB O?J~iL)f;]Np?)5<q)LJjRO&̛6W3C̴U54>]oy:(V<9˹F yCmλɐyfz3ǃnI rC};@K ?nqмܽɽ;!\MZ> {pA7}=넵rW֍g ku1W"njQSWn{˄2 k e~"7V{"ovc`1a67VX+DUj?TZ=sql//0ZywZk~a}9MkKz<#^X+&˘A ;4;R:?B 5wMMu1{ria=Nƻ2aBbw=p2a@ܛpV$h %D@DZ? #[vܩjY{9ybVnuO&vwڸ'Ĕ4+Nq_q~4kEc1[DZ5Di"'Ľ3'{bVwfS ku~uؗ{'#*+WkE02΍no4 k5sͩDѼbODtVnܓ5赻r570gEEf S'.+NNE_5omrfX+2cP5ڄ|}{oOG=dd%l7(p(pB 2n{$ C(%˘0#3abܻP+Z ht[xً&9a/|{/{/x4y-Ɔ_v452_1#3^g=[b=@_ΏGo A~FW 9!p?w_V `pZYa<{=A pA C!{=A[ދٺyrH!k\@q':jz̒"iB~:ܳ*LtI{gFCqCCL<ܷzX+^D.)Ғ&Ʒ2Rn É}M*%EӄDյ^+v_xč@-6šbϩ"QlΓXg *_N;o\RN_ܳH[|KsHp"%H<1U[HLwOpZˡ_R܃cϹiɪv1øSQ w]aQ;{61{"OGRY{ܫcޑin &.z_ v Ꞽ p?m/KcOM pOި 9gi[2N}%E4!p?-5$Tw9_.UӄFHXTawܟ /{ܟ7mA9{Dn,c;!\MZ> {pA7=A pA C!{=A pA C[}s'Bh&wl޼)H=qNJ+oC p?BF6SZa#x uMSGt-q7ӡ)Ўkso3{:?ǒqJb#ɷuJ{}4mzlׄZřE΅#{]NWX֥ZB,Q{$#d4[˂u2b 1b+&֍βH3'{{'R Vp/DsϼJo+"*^'ˉm߉-[Ctvk{$qd%-٢w|og(^{Sy~$9 $+ݕ؂ꯚkh2jKvFd_U#GjŽX\semg,so5jc[`ݕ&R׎|{ځzևuWA;fu,ge(7Hy侮$8uGLG?x;dWe;{2W+{({׭cZZAŽEx;RƗ9!>S^]dr!hy\잕{sC@~^9WM k=Gn>&Ȝ3nY m #a]'_i4ZTTu]Gm?=C~ø$vH#~Ν&-{= pA C!܃{{=A pA C!f] >:3U'YsPNKGl) wXPkSOM] d)ыYXQˌa,1X27ʧ^؅Rma p߇o#*X~S,(H΢yhs#o?$Kb;I܃ZZ0U.5[/7Gzzp Yȥ0M|#W9_TsA\CfeFHB]-e~%jZ`;/Ds/VH5]:cke<ϫR*MntkTaEf֬{ډ Kdl40?{F9'Uq=\uxL{>6$yI^K!u%X1j61zYj.JLd,53GZq'9:uJ|;Dfu,geF$($7Fo/~ 7^,Gvb L{^'O/6Q\<{t{Q-mpof׀}܏~{$3KVqrϏ^E|<( hǽ=nuJ=g,Y?97{wzJht04?AG#s?KXqS+FUSH~C0~{ґg/gɝ1Jn 7vE 3CXqIύ LYm=<7D[o~D>?{`?wQ!pp`lxTEw~ܣ&B>;!\MZ> {pA7=A pA C!{=A pA CHH]6o~OGm[:X>pc">,LE6 *~Xخ#jq R5=o;Z0ơ1V+ 2|{o1wN"Kh]["oN^؅Rm3)uG10rT"MLrC# r΅ⲷ3NWzY)XF5H/l{p=7<7|!^~l +l5;^D#ȢɌU x,ˋMW"R+U_8ry,AYVoXShr&VI({y i] S-%GB[N-K?@v 28+|nE}rANR 4 p;c}.{WRkeulW]fInΗ{g8/9a z -zWP\WLq, ]Bձ"/qbslX:B!{_B/p/uc땬ۋ, ۸wdr/>7hȫ;7{zsͽ#Pǽ+x;RƗ øvzнbPo>{\;b{gx&ۋܓ=񁓀`~y^F!qVGl<7 =@.`&o<3 i">CTͽvvߢ SEaQ1 h=coy~9fb2O<6w;C̙j}S73ؖ}dEVHdGyuffp9:zc[Pa /ྕ7C~ ~۾._> jBW؃{!r2M wJ CLs{} ~ pARs pArӅ[eA7=A;pA RǁW95(9.t:Ef p!nCP]O&mS}2|m!f~e+J s;ozpmi~r{k,ws{ͽ9ayZorvwlúY[>K'kOs^fܯXao8մ|>/_a} dܯLK{~yT`oUCPo ?/{8lNsf?jnG͸ԕ _&\oܧMM*-ܯ|?tH=(cߪno_~|l: pAݹ;!܃{!Xws@+OP0ՄP!j#IENDB`CMF-1.3/docs/Audiences.stx0100644000076500007650000000153307362665202015303 0ustar tseavertseaverThe Zope Content Managment Framework This is a list of audiences and their needs. Prospective Client and Information Architect Overview of CMF architecture and features Content Author Overview of Content Authoring Basic Authoring Workflow How to Edit Basic Content Types Using Structured Text What is Metadata, how to use it and why to use it Integration with Authoring Tools Presentation Designers Using Skins How to Customize Skins Using Page Templates Integration with Design Tools Site Manager Portal Configuration Using Portal Types Configuring Workflow Using Portal Tools Using the Zope Managment Interface Configuring Authentication Site Developer Scripting Portal Services (workflow, etc.) API Docs for scriptable portal objects/services CMF-1.3/docs/CMFDocumentation.stx0100644000076500007650000022107507360573263016552 0ustar tseavertseaverThe Zope Content Managment Framework Introduction Web sites today have become sophisticated, dynamic experiences that provide powerful services. At the same time, businesses want new ways to leverage brand and engage customers, usually on a very large scale. Yet with the explosion of content on websites, managing the daily flow has become a very expensive, brittle proposition. This is the market that content management systems (CMS) are ideal for. The Content Management Framework (CMF) from Zope Corporation is designed to solve these content management issues by delivering the following benefits: - Superior speed to market for applications and content. - Dramatically decrease website staff workload by safe delegation of content authoring. - Powerful searches using sophisticated organization of content. The CMF is designed as a framework of components for the Zope application server. This approach to content management is "both buy and build", as it delivers an extremely extensible foundation for customization as well as providing useful tools "out of the box". In the CMF worldview, everything is content. This applies to traditional things such as HTML pages. But it also applies to dynamic information such as posts in a threaded discussion or calendar events. It also means that images, dowloadable executables, logic in scripts, etc. are also content. The goal of the CMF is to unify the management of content and apply a suite of services. These services include cataloging, workflow, and syndication. The CMF works hard to allow authors to use common tools such as Adobe GoLive, Dreamweaver and MS Office to produce content by supporting standard protocols such as FTP and WebDAV. Specific tools are not required, however, as everything in CMF can be done using only a Web browser. Note - find more intro material to put here Building and managing a CMF site Initial setup and design of a CMF site is performed by a highly privileged user referred to as the "Site Manager". The Site Manager is responsible for the overall configuration and organization of a CMF site, and sets site-wide policies for things such as security, workflow, metadata and syndication. Creating a CMF site The top-level concept in the CMF is the idea of a "CMF Site". A CMF site is a content-oriented Web site with specific business goals, workflows, collaborations and audiences (content consumers). The "CMF Site" object is used in Zope to represent and manage a CMF Web site. The CMF Site object acts as a container for site components and content, and provides interfaces for configuring the functionality of the site. To create a new CMF Site object, you must be logged into the Zope management interface (ZMI). You must also have the "Add CMF Sites" permission in the Zope Folder where you want to create the new site. From the ZMI, select "CMF Site" from the add list and click the "Add" button. This will bring up the "Add CMF Site" Web form. The elements on the add form are: - **Id** -- The id to be used for the new CMF Site object. This id will appear in urls to the site and its subobjects. The id field is a required field. - **Title** -- The title to be used for the new CMF Site object. The title provides a more human-friendly label for the site object. Providing a title is optional, but recommended. - **Membership Source** -- The source of member information to be used by the new CMF Site. The default for this field is "Create a new user folder in the CMF Site". This option will create a new User Folder in the CMF Site to be used as the source of member data. You may also select "I have an existing user folder and want to use it instead". In this case, the CMF Site will draw its member information from a User Folder that already exists in the Zope object heirarchy above the new CMF Site. - **Description** -- A short description of the site. This description may be made available with syndicated content and may be used by some of the default user interface elements of the site. Providing a description is optional, but recommended. After completing the Web form, click the "Add" button to create the new CMF Site object. After submitting the form, the right frame of the ZMI should contain an administrative "welcome" page of the new CMF site. The welcome page provides links to: - **The site configuration form** -- This form allows you manage sitewide policies and configuration options. This should be your first stop after creating a CMF Site object. - **The management interface** -- The Zope management interface (ZMI) for CMF Site objects provides management-level access to the individual components of the site and provides for more advanced configuration options. - **The site home page** -- The default homepage of the new CMF site. This is what visitors and members of the site will initially see when they access the site through the Web. Now that the basic CMF Site object has been created, you should visit the site configuration form to continue setting up the new site. Configuring a CMF site The "site configuration form" of a CMF Site object provides a simple way to set and change the sitewide configuration options and policies for a CMF site. Theses options include some of the information that was provided when the CMF Site was created (such as site title and description), as well as other options that were given defaults when the CMF site was created. A user logged into the CMF site who has the "Change configuration" permission will see a "Reconfigure site" link in the actions box. Clicking the "Reconfigure site" link will bring up the site configuration form. The configuration options available from the site configuration form are: - **Site 'From' Name** -- The name to be used as the (apparent) sender when the site generates email. The site may generate email to provide information to new members, or to notify members of various events. The default value for this name is 'Site Administrator'. A value for this field is required in order to send mail from the site. - **Site 'From' Address** -- The email address used as the (apparent) return address when the site generates email. The default value for the from address is 'postmaster@localhost'. A value for this field is required in order to send mail from the site. - **SMTP Server** -- The address of the SMTP (outgoing mail) server to be used when the site generates email. The default value for the SMTP server address is 'localhost', which presumes that you have an SMTP server running on the same machine as the Zope software. A valid SMTP server address is required in order to send mail from the site. - **Site Title** -- The title of the site that appears at the top of all site pages (when using the default site skins). Providing a title is optional, but recommended. - **Site Description** -- A short description of the site. This description may be made available with syndicated content and may be used by some of the default user interface elements of the site. Providing a description is optional, but recommended. - **Password Policy** -- The password policy configuration option allows you to choose the way that the site handles passwords when members register with the site. If you select "Generate an email member's initial password" the site will randomly generate an initial password that members must use to log into the site and email that password to the address provided by the member. This option may be preferred if you want to verify a prospective member's email address before granting membership to the site. If you select "Allow members to select their initial password" (the default), the site will allow new members to enter their own password at registration time. After making changes to the site configuration options, click the "Change" button to save the changes. Creating CMF Folders Folders may used in CMF to help organize content. Folders may contain any kind of content object, including subfolders. To create a new Folder at a given place in the site heirarchy, navigate to the place where you want to add the new Folder, then click the "Folder Contents" link in the actions box. This will bring up the "desktop" view of the current Folder, listing the content objects and subfolders. In the desktop view, click the "New..." button. You will now see the "Add Content" form. This form provides a list of the kinds of objects you can add at this location (based on the permissions you have), and descriptions of the available objects. Note that you must have the "Add portal folders" permission to add new Folders to the CMF site. Select "Folder" from the listing, enter an id for the new folder in the "id" field located below the listing of available object, and click the "Add" button to add the new Folder. After submitting the add form, you will be taken to the "desktop" view of the newly created Folder. Configuring allowed content types The site manager of a CMF site can control what types of content may be created by users of the site. The normal way of doing this is to restrict the types of content that can be created in CMF folders, which provide the structure of the site. You may, for example, decide that site users should only be able to create documents, images and subfolders on the site. To implement this policy, visit the CMF Site object in the ZMI and click on the "Types Tool" (named "portal_types") in the contents listing of the site object. Click on the "Contents" tab of the Types Tool to see the listing of available content types for your site. Each type in the Types Tool has a "Properties" page that allows you to configure certain options for that type. Types that are logically "containers" (as CMF folders are), provide an option on the properties page to restrict the types of content that may be created in objects of that type. To make our restriction for CMF Folders, click on the "Folder" link in the content type listing of the Types Tool. This will take you to the Properties view of the CMF Folder type. Among other properties of the type, you will see a field called "Allowed content types". Opposite the field label is a multiple selection list containing each of the types installed on your site. To arrange for folders to only contain documents, images and other subfolders, select Document, Image and Folder from the multiple select list and click the "Save Changes" button. You may use this same approach to restrict the contents of other container types on your site. Creating a CMF Topic One of the ways you can manage the structure of a CMF site is by using CMF Topics. Often a site is comprised of a large amount of content through which visitors are able to navigate. A Topic allows you to create a dynamic view onto the available content enabling visitors to "drill down" into that content. Topic objects define a set of search criteria. Those criteria are applied to the content catalog to produce a virtual list of content that appear "in" that Topic (though that content is not actually physically contained in the Topic object). The search criteria for a topic may be based on any of the data or metadata of your site content. One common and useful piece of metadata that can be used effectively by Topics is "Subject". The "Subject" property is usually configured to allow a set of categories to be associated with a piece of content at the time it is created. If content creators consistently select appropriate categories for content as it is created, a Topic can be used to provide site visitors categorized views of your site. A standard pattern is to create a number of Topics that each correspond to a particular category. Another example of a useful Topic is one which filters your site content by creation or modification date in order to display all recently changed content. To create a new Topic, navigate to the place where you want to add the new Topic, then click the "Folder Contents" link in the actions box. This will bring up the "desktop" view of the current Folder. From the desktop view, click the "New..." button. You will now see the "Add Content" form. This form provides a list of the kinds of objects you can add at this location (based on the permissions you have), and descriptions of the available objects. Note that you must have the "Add portal topics" permission to add Topic objects. Select "Topic" from the listing, enter an id for the new Topic in the "id" field located below the listing of available object, and click the "Add" button to add the new Topic. After submitting the add form, you will be taken to the "Edit Topic" form of the newly created Topic. The Topic edit form allows you to provide some basic information about the Topic: - **Title** -- The title to be used for the Topic. The title provides a more human-friendly label for the Topic and appears in the default pages that display the Topic. Providing a title is optional, but recommended. - **Description** -- A short description of the Topic. This description may be made available with syndicated content and may be used by some of the default user interface elements of the site. Providing a description is optional, but recommended. - **Acquire criteria from parent** -- This checkbox determines whether the search criteria of this topic should be joined with the search criteria of containing Topic objects when constructing the virtual contents list. Selecting this option (the default) allows you to provide progressively narrower drill-down views of your content by building a heirarchy of Topics that refine the searches of higher level Topics. Click the "Change" button to save the changes to the Topic object. After clicking "Change," you will see the default view of your new Topic (the list of content which match the Topic's criteria and the list of the Topic's criteria). Note that since you have not yet defined any criteria, the Topic will match all content objects in the site content catalog. Configuring CMF Topics After you have created a Topic, you need to define search criteria for the Topic so that visitors will see an appropriate virtual contents list when they view the Topic. By default no search criteria are defined, which means that the virtual contents list of the Topic will include all content in the site content catalog. To begin defining the search criteria for a Topic, visit the Topic in your Web browser and click the "Criteria" link in the action box. This will bring up the "Topic Criteria" form. This form is made up of two parts. The top part of the form shows the search criteria already defined for the Topic, and allows you to change or delete existing criteria. The bottom of the form allows you to add new criteria. Let's look at adding new criteria first. At the bottom of the form are two drop-down lists: - **Field Id** -- This field allows you select the content attribute to which the search criterion applies. This drop-down includes all of the standard metadata attributes of content objects, as well as "Searchable Text". If you select "Searchable Text" the criteria will be applied against the full text of content objects on the site. - **Criteria Type** -- The type of the value to search for in the search criterion. Options for this field are: - **String Criterion** -- The value to search for is a string. - **Integer Criteron** -- The value to search for is an integer. This may be used to match against integer content attributes. - **List Criterion** -- Match against the selected attribute using a sequence of (string) values. When using a list criterion, an "OR" search is performed, matching content where the selected field matches any of the specified values. - **Friendly Date** -- Match against the selected attribute using a date rule (such as "less than 10 days old"). This option is useful for providing views of what's new on the site. This criterion type may only be applied against date typed content attributes (such as 'created', 'modified' or 'effective'). Once you have selected the field id and criterion type, click the "Add" button to add the search criterion. The new criterion will now appear at the top of the form along with any other criteria that you have already added. After you have added a criterion, you can provide the value or values to be used in the search in the "Value" field of the criterion. For string, criterion, simply enter the value to search for in the text box provided. For integer criterion, you may enter a value as well as select a modifier (minimum, maximum, or both, meaning exact match). For list criterion, enter each of the values to search for (one per line) in the textbox for that criterion. For "friendly date" criterion, compose the appropriate search rule using the drop-down lists provided. Remember that the friendly date search constrains matches based on this rule relative to the time the search is performed. For example, if you specify "At the most: 5 days old" for a date criterion against the "modified" attribute of content objects, the Topic will always contain those content objects modified within the last five days, measuring from the time the Topic is being viewed. You may define any number of criteria for a Topic, which are applied together (as an "AND" search) to constrain the virtual contents of the Topic. Remember that if you selected "Acquire criteria from parent" on the edit form for the Topic, the criteria of your Topic will also be ANDed with the criteria of containing Topic to produce the contents. When you are finished adding or changing the values for your defined criteria, click the "Save Changes" button to save your changes. If you want to remove one or more criteria, select the checkbox to the left of the criteria you wish to delete and click the "Delete selected" button. Configuring security policies Access to CMF content objects can be "public" (unrestricted) or restricted to particular users based on permission settings. CMF objects use Zope's flexible mechanisms for defining security policies, which allows you to provide powerful features to your users and allow large groups of people to safely work together to maintain your site. You must have the "View management screens" permission as well as the "Manage permissions" permission to manage security for CMF objects. You need the "View management screens" permission because the CMF does not provide a specific interface to security information. You use the Zope management interface (ZMI) to control access to CMF objects. To access the ZMI for a CMF object, visit the url of the object with the string '/manage_workspace' appended in your Web browser. For example, to view the ZMI for a content object at the url: '/Sports/TopStories.html', you would visit: '/Sports/TopStories.html/manage_workspace' in your browser. This will bring up the standard tabbed Zope interface. Select the "Security" tab to view and modify the security settings for the object. Managing security and using the ZMI to set and change security policies is covered in depth in the standard Zope documentation in "Chapter 6: Users and Security", http://www.zope.org/Members/michel/ZB/Security.dtml . Associating workflow with content types Different types of content may be published according to different business rules and processes on a CMF site. In the CMF, Workflow objects represent the different sets of processes applied to different types of content. Workflow objects are defined by workflow designers in the CMF Workflow Tool. Once workflows have been defined in the Workflow Tool, a site manager can then associate one or more of those workflows with particular CMF content types. The CMF comes with a default workflow that is associated with all of the built-in content types "out of the box". The default workflow is a simple review / publish workflow that requires a review to check content before it is made generally visible on the site. To change the workflow associations among the types of content on your site, visit the Zope management interface of the CMF Site object that represents your site. The "Contents" view a CMF Site object lists a number of "tool" objects, including the Workflow Tool which is named "portal_workflow" in the contents listing. Click on the "portal_workflow" tool to navigate the Workflow Tool. Now you should be looking at the "Overview" tab of the Workflow Tool. This page provides a basic description of the tool. To view the current set of associations between workflows and content types, click the "Workflows" tab. This will bring up a form with two columns. On the left are listed the content types in use on your site. On the right of each is a textbox containing the name or names of the workflows associated with that content type. To change the workflow association for a content type, type the name of the workflow to use in the textbox and click the "Change" button. If you wish to specify multiple workflows for a particular content type, type the names of the workflows to use in the textbox, separated by commas. If you need to find out the names of the workflows that are currently defined in the Workflow Tools, click on the "Contents" tab of the tools, which will list the available Workflow objects. If you have never changed the workflow associations for your content types, the value will be the special value "(Default)". Note that there is also an entry "(Default)" at the bottom of the left-hand column listing the available content types. This is a convenience that lets you easily change the "default" workflow to be used without having to update the value for every content type individually. The content types whose value is '(Default)' will use the workflow named by the '(Default)' item at the bottom of the form. After you have finished changing workflow operations, you must click the "Update security settings" button at the bottom of the workflow mapping form. Workflow often has side effects on the security settings of the objects they are associated with that need to be reconciled when the workflow mappings change. Doing the reconciliation is a potentially time-consuming operation, so rather than slow down the process of re-mapping workflow by doing it for each item changed, you click "Update security settings" when you are done making changes to the workflow associations. Configuring metadata policies Content objects in the CMF support rich metadata. The CMF supports the Dublic Core metadata standard, and instances of all of the builtin CMF content types are associated with Dublin Core metadata. Content objects provide Web forms that allow content creators to enter and maintain metadata for their content. As a site manager, the CMF also gives some metadata configuration options to make the use of metadata more effective for your site. To change the metadata policies for the content types used on your site, visit the Zope management interface of the CMF Site object that represents your site. From the "Contents" view of the site object, click on the "portal_metadata" tool to navigate the Metadata Tool. Now you should be looking at the "Overview" tab of the Metadata Tool. This page provides a basic description of the tool. To view the current metadata policies for your content types, click the "Elements" tab. This will bring up the metadata policies form. The top row of the form (labeled "Element"), is a set of hyperlinks for each metadata element that is available to be configured. This list includes "Description", "Format", "Language", "Rights", "Subject" and "Title" on a normal CMF site. This top row allows you to select which metadata element you are configuring. The metadata element that is *not* a hyperlink is the one that you are currently working on. To work on a different metadata element, click on the name of that element. When you first visit the metadata policies form, the first metadata element in the top row ("Description") will be selected. The rest of the form is divided into sections. The sections represent the current settings for the selected metadata element for different content types in the system. If you have never changed the settings for a metadata element, you will see two sections. In the first section, the name of the content type is '', which means this setting will apply to all content types for which you have not defined explicit settings for the currently selected metadata element. At the bottom of the form, the last section (headed "") allows you to add settings for a specific content type. To add settings for a content type, select the content type that your settings will apply to from the "Content Type" dropdown list. The rest of the fields in each section allow you to actually set the policy for the selected metadata element: - **Required** -- This determines whether content creators are required to supply a value for the selected metadata element. Check the box to require a value. - **Default** -- The default value to be used for the metadata element. - **Supply Default** -- This option determines whether the edit forms for content should pre-fill the form field for the metadata element with the defined default value. - **Vocabulary** -- The vocabulary option allows you to restrict the possible values for a metadata element to a set of predefined options. If a vocabulary is defined for a metadata element, the content edit forms will show a drop-down box with the defined vocabulary items rather than allow free text entry. To supply the vocabulary for a metadata element, enter the possible values to be displayed to content authors (one per line) in the textbox provided. - **Enforce Vocabulary** - This option determines whether the allowed value for the metadata element is restricted to those that are defined in the vocabulary. Once you have finished choosing the settings, click the "Add" button to save your settings for that content type. You may also edit the settings that you have already defined for content types. Make the changes in the sections for each content type you want to change and click the "Update" button to save these changes. Configuring syndication policies Syndication is the process by which a site is able to share information with other sites. The AP news service, for example, allows newspapers and other media to recieve news stories which they can publish with their own presentation without actually generating the content in-house. Content syndication in the CMF allows you to make content available to other sites. The Syndication Tool allows site managers to control sitewide syndication of content. Syndicated content is made available in RSS format for folders where syndication has been enabled. The DTML Method objects that control the RSS formatting for RSS feeds are located in the "generic" skin: - RSS.dtml - itemRSS.dtml - rssBody.dtml - rssDisabled Advanced users with a knowledge of the RSS format may edit these DTML Methods to customize the RSS output. Before content can be syndicated from a site, the site manager must enable syndication using the Syndication Tool. To access this tool, visit the Zope management interface of the CMF Site object that represents your site. From the "Contents" view of the site object, click on the "portal_syndication" tool to navigate to the Syndication Tool. Now you should be looking at the "Overview" tab of the Syndication Tool. This page provides a basic description of the tool. To view the current syndication policy for the site, click the "Properties" tab. If you have never visited the Syndication Tool before, syndication is disabled for the site and you will see a single button "Enable syndication". To enable syndication, click the "Enable syndication" button. Once you have enabled syndication, the "Properties" form will display the sitewide syndication properties: - **Update Period** -- Describes the period over which the channel feed is updated. Acceptable values are: hourly, daily, weekly, monthly, yearly. If omitted, daily is assumed. - **Update Frequency** -- Used to describe the frequency of updates in relation to the update period. A positive integer indicates how many times in that period the channel is updated. For example, an updatePeriod of daily, and an updateFrequency of 2 indicates the channel format is updated twice daily. If omitted a value of 1 is assumed. - **Update Base** -- Defines a base date to be used in concert with updatePeriod and updateFrequency to calculate the publishing schedule. By default the sitewide date is the DateTime of the tool initialization. The date format should be of the form: 'yyyy-mm-ddThh:mm'. - **Max Items** -- Defines the max number of items which are included in the syndication feed. The RSS specification recommends this not exceed 15, which is the default. Click "Save" to save your changes to the sitewide syndication policy. If you later decide you want to disable content syndication, you can visit the "Properties" tab of the Syndication Tool and click the "Disable syndication" button. Once syndication has been enabled in the Syndication Tool, CMF folders will be syndicateable. A new "Syndication" action will become available in the actions box of folders. You can click on the "Syndication" link in the actions box of a folder to enable syndication for that Folder. For example, let's say you have a '/news' folder on your site containing various News Items. After sitewide syndication has been enabled, you can enable syndication for the '/news' folder using the "Syndication" link in the action box. After that, you can visit the url to your news folder and append 'RSS' to obtain the RSS feed: 'http://www.example.com/news/RSS'. Configuring discussion policies Most content objects in the CMF support "discussions" which allow site visitors to post comments and feedback to site content. When discussion is enabled for a content type, members of the site will see a "Reply" link in the actions box when they view a piece of discussable content. Members can click the "Reply" link to add a Discussion Item to the content. A Discussion Item is a document that is a reply to other content. The Discussion Tool (found the contents view of your CMF Site object in the ZMI) actually implements discussability for content types on the site, but in this case you do not visit the Discussion Tool directly to enable discussion of content. Instead, you use the "Types Tool" (also found in the contents view of the CMF Site object in the ZMI) to enable or disable discussability. This allows to set discussion policies so that only certain types of content allow discussion. To enable discussion for a content type, navigate to the Types Tool in the ZMI. Select the "Contents" tab, which will list the content types defined for your site. Next, click on the name of the content type in the listing that you want to configure. This will bring up the Type Information form for that content type. At the bottom of the form is an "Allow Discussion?" checkbox. To enable discussion for the selected content type, check the box and click "Save Changes". To disable discussion, deselect the box and click "Save Changes". Managing site membership Visitors that have registered with a CMF site and received a user name and password are referred to as "Members" of the site. Members are known by the system and often have higher privileges than unknown visitors to the site. Depending on the site configuration, members may be able to contribute their own content for review and publication, comment on existing site content or help in the reviewing and publication of content for a sub-web or community. Adding members to the site [ Not yet done ] Changing member information **Actor**: Membership Manager - Roles on user - Local Roles on Folder Browsing the member roster [ Not yet done ] Revoking membership [ Not yet done ] Designing the user interface for your site The CMF provides powerful tools for customizing the look and feel of the content of your site. "Skins" are sets of templates and script objects that work together to provide the presentation of site content. Changing site appearance [ Not yet done ] Changing site behavior [ Not yet done ] Changing the default skin [ Not yet done ] Creating new skins [ Not yet done ] Implementing workflows for content Content objects in the CMF can be associated with Workflow objects. Workflow objects define the rules for how content is produced, reviewed and ultimately published. Creating new workflows To create a new workflow, visit the 'Contents' tab of your portal_workflow tool and click 'Add Workflow'. Enter an ID and choose from the list of workflow types. If this is the first time you're setting up a workflow, choose "Web-configurable workflow [Classic]" to get a workflow like the default workflow. Then press the "Add" button. Modifying existing workflows You must have management privileges to change workflows. Creating content The CMF enables members to create content for a site, subject to the security and workflow policies that have been put in place. Different members may be able to create different types of content, based on their roles in the system. Viewing personal content Many times content creators will be required to revist content items for updating the content or performing some other series of editing tasks. The following steps walk you through searching for all of the content in the CMF you have created. First, click the 'search' link in the top navigation bar. The 'search' link takes you to the advanced search page from which you will perform your query to obtain a list of content you have created. Second, enter your login name in the form field labled *Creator*. Make sure you spell it exactly as you type it when you login to the CMF. Third, click the search button at the bottom of the page. All of the content you have personally created will be returned in a list from which you can browse and view your content. Creating content objects CMF Sites are comprised of content; to add content to your CMF site you will need to create content objects. There are several stock content objects which come with the CMF. The stock content types are Document, Image, File, Link, and Favorites. To add a content object in the CMF, you will need to navigate to a location with the site you have the appropriate privelages to create content. This could range from a specific folder in your CMF which has been configured by your CMF administrator or by selecting the 'My Stuff' link from your navigation bar. Once you have identified the location you wish to add your content object, select the 'Folder contents' link from the action's menu on the left side of your browser window. You will be presented with the content add form. The content add form provides a list of the content types you have permission to add at this location within the CMF. For this example, we are going to use a Document as our sample content type to add. Select Document from the list, and enter an ID for the document in the form field labled **ID** at the bottom of the content add form. Click the Add button to complete the first stage of creating your document object. After submitting the add form, you will proceed to the 'Standard Resource Metadata' edit form. This metadata form is common across all of the default content types which come stock with the CMF. This form is where you will enter basic metadata about your new content type. - Title -- A string used to identify your content; a title provides a more human-friendly lable for the document. The title displays in the view of the document, as well as various other places throughout the CMF (e.g. search results). - Description -- A short summary of the content. - Subject -- A list of keywords which is used in cataloging your content. After entering the title, your content's description, and a subject or list of subjects, you have 3 options to proceed: - Change - Commit your changes and return to the 'Standard Resource Metadata' form. - Change and Edit - Commit your changes and proceed to the edit form for adding the "body" of your content. The edit form is not shared across the different content types and will be discussed in more detail below. - Change and View - Commit your changes and proceed to viewing your new piece of content. **Note** although there are 3 options to proceed, to complete adding your content, the logical processions is selecting 'Change and Edit' from the 'Standard Resource Metadata' form. After selecting 'Change and Edit', you proceed onto the edit form for your document. The docuemtn edit form consists of 2 options. The first option, 'File Upload', allows you to author your content offline and upload it to the CMF through your web browser. The second option consists of a 'Body' textarea form field. Here you are able to edit and add your content through your web browser. The following list is a short description of the stock content type edit forms: - 'Body Textarea/File Upload' edit form (Documents) - 'Lead-in/Body' edit form (News Item) - 'File Upload' edit form (Files, Images) - 'Simple' edit form (Link, Favorite, Event) After you fillout the edit form, select change the change button. This concludes creating a content object in the CMF. Depending on your site's buisness rules, you could be required to submit your new content through a workflow or series of workflows. Managing content metadata The metadata is an integral part of a content object. For example, metadata can allow you to ensure users can locate it via the advanced searching mechanism of the CMF. It also allows you to set a content object's effective date range, so that the content only shows on your site during it's effective date range. As described below, you will see there are important facets to managing your content's metadata and keeping it updated. To edit a content item's metadata you will need to navigate to the content item and view it. After viewing it, you will click the 'Metadata' link from the actions box. After clicking the 'Metadata' link, you will proceed to the 'Standard Resource Metadata' form we discussed earlier in describing how to create a content object. On the 'Standard Resource Metadata' form, you will click the 'Edit all metadata' link on the upper right side of this form. After clicking the 'Edit all metadata' link, you will proceed to the 'Full Resource Metadata' form from which you have the opportunity to edit all the content item's metadata. You will recognize several fields on the form from the 'Standard Resource Metadata' form (i.e. Title, Description, and Subject), but there are also 6 new items which we will discuss below. The possible metadata elements you are able to modify are as follows: - Title -- A string used to identify your content; a title provides a more human-friendly lable for the document. The title displays in the view of the document, as well as various other places throughout the CMF (e.g. search results). - Description -- A short summary, an abstract, or a table-of-contents for your content item. - Subject -- A list of keywords which is used in cataloging your content. - Contributors -- Used to convey others besides the Creator who have contributed to the content. - Effective Date -- The date when the content is effective to be publically displayed on the CMF site. It this is left None, the content is considered to be always effective. - Expiration Date -- The date when the content is to expire from being publically displayed on the CMF site. It defaults to an extreme date in the future to ensure it is by default, never expired. - Format -- The kind of physical representation of the content item, for example, 'text/html'. - Language -- An abbreviation of the language the content item is wirtten in, for example, 'en', 'de, etc... - Rights -- A copyright and other IP information related to the content item. After you have made the necessary updates for your content item, you next click the Change button. After clicking the Change button, you proceed to the 'Standard Resource Metadata' form with a notification message your changes have been sucessfull(1). **Notes:** 1. This seems like a strange procession, we should most likely redirect to the full_metadata_editform rather then the short edit form. Updating content After you have performed your site query to obtain a list of content you have created, you are returned a list from which you can view and browse all the content you have created in the CMF. Once you have identified which piece of content you wish to update, click on it's title from the list and navigate to it. After selecting the piece of content you wish to update, you are taken to the 'view' of the content. From the view, select 'Edit' from the actions box. The 'Edit' link will take you to the edit form for your particular piece of content you wish to edit. For this example, we will be using a document as our sample, but the following list gives a short description of the edit forms for the stock CMF content objects you may be editing: - 'Body Textarea/File Upload' edit form (Documents) - 'Lead-in/Body' edit form (News Item) - 'File Upload' edit form (Files, Images) - 'Simple' edit form (Link, Favorite, Event) After you click the 'Edit' link from the actions box, you will proceed to the document edit form. As when you created your document, this edit form gives your two possible options for editing your content. If you have made your changes to the content in an editor on your local machine, select the file upload functionality. Click the browse button and navigate to the place you edited it on your local machine. Select the updated content and select ok. You can also choose to make your changes to the content in your web browser through the web by using the textarea form. To make your changes through the web, find the area(s) in your document which require editing in the textarea. Enter the changes you wish to make to the content and click the change button. Your changes will be saved, and a change notification will be displayed on the edit form confirming your changes have succeeded. If you wish to view your changes, click the 'View' link from the actions box and view your content with the changes in place. You can repeat the above steps as necessary to update your content, each change happening live to what you will see on your CMF site. Undoing changes If you have modified or removed content and realize you have made an error, you have the opportunity to undo your transaction. There are some cases which make content objects undoable, but for this example we are going to assume no transactions have made the change you wish to undo undoable. We will assume you immediately realized you updated some content accidentally and wish to return it to it's premodified state. To undo an action, select 'Undo' from your actions box. You will proceed to the undo form which lists the transaction you wish to undo. Select the checkbox next to the transaction which you wish to undo (e.g. manage_edit or manage_delete). To complete the undo, click the undo button. You will redirect to the undo list with a notification your change has been undone. In the case your transaction is undoable, you will recieve a notification message indicating so. As mentioned above, it's possible that a transaction is undoable. This results from other transactions effecting the object you are trying to undo; in which case you must also undo the transactions prior to the one you are attempting to undo. Removing content Often times it's necessary to remove content from your site; either when it's obsolete or no longer needed. To remove content from the CMF, you will need to identify the piece of content you wish to remove from the CMF. You can do this by searching for the content, navigating to the content directly or navigating to the folder which contains your content directly. If you directly navigate to the content, select the 'Folder contents' link from your actions box. Content is batched in the folder contents view, so if you have more then the default number of items in a folder, you might need to browse through the folder's contents to find the item you wish to remove from the CMF. To do this, use the 'Next' and 'Previous' links at the bottom of the folder contents listing. After identifying the the piece of content you wish to remove and select the check box next to it's ID. To remove the selected content, click the delete button at the bottom of the folder contents view form. After clicking the delete button, the content has been deleted from the CMF. You will be redirected to the folder contents listing with a notification message your content removal has been successful. Publishing content After you have created your content for your web site, you need to submit it through the publication workflow to allow site visitors to see it. To submit your content for publication, navigate to the piece of content you wish to submit for publication. This may be down by selecting 'Folder contents' from your actions box or by directly navigating to it by putting it's URL into your web browser. Once you have selected the content you wish to submit for publication, select the 'Submit' link from your actions box. The submit action is visible while you are viewing a piece of content you have the appropriate permissions to submit for review. After you have selected the 'Submit' link, you will proceed to the 'Content Submit' form where you enter comments regarding the content you wish to submit for publication. After entering any relevant comments, select the 'Submit' button. Your content has now been submitted for publication. Your content is now in the 'Pending' state. Site reviewers will be notified your content is pending review and have the opportunity to publish it live on your website or reject it. In this state, you also have the ability to 'Retract' if for further editing, after which you can follow the above process to re-submit it for publication. Controlling access to content If you find that you require to collaborate with other content contributors, you have the ability to give them roles within a folder you own. By modifying a users local roles allows them to add, update, and contribute content with any number of other CMF content contributors in that given location, without having to give them permissions in other areas of the CMF you'd prefer them not to. To give another content contributor local roles, navigate to a folder where you wish to give them local roles. You can also navigate to a content item and select the 'Folder contents' link from the actions box. From the folder contents form, you select 'Set local roles' from the actions box. The 'Set local roles' action takes you to the manage local roles form; from which you can add or delete users who have additional roles in this location. To add a user to have additional local roles in this location, enter the name or email address of the user which you wish to assign local roles in the folder into the form field labled **search term**. Make sure you have selected the appropriate drop down which corresponds to the search term you have entered (i.e. if you choose to search for a user with their email, make sure you select 'Email Address' from the **Search by** drop down). By clikcing the Search button, you will be returned a list of user's matching your query. Select the checkbox corresponding to the user(s) from the list returned by your search. Next, Select the role you'd like to give them from the drop down menu labled **Role to Assign**. Click Assign Roles button to complete the process of adding the local roles to the user(s) you selected from your search query. You will be returned to the manage local roles form with a notification indicating the user(s) have now been added the roles you've choosen to assign. Reviewing content "Reviewers" are members of the site who are responsible for ensuring the quality of site content. Members with review responsibility participate in the workflow of content publishing by checking and approving (or rejecting) the content for publication. Publication of content makes it visible to all visitors of the site (subject to any security setting made on the content). Browsing content submitted for review If you are tasked with reviewing content submitted for publication from your site's content contributors, you are easily notified when you need to browse the items which are pending review. If items are in the pending state, you will see a link in your actions box, indicating items are pending review. To browse the content which has been submitted for review, select the link which is displayed in your actions box similiarly to the following example: 'Pending review (10)'. In this example, it indicates there are 10 item awaiting review in the pending queue. After clicking the link, you proceed to the pending review queue which lists all the content which is awaiting publishing. You can browse the content from this list. The title, content type, creation date, and a short description is displayed for each item in the pending review queue. From this list, you are able to browse directly to the content by clicking on it's title from the results query. Approving content for publication After you've browsed a list of content items pending review, you can browse directly to reviewing a piece of content by selecting the item from the list of content pending review. After reviewing the content, if you determine you wish to publish it live on your CMF site, from the actions box, select the 'Publish' link. Selecting the 'Publish' link takes you to the content publish form. Here you have the option of entering comments which are attributed to the your publishing of the content. Enter comments as appropriate and select the Publish this item button. After selecting the Publish this item button, you are redirected the 'View' of the content object with a notification it's status has now been changed. If you have additional items requiring review, you can repeat the process until you no longer have items in the review queue. Rejecting content [ Not yet done ] Using the default CMF site The CMF provides an "out-of-the-box" Web site that provides many commonly needed site features automatically. The default site provides a basic site skin for common look and feel, member registration and login management and tools to perform full-text and fielded searches on site content. Becoming a site member Becoming a member of a site allows you to access the additional services of the site. Often this includes a personal online work area (your "desktop"), the ability create and submit your own content for publication and the ability to personalize the look and behavior of the site to better meet your needs. Note that different sites have different purposes, and the specific services available to site members depends on the choices of the site administrators. By default, a CMF site provides members with a private "desktop", the ability to create certain types of basic content and the ability to select the visual style of the site that they see. To become a member of a site, visit the site homepage and click on the "Join" link in the menu located on the left side of the page. Clicking the "Join" link will take you to a form. Complete the fields on the form and click the "Register" button to become a registered member of the site. The exact information required by the registration form will vary from site to site. The form for a default CMF site requires visitors to provide at least a login name, a password and a valid email address to become a member. The default form also gives you an option to have the password you provided at registration time emailed to you for future reference. After submitting the member registration form, you should see a page informing you that you have successfully been registered as a site member. This page also provides a link that you can use to log into the site immediately. Logging in to the site Visitors to a site who have registered as members must login to the site to use member-only services. To login to the site, visit the homepage of the site and click the "Log in" link on the menu (located at the left of the page in a default CMF site). The "Log in" link will take you to a form where you may enter your username and password for the site. You may also select the "remember by name" checkbox and the site will fill in your username on the form for you the next time you login. Once you have entered your name and password, click the "Login" button to login to the site. You should then see a message letting you know that you have been successfully logged in. If you did not type your username or password correctly, you will see a page telling you that the login did not succeed. The site homepage You can browse the homepage of the CMF via 2 access methods. - Directly entering the site's homepage url. - Selecting the 'home' link from the top navigation bar. After you have accessed the site's homepage, you can browse the content published there (e.g. 10 most recent News Announcements, etc...) Personalization options There are times when you are required to change your preferences for your account within a CMF site, for example your email address has changed or you want to try out the new nifty site skin that has just been announced. To change your preferences, click the 'Preferences' link in your top user actions bar. This link takes you to the personalization form. On the personalization form, you have the option of modifying the any of the following: - Email address -- Your contact email address the site administrator will use in contacting you when necessary. - Listing status (off/on) -- Determines if you're login name is visible to other members when they select the 'members' link from the navigation bar or view the site roster. - Skin -- The 'look and feel' skin which is applied around the content of the site. The skin effects your 'view' while navigating the site. While viewing the personalization form, make the necessary adjustments to your e-mail, listing status, and/or skin preferences and select the change button. You will return to the personalization form with a notification indicating your preferences have been changed sucessfully. Searching site content To find content on a CMF site, you often need to conduct a search query to return a list of content items which meet a group of search conditions to narrow down the returned search results. To search the site for information, you have 2 different options. - Quick Search - Advanced Search If you choose to search the site via the quick search feature, enter the keyword (s) in the search text box in your top navigation bar and select the go button. This allows you to search the content cataloged on the site only via the keywords you've choosen to enter into the quick search field. After selecting the go button, you proceed to the search results list which displays the results of your query. The advanced search form is discussed in more detail next. If you would like to further parameterize your search or strictly limit your search to only content of a spefic type, you will want to use the advanced search capabilities of the CMF. To access the advanced search form, click the 'search' link in the top navigation bar. This will take you to the advanced search form. Enter your search criteria and select the search button. The advanced search form allows you to search for content based upon any combination of the following: - Full text search -- The full text search searches for your keyword(s) in the fulltext of the content item, for example the entire body of a document. - Title -- The title searches for your keyword(s) only in the title of a content items. - Subject -- The subject search locates content items which match your keyword(s) found in the subject of content items. - Description -- The description search locates content items which match your keyword(s) found in the description of content items. - Confined date range for new items -- This date range search allows you to only return content items which are new since a specific period; for example only search for items which have been created in the past month. - Type of content you are interestded in -- Searching by content type allows you to only search for content which you are interested in. For example, you might only wish to view News Items which have the word 'convention' mentioned in their description. - Creator -- Searching by creator allows you to narrow your search down to only content items created by the content creator id you enter into this field. After defining the parameters for your search, click the Search button. You will proceed to the search results, listing those content objects found which meet the conditions of your search. From the search results page, you can browse content on the site by title, description, type and the date the content item was last modified. It's possible for your search query to be too restrictive and no content is found which meets your search conditions. You might wish to return to the advanced search form and alter your query parameters to be less restrictive. Browsing topics [ Not yet done ] Providing feedback on content There are various reasons why you might wish to comment on a piece of content posted on a CMF site. You might wish to contribute content, ask a question, or engage in a discussion with other users who visit the CMF site. To provide feedback on a piece of content, you must first navigate to the item you wish to comment upon. After viewing the content and you decide you wish to provide a comment on it, select the 'Reply' link from the actions box. After selecting the 'Reply' link, you will proceed to the feedback form for the content you have choosen to provide feedback upon. Next, you need to enter your comment into the textarea labled **Reply body**. After entering your comment or reply, you can either select the 'preview' or 'reply' button to proceed. If you click the reply button, your comment is appended to the content at the bottom. If you click the preview button, you are able to preview your comment before committing it. After previewing your comment, you can click the review button to commit your comment to the content item or click the edit button to edit your comment. After editing your comment, you can then go through the preview process again by clicking the preview button or commit it by clicking the reply button. Advanced customization The CMF provides many ways for site developers to significantly customize or extend a CMF-based site. The current version of this document does not go into detail on advanced development topics. Advanced developers should visit the "CMF development site", http://cmf.zope.org for more information on resources to help site developers with advanced customization. Glossary **Site Manager** -- The actor responsible for implementing site policies such as security, workflow associations, metadata and syndication policies. The Site Manager is also responsible for the overall organizational structure of the site. **Membership Manager** -- The actor responsible for managing who has access to a site (particularly back-end line of business users), and controls the privileges and properties of users. **Site Developer** -- The actor responsible for implementing new functionality for a site and making changes to existing site capabilities. This is a "programmer" type of role, and users acting the Site Developer capacity are technical people. **Add-on Developer** -- The actor responsible for implementing new functionality that is suitable for distribution to one or more sites. ** **Site Designer** -- The Site Designer is responsible for producing and maintaining the "look and feel" of a site. This includes graphics, layout, navigation and other human factors. **Workflow Designer** -- The Workflow Designer is responsible for defining new workflows and customizing existing workflows to meet business goals. **Content Creator** -- Content Creators are responsible for producing and maintaining the actual content of a site. **Reviewer** -- The actor responsible for ensuring the quality and correctness of site content. **Site Visitor** -- A Site Visitor is an "end user" of the site. The visitor may or may not have an identity known to the system. Visitors with a known identity are referred to as "Members" of the site, and often can do more on a site than visitors without a known identity ("Guests"). Member visitors often have a participatory role on the site. Site Visitors have some general goals that are applicable to most sites, but many of the specific goals and expectations of Site Visitors are dependent upon the specific CMF site. CMF-1.3/docs/Configuration.stx0100644000076500007650000000224407404017310016176 0ustar tseavertseaverChapter 5: Configuring and Customizing the CMF Services - Overview of services - they are customizable and configurable. The following sections describe each service, including it purpose and use. Site Configuration XXX Portal configuration stuff Controlling Look and Feel Skins (how to configure skins and override look and feel elements), DTML, Page Templates, etc. Workflow Configuring workflow states, transitions, actions using DCWorkflow. This section should be large and should show example workflows. Search cataloging, building search forms, searching public and private content Membership (using various authentication sources, managing users and their preferences, user data schemas) Discussion XXX It's unclear how to configure this, the tool has no controls and there are no permissions related to discussion. Security Setting local roles. Also basic Zope security info. permissions needed to add various types of content and other objects. Syndication XXX It's unclear how this works Topics creating and managing topics. Conclusion XXXCMF-1.3/docs/Content.stx0100644000076500007650000002353607404017310015010 0ustar tseavertseaverChapter 4: Content As its name suggests, the central purpose of the CMF is managing content. In this chapter you'll learn about how you can create, manage content using the CMF. Content Types As you learned in the last chapter, Zope treats content as "objects" such as Documents, Files, Images, etc. Different types of content objects are known as different content types. The CMF comes with a collection of basic content types, and you can create new content types. In the following sections you'll learn about the basic CMF content types. Using Documents The Document is the most important content type. It contains text. You can use a document to hold plain text, or text marked up in Structured Text or HTML format. Structured Text is covered in the "Structured Text" section later in the chapter. Documents are appropriate to use for ad hoc and miscellaneous text and web pages. If you are creating formal, structured documents such as a purchase orders, or reports, you should probably create a custom content type. To create a document, enter the "Desktop" view by clicking "My Stuff" or "Folder contents". Then click the "New..." button. You'll be presented with a list of content types to choose from. Select "Document" and enter an ID for your document. The ID is like a filename. It should be unique within its folder and it can include a file extension. For Documents you may wish to use IDs like "Readme.txt" or "MyPage.html". Then click the "Add" button. Next you'll be taken to the metadata editing form. This form allows you to enter information about your document. You should always provide metadata for your content. Adding metadata may seem like a pain, but it really pays off because it makes your web site much easier to search and maintain. See the "Using Metadata" section later in this chapter for more information about metadata. As a rule of thumb, always provide at least a title and a one sentence description for your document. Then click the "Change and Edit" button. Now you can type in your document. Enter the contents of your document in the "Edit" field. If you already have the text of your document in a file, you can upload that file using the "Upload" field and the "Browse" button. Once you've entered your content click the "Change and View" button. This will show you what your Document looks like. When viewing your document, notice how there is a document icon and with the ID of your document in your current object actions area. This indicates which object you are currently working on. You can navigate back to the editing form by clicking the "Edit" link. Likewise, you can change your document's metadata with the "Metadata" link. You can also see what your document looks like with the "View" link. The "Status", "Publish" and "Status History" links provide workflow actions. They are discussed in the "Using Workflow" section later in this chapter. Using Files You can store binary data and multi-media content (such as PDF, Flash, and Java applet files) in Files. Just as Documents are meant to hold ad hoc and miscellanious text content, Files are used for all sorts of binary content. You should not use Files for specialized binary content that is supported by other content types. For example, you should use Image objects for pictures such as JPG, GIF, and PNG files. You may also use cutom content types to support other special types of binary data. The process of creating a file is very similar to creating a document. In fact, all CMF content types are created and edited in basically the same fashion. First, enter the "Desktop" view. Then click the "New..." button. Next select the content type (in this case File), provide an ID and click the "Add button". You'll then be taken to the metadata editing form. Notice how this form is exactly the same for Files as it is for Documents. The CMF requires that all content use the same metadata. This makes it easy to classify and search for content of all types. See the "Using Metadata" section later in this chapter for more information on metadata. Once you have provided metadata for your file you can edit it. Click the "Edit" link and you'll be taken to a form where you can upload your file. Use the "Browse" button to locate the file on your local machine and click the "Change and View" button to upload it. The "View" link shows you information about your file (filename, size, etc.) and allows you to download it. You can also download a file by clicking on its "Download" action. If you look carefully you'll notice that the URL of the download link is simply the URL of the File. This allows you to easily create hyperlinks to your Files. There's no need for special URLs when referencing your PDF, Flash, Java applet, or other files. Using Images Images hold picture data such as JGP, GIF, and PNG files. Images are designed to hold pictures which are displayed on web pages. You can create an Image in the same way as you create other CMF content types: navigate to the "Desktop" view, click "New..." select "Image", provide an ID, and click "Add". As usual, you'll be taken to the metadata editing page. Type in some metadata for your image and click "Change and Edit". You edit images the same way you edit files: by uploading a file. Once you upload your picture you can view it by clicking on the "View" link. You can display Images in your HTML pages either using the HTML IMG tag. Simply create an IMG tag that refers to the URL of the Image, and it will work normally. Zope also provides some convenience functions for creating IMG tags. For more information see the description of Image objects in "The Zope Book":http://www.zope.org/Members/michel/ZB/BasicObject.dtml Using Links and Favorites Links and Favorites hold URLs. Links point to external resources, while Favorites point to objects within your site. Favorites are part of the CMF user interface and provide you with a simple book marking mechanism. Links come in handy to collect metadata about external resources. In Chapter 3. you learned how to create and manage Favorites with the "Add to Favorites" and "My Favorites" links. You can also create Favorites and Links using the normal content adding procedure. Navigate to the "Desktop" view. Click the "New..." button, select "Link" or "Favorite", type in an ID, and click "Add". Next you'll be taken to the metadata editing page. Enter metadata about the Link or Favorite and click "Change and Edit". The "Edit" view for Links and Favorites looks the same. In both cases you enter the URL. However, there's an important difference. For Links you should provide an absolute URL such as, "http://www.zope.org". For Favorites you should use a URL that's relative to your site object, for example, "Members/Joe/Doc.html". Once you provide a URL for your Favorite or Link you can view it by clicking the "View" link. The "View" page shows you information about your Favorite or Link and gives you a hyperlink to the resource. Using News Items News items convey timely information to site visitors. You should use News Items to inform others about events. If you have substantive things to say you should create a document (or other appropriate content object) to hold your information and then create a news item to announce it. Later if you substantially change your document (or other content) you can create another news item to announce the changes. You can create a News Item in the same way as you create other CMF content types: navigate to the "Desktop" view, click "New..." select "News Item", provide an ID, and click "Add". As usual, you'll be taken to the metadata editing page. Type in some metadata for your news item and click "Change and Edit". You must provide a lead-in and a body for your news item. The lead-in provides a short summary of the news item. You may notice that the lead-in text is the same as the news items's "Description" metadata. The news item's body is its complete text in HTML format. To view your News Item click the "View" link. News Items look pretty much like Documents. Unlike Documents, however, public News Items automatically appear on the site's news page. You can navigate to this page by clicking the site's "news" action. Later in this chapter you'll learn how to make your News Items and other content public. Example Content Types The CMF ships with some example content types. Depending on how your site is configured you may or may not have access to these content types. The following is a brief description of example add on packages and their content types: CMFCalendar -- This example package adds a calendaring capabilities. It treats events as content objects. CMFWiki -- Wikis enable low-tech collaboration. The CMFWiki package treats wiki pages as content objects. CMFCollector -- The collector provides issue tracking. It treats issues (such as bug reports) as content objects. Custom Content Types XXX Content Authoring Formats HTML XXX Structured Text XXX Using Metadata XXX Using Workflow XXX Remote Authoring XXX Conclusion XXXCMF-1.3/docs/Introduction.stx0100644000076500007650000001146207401036611016054 0ustar tseavertseaverChapter 1: Introduction Zope's Content Management Framework (CMF) allows you to manage your organizations digital assets. The CMF lets you: * Create and edit all kinds of content. * Manage your content using custom business logic and workflow rules. * Deliver your content in a flexible, powerful way. Instead of taking a one-size-fits-all approach, the CMF allows you to build your own content management system that meets your needs. You can use the CMF to create all kinds of content management systems including: * Intranets - share knowledge and automate business processes within your organization. * Portals - deploy personalized content and leverage visitor contributed content. You will learn how to use and customize the CMF in the chapters that follow. CMF Content A central idea of the CMF is that all kinds of digital assets (including articles, reports, graphics, multi-media, etc.) are treated as "content" objects. Content objects all share certain features including: remote editing, security, workflow, and customizable look and feel. The CMF provides standard tools to edit and manage content. You can create content through the web using a standard web browser, or you can edit it remotely with desktop tools. You can create custom content types to fit with your organization's needs. For example, you might want to create a custom purchase order content type. The CMF promotes distributed authorship. Different categories of users from managers to anonymous site visitors can create and edit content. All content functions are controlled by security and workflow rules. This allows you to construct a content management system that meets your organization's needs. You can delegate some responsibilities while retaining control. CMF Services In addition to content, the other central idea of the CMF is "services". Services provide centralized management of site features. For example, the workflow service configures business process rules that govern how content is developed and deployed. Here's a brief overview of key CMF services: Membership The membership service controls how users join your site. For example, some sites allow visitors to become contributing members, while other sites draw authentication information from LDAP databases. The membership service controls both authentication and user data. Workflow The workflow service lets you configure business rules that govern how content is developed and deployed. You can tailor workflow to control how content is routed between different people as it is developed. For example, a news article might be created by an author, and then submitted to an editor for review. You can configure different workflow rules for different types of content. Security Zope provides a unified security architecture that controls all site functions. You can easily tailor the security policy for different content types, and different locations in your site. You can also delegate safely security policy maintenance so that managers can set the security policy in their part of the site. Undo The Undo service allows you to recover from mistakes. All site changes (including changes to logic, presentation, and content components) can be undone. Zope uses transactions that ensure that the site never gets into an inconsistent state. History and Versions Zope provides a history and versions facility to content. This allows you to keep track of content versions. You can track changes in content over time and check point different content revisions. Look and Feel The CMF provides a unified look and feel service. This service allows you to provide a consistent look and feel as well as personalize a site for different users. You can also reuse content with different look and feel. Metadata The metadata service allows you to configure content metadata. Metadata categorizes content by keeping track of attributes such as title, author, last modified date, key words, etc. By using metadata you can easily categorize and search your content. Search Zope provides a powerful and fast search services. The search service allows you to locate content based on full text search as well as metadata. The search service can be used to build reports as well as to provide search services to site visitors. Conclusion The Zope CMF gives you the tools you need to build a powerful content management system that meets your needs. In the following chapters you'll learn more about CMF content and services. CMF-1.3/docs/Outline.stx0100644000076500007650000000525007404017310015006 0ustar tseavertseaverThe Zope Content Managment Framework Introduction - What is content management, what is unique about CMF, what's the future of the CMF - Content and services. Introduce core ideas including: metadata, search, distributed control, workflow, security, etc. Quick Tour - A quick, hands-on tour of CMF features. Using the CMF - Accounts - how to create an account, preferences, log in, log out, etc. - Getting around - how to use navigation, favorites, member folder, actions box, folder contents, cutting and pasting, undo, etc. - Using Services - use of pubic services: search, discussion, etc. Working with Content - Overview of content - distributed authorship, distributed through out site, ownership, access controls (ie public versus private), workflow, metadata, catalogability, etc. - Folder, Topic, Document, File, Image, etc. Desribe what each built-in content type is good for, how to create and edit them, and other features (if applicable). - Formats (stx, html) - Metadata - overview, why it's important, and definition of the various metadata fields. - Remote authoring - FTP & WebDAV. How to use various editors. How to set metadata headers, how to handle errors, etc. - Workflow - how content flows through workflow. How to move content along in workflow. Your inbox. Configuring and Customizing the CMF - Overview of services - they are customizable and configurable. The following sections describe each service, including it purpose and use. - Basic configuration - Portal configuration stuff - Look and Feel - Skins (how to configure skins and override look and feel elements), DTML, Page Templates, etc. - Workflow - configuring workflow states, transitions, actions - Search - cataloging, building search forms, searching public and private content - Membership (using various authentication sources, managing users and their preferences, user data schemas) - Discussion - Security - setting roles and local roles - permissions needed to add various types of content and other objects. - Syndication - Versioning XXX fiction? XXX - Personalization XXX fiction? XXX Scripting the CMF - Overview of scripting CMF - Customizing Skins - how to customize skins by overriding scripts. - API docs for CMF tools and other scriptable objects. Should include plenty of examples. Glossary - XXX is this needed? XXX Notes - Emphasize the *demo* aspect of demo folder - Emphasize fundamental ideas: content types, services, skins CMF-1.3/docs/Tour.stx0100644000076500007650000001651407404017310014325 0ustar tseavertseaverChapter 2: CMF Tour The Zope Content Management Framework provides a whole range of content management solutions. In this chapter, we'll take you on a quick tour of an online newspaper. Keep in mind that this is just one example of using the CMF to manage content. Imagine a small newspaper that has reporters, editors, and readers. The reporters create news stories and submit them to the editor. The editor needs to either approve or reject those stories. Approved stories go on to the front page of the newspaper, but rejected stories are sent back to the reporter to be revised and reviewed again. This may sound like a simple problem to solve with email, but an email system does not ensure that the proper business process is followed. If a reporter sends their story to the editor by email, the editor may simply forget to take care of it. The email system can't remind him to take care of it. If you use email to manage workflow, everyone must remember to perform their tasks and to notify others as appropriate. If one person forgets to perform a task or notify someone, the whole system breaks down. The more people you add to an email-based system, the faster it becomes unmanageable. A true workflow system like Zope's CMF keeps users focused on achieving their goals. For example, editors don't need to manually keep track of the articles they have to review because they CMF does it for them. When an editor logs in they are shown a list of pending articles they (or someone with the same editorial "role") must review. In the next sections, we'll walk you through six different day to day scenarios at a fictitious newspaper. In later chapters, we'll show you how you can build a site like this using Zope's CMF in no time. As you'll see, this example application doesn't require advanced techniques are to create it. Scenario: Create Content The most basic task in the Zope CMF is to create content. *Content* consists of things like documents, folders, news items, files, images, and discussion topics. In the following screen shot: You see the content management interface for Fred. Fred is a reporter that works at our newspaper, and today he is going to write a story. He does this by creating a news object in his personal area. He can do this by clicking on the *New* button. After clicking the *New* button, Fred is taken to this screen where he can choose a type of content to create. He's going to select *News Item* type in an id, and click *Add and Edit*. Here we see Fred is now editing the news item he just created. He types in some news about the local school BBQ, and clicks *Change*. Now Fred has created some content in Zope. He can repeat this task as many times as he wants for lots of content. Scenario: Submit, Review, and Approve Content We just showed how Fred can create News content in his area. Now in a newspaper, this news should be first read and approved by the editor. Standard business procedures like this are called *workflow*. Fred writes the content and the editor, Janet, reads and either approves or rejects Fred's stories. This kind of simple workflow is built right into the CMF. Let's look at the last news item Fred created: Notice the *Submit* link on the left. When Fred clicks this link, Zope will submit this news item for review. When Janet logs in, she gets a special message that tells her if she has any pending items: Janet can now click on the *Pending (1)* link in the bottom left hand corner of her member area. When Janet clicks on this link, it takes her to this screen: Here, Janet can either accept or reject Fred's stories. To do that, she clicks on the new story and selects either 'Publish' or 'Reject' on the left of the screen: If she accepts a story, it becomes public on the site and the newspapers readers will see the story on the front page. If she rejects a story, she can type in a reason why into a box and a message is sent back to Fred, telling him that his story was rejected and the reason why. Fred can now revise the story and submit it again for approval. Scenario: Search Content Recent stories on the front page are easily available, but sometimes users may want to search through a large amount of news, or search news in the past. The CMF lets you do many kinds of searches on your content. At the top left of every screen is a *search* link. Clicking this link will take you to the CMF search page: Here, you can specify search terms and get reports on matching content. For example, you can search for content that contains the words "baked beans" by typing those words into the *Text* field and then clicking *Search*: Here are the results of the search. You can also specify more than one term. For example, "Fred" can be selected from the *Author* field and only content created by Fred will be searched. Scenario: Undo Mistakes People make mistakes. Fred could submit a story and then realize that he forgot some details. Janet could reject a story by accidentally clicking the wrong button. The CMF lets you undo all of the actions you take. After creating a new news item, here is what Fred sees when he clicks the *Undo* link in his personal area: The first item on the list represents the creation of the news item. Fred can undo this action by selecting the first check box and clicking *Undo*. Now when he goes back to his Member area, the news item he just created is gone. All users have the same interface for undoing their actions. If Janet accidentally clicks the wrong button, she can undo that mistake the same way Fred undid his. Scenario: Organize Content Being able to search through content is pretty useful, but it would be more useful if you could categorize your content with your own keywords. This way, you could tag certain news items with special keywords like 'Local' or 'Financial' and offer your users the ability to look for items that match those keywords. When Fred creates a new news item, he can click on the *Metadata* link on the left of the content's interface. On this next screen, he can enter keywords for the content in the *Category* form element: Here, Fred gave this news item the keyword 'birds'. Fred can add as many keywords as he wants. The next time he creates content, all of the keywords he has defined so far will show up in a pull down box on the *Metadata* screen, so that he can just select the ones he wants. Now, when Fred goes to the *search* link, he can choose from a list of keywords which will return only content items that are tagged with those categories. Scenario: Share Content Reporter 1 adds content, gives report 2 'Owner' role Reporter 2 can access content and change it XXX unfinished Conclusion XXX XXX screenshots need to be done correctly, also they should to retaken to reflect the new ZPT skins. CMF-1.3/docs/Using.stx0100644000076500007650000004354007404017310014460 0ustar tseavertseaverChapter 3: Using the CMF This chapter shows you how to get started with the CMF. It provides a hands-on introduction to the basic features of the CMF. Getting Started Before you can begin you must have access to a Zope site with the CMF installed. See *The Zope Book* for more information on downloading and installing Zope. See XXX for more information on downloading and installing the CMF. Creating an Account To use the CMF you must obtain a user account. Visit the CMF site in your web browser. You should see a page as shown in [3-1]: "A CMF site":img:3-1:imgs/3-1.png The dark blue bar across the top of the page gives you information the CMF site and links to various site functions as well as a search box. These links are called "Site Actions". The light blue bar gives you information and links which relate to your user account. The word "Guest" tells you that you are not logged in. The "Log in" and "Join" links let you log in (if you already have an account) or join the site to create a new account. The links in the light blue bar are called "User Actions". The gray box on the left of the page tells you about the object you are currently visting. In this case the XXX icon and the name "XXX" indicate that you are currently viewing a document named XXX. The "View" link allows you to view to document (which you are currently doing). Depending on your user account and the type of object you are visiting, you may have many different options listed in the gray box. Links in the gray box are called "Current Object Actions". To the right of the gray box is an area of the page that tells you the title and description of the object you are currently visiting. There is also a "bread-crumbs" style navigation device here which indicates where you are in the site, and provides links to navigate back up to higher levels of the site. XXX should explain this more? Below the current object actions and the description of the current object is the bulk of the page. While the rest of the page will maintain a constant structure, this area will change as you navigate between objects. Depending on how your site is configured, things may look a bit different. Nevertheless, you will always have access to site, user, and current object actions on every page. Click on the "Join" link to create a new account. You will be taken to a form as shown in [3-2]: "A join form":img:3-2:imgs/3-2.png To create an account, simply fill out this form and click the "Register" button. Depending on how your site is configured you may see slightly different options. For example, some site allow you to log in immediately, while others send you your password via email. Once you have your password, it's time to log in. Logging In and Out To log in, click the "Log in" link. You will be taken to a log in form as shown in [3-3]: "A log in form":img:3-3:imgs/3-3.png Type in your user name and password and click the "Login" button. If all goes well you will be taken to a confirmation page that tells you that you've logged in. If you've forgotten your password you can try to log in again, or you can arrange to have your password mailed to you. Once you've logged in notice how light blue bar now says your name. This tells you that you're logged in. Also notice that you now have many more options in the current object actions area than you had before. See [3-4] for an example: "Logged in as Bob":img:3-4:imgs/3-4.png Click the "Log out" link. You should be taken to a confirmation page that tells you that you're successfully logged out. Notice how the user and current object actions change. You are now identified as "Guest", and have few actions available to you. Now you know how to log in and log out of your site. In general you should log in when you want to do some work on the site, and you should log out of the site after your done using it. Personalizing Your Account Now log in to your site again. You can personalize the site by clicking the "Preferences" link in the user actions area. It takes you to a form where you can set your preferences as shown in [3-5]: "Preferences form":img:3-5:imgs/3-5.png Depending on how your site is configured you may have different preferences available. In this example you can change your email address, whether you show up on the public member roster or not, and your "skin". Skins give the site a customized look and feel. Try selecting another skin and notice how the entire site changes. You can still perform the same actions, but the new skin changes the colors and formatting of the site. Getting Around Now let's take a look at some of the things you can do now that you have an CMF account. Using Navigation You can navigate a CMF site using the standard site action links. These are: home -- The home or "root" of the CMF site. members -- The members folder of the site. From here you can navigate to individual member folders where site users keep most of their content. news -- A listing of recent news items. search -- A detailed search form. Instead of using the detailed search form, you can also perform a quick search using the search box. As previously mentioned there are also "breadcrumb" links above the title of the current object. These links show you where the current object is located in the site, and allow you to navigate up to enclosing folders. The "root" link takes you to the top (or home) of the site. Your Member Folder Click the "My Stuff" user action link. You will be taken to your member area as shown in [3-6]: "Member area":img:3-6:imgs/3-6.png This is the part of the site where you can create your own content. Your content is shown on this screen. Here you can create new content and modify existing content. Creating and editing content is covered in the next chapter. Content is arranged in folders. The objects you see in your member area are contained in your member folder. You can create sub-folders inside your member folder. In fact you can think of the entire site as a collection of objects within folders. The word "Desktop" at the top of the screen indicates that you are in the "Desktop" view. This view is similar to your computer's file manager in that it displays folders and objects. Using Folders Create a new folder in your member folder by clicking the "New..." button. Select "Folder" from the list of objects and type "myFolder" into the "ID" field. Then click the "Add" button. Next you'll be taken to a page where you can provide a title and a description for your folder. Give the folder a title of "example folder", and click the "Change" button. Zope should inform you that the folder has been changed. Now click the "My Stuff" link again. Notice that you now have a folder named "myFolder" in your member area. Click on the "myFolder" folder to see what's inside it. The folder is empty, but it does have a link (shown as a folder with an up arrow) which allows you to return to its parent (in this case your member folder). Now you know how to navigate between folders. Click on a folder to enter it, and click on its parent link to return to the enclosing folder. Cutting and Pasting Return to your member area by clicking the "My Stuff" link. Notice that the objects in your member folder have check boxes next to them. These controls let you select objects. Select the "myFolder" folder that you created in the last section. Now click the "Copy" button. Zope should inform you that it successfully copied the folder. Now click the "Paste" button. You should now have a "copy_of_myFolder" folder. Select the copied folder and click the "Rename" button. Rename the folder to "anotherFolder" and click the "Ok" button. When you copy and paste objects you get a complete copy of the object including it's properties and sub-objects. For example, notice that the copied folder has the same title as the original. If the original folder had contained other objects, the copy would have had copies of them as well. The "Cut" button lets you copy an object and delete it from its original location. Select the "anotherFolder" folder and click "Cut". Zope tells you that the object has been cut, but it does not disappear. Now enter the "myFolder" folder and click the "Paste" button. The cut folder is now inside the "myFolder" folder. Return to your member folder and notice that the cut folder is now gone. The "Delete" button allows you to remove an object without copying it. Select the "myFolder" folder and click "Delete". The folder disappears. You **cannot** click "Paste" to get the folder back. You can however use "undo" to get the folder back. Undo You can recover from mistakes with the undo facility. Click the "Undo" link. You'll be taken to a page that lists transactions. Each transaction is a collection of changes made by a person. Select the last transaction (it will have a name like "/Site/folder_delete" and click the "Undo" button. Zope will tell you that the transaction was undone. Now notice that your folder has returned. You can undo all kinds of actions, not just those related to moving objects around. For example suppose you made a new item public, and as a result it appeared on the site's home page. You could make the new item private again, with undo. Undo reverts all changes that are part of a transaction (for example changing the news item's status as well as removing it from the home page) at once. View Filters Sometimes it can get tedious looking through folders for things you're interested in. To help you manage folder contents you can use view filters. A view filter hides objects that you're not interested in. To create a view filter click the "Set View Filter" button in the "Desktop" view. You'll be taken to a form where you can specify what types of objects you want to see. You can filter objects by subject and/or by type. To filter objects by subject type a keyword in the "Subject" field. (See Chapter 4. for more information on metadata and subject keywords.) To filter by type select the content types that you want to see in the "Content Type" multiple selection list. If you specify a subject as well as content types, you will only see objects that match both criteria. After you can selected your subject and/or content type click the "Set Filter" button. After you set the view filter notice that your folder contents are changed to take the filter into account. Also notice how the filter stays visible at the bottom of the "Desktop" view. If you wish to hide the filter details click the "Close View Filter" button. The view filter will still be active, but it's details will be hidden. To show the view filter details click the "Set View Filter" button. You can get rid of the filter by clicking the "Clear View Filter" button. You can change the view filter by selecting new filtering criteria and clicking the "Set View Filter" button. If you don't have many objects in your folders then the view filter won't be much use to you. However, if you're managing a lot of objects, it can be a great boon. Favorites The "Favorites" feature lets you manage personal bookmarks for your site. Favorites give you a way to quickly navigate your site. Enter the "myFolder" folder you created earlier in this chapter. Then enter the contained "anotherFolder" folder. Now click the "Add to Favorites" link. You just created a shortcut to this folder. Notice that you now have a "My Favorites" link your user actions. Return to your member folder by clicking the "My Stuff" link. Notice that you now have a "Favorites" folder in your member folder. This folder holds your book marks. To visit a favorite, click the "My Favorites" link. You'll be taken to your Favorites folder. In this folder you'll see links to all your favorites. Favorites are automatically given Id's by Zope which have names like "fav_1007078408". Click on the favorite. You'll be taken to a page that shows the URL of your favorite. To visit the favorite, click on the URL. Notice that the URL in your browser's location box changes. In this case the appearance of page doesn't differ from the appearance of your home page. Other Actions Depending on how your site is configured you may have a number of links in your current object actions that haven't been discussed so far. Here's a quick description of them: * The "Folder contents" action simply takes you to the "Desktop" view showing you the contents of the current folder. If you are exploring a part of this site other than your member directory, you can use the "Folder contents" link to inspect the objects at this location. * The "View" link shows you the public view of content or folders. A folder's public view is provided by an object named "index_html". Notice how there is an "index_html" document in your member directory. If you click "View" in your member folder you will see the public view of the "index_html" document. If a folder has no "index_html" object, Zope looks in its parent folder for an "index_html" object. Zope continues to look in parent folders until it finds an "index_html". * The "Local Roles" link provides security controls that are discussed in the Chapter 5. * The "Syndication" link allows you to control content syndication. Syndication is covered in Chapter 5. * The "Edit", "Metadata", "Publish", and "Status history" links let you edit content. Content editing is discussed in the Chapter 4. * The "Reconfigure portal" links allows you to set site preferences. It is discussed in Chapter 5. Using Services The CMF provides a many services to your site. Here you'll learn about publicly available services: search and discussion. In the next chapter you'll find out about management services such as workflow. Searching You can easily locate content on a CMF site using the search service. All CMF content is searchable. The CMF provides easy to use and powerful searching. Type your username into the search box in the upper right corner of the page and click the "Go" button. You should see your home page document in the search results. Now click the "search" link at the top of the page to perform an advanced search. You'll be taken to a page where you can provide detailed information about what you're looking for. All CMF content is indexed using its full text as well as its metadata (title, description, author, etc.) This allows you to perform very precise searches. The search service won't return content that you aren't privileged to see. In the next chapter you'll learn about how access to content protected. The search results are tailored to the user doing the search to ensure that security settings are respected. XXX is this true, or are private items simply not cataloged? Using Discussion Sometimes when reading content you may want to provide feedback to the author or to add comments to aid readers. You can discuss content using the CMF's discussion feature. To offer a comment or provide feedback on content click the "Reply" action. This will take you to a form where you can provide your input. Note that depending on how your site is configured you may or may not see a "Reply" link in your current object actions when viewing content. Give you comment a short title and then type the body of your comment in the "Reply body" field. When you're done you can preview your comment by clicking the "Preview" button. If you're statisfied with your comment click the "Reply" button, otherwise click the "Edit" button to change your comment. Once you submit your comment, it will be visible at the bottom of the page. This way site visitors will be able to read your comment when viewing the content. In addition to replying to content, you can reply to a comment. The CMF supported threaded discussions. You reply to a comment in excactly the same way you reply to other content; simply click the "Reply" link while reading the comment. Zope displays threaded comments using a tree widget. XXX doesn't seem to be turned on by default. The discussion tool offers no knobs. Argh. Conclusion The CMF is structured around two central ideas: content and services. Content consists of objects located in folders. You can perform actions on content using links in your current object actions. You can invoke services either through site actions, or actions on objects. In the next chapter you'll learn more about content. XXX how many screen shots should there be in this chapter? CMF-1.3/docs/Versioning.txt0100644000076500007650000003540407360413761015532 0ustar tseavertseaverTitle: Version Control in Zope Description: Content authors and site designers working in complex web sites require support for version control. This chapter introduces versioning in Zope, shows how it can be used, and provides some architectural information about how Zope handles versioning. Subject: products Working on a web site is often a complex activity. There are teams of people with various roles producing content and site design resources. As the site changes over time, it is important to manage the changes. This is the domain of *version control*. This chapter introduces content authors and site designers to the use of versioning in Zope. o Goals of versioning o Quick tour of using the basics in versioning o Advanced operations o Versioning architecture o Glossary of versioning terms Goals There are numerous goals for different audiences that a version control system addresses. xxx fill in some information on goals here. Zope's approach to fulfilling these goals is based on the *DeltaV* model. DeltaV is an extension of the WebDAV protocol, and has recently become achieved "Proposed Standard" status. DeltaV represents the collected wisdom of many professionals in the field of version control systems. While Zope will use the architecture and jargon from DeltaV to accomplish the goals of versioning, there are no current plans to make Zope compliant with the DeltaV protocol. Work on this would be a follow-on project. Quick Start For a quick overview of the basics of version control for content authors, imagine a structured website at a newspaper called Whoville Times (WT), Inc. The WT website is organized similar to the newspaper sections, with extra structure for the web content:: / /index.html /logo.gif /styles.css /contacts.html /localnews/ /localnews/index.html /localnews/policeblotter.html /sports /sports/index.html /sports/scores.html /livechats/ /livechats/index.html There are several people that have access to work on the website, corresponding to editors and writers of the sections of the newspaper. Additionally there are website producers that have privileges in different areas of the site. For instance: o Jane is the senior editor for the website. She can work on any section and approve changes made by others. She is the primary producer for web-only content, such as the Live Chat feature. o Mary is the editor of the Local News section of the newspaper. She also administers this section of the online edition. o Art is the copy editor in the sports department that works on the website. In addition to the site structure, there is another kind of organization that is common in large web productions. That is, the test and production server approach is used to allow material to be authored in one location and deployed in another location. These locations might be on separate machines, in separate Zope processes on the same machine, or in separate folders in the same Zope process. This test/production split is used to allow updates in isolation, thus increasing the quality and stability of the production site. Thus we have an addition to the structure:: working/ (Test site) working/index.html working/logo.gif working/styles.css working/contacts.html working/localnews/ working/localnews/index.html working/localnews/policeblotter.html working/localnews/headlines.html working/sports working/sports/index.html working/sports/scores.html working/sports/springtraining.html working/livechats/ working/livechats/index.html prod/ (Production site) prod/index.html prod/logo.gif prod/styles.css prod/contacts.html prod/localnews/ prod/localnews/index.html prod/localnews/policeblotter.html prod/localnews/headlines.html prod/sports prod/sports/index.html prod/sports/scores.html prod/sports/springtraining.html prod/livechats/ prod/livechats/index.html In this quickstart section, let's presume that the 'working' and 'prod' sites are already under version control. Making Changes With Versioning Mary begins her website work by updating the police blotter page with new information from the police department. She logs in to the 'working' website, where she authors all her content. Mary then navigates to the police blotter page at '/localnews/policeblotter.html'. Since she is logged in, Mary sees an application bar that isn't visible on the page that the site visitors will see on the production site. The application bar tells her that the police blotter page is checked in and that she is viewing version 1.8 of the page. Mary clicks on 'Edit', makes a change, then clicks on save. The page now says she is viewing version 1.9 of the page. This is because her part of the website is setup to do *autoversioning*. Behind the scenes, every save does a checkout/update/checkin cycle. Mary now wants to update the headlines. This is a page that several of the people in her department edit as soon as something big happens. In the past this has meant that people's changes sometimes overwrite her work. She learned that, to prevent this, she needs to *lock* the page before doing her edits, so that others can neither open the page for editing nor save any changes. Mary visits the 'headlines.html' page which is at version 1.32. This time she clicks on 'Lock and edit'. The application bar indicates that the page is now 'Checked out with lock'. She makes a change and saves the change. She then receives a phone call and makes three more saves during the course of the call. When she is done with the call and her changes, Mary clicks on 'Unlock and checkin' to finish her work on the 'headlines.html' page. She is prompted for a comment to accompany the unlock and checkin operation. The application bar reports that the page is now 'Checked in' and that the version has only increased to 1.33. Why not 1.36? Even though she saved the page 4 times, 'Lock and edit' tells Zope to only do a checkin when the page is unlocked. Thus, in this mode of editing, only one new version is changed for the editing cycle. Art has been working on a major feature about this year's spring training camp for baseball. He has a page at 'sports/springtraining.html' where he saves his work and periodically makes it available for review by checking in the page. Art visits the website to makes some changes to the page and to check in the changes. The application bar for the page reports that 'springtraining.html' is 'Checked in' and at version 1.5. Art clicks on 'Check out'. The application bar now reports the status of the page as 'Checked out'. Art makes seven edits and realizes he needs some help from a collegue on some wording. The collegue visits the page, and since it isn't locked, makes a quick change. Art then reloads the contents to get the change, makes some more editing, and prepares to finish. Art checks in his changes by clicking on 'Check in'. He is prompted for a comment. He types in a status message and clicks 'Done'. The application bar now shows the page as 'Checked in' at version 1.6. Jane is told that the number for the circulation department has changed and she needs to update the site. Since she is an advanced user, Jane uses a desktop editing tool that lets her author content more productively. This editing tool supports the WebDAV protocol. Jane opens her editor, goes to the folder containing the website, and double-clicks on 'contacts.html'. The editor locks the page and opens it for editing. The editor has a property inspector that lets her look at the DAV properties, showing her the current version. Jane makes several changes, saves the document, and closes the document. When the document is closed, the editing tool unlocks the page, prompting Zope to check in a new version. Jane opens the document again and sees that the property sheet says the version has increased. Using Private Workspaces Later in the day, Jane hears that the HTML used by the Local News staff isn't compliant with the HTML 4 specification. She'd like to update all the content, but do so in a way that allows Mary to continue working. To accomplish this, Jane decides to checkout her own version of the content used in the 'localnews' folder. Remember, the folder at '/localnews' and all the content it contains are really checkouts themselves. There is an authoritative copy in the repository, along with all previous versions of the content. Jane uses her web browser to visit the '/localnews' folder. She sees that the folder itself is 'Checked in' and at version 1.3. Jane clicks on 'Copy new checkout'. She then visits her user folder by clicking on 'My Content'. She goes to her 'WORKSPACES' folder and clicks 'Paste'. Zope then does a new checkout of the folder at '/localnews' and the content contained by the folder. Note that, as Jane checks in changes on her own workspace to the local news, the content in '/localnews' doesn't change. Getting Jane's private changes updated in the '/localnews' area requires an explicit step. However, as Mary visits the content in '/localnews', the application bar will indicate that some of the content has been updated in another checkout. xxx What happens if Mary tries to save a change to a version that is no longer the tip? Jane now has her own checkout of the content. Again, Jane is a power user and wants to run a command-line tool that automatically converts all the content to be compliant with the HTML 4 standard. She runs 'sitecopy', a WebDAV tool that downloads all the resources from '~jane/WORKSPACE/localnews' and saves the pages on her local drive. She then runs the 'tidy' command line tool to reformat the HTML. Finally, she runs sitecopy again to upload her changes. Her 'WORKSPACE' area is configured to *not* do autoversioning. Thus, the changes to the resources in '~jane/WORKSPACE/localnews' haven't yet been checked in. Jane uses her web browser to go to the 'localnews' folder in her workspace and verifies that the changes have been uploaded. She then clicks on 'Checkin' to checkin her changes, supplies a comment, then clicks 'Done'. Setting Baselines For Production Content The work for the day is now done and it is time to put out a new edition of the newspaper. In Zope (and DeltaV) jargon, an edition is known as a *baseline*. A baseline is a way to organize the correct version of all the material needed for a website at a certain time. Thus, the live website can run a different baseline than test site. When the test baseline is ready, a new baseline is made from the test site. The production site is then updated to use the new baseline. If something goes wrong, the production site can quickly switch back to the old baseline. The deadline is approaching and everyone has informed Jane that they are finished. Jane goes to the URL at 'working/' and browses the site looking for problems. She notes that the application bar says she is working in 'Baseline: Prod126'. Jane clicks on 'Versioning', then clicks on 'Baseline'. She gets a page that describes all the changes to the site since the last baseline. Everything is in order, so she types 'Prod127' as the label for the new baseline and clicks 'Submit'. The baseline is created and the site is now using the new baseline. Thus, there are no items in the list of changes since the baseline was set. Jane now starts switching the live site to use the new baseline. She goes to the URL at 'prod/' and clicks on 'Version Control', then 'Switch to baseline'. She provides 'Prod127' as the baseline to switch to and clicks 'Done'. After several seconds, the site has all the new and updated content from the test site. Any renames or deletes are also reflected in the production site. Summary This quickstart section provided an overview of how versioning is used in Zope. We showed the various ways that content authors can work on resources using versioning. We also saw how site managers can perform work on the entire body of content. In the remainder of this chapter we will focus on the specifics of each aspect introduced here. We will also look under the covers at the versioning architecture in Zope. Using Version Control Setting Up The Repository - Create new repository, connect to existing repository, disconnect from existing repository Initial Checkout o setting parameters like autoversioning Modes of Authoring - autoversioning on edit, autoversioning on unlock, checkout - viewing changes in context - versioning of configuration content - locking - checked in content can't be edited - working from DAV/ftp o editing and synchronizing o changing the view to show DAV property info - issues for skins and site designers Commit, Update, Discard Working With Version Histories - view versions and change history, compare between versions, copy old version to new version, Workspaces Branches - branching, merging Baselines -- grouping together versions of resources Branching and Merging Reports Configuration and Administration of Versioning - Site policies, collection policies, permissions on version operations, - Administration on repository (e.g. archiving), Architecture o Properties and property sheets as part of versioned resource o DAV properties from DeltaV: which supported? o Different repository implementations (pack, undo, diff, etc.) o Will we have a catalog for fast operations on all versioned content? o Versioned collection issues o Issues involving interactions between versioning and both site catalogs and workflow (e.g. will versioned objects be searchable when they aren't checked in, will two resources that point to the same version both be searchable) o Issues for developers - APIs for versioning, properties for versioning, serialization - preconditions and postconditions on version commits - are properties a logical part of the content being versioned, and does a change to a property generate a new version? Glossary Working server Production server Autoversioning Notes o Is the Site a workspace, baseline, activity, collection, etc.?