vendor/CMF/1.5.4/CMFDefault

changeset 0:dddf0f662143 CMFDefault tip

Vendor import of CMF 1.5.4
author fguillaume
date Mon, 05 Sep 2005 14:27:33 +0000
parents
children
files DEPENDENCIES.txt DefaultWorkflow.py DiscussionItem.py DiscussionTool.py Document.py DublinCore.py Extensions/Upgrade.py Extensions/fix_cmf_permissions.py Extensions/migrate_ptk.py Extensions/update_catalogIndexes.py Extensions/update_discussion.py Favorite.py File.py Image.py Link.py MembershipTool.py MetadataTool.py NewsItem.py Portal.py PropertiesTool.py README.txt RegistrationTool.py SkinnedFolder.py SyndicationInfo.py SyndicationTool.py URLTool.py __init__.py bridge.zcml browser/__init__.py browser/configure.zcml browser/five_template.pt configure.zcml dtml/addPortal.dtml dtml/discussionEdit.dtml dtml/discussionView.dtml dtml/explainDiscussionTool.dtml dtml/explainMembershipTool.dtml dtml/explainMetadataTool.dtml dtml/explainPropertiesTool.dtml dtml/membershipRolemapping.dtml dtml/metadataElementPolicies.dtml dtml/metadataProperties.dtml dtml/synOverview.dtml dtml/synPolicies.dtml dtml/synProps.dtml dtml/synReports.dtml dtml/zmi_editDocument.dtml dtml/zmi_editLink.dtml dtml/zmi_metadata.dtml exceptions.py help/ActorDefinitions.stx help/Actor_ContentCreator.stx help/Actor_MembershipManager.stx help/Actor_Reviewer.stx help/Actor_SiteDesigner.stx help/Actor_SiteManager.stx help/Actor_SiteVisitor.stx help/AddContentFolders.stx help/ApproveForPublication.stx help/BecomeAMember.stx help/BrowseCMFSiteHomepage.stx help/BrowseNewsItems.stx help/BrowseSubmittedForReview.stx help/ChangeContent.stx help/ConfigureCMFSite.stx help/ConfigurePersonalization.stx help/CreateCMFFolder.stx help/CreateCMFSite.stx help/CreateCMFTopic.stx help/CreateNewContent.stx help/LoginAsMember.stx help/ManageLocalRoles.stx help/MoveCopyContent.stx help/ProvideFeedback.stx help/RemoveContent.stx help/RenameContent.stx help/SearchCMFSite.stx help/SubmitContentForPublication.stx help/Syndication-Tool_Overview.stx help/Syndication-Tool_Policies.stx help/Syndication-Tool_Properties.stx help/Syndication-Tool_Reporting.stx help/TODO.stx help/UndoChanges.stx help/UnpublishContent.stx help/ViewMyContent.stx images/workflow.gif implements.zcml interfaces/Document.py interfaces/__init__.py interfaces/portal_membership.py permissions.py portal.gif profiles/default/actions.xml profiles/default/export_steps.xml profiles/default/import_steps.xml profiles/default/properties.xml profiles/default/rolemap.xml profiles/default/skins.xml profiles/default/toolset.xml profiles/default/types/Discussion_Item.xml profiles/default/types/Document.xml profiles/default/types/Favorite.xml profiles/default/types/File.xml profiles/default/types/Folder.xml profiles/default/types/Image.xml profiles/default/types/Link.xml profiles/default/types/News_Item.xml profiles/default/types/Topic.xml profiles/default/typestool.xml profiles/default/workflows.xml profiles/default/workflows/default_workflow/definition.xml scripts/addImagesToSkinPaths.pys scripts/convertCatalogGetIconColumn.pys setuphandlers.py skins/Images/UpFolder_icon.gif skins/Images/Zope_logo.gif skins/Images/c.gif skins/Images/go.gif skins/Images/logo.jpg skins/Images/logo.png skins/Images/spacer.gif skins/Images/tinyzope.jpg skins/content/aboveInThread.py skins/content/content_hide_form.dtml skins/content/content_publish_form.dtml skins/content/content_reject_form.dtml skins/content/content_retract_form.dtml skins/content/content_show_form.dtml skins/content/content_status_history.dtml skins/content/content_status_modify.py skins/content/content_submit_form.dtml skins/content/discussionitem_icon.gif skins/content/discussionitem_view.dtml skins/content/document_edit.py skins/content/document_edit_form.dtml skins/content/document_icon.gif skins/content/document_view.dtml skins/content/favorite_view.dtml skins/content/file_edit.py skins/content/file_edit_form.dtml skins/content/file_icon.gif skins/content/file_view.dtml skins/content/folder_edit.py skins/content/folder_edit_form.dtml skins/content/folder_icon.gif skins/content/folder_view.dtml skins/content/full_metadata_edit_form.dtml skins/content/image_edit.py skins/content/image_edit_form.dtml skins/content/image_icon.gif skins/content/image_view.dtml skins/content/link_edit.py skins/content/link_edit_form.dtml skins/content/link_icon.gif skins/content/link_view.dtml skins/content/metadata_edit.py skins/content/metadata_edit_form.dtml skins/content/newsitem_edit.py skins/content/newsitem_edit_form.dtml skins/content/newsitem_icon.gif skins/content/newsitem_view.dtml skins/content/source_html.dtml skins/control/addtoFavorites.py skins/control/change_password.py skins/control/disableSyndication.py skins/control/editSynProperties.py skins/control/enableSyndication.py skins/control/expireAuthCookie.py skins/control/finish_portal_construction.dtml skins/control/folder_copy.py skins/control/folder_cut.py skins/control/folder_delete.py skins/control/folder_localrole_edit.py skins/control/folder_paste.py skins/control/folder_rename.py skins/control/folder_rename_items.py skins/control/isDiscussable.py skins/control/logout.py skins/control/mail_password.py skins/control/personalize.py skins/control/reconfig.py skins/control/register.py skins/control/search_debug.dtml skins/control/setAuthCookie.py skins/control/synPropertiesForm.dtml skins/control/undo.py skins/generic/RSS.dtml skins/generic/TitleOrId.py skins/generic/actions_box.dtml skins/generic/clearCookie.py skins/generic/content_byline.dtml skins/generic/css_inline_or_link.py skins/generic/default_stylesheet.dtml skins/generic/discussion_reply.py skins/generic/discussion_reply_form.dtml skins/generic/discussion_reply_preview.dtml skins/generic/discussion_thread_view.dtml skins/generic/doFormSearch.py skins/generic/filterCookie.py skins/generic/folder_add.dtml skins/generic/folder_contents.dtml skins/generic/folder_factories.dtml skins/generic/folder_filter_form.dtml skins/generic/folder_localrole_form.dtml skins/generic/folder_rename_form.dtml skins/generic/iconHTML.py skins/generic/index_html.dtml skins/generic/itemRSS.dtml skins/generic/join_form.dtml skins/generic/listMetaTags.py skins/generic/logged_in.dtml skins/generic/logged_out.dtml skins/generic/login_form.dtml skins/generic/mail_password_form.dtml skins/generic/mail_password_response.dtml skins/generic/mail_password_template.dtml skins/generic/menu.dtml skins/generic/metadata_help.dtml skins/generic/news_box.dtml skins/generic/password_form.dtml skins/generic/personalize_form.dtml skins/generic/recent_news.dtml skins/generic/reconfig_form.dtml skins/generic/registered.dtml skins/generic/registered_notify_template.dtml skins/generic/roster.dtml skins/generic/rssBody.dtml skins/generic/rssDisabled.dtml skins/generic/search.dtml skins/generic/search_form.dtml skins/generic/showThreads.dtml skins/generic/simple_metadata.dtml skins/generic/standard_html_footer.dtml skins/generic/standard_html_header.dtml skins/generic/standard_top_bar.dtml skins/generic/stylesheet_properties.props skins/generic/truncID.py skins/generic/undo_form.dtml skins/generic/viewThreadsAtBottom.dtml skins/no_css/stylesheet_properties.props skins/nouvelle/nouvelle_stylesheet.dtml skins/nouvelle/stylesheet_properties.props skins/zpt_content/aboveInThread.py skins/zpt_content/content_hide_form.pt skins/zpt_content/content_publish_form.pt skins/zpt_content/content_reject_form.pt skins/zpt_content/content_retract_form.pt skins/zpt_content/content_show_form.pt skins/zpt_content/content_status_history.pt skins/zpt_content/content_status_modify.py skins/zpt_content/content_submit_form.pt skins/zpt_content/discussionitem_icon.gif skins/zpt_content/discussionitem_view.pt skins/zpt_content/document_edit_control.py skins/zpt_content/document_edit_form.py skins/zpt_content/document_edit_template.pt skins/zpt_content/document_icon.gif skins/zpt_content/document_view.pt skins/zpt_content/favorite_view.pt skins/zpt_content/file_edit_control.py skins/zpt_content/file_edit_form.py skins/zpt_content/file_edit_template.pt skins/zpt_content/file_icon.gif skins/zpt_content/file_view.pt skins/zpt_content/folder_edit_control.py skins/zpt_content/folder_edit_form.py skins/zpt_content/folder_edit_template.pt skins/zpt_content/folder_icon.gif skins/zpt_content/folder_view.pt skins/zpt_content/full_metadata_edit_form.py skins/zpt_content/full_metadata_edit_template.pt skins/zpt_content/getBaseTag.pt skins/zpt_content/image_edit_control.py skins/zpt_content/image_edit_form.py skins/zpt_content/image_edit_template.pt skins/zpt_content/image_icon.gif skins/zpt_content/image_view.pt skins/zpt_content/link_edit_control.py skins/zpt_content/link_edit_form.py skins/zpt_content/link_edit_template.pt skins/zpt_content/link_icon.gif skins/zpt_content/link_view.pt skins/zpt_content/metadata_edit_control.py skins/zpt_content/metadata_edit_form.py skins/zpt_content/metadata_edit_template.pt skins/zpt_content/newsitem_edit_control.py skins/zpt_content/newsitem_edit_form.py skins/zpt_content/newsitem_edit_template.pt skins/zpt_content/newsitem_icon.gif skins/zpt_content/newsitem_view.pt skins/zpt_content/source_html.pt skins/zpt_content/subjectsList.py skins/zpt_content/transition_form.pt skins/zpt_content/validateHTML.py skins/zpt_content/validateTextFile.py skins/zpt_control/addtoFavorites.py skins/zpt_control/change_password.py skins/zpt_control/disableSyndication.py skins/zpt_control/editSynProperties.py skins/zpt_control/enableSyndication.py skins/zpt_control/finish_portal_construction.pt skins/zpt_control/folder_add_control.py skins/zpt_control/folder_bottom_control.py skins/zpt_control/folder_copy_control.py skins/zpt_control/folder_cut_control.py skins/zpt_control/folder_delete_control.py skins/zpt_control/folder_down_control.py skins/zpt_control/folder_localrole_edit.py skins/zpt_control/folder_paste_control.py skins/zpt_control/folder_rename_control.py skins/zpt_control/folder_sort_control.py skins/zpt_control/folder_top_control.py skins/zpt_control/folder_up_control.py skins/zpt_control/isDiscussable.py skins/zpt_control/logout.py skins/zpt_control/mail_password.py skins/zpt_control/members_add_control.py skins/zpt_control/members_delete_control.py skins/zpt_control/personalize.py skins/zpt_control/portal_config_control.py skins/zpt_control/reconfig.py skins/zpt_control/reverseList.py skins/zpt_control/setRedirect.py skins/zpt_control/setStatus.py skins/zpt_control/synPropertiesForm.pt skins/zpt_control/undo.py skins/zpt_control/validateId.py skins/zpt_control/validateItemIds.py skins/zpt_control/validateMemberIds.py skins/zpt_control/validatePassword.py skins/zpt_control/validateType.py skins/zpt_generic/RSS.py skins/zpt_generic/RSS_template.pt skins/zpt_generic/TitleOrId.py skins/zpt_generic/actions_box.pt skins/zpt_generic/batch_widgets.pt skins/zpt_generic/breadcrumbs.py skins/zpt_generic/clearCookie.py skins/zpt_generic/content_byline.pt skins/zpt_generic/discitem_delete.py skins/zpt_generic/discussion_reply.py skins/zpt_generic/discussion_reply_form.pt skins/zpt_generic/discussion_reply_preview.pt skins/zpt_generic/expanded_title.py skins/zpt_generic/filterCookie.py skins/zpt_generic/folder_add.pt skins/zpt_generic/folder_contents.py skins/zpt_generic/folder_contents_template.pt skins/zpt_generic/folder_factories.py skins/zpt_generic/folder_factories_template.pt skins/zpt_generic/folder_filter_form.pt skins/zpt_generic/folder_localrole_form.pt skins/zpt_generic/folder_rename_form.py skins/zpt_generic/folder_rename_template.pt skins/zpt_generic/form_widgets.pt skins/zpt_generic/getBatchItemInfos.py skins/zpt_generic/getBatchNavigation.py skins/zpt_generic/get_permalink.py skins/zpt_generic/index_html.py skins/zpt_generic/index_html_categorized.pt skins/zpt_generic/index_html_template.pt skins/zpt_generic/index_html_utils.html skins/zpt_generic/join_form.py skins/zpt_generic/join_template.pt skins/zpt_generic/logged_in.pt skins/zpt_generic/logged_out.pt skins/zpt_generic/login_form.pt skins/zpt_generic/mail_password_form.pt skins/zpt_generic/mail_password_response.pt skins/zpt_generic/mail_password_template.pt skins/zpt_generic/main_template.pt skins/zpt_generic/members_manage_form.py skins/zpt_generic/members_manage_template.pt skins/zpt_generic/metadata_help.pt skins/zpt_generic/news_box.pt skins/zpt_generic/password_form.pt skins/zpt_generic/permalink.py skins/zpt_generic/personalize_form.pt skins/zpt_generic/publishItems.py skins/zpt_generic/recent_news.pt skins/zpt_generic/reconfig_form.py skins/zpt_generic/reconfig_template.pt skins/zpt_generic/registered.pt skins/zpt_generic/registered_notify_template.pt skins/zpt_generic/rejectItems.py skins/zpt_generic/review.pt skins/zpt_generic/roster.pt skins/zpt_generic/rssDisabled.pt skins/zpt_generic/search.py skins/zpt_generic/search_form.pt skins/zpt_generic/search_results_template.pt skins/zpt_generic/setup_talkback_tree.py skins/zpt_generic/standard_error_message.pt skins/zpt_generic/stxmethod_view.pt skins/zpt_generic/talkback_tree.pt skins/zpt_generic/truncID.py skins/zpt_generic/unauthRedirect.py skins/zpt_generic/undo_form.pt skins/zpt_generic/viewThreadsAtBottom.pt skins/zpt_generic/zpt_stylesheet.css tests/TestImage.jpg tests/__init__.py tests/test_DefaultWorkflow.py tests/test_DiscussionReply.py tests/test_DiscussionTool.py tests/test_Discussions.py tests/test_Document.py tests/test_DublinCore.py tests/test_Favorite.py tests/test_Image.py tests/test_Link.py tests/test_MembershipTool.py tests/test_MetadataTool.py tests/test_NewsItem.py tests/test_Portal.py tests/test_PropertiesTool.py tests/test_RegistrationTool.py tests/test_SyndicationTool.py tests/test_all.py tests/test_join.py tests/test_utils.py tool.gif utils.py version.txt
diffstat 425 files changed, 25962 insertions(+), 0 deletions(-) [+]
line diff
     1.1 new file mode 100644
     1.2 --- /dev/null
     1.3 +++ b/DEPENDENCIES.txt
     1.4 @@ -0,0 +1,3 @@
     1.5 +Zope >= 2.7.0
     1.6 +CMFCore
     1.7 +CMFTopic
     2.1 new file mode 100644
     2.2 --- /dev/null
     2.3 +++ b/DefaultWorkflow.py
     2.4 @@ -0,0 +1,328 @@
     2.5 +##############################################################################
     2.6 +#
     2.7 +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
     2.8 +#
     2.9 +# This software is subject to the provisions of the Zope Public License,
    2.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
    2.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
    2.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    2.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
    2.14 +# FOR A PARTICULAR PURPOSE.
    2.15 +#
    2.16 +##############################################################################
    2.17 +""" A simple submit/review/publish workflow.
    2.18 +
    2.19 +$Id: DefaultWorkflow.py 37237 2005-07-18 14:20:02Z anguenot $
    2.20 +"""
    2.21 +
    2.22 +from Acquisition import aq_base
    2.23 +from Acquisition import aq_inner
    2.24 +from Acquisition import aq_parent
    2.25 +from AccessControl import ClassSecurityInfo
    2.26 +from DateTime import DateTime
    2.27 +from Globals import InitializeClass
    2.28 +
    2.29 +from Products.CMFCore.interfaces.portal_workflow \
    2.30 +        import WorkflowDefinition as IWorkflowDefinition
    2.31 +from Products.CMFCore.utils import _checkPermission
    2.32 +from Products.CMFCore.utils import _modifyPermissionMappings
    2.33 +from Products.CMFCore.utils import getToolByName
    2.34 +from Products.CMFCore.utils import SimpleItemWithProperties
    2.35 +from Products.CMFCore.WorkflowTool import addWorkflowClass
    2.36 +
    2.37 +from exceptions import AccessControl_Unauthorized
    2.38 +from permissions import ModifyPortalContent
    2.39 +from permissions import RequestReview
    2.40 +from permissions import ReviewPortalContent
    2.41 +from permissions import View
    2.42 +
    2.43 +
    2.44 +class DefaultWorkflowDefinition (SimpleItemWithProperties):
    2.45 +    """ Default workflow definition.
    2.46 +    """
    2.47 +
    2.48 +    __implements__ = IWorkflowDefinition
    2.49 +
    2.50 +    meta_type = 'Workflow'
    2.51 +    id = 'default_workflow'
    2.52 +    title = 'Simple Review / Publish Policy'
    2.53 +    _isAWorkflow = 1
    2.54 +
    2.55 +    security = ClassSecurityInfo()
    2.56 +
    2.57 +    def __init__(self, id):
    2.58 +        self.id = id
    2.59 +
    2.60 +    security.declarePrivate('getReviewStateOf')
    2.61 +    def getReviewStateOf(self, ob):
    2.62 +        tool = aq_parent(aq_inner(self))
    2.63 +        status = tool.getStatusOf(self.getId(), ob)
    2.64 +        if status is not None:
    2.65 +            review_state = status['review_state']
    2.66 +        else:
    2.67 +            if hasattr(aq_base(ob), 'review_state'):
    2.68 +                # Backward compatibility.
    2.69 +                review_state = ob.review_state
    2.70 +            else:
    2.71 +                review_state = 'private'
    2.72 +        return review_state
    2.73 +
    2.74 +    security.declarePrivate('getCatalogVariablesFor')
    2.75 +    def getCatalogVariablesFor(self, ob):
    2.76 +        '''
    2.77 +        Allows this workflow to make workflow-specific variables
    2.78 +        available to the catalog, making it possible to implement
    2.79 +        queues in a simple way.
    2.80 +        Returns a mapping containing the catalog variables
    2.81 +        that apply to ob.
    2.82 +        '''
    2.83 +        return {'review_state': self.getReviewStateOf(ob)}
    2.84 +
    2.85 +    security.declarePrivate('listObjectActions')
    2.86 +    def listObjectActions(self, info):
    2.87 +        '''
    2.88 +        Allows this workflow to
    2.89 +        include actions to be displayed in the actions box.
    2.90 +        Called only when this workflow is applicable to
    2.91 +        info.object.
    2.92 +        Returns the actions to be displayed to the user.
    2.93 +        '''
    2.94 +        if info.isAnonymous:
    2.95 +            return None
    2.96 +
    2.97 +        # The following operation is quite expensive.
    2.98 +        # We don't need to perform it if the user
    2.99 +        # doesn't have the required permission.
   2.100 +        content = info.object
   2.101 +        content_url = info.object_url
   2.102 +        content_creator = content.Creator()
   2.103 +        pm = getToolByName(self, 'portal_membership')
   2.104 +        current_user = pm.getAuthenticatedMember().getId()
   2.105 +        review_state = self.getReviewStateOf(content)
   2.106 +        actions = []
   2.107 +
   2.108 +        allow_review = _checkPermission(ReviewPortalContent, content)
   2.109 +        allow_request = _checkPermission(RequestReview, content)
   2.110 +
   2.111 +        append_action = (lambda name, p, url=content_url, a=actions.append:
   2.112 +                         a({'name': name,
   2.113 +                            'url': url + '/' + p,
   2.114 +                            'permissions': (),
   2.115 +                            'category': 'workflow'}))
   2.116 +
   2.117 +        show_reject = 0
   2.118 +        show_retract = 0
   2.119 +
   2.120 +        if review_state == 'private':
   2.121 +            if allow_review:
   2.122 +                append_action('Publish', 'content_publish_form')
   2.123 +            elif allow_request:
   2.124 +                append_action('Submit', 'content_submit_form')
   2.125 +
   2.126 +        elif review_state == 'pending':
   2.127 +            if content_creator == current_user and allow_request:
   2.128 +                show_retract = 1
   2.129 +            if allow_review:
   2.130 +                append_action('Publish', 'content_publish_form')
   2.131 +                show_reject = 1
   2.132 +
   2.133 +        elif review_state == 'published':
   2.134 +            if content_creator == current_user and allow_request:
   2.135 +                show_retract = 1
   2.136 +            if allow_review:
   2.137 +                show_reject = 1
   2.138 +
   2.139 +        if show_retract:
   2.140 +            append_action('Retract', 'content_retract_form')
   2.141 +        if show_reject:
   2.142 +            append_action('Reject', 'content_reject_form')
   2.143 +        if allow_review or allow_request:
   2.144 +            append_action('Status history', 'content_status_history')
   2.145 +
   2.146 +        return actions
   2.147 +
   2.148 +    security.declarePrivate('listGlobalActions')
   2.149 +    def listGlobalActions(self, info):
   2.150 +        '''
   2.151 +        Allows this workflow to include actions to be displayed
   2.152 +        in the actions box.  Called on every request.
   2.153 +
   2.154 +        Returns the actions to be displayed to the user.
   2.155 +        '''
   2.156 +        if info.isAnonymous:
   2.157 +            return None
   2.158 +
   2.159 +        actions = []
   2.160 +        catalog = getToolByName(self, 'portal_catalog', None)
   2.161 +        if catalog is not None:
   2.162 +            pending = len(catalog.searchResults(
   2.163 +                review_state='pending'))
   2.164 +            if pending > 0:
   2.165 +                actions.append(
   2.166 +                    {'name': 'Pending review (%d)' % pending,
   2.167 +                     'url': info.portal_url +
   2.168 +                     '/search?review_state=pending',
   2.169 +                     'permissions': (ReviewPortalContent, ),
   2.170 +                     'category': 'global'}
   2.171 +                    )
   2.172 +        return actions
   2.173 +
   2.174 +    security.declarePrivate('isActionSupported')
   2.175 +    def isActionSupported(self, ob, action, **kw):
   2.176 +        '''
   2.177 +        Returns a true value if the given action name is supported.
   2.178 +        '''
   2.179 +        return (action in ('submit', 'retract', 'publish', 'reject',))
   2.180 +
   2.181 +    security.declarePrivate('doActionFor')
   2.182 +    def doActionFor(self, ob, action, comment=''):
   2.183 +        '''
   2.184 +        Allows the user to request a workflow action.  This method
   2.185 +        must perform its own security checks.
   2.186 +        '''
   2.187 +        allow_review = _checkPermission(ReviewPortalContent, ob)
   2.188 +        allow_request = _checkPermission(RequestReview, ob)
   2.189 +        review_state = self.getReviewStateOf(ob)
   2.190 +        tool = aq_parent(aq_inner(self))
   2.191 +
   2.192 +        if action == 'submit':
   2.193 +            if not allow_request:
   2.194 +                raise AccessControl_Unauthorized('Not authorized')
   2.195 +            elif review_state != 'private':
   2.196 +                raise AccessControl_Unauthorized('Already in submit state')
   2.197 +            self.setReviewStateOf(ob, 'pending', action, comment)
   2.198 +
   2.199 +        elif action == 'retract':
   2.200 +            if not allow_request:
   2.201 +                raise AccessControl_Unauthorized('Not authorized')
   2.202 +            elif review_state == 'private':
   2.203 +                raise AccessControl_Unauthorized('Already private')
   2.204 +            content_creator = ob.Creator()
   2.205 +            pm = getToolByName(self, 'portal_membership')
   2.206 +            current_user = pm.getAuthenticatedMember().getId()
   2.207 +            if (content_creator != current_user) and not allow_review:
   2.208 +                raise AccessControl_Unauthorized('Not creator or reviewer')
   2.209 +            self.setReviewStateOf(ob, 'private', action, comment)
   2.210 +
   2.211 +        elif action == 'publish':
   2.212 +            if not allow_review:
   2.213 +                raise AccessControl_Unauthorized('Not authorized')
   2.214 +            self.setReviewStateOf(ob, 'published', action, comment)
   2.215 +
   2.216 +        elif action == 'reject':
   2.217 +            if not allow_review:
   2.218 +                raise AccessControl_Unauthorized('Not authorized')
   2.219 +            self.setReviewStateOf(ob, 'private', action, comment)
   2.220 +
   2.221 +    security.declarePrivate('isInfoSupported')
   2.222 +    def isInfoSupported(self, ob, name):
   2.223 +        '''
   2.224 +        Returns a true value if the given info name is supported.
   2.225 +        '''
   2.226 +        return (name in ('review_state', 'review_history'))
   2.227 +
   2.228 +    security.declarePrivate('getInfoFor')
   2.229 +    def getInfoFor(self, ob, name, default):
   2.230 +        '''
   2.231 +        Allows the user to request information provided by the
   2.232 +        workflow.  This method must perform its own security checks.
   2.233 +        '''
   2.234 +        # Treat this as public.
   2.235 +        if name == 'review_state':
   2.236 +            return self.getReviewStateOf(ob)
   2.237 +
   2.238 +        allow_review = _checkPermission(ReviewPortalContent, ob)
   2.239 +        allow_request = _checkPermission(RequestReview, ob)
   2.240 +        if not allow_review and not allow_request:
   2.241 +            return default
   2.242 +
   2.243 +        elif name == 'review_history':
   2.244 +            tool = aq_parent(aq_inner(self))
   2.245 +            history = tool.getHistoryOf(self.getId(), ob)
   2.246 +            # Make copies for security.
   2.247 +            return tuple(map(lambda dict: dict.copy(), history))
   2.248 +
   2.249 +    security.declarePrivate('setReviewStateOf')
   2.250 +    def setReviewStateOf(self, ob, review_state, action, comment):
   2.251 +        tool = aq_parent(aq_inner(self))
   2.252 +        pm = getToolByName(self, 'portal_membership')
   2.253 +        current_user = pm.getAuthenticatedMember().getId()
   2.254 +        status = {
   2.255 +            'actor': current_user,
   2.256 +            'action': action,
   2.257 +            'review_state': review_state,
   2.258 +            'time': DateTime(),
   2.259 +            'comments': comment,
   2.260 +            }
   2.261 +        tool.setStatusOf(self.getId(), ob, status)
   2.262 +        self.updateRoleMappingsFor(ob)
   2.263 +
   2.264 +    security.declarePrivate('notifyCreated')
   2.265 +    def notifyCreated(self, ob):
   2.266 +        '''
   2.267 +        Notifies this workflow after an object has been created
   2.268 +        and put in its new place.
   2.269 +        '''
   2.270 +        self.setReviewStateOf( ob, 'private', 'created', '' )
   2.271 +        self.notifySuccess(ob, 'created', '')
   2.272 +
   2.273 +    security.declarePrivate('notifyBefore')
   2.274 +    def notifyBefore(self, ob, action):
   2.275 +        '''
   2.276 +        Notifies this workflow of an action before it happens,
   2.277 +        allowing veto by exception.  Unless an exception is thrown, either
   2.278 +        a notifySuccess() or notifyException() can be expected later on.
   2.279 +        The action usually corresponds to a method name.
   2.280 +        '''
   2.281 +        pass
   2.282 +
   2.283 +    security.declarePrivate('notifySuccess')
   2.284 +    def notifySuccess(self, ob, action, result):
   2.285 +        '''
   2.286 +        Notifies this workflow that an action has taken place.
   2.287 +        '''
   2.288 +        pass
   2.289 +
   2.290 +    security.declarePrivate('notifyException')
   2.291 +    def notifyException(self, ob, action, exc):
   2.292 +        '''
   2.293 +        Notifies this workflow that an action failed.
   2.294 +        '''
   2.295 +        pass
   2.296 +
   2.297 +    security.declarePrivate('updateRoleMappingsFor')
   2.298 +    def updateRoleMappingsFor(self, ob):
   2.299 +        '''
   2.300 +        Changes the object permissions according to the current
   2.301 +        review_state.
   2.302 +        '''
   2.303 +        review_state = self.getReviewStateOf(ob)
   2.304 +        if review_state == 'private':
   2.305 +            anon_view = 0
   2.306 +            owner_modify = 1
   2.307 +            reviewer_view = 0
   2.308 +        elif review_state == 'pending':
   2.309 +            anon_view = 0
   2.310 +            owner_modify = 0  # Require a retraction for editing.
   2.311 +            reviewer_view = 1
   2.312 +        elif review_state == 'published':
   2.313 +            anon_view = 1
   2.314 +            owner_modify = 0
   2.315 +            reviewer_view = 1
   2.316 +        else:   # This object is in an unknown state
   2.317 +            anon_view = 0
   2.318 +            owner_modify = 1
   2.319 +            reviewer_view = 0
   2.320 +
   2.321 +        # Modify role to permission mappings directly.
   2.322 +
   2.323 +        new_map = { View: {'Anonymous': anon_view,
   2.324 +                           'Reviewer': reviewer_view,
   2.325 +                           'Owner': 1}
   2.326 +                  , ModifyPortalContent: {'Owner': owner_modify}
   2.327 +                  }
   2.328 +        return _modifyPermissionMappings(ob, new_map)
   2.329 +
   2.330 +InitializeClass(DefaultWorkflowDefinition)
   2.331 +
   2.332 +addWorkflowClass(DefaultWorkflowDefinition)
     3.1 new file mode 100644
     3.2 --- /dev/null
     3.3 +++ b/DiscussionItem.py
     3.4 @@ -0,0 +1,439 @@
     3.5 +##############################################################################
     3.6 +#
     3.7 +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
     3.8 +#
     3.9 +# This software is subject to the provisions of the Zope Public License,
    3.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
    3.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
    3.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    3.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
    3.14 +# FOR A PARTICULAR PURPOSE.
    3.15 +#
    3.16 +##############################################################################
    3.17 +""" Discussion item portal type.
    3.18 +
    3.19 +$Id: DiscussionItem.py 37629 2005-08-01 18:42:53Z efge $
    3.20 +"""
    3.21 +
    3.22 +from AccessControl import ClassSecurityInfo
    3.23 +from Acquisition import Implicit, aq_base, aq_inner, aq_parent
    3.24 +from DateTime import DateTime
    3.25 +from Globals import InitializeClass
    3.26 +from Globals import Persistent
    3.27 +from Globals import PersistentMapping
    3.28 +from OFS.Traversable import Traversable
    3.29 +
    3.30 +from Products.CMFCore.interfaces.Discussions import Discussable
    3.31 +from Products.CMFCore.interfaces.Discussions import DiscussionResponse
    3.32 +from Products.CMFCore.utils import getToolByName
    3.33 +
    3.34 +from Document import Document
    3.35 +from permissions import AccessContentsInformation
    3.36 +from permissions import ManagePortal
    3.37 +from permissions import ReplyToItem
    3.38 +from permissions import View
    3.39 +from utils import scrubHTML
    3.40 +
    3.41 +
    3.42 +factory_type_information = (
    3.43 +  { 'id'             : 'Discussion Item'
    3.44 +  , 'meta_type'      : 'Discussion Item'
    3.45 +  , 'description'    : """\
    3.46 +Discussion Items are documents which reply to other content.
    3.47 +They should *not* be addable through the standard 'folder_factories' interface.
    3.48 +"""
    3.49 +  , 'icon'           : 'discussionitem_icon.gif'
    3.50 +  , 'product'        : '' # leave blank to suppress
    3.51 +  , 'factory'        : ''
    3.52 +  , 'immediate_view' : ''
    3.53 +  , 'aliases'        : {'(Default)':'discussionitem_view',
    3.54 +                        'view':'discussionitem_view'}
    3.55 +  , 'actions'        : ( { 'id'            : 'view'
    3.56 +                         , 'name'          : 'View'
    3.57 +                         , 'action': 'string:${object_url}/discussionitem_view'
    3.58 +                         , 'permissions'   : (View,)
    3.59 +                         }
    3.60 +                       ,
    3.61 +                       )
    3.62 +  }
    3.63 +,
    3.64 +)
    3.65 +
    3.66 +
    3.67 +def addDiscussionItem(self, id, title, description, text_format, text,
    3.68 +                      reply_to, RESPONSE=None):
    3.69 +    """
    3.70 +    Add a discussion item
    3.71 +
    3.72 +    'title' is also used as the subject header
    3.73 +    if 'description' is blank, it is filled with the contents of 'title'
    3.74 +    'reply_to' is the object (or path to the object) which this is a reply to
    3.75 +
    3.76 +    Otherwise, same as addDocument
    3.77 +    """
    3.78 +
    3.79 +    if not description: description = title
    3.80 +    text = scrubHTML(text)
    3.81 +    item = DiscussionItem( id )
    3.82 +    item.title = title
    3.83 +    item.description = description
    3.84 +    item.text_format = text_format
    3.85 +    item.text = text
    3.86 +    item.setReplyTo(reply_to)
    3.87 +
    3.88 +    item._parse()
    3.89 +    self._setObject(id, item)
    3.90 +
    3.91 +    if RESPONSE is not None:
    3.92 +        RESPONSE.redirect(self.absolute_url())
    3.93 +
    3.94 +
    3.95 +class DiscussionItem(Document):
    3.96 +    """
    3.97 +        Class for content which is a response to other content.
    3.98 +    """
    3.99 +
   3.100 +    __implements__ = (DiscussionResponse, Document.__implements__)
   3.101 +
   3.102 +    meta_type           = 'Discussion Item'
   3.103 +    portal_type         = 'Discussion Item'
   3.104 +    allow_discussion    = 1
   3.105 +    in_reply_to         = None
   3.106 +    # XXX this is wrong, it precludes the use of a normal workflow.
   3.107 +    review_state        ='published'
   3.108 +
   3.109 +    security = ClassSecurityInfo()
   3.110 +
   3.111 +    security.declareProtected(View, 'listCreators')
   3.112 +    def listCreators(self):
   3.113 +        """ List Dublin Core Creator elements - resource authors.
   3.114 +        """
   3.115 +        if not hasattr(aq_base(self), 'creators'):
   3.116 +            # for content created with CMF versions before 1.5
   3.117 +            if hasattr(aq_base(self), 'creator') and self.creator != 'unknown':
   3.118 +                self.creators = ( self.creator, )
   3.119 +            else:
   3.120 +                self.creators = ()
   3.121 +        return self.creators
   3.122 +
   3.123 +    #
   3.124 +    #   DiscussionResponse interface
   3.125 +    #
   3.126 +    security.declareProtected(View, 'inReplyTo')
   3.127 +    def inReplyTo( self, REQUEST=None ):
   3.128 +        """
   3.129 +            Return the Discussable object to which we are a reply.
   3.130 +
   3.131 +            Two cases obtain:
   3.132 +
   3.133 +              - We are a "top-level" reply to a non-DiscussionItem piece
   3.134 +                of content;  in this case, our 'in_reply_to' field will
   3.135 +                be None.
   3.136 +
   3.137 +              - We are a nested reply;  in this case, our 'in_reply_to'
   3.138 +                field will be the ID of the parent DiscussionItem.
   3.139 +        """
   3.140 +        tool = getToolByName( self, 'portal_discussion' )
   3.141 +        talkback = tool.getDiscussionFor( self )
   3.142 +        return talkback._getReplyParent( self.in_reply_to )
   3.143 +
   3.144 +    security.declarePrivate(View, 'setReplyTo')
   3.145 +    def setReplyTo( self, reply_to ):
   3.146 +        """
   3.147 +            Make this object a response to the passed object.
   3.148 +        """
   3.149 +        if getattr( reply_to, 'meta_type', None ) == self.meta_type:
   3.150 +            self.in_reply_to = reply_to.getId()
   3.151 +        else:
   3.152 +            self.in_reply_to = None
   3.153 +
   3.154 +    security.declareProtected(View, 'parentsInThread')
   3.155 +    def parentsInThread( self, size=0 ):
   3.156 +        """
   3.157 +            Return the list of items which are "above" this item in
   3.158 +            the discussion thread.
   3.159 +
   3.160 +            If 'size' is not zero, only the closest 'size' parents
   3.161 +            will be returned.
   3.162 +        """
   3.163 +        parents = []
   3.164 +        current = self
   3.165 +        while not size or len( parents ) < size:
   3.166 +            parent = current.inReplyTo()
   3.167 +            assert not parent in parents  # sanity check
   3.168 +            parents.insert( 0, parent )
   3.169 +            if parent.meta_type != self.meta_type:
   3.170 +                break
   3.171 +            current = parent
   3.172 +        return parents
   3.173 +
   3.174 +InitializeClass( DiscussionItem )
   3.175 +
   3.176 +
   3.177 +class DiscussionItemContainer( Persistent, Implicit, Traversable ):
   3.178 +    """
   3.179 +        Store DiscussionItem objects. Discussable content that
   3.180 +        has DiscussionItems associated with it will have an
   3.181 +        instance of DiscussionItemContainer injected into it to
   3.182 +        hold the discussion threads.
   3.183 +    """
   3.184 +
   3.185 +    __implements__ = Discussable
   3.186 +
   3.187 +    # for the security machinery to allow traversal
   3.188 +    #__roles__ = None
   3.189 +
   3.190 +    security = ClassSecurityInfo()
   3.191 +
   3.192 +    def __init__(self):
   3.193 +        self.id = 'talkback'
   3.194 +        self._container = PersistentMapping()
   3.195 +
   3.196 +    security.declareProtected(View, 'getId')
   3.197 +    def getId( self ):
   3.198 +        return self.id
   3.199 +
   3.200 +    security.declareProtected(View, 'getReply')
   3.201 +    def getReply( self, reply_id ):
   3.202 +        """
   3.203 +            Return a discussion item, given its ID;  raise KeyError
   3.204 +            if not found.
   3.205 +        """
   3.206 +        return self._container.get( reply_id ).__of__(self)
   3.207 +
   3.208 +    # Is this right?
   3.209 +    security.declareProtected(View, '__bobo_traverse__')
   3.210 +    def __bobo_traverse__(self, REQUEST, name):
   3.211 +        """
   3.212 +        This will make this container traversable
   3.213 +        """
   3.214 +        target = getattr(self, name, None)
   3.215 +        if target is not None:
   3.216 +            return target
   3.217 +
   3.218 +        else:
   3.219 +            try:
   3.220 +                return self.getReply(name)
   3.221 +            except:
   3.222 +                parent = aq_parent( aq_inner( self ) )
   3.223 +                if parent.getId() == name:
   3.224 +                    return parent
   3.225 +                else:
   3.226 +                    REQUEST.RESPONSE.notFoundError("%s\n%s" % (name, ''))
   3.227 +
   3.228 +    security.declarePrivate('manage_afterAdd')
   3.229 +    def manage_afterAdd(self, item, container):
   3.230 +        """
   3.231 +            We have juste been added or moved.
   3.232 +            Add the contained items to the catalog.
   3.233 +        """
   3.234 +        if aq_base(container) is not aq_base(self):
   3.235 +            for obj in self.objectValues():
   3.236 +                obj.__of__(self).manage_afterAdd(item, container)
   3.237 +
   3.238 +    security.declarePrivate('manage_afterClone')
   3.239 +    def manage_afterClone(self, item):
   3.240 +        """
   3.241 +            We have just been cloned.
   3.242 +            Notify the workflow about the contained items.
   3.243 +        """
   3.244 +        for obj in self.objectValues():
   3.245 +            obj.__of__(self).manage_afterClone(item)
   3.246 +
   3.247 +    security.declarePrivate( 'manage_beforeDelete' )
   3.248 +    def manage_beforeDelete(self, item, container):
   3.249 +        """
   3.250 +            Remove the contained items from the catalog.
   3.251 +        """
   3.252 +        if aq_base(container) is not aq_base(self):
   3.253 +            for obj in self.objectValues():
   3.254 +                obj.__of__( self ).manage_beforeDelete( item, container )
   3.255 +
   3.256 +    #
   3.257 +    #   OFS.ObjectManager query interface.
   3.258 +    #
   3.259 +    security.declareProtected(AccessContentsInformation, 'objectIds')
   3.260 +    def objectIds( self, spec=None ):
   3.261 +        """
   3.262 +            Return a list of the ids of our DiscussionItems.
   3.263 +        """
   3.264 +        if spec and spec is not DiscussionItem.meta_type:
   3.265 +            return []
   3.266 +        return self._container.keys()
   3.267 +
   3.268 +
   3.269 +    security.declareProtected(AccessContentsInformation, 'objectItems')
   3.270 +    def objectItems(self, spec=None):
   3.271 +        """
   3.272 +            Return a list of (id, subobject) tuples for our DiscussionItems.
   3.273 +        """
   3.274 +        r=[]
   3.275 +        a=r.append
   3.276 +        g=self._container.get
   3.277 +        for id in self.objectIds(spec):
   3.278 +            a( (id, g( id ) ) )
   3.279 +        return r
   3.280 +
   3.281 +
   3.282 +    security.declareProtected(AccessContentsInformation, 'objectValues')
   3.283 +    def objectValues(self):
   3.284 +        """
   3.285 +            Return a list of our DiscussionItems.
   3.286 +        """
   3.287 +        return self._container.values()
   3.288 +
   3.289 +    #
   3.290 +    #   Discussable interface
   3.291 +    #
   3.292 +    security.declareProtected(ReplyToItem, 'createReply')
   3.293 +    def createReply( self, title, text, Creator=None, text_format='structured-text' ):
   3.294 +        """
   3.295 +            Create a reply in the proper place
   3.296 +        """
   3.297 +        container = self._container
   3.298 +
   3.299 +        id = int(DateTime().timeTime())
   3.300 +        while self._container.get( str(id), None ) is not None:
   3.301 +            id = id + 1
   3.302 +        id = str( id )
   3.303 +
   3.304 +        item = DiscussionItem( id, title=title, description=title )
   3.305 +        self._container[id] = item
   3.306 +        item = item.__of__(self)
   3.307 +
   3.308 +        item._edit( text_format=text_format, text=text )
   3.309 +        item.addCreator(Creator)
   3.310 +        item.indexObject()
   3.311 +
   3.312 +        item.setReplyTo( self._getDiscussable() )
   3.313 +        item.notifyWorkflowCreated()
   3.314 +
   3.315 +        return id
   3.316 +
   3.317 +    security.declareProtected(ManagePortal, 'deleteReply')
   3.318 +    def deleteReply( self, reply_id ):
   3.319 +        """ Remove a reply from this container """
   3.320 +        if self._container.has_key( reply_id ):
   3.321 +            reply = self._container.get( reply_id ).__of__( self )
   3.322 +            my_replies = reply.talkback.getReplies()
   3.323 +            for my_reply in my_replies:
   3.324 +                my_reply_id = my_reply.getId()
   3.325 +                if hasattr( my_reply, 'unindexObject' ):
   3.326 +                    my_reply.unindexObject()
   3.327 +
   3.328 +                del self._container[my_reply_id]
   3.329 +
   3.330 +            if hasattr( reply, 'unindexObject' ):
   3.331 +                reply.unindexObject()
   3.332 +
   3.333 +            del self._container[reply_id]
   3.334 +
   3.335 +
   3.336 +    security.declareProtected(View, 'hasReplies')
   3.337 +    def hasReplies( self, content_obj ):
   3.338 +        """
   3.339 +            Test to see if there are any dicussion items
   3.340 +        """
   3.341 +        outer = self._getDiscussable( outer=1 )
   3.342 +        if content_obj == outer:
   3.343 +            return bool( len(self._container) )
   3.344 +        else:
   3.345 +            return bool( len( content_obj.talkback._getReplyResults() ) )
   3.346 +
   3.347 +    security.declareProtected(View, 'replyCount')
   3.348 +    def replyCount( self, content_obj ):
   3.349 +        """ How many replies do i have? """
   3.350 +        outer = self._getDiscussable( outer=1 )
   3.351 +        if content_obj == outer:
   3.352 +            return len( self._container )
   3.353 +        else:
   3.354 +            replies = content_obj.talkback.getReplies()
   3.355 +            return self._repcount( replies )
   3.356 +
   3.357 +    security.declarePrivate('_repcount')
   3.358 +    def _repcount( self, replies ):
   3.359 +        """  counts the total number of replies by recursing thru the various levels
   3.360 +        """
   3.361 +        count = 0
   3.362 +
   3.363 +        for reply in replies:
   3.364 +            count = count + 1
   3.365 +
   3.366 +            #if there is at least one reply to this reply
   3.367 +            replies = reply.talkback.getReplies()
   3.368 +            if replies:
   3.369 +                count = count + self._repcount( replies )
   3.370 +
   3.371 +        return count
   3.372 +
   3.373 +    security.declareProtected(View, 'getReplies')
   3.374 +    def getReplies( self ):
   3.375 +        """
   3.376 +            Return a sequence of the DiscussionResponse objects which are
   3.377 +            associated with this Discussable
   3.378 +        """
   3.379 +        objects = []
   3.380 +        a = objects.append
   3.381 +        result_ids = self._getReplyResults()
   3.382 +
   3.383 +        for id in result_ids:
   3.384 +            a( self._container.get( id ).__of__( self ) )
   3.385 +
   3.386 +        return objects
   3.387 +
   3.388 +    security.declareProtected(View, 'quotedContents')
   3.389 +    def quotedContents(self):
   3.390 +        """
   3.391 +            Return this object's contents in a form suitable for inclusion
   3.392 +            as a quote in a response.
   3.393 +        """
   3.394 +
   3.395 +        return ""
   3.396 +
   3.397 +    #
   3.398 +    #   Utility methods
   3.399 +    #
   3.400 +    security.declarePrivate( '_getReplyParent' )
   3.401 +    def _getReplyParent( self, in_reply_to ):
   3.402 +        """
   3.403 +            Return the object indicated by the 'in_reply_to', where
   3.404 +            'None' represents the "outer" content object.
   3.405 +        """
   3.406 +        outer = self._getDiscussable( outer=1 )
   3.407 +        if in_reply_to is None:
   3.408 +            return outer
   3.409 +        parent = self._container[ in_reply_to ].__of__( aq_inner( self ) )
   3.410 +        return parent.__of__( outer )
   3.411 +
   3.412 +    security.declarePrivate( '_getDiscussable' )
   3.413 +    def _getDiscussable( self, outer=0 ):
   3.414 +        """
   3.415 +        """
   3.416 +        tb = outer and aq_inner( self ) or self
   3.417 +        return getattr( tb, 'aq_parent', None )
   3.418 +
   3.419 +    security.declarePrivate( '_getReplyResults' )
   3.420 +    def _getReplyResults( self ):
   3.421 +        """
   3.422 +           Get a list of ids of DiscussionItems which are replies to
   3.423 +           our Discussable.
   3.424 +        """
   3.425 +        discussable = self._getDiscussable()
   3.426 +        outer = self._getDiscussable( outer=1 )
   3.427 +
   3.428 +        if discussable == outer:
   3.429 +            in_reply_to = None
   3.430 +        else:
   3.431 +            in_reply_to = discussable.getId()
   3.432 +
   3.433 +        result = []
   3.434 +        a = result.append
   3.435 +        for key, value in self._container.items():
   3.436 +            if value.in_reply_to == in_reply_to:
   3.437 +                a( ( key, value ) )
   3.438 +
   3.439 +        result.sort( lambda a, b: cmp(a[1].creation_date, b[1].creation_date) )
   3.440 +
   3.441 +        return [ x[0] for x in result ]
   3.442 +
   3.443 +InitializeClass( DiscussionItemContainer )
     4.1 new file mode 100644
     4.2 --- /dev/null
     4.3 +++ b/DiscussionTool.py
     4.4 @@ -0,0 +1,135 @@
     4.5 +##############################################################################
     4.6 +#
     4.7 +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
     4.8 +#
     4.9 +# This software is subject to the provisions of the Zope Public License,
    4.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
    4.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
    4.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    4.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
    4.14 +# FOR A PARTICULAR PURPOSE.
    4.15 +#
    4.16 +##############################################################################
    4.17 +""" Basic portal discussion access tool.
    4.18 +
    4.19 +$Id: DiscussionTool.py 36827 2005-03-20 20:34:34Z yuppie $
    4.20 +"""
    4.21 +
    4.22 +from AccessControl import ClassSecurityInfo
    4.23 +from Acquisition import aq_base
    4.24 +from Globals import DTMLFile
    4.25 +from Globals import InitializeClass
    4.26 +from OFS.SimpleItem import SimpleItem
    4.27 +
    4.28 +from Products.CMFCore.ActionInformation import ActionInformation
    4.29 +from Products.CMFCore.ActionProviderBase import ActionProviderBase
    4.30 +from Products.CMFCore.Expression import Expression
    4.31 +from Products.CMFCore.interfaces.Discussions \
    4.32 +        import DiscussionResponse as IDiscussionResponse
    4.33 +from Products.CMFCore.interfaces.portal_discussion \
    4.34 +        import portal_discussion as IDiscussionTool
    4.35 +from Products.CMFCore.utils import getToolByName
    4.36 +from Products.CMFCore.utils import UniqueObject
    4.37 +
    4.38 +from DiscussionItem import DiscussionItemContainer
    4.39 +from exceptions import AccessControl_Unauthorized
    4.40 +from exceptions import DiscussionNotAllowed
    4.41 +from permissions import ManagePortal
    4.42 +from permissions import ModifyPortalContent
    4.43 +from permissions import ReplyToItem
    4.44 +from utils import _dtmldir
    4.45 +
    4.46 +
    4.47 +class DiscussionTool( UniqueObject, SimpleItem, ActionProviderBase ):
    4.48 +    """ Links content to discussions.
    4.49 +    """
    4.50 +
    4.51 +    __implements__ = (IDiscussionTool, ActionProviderBase.__implements__)
    4.52 +
    4.53 +    id = 'portal_discussion'
    4.54 +    meta_type = 'Default Discussion Tool'
    4.55 +    _actions = (ActionInformation(id='reply'
    4.56 +                                , title='Reply'
    4.57 +                                , action=Expression(
    4.58 +                text='string:${object_url}/discussion_reply_form')
    4.59 +                                , condition=Expression(
    4.60 +                text='python: object is not None and ' +
    4.61 +                'portal.portal_discussion.isDiscussionAllowedFor(object)')
    4.62 +                                , permissions=(ReplyToItem,)
    4.63 +                                , category='object'
    4.64 +                                , visible=1
    4.65 +                                 )
    4.66 +               ,
    4.67 +               )
    4.68 +
    4.69 +    security = ClassSecurityInfo()
    4.70 +
    4.71 +    manage_options = (ActionProviderBase.manage_options +
    4.72 +                     ({ 'label' : 'Overview', 'action' : 'manage_overview' }
    4.73 +                     ,
    4.74 +                     ) + SimpleItem.manage_options)
    4.75 +
    4.76 +    #
    4.77 +    #   ZMI methods
    4.78 +    #
    4.79 +    security.declareProtected(ManagePortal, 'manage_overview')
    4.80 +    manage_overview = DTMLFile( 'explainDiscussionTool', _dtmldir )
    4.81 +
    4.82 +    #
    4.83 +    #   'portal_discussion' interface methods
    4.84 +    #
    4.85 +
    4.86 +    security.declarePublic( 'overrideDiscussionFor' )
    4.87 +    def overrideDiscussionFor(self, content, allowDiscussion):
    4.88 +        """ Override discussability for the given object or clear the setting.
    4.89 +        """
    4.90 +        mtool = getToolByName( self, 'portal_membership' )
    4.91 +        if not mtool.checkPermission(ModifyPortalContent, content):
    4.92 +            raise AccessControl_Unauthorized
    4.93 +
    4.94 +        if allowDiscussion is None or allowDiscussion == 'None':
    4.95 +            if hasattr( aq_base(content), 'allow_discussion'):
    4.96 +                del content.allow_discussion
    4.97 +        else:
    4.98 +            content.allow_discussion = bool(allowDiscussion)
    4.99 +
   4.100 +    security.declarePublic( 'getDiscussionFor' )
   4.101 +    def getDiscussionFor(self, content):
   4.102 +        """ Get DiscussionItemContainer for content, create it if necessary.
   4.103 +        """
   4.104 +        if not self.isDiscussionAllowedFor( content ):
   4.105 +            raise DiscussionNotAllowed
   4.106 +
   4.107 +        if not IDiscussionResponse.isImplementedBy(content) and \
   4.108 +                getattr( aq_base(content), 'talkback', None ) is None:
   4.109 +            # Discussion Items use the DiscussionItemContainer object of the
   4.110 +            # related content item, so only create one for other content items
   4.111 +            self._createDiscussionFor(content)
   4.112 +
   4.113 +        return content.talkback # Return wrapped talkback
   4.114 +
   4.115 +    security.declarePublic( 'isDiscussionAllowedFor' )
   4.116 +    def isDiscussionAllowedFor( self, content ):
   4.117 +        """ Get boolean indicating whether discussion is allowed for content.
   4.118 +        """
   4.119 +        if hasattr( content, 'allow_discussion' ):
   4.120 +            return bool(content.allow_discussion)
   4.121 +        typeInfo = getToolByName(self, 'portal_types').getTypeInfo( content )
   4.122 +        if typeInfo:
   4.123 +            return bool( typeInfo.allowDiscussion() )
   4.124 +        return False
   4.125 +
   4.126 +    #
   4.127 +    #   Utility methods
   4.128 +    #
   4.129 +    security.declarePrivate( '_createDiscussionFor' )
   4.130 +    def _createDiscussionFor( self, content ):
   4.131 +        """ Create DiscussionItemContainer for content, if allowed.
   4.132 +        """
   4.133 +        if not self.isDiscussionAllowedFor( content ):
   4.134 +            raise DiscussionNotAllowed
   4.135 +
   4.136 +        content.talkback = DiscussionItemContainer()
   4.137 +        return content.talkback
   4.138 +
   4.139 +InitializeClass( DiscussionTool )
     5.1 new file mode 100644
     5.2 --- /dev/null
     5.3 +++ b/Document.py
     5.4 @@ -0,0 +1,459 @@
     5.5 +##############################################################################
     5.6 +#
     5.7 +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
     5.8 +#
     5.9 +# This software is subject to the provisions of the Zope Public License,
    5.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
    5.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
    5.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    5.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
    5.14 +# FOR A PARTICULAR PURPOSE.
    5.15 +#
    5.16 +##############################################################################
    5.17 +""" Basic textual content object, supporting HTML, STX and plain text.
    5.18 +
    5.19 +$Id: Document.py 37043 2005-06-14 16:39:34Z tiran $
    5.20 +"""
    5.21 +
    5.22 +from AccessControl import ClassSecurityInfo
    5.23 +from AccessControl import getSecurityManager
    5.24 +from Acquisition import aq_base
    5.25 +from DocumentTemplate.DT_Util import html_quote
    5.26 +from Globals import DTMLFile
    5.27 +from Globals import InitializeClass
    5.28 +from StructuredText.StructuredText import HTML
    5.29 +try:
    5.30 +    import transaction
    5.31 +except ImportError:
    5.32 +    # BBB: for Zope 2.7
    5.33 +    from Products.CMFCore.utils import transaction
    5.34 +
    5.35 +from Products.CMFCore.PortalContent import PortalContent
    5.36 +from Products.CMFCore.utils import contributorsplitter
    5.37 +from Products.CMFCore.utils import keywordsplitter
    5.38 +
    5.39 +from DublinCore import DefaultDublinCoreImpl
    5.40 +from exceptions import EditingConflict
    5.41 +from exceptions import ResourceLockedError
    5.42 +from interfaces.Document import IDocument
    5.43 +from interfaces.Document import IMutableDocument
    5.44 +from permissions import ModifyPortalContent
    5.45 +from permissions import View
    5.46 +from utils import _dtmldir
    5.47 +from utils import bodyfinder
    5.48 +from utils import formatRFC822Headers
    5.49 +from utils import html_headcheck
    5.50 +from utils import parseHeadersBody
    5.51 +from utils import SimpleHTMLParser
    5.52 +
    5.53 +factory_type_information = (
    5.54 +  { 'id'             : 'Document'
    5.55 +  , 'meta_type'      : 'Document'
    5.56 +  , 'description'    : """\
    5.57 +Documents contain text that can be formatted using 'Structured Text.'
    5.58 +They may also contain HTML, or "plain" text.
    5.59 +"""
    5.60 +  , 'icon'           : 'document_icon.gif'
    5.61 +  , 'product'        : 'CMFDefault'
    5.62 +  , 'factory'        : 'addDocument'
    5.63 +  , 'immediate_view' : 'metadata_edit_form'
    5.64 +  , 'aliases'        : {'(Default)':'document_view',
    5.65 +                        'view':'document_view',
    5.66 +                        'gethtml':'source_html'}
    5.67 +  , 'actions'        : ( { 'id'            : 'view'
    5.68 +                         , 'name'          : 'View'
    5.69 +                         , 'action': 'string:${object_url}/document_view'
    5.70 +                         , 'permissions'   : (View,)
    5.71 +                         }
    5.72 +                       , { 'id'            : 'edit'
    5.73 +                         , 'name'          : 'Edit'
    5.74 +                         , 'action': 'string:${object_url}/document_edit_form'
    5.75 +                         , 'permissions'   : (ModifyPortalContent,)
    5.76 +                         }
    5.77 +                       , { 'id'            : 'metadata'
    5.78 +                         , 'name'          : 'Metadata'
    5.79 +                         , 'action': 'string:${object_url}/metadata_edit_form'
    5.80 +                         , 'permissions'   : (ModifyPortalContent,)
    5.81 +                         }
    5.82 +                       )
    5.83 +  }
    5.84 +,
    5.85 +)
    5.86 +
    5.87 +def addDocument(self, id, title='', description='', text_format='',
    5.88 +                text=''):
    5.89 +    """ Add a Document """
    5.90 +    o = Document(id, title, description, text_format, text)
    5.91 +    self._setObject(id,o)
    5.92 +
    5.93 +
    5.94 +class Document(PortalContent, DefaultDublinCoreImpl):
    5.95 +    """ A Document - Handles both StructuredText and HTML """
    5.96 +
    5.97 +    __implements__ = (IDocument, IMutableDocument,
    5.98 +                      PortalContent.__implements__,
    5.99 +                      DefaultDublinCoreImpl.__implements__)
   5.100 +
   5.101 +    meta_type = 'Document'
   5.102 +    effective_date = expiration_date = None
   5.103 +    cooked_text = text = text_format = ''
   5.104 +    _size = 0
   5.105 +    _isDiscussable = 1
   5.106 +
   5.107 +    _stx_level = 1                      # Structured text level
   5.108 +
   5.109 +    _last_safety_belt_editor = ''
   5.110 +    _last_safety_belt = ''
   5.111 +    _safety_belt = ''
   5.112 +
   5.113 +    security = ClassSecurityInfo()
   5.114 +
   5.115 +    def __init__(self, id, title='', description='', text_format='', text=''):
   5.116 +        DefaultDublinCoreImpl.__init__(self)
   5.117 +        self.id = id
   5.118 +        self.title = title
   5.119 +        self.description = description
   5.120 +        self._edit( text=text, text_format=text_format )
   5.121 +        self.setFormat( text_format )
   5.122 +
   5.123 +    security.declareProtected(ModifyPortalContent, 'manage_edit')
   5.124 +    manage_edit = DTMLFile('zmi_editDocument', _dtmldir)
   5.125 +
   5.126 +    security.declareProtected(ModifyPortalContent, 'manage_editDocument')
   5.127 +    def manage_editDocument( self, text, text_format, file='', REQUEST=None ):
   5.128 +        """ A ZMI (Zope Management Interface) level editing method """
   5.129 +        Document.edit( self, text_format=text_format, text=text, file=file )
   5.130 +        if REQUEST is not None:
   5.131 +            REQUEST['RESPONSE'].redirect(
   5.132 +                self.absolute_url()
   5.133 +                + '/manage_edit'
   5.134 +                + '?manage_tabs_message=Document+updated'
   5.135 +                )
   5.136 +
   5.137 +    def _edit(self, text, text_format='', safety_belt=''):
   5.138 +        """ Edit the Document and cook the body.
   5.139 +        """
   5.140 +        if not self._safety_belt_update(safety_belt=safety_belt):
   5.141 +            msg = ("Intervening changes from elsewhere detected."
   5.142 +                   " Please refetch the document and reapply your changes."
   5.143 +                   " (You may be able to recover your version using the"
   5.144 +                   " browser 'back' button, but will have to apply them"
   5.145 +                   " to a freshly fetched copy.)")
   5.146 +            raise EditingConflict(msg)
   5.147 +
   5.148 +        self.text = text
   5.149 +        self._size = len(text)
   5.150 +
   5.151 +        if not text_format:
   5.152 +            text_format = self.text_format
   5.153 +        if text_format == 'html':
   5.154 +            self.cooked_text = text
   5.155 +        elif text_format == 'plain':
   5.156 +            self.cooked_text = html_quote(text).replace('\n', '<br />')
   5.157 +        else:
   5.158 +            self.cooked_text = HTML(text, level=self._stx_level, header=0)
   5.159 +
   5.160 +    #
   5.161 +    #   IMutableDocument method
   5.162 +    #
   5.163 +
   5.164 +    security.declareProtected(ModifyPortalContent, 'edit')
   5.165 +    def edit(self, text_format, text, file='', safety_belt=''):
   5.166 +        """ Update the document.
   5.167 +
   5.168 +        To add webDav support, we need to check if the content is locked, and if
   5.169 +        so return ResourceLockedError if not, call _edit.
   5.170 +
   5.171 +        Note that this method expects to be called from a web form, and so
   5.172 +        disables header processing
   5.173 +        """
   5.174 +        self.failIfLocked()
   5.175 +        if file and (type(file) is not type('')):
   5.176 +            contents=file.read()
   5.177 +            if contents:
   5.178 +                text = contents
   5.179 +        if html_headcheck(text) and text_format.lower() != 'plain':
   5.180 +            text = bodyfinder(text)
   5.181 +        self.setFormat(text_format)
   5.182 +        self._edit(text=text, text_format=text_format, safety_belt=safety_belt)
   5.183 +        self.reindexObject()
   5.184 +
   5.185 +    security.declareProtected(ModifyPortalContent, 'setMetadata')
   5.186 +    def setMetadata(self, headers):
   5.187 +        headers['Format'] = self.Format()
   5.188 +        new_subject = keywordsplitter(headers)
   5.189 +        headers['Subject'] = new_subject or self.Subject()
   5.190 +        new_contrib = contributorsplitter(headers)
   5.191 +        headers['Contributors'] = new_contrib or self.Contributors()
   5.192 +        haveheader = headers.has_key
   5.193 +        for key, value in self.getMetadataHeaders():
   5.194 +            if not haveheader(key):
   5.195 +                headers[key] = value
   5.196 +        self._editMetadata(title=headers['Title'],
   5.197 +                          subject=headers['Subject'],
   5.198 +                          description=headers['Description'],
   5.199 +                          contributors=headers['Contributors'],
   5.200 +                          effective_date=headers['Effective_date'],
   5.201 +                          expiration_date=headers['Expiration_date'],
   5.202 +                          format=headers['Format'],
   5.203 +                          language=headers['Language'],
   5.204 +                          rights=headers['Rights'],
   5.205 +                          )
   5.206 +
   5.207 +    security.declarePrivate('guessFormat')
   5.208 +    def guessFormat(self, text):
   5.209 +        """ Simple stab at guessing the inner format of the text """
   5.210 +        if html_headcheck(text): return 'html'
   5.211 +        else: return 'structured-text'
   5.212 +
   5.213 +    security.declarePrivate('handleText')
   5.214 +    def handleText(self, text, format=None, stx_level=None):
   5.215 +        """ Handles the raw text, returning headers, body, format """
   5.216 +        headers = {}
   5.217 +        if not format:
   5.218 +            format = self.guessFormat(text)
   5.219 +        if format == 'html':
   5.220 +            parser = SimpleHTMLParser()
   5.221 +            parser.feed(text)
   5.222 +            headers.update(parser.metatags)
   5.223 +            if parser.title:
   5.224 +                headers['Title'] = parser.title
   5.225 +            body = bodyfinder(text)
   5.226 +        else:
   5.227 +            headers, body = parseHeadersBody(text, headers)
   5.228 +            if stx_level:
   5.229 +                self._stx_level = stx_level
   5.230 +        return headers, body, format
   5.231 +
   5.232 +    security.declarePublic( 'getMetadataHeaders' )
   5.233 +    def getMetadataHeaders(self):
   5.234 +        """Return RFC-822-style header spec."""
   5.235 +        hdrlist = DefaultDublinCoreImpl.getMetadataHeaders(self)
   5.236 +        hdrlist.append( ('SafetyBelt', self._safety_belt) )
   5.237 +        return hdrlist
   5.238 +
   5.239 +    security.declarePublic( 'SafetyBelt' )
   5.240 +    def SafetyBelt(self):
   5.241 +        """Return the current safety belt setting.
   5.242 +        For web form hidden button."""
   5.243 +        return self._safety_belt
   5.244 +
   5.245 +    def _safety_belt_update(self, safety_belt=''):
   5.246 +        """Check validity of safety belt and update tracking if valid.
   5.247 +
   5.248 +        Return 0 if safety belt is invalid, 1 otherwise.
   5.249 +
   5.250 +        Note that the policy is deliberately lax if no safety belt value is
   5.251 +        present - "you're on your own if you don't use your safety belt".
   5.252 +
   5.253 +        When present, either the safety belt token:
   5.254 +         - ... is the same as the current one given out, or
   5.255 +         - ... is the same as the last one given out, and the person doing the
   5.256 +           edit is the same as the last editor."""
   5.257 +
   5.258 +        this_belt = safety_belt
   5.259 +        this_user = getSecurityManager().getUser().getId()
   5.260 +
   5.261 +        if (# we have a safety belt value:
   5.262 +            this_belt
   5.263 +            # and the current object has a safety belt (ie - not freshly made)
   5.264 +            and (self._safety_belt is not None)
   5.265 +            # and the safety belt doesn't match the current one:
   5.266 +            and (this_belt != self._safety_belt)
   5.267 +            # and safety belt and user don't match last safety belt and user:
   5.268 +            and not ((this_belt == self._last_safety_belt)
   5.269 +                     and (this_user == self._last_safety_belt_editor))):
   5.270 +            # Fail.
   5.271 +            return 0
   5.272 +
   5.273 +        # We qualified - either:
   5.274 +        #  - the edit was submitted with safety belt stripped, or
   5.275 +        #  - the current safety belt was used, or
   5.276 +        #  - the last one was reused by the last person who did the last edit.
   5.277 +        # In any case, update the tracking.
   5.278 +
   5.279 +        self._last_safety_belt_editor = this_user
   5.280 +        self._last_safety_belt = this_belt
   5.281 +        self._safety_belt = str(self._p_mtime)
   5.282 +
   5.283 +        return 1
   5.284 +
   5.285 +    ### Content accessor methods
   5.286 +
   5.287 +    #
   5.288 +    #   IContentish method
   5.289 +    #
   5.290 +
   5.291 +    security.declareProtected(View, 'SearchableText')
   5.292 +    def SearchableText(self):
   5.293 +        """ Used by the catalog for basic full text indexing """
   5.294 +        return "%s %s %s" % ( self.Title()
   5.295 +                            , self.Description()
   5.296 +                            , self.EditableBody()
   5.297 +                            )
   5.298 +
   5.299 +    #
   5.300 +    #   IDocument methods
   5.301 +    #
   5.302 +
   5.303 +    security.declareProtected(View, 'CookedBody')
   5.304 +    def CookedBody(self, stx_level=None, setlevel=0):
   5.305 +        """ Get the "cooked" (ready for presentation) form of the text.
   5.306 +
   5.307 +        The prepared basic rendering of an object.  For Documents, this
   5.308 +        means pre-rendered structured text, or what was between the
   5.309 +        <BODY> tags of HTML.
   5.310 +
   5.311 +        If the format is html, and 'stx_level' is not passed in or is the
   5.312 +        same as the object's current settings, return the cached cooked
   5.313 +        text.  Otherwise, recook.  If we recook and 'setlevel' is true,
   5.314 +        then set the recooked text and stx_level on the object.
   5.315 +        """
   5.316 +        if (self.text_format == 'html' or self.text_format == 'plain'
   5.317 +            or (stx_level is None)
   5.318 +            or (stx_level == self._stx_level)):
   5.319 +            return self.cooked_text
   5.320 +        else:
   5.321 +            cooked = HTML(self.text, level=stx_level, header=0)
   5.322 +            if setlevel:
   5.323 +                self._stx_level = stx_level
   5.324 +                self.cooked_text = cooked
   5.325 +            return cooked
   5.326 +
   5.327 +    security.declareProtected(View, 'EditableBody')
   5.328 +    def EditableBody(self):
   5.329 +        """ Get the "raw" (as edited) form of the text.
   5.330 +
   5.331 +        The editable body of text.  This is the raw structured text, or
   5.332 +        in the case of HTML, what was between the <BODY> tags.
   5.333 +        """
   5.334 +        return self.text
   5.335 +
   5.336 +    #
   5.337 +    #   IDublinCore method
   5.338 +    #
   5.339 +
   5.340 +    security.declareProtected(View, 'Format')
   5.341 +    def Format(self):
   5.342 +        """ Dublin Core Format element - resource format.
   5.343 +        """
   5.344 +        if self.text_format == 'html':
   5.345 +            return 'text/html'
   5.346 +        else:
   5.347 +            return 'text/plain'
   5.348 +
   5.349 +    #
   5.350 +    #   IMutableDublinCore method
   5.351 +    #
   5.352 +
   5.353 +    security.declareProtected(ModifyPortalContent, 'setFormat')
   5.354 +    def setFormat(self, format):
   5.355 +        """ Set text format and Dublin Core resource format.
   5.356 +        """
   5.357 +        value = str(format)
   5.358 +        old_value = self.text_format
   5.359 +
   5.360 +        if value == 'text/html' or value == 'html':
   5.361 +            self.text_format = 'html'
   5.362 +        elif value == 'text/plain':
   5.363 +            if self.text_format not in ('structured-text', 'plain'):
   5.364 +                self.text_format = 'structured-text'
   5.365 +        elif value == 'plain':
   5.366 +            self.text_format = 'plain'
   5.367 +        else:
   5.368 +            self.text_format = 'structured-text'
   5.369 +
   5.370 +        # Did the format change? We might need to re-cook the content.
   5.371 +        if value != old_value:
   5.372 +            if html_headcheck(self.text) and value != 'plain':
   5.373 +                self.text = bodyfinder(self.text)
   5.374 +
   5.375 +            self._edit( self.text
   5.376 +                      , text_format=self.text_format
   5.377 +                      , safety_belt=self._safety_belt
   5.378 +                      )
   5.379 +
   5.380 +    ## FTP handlers
   5.381 +    security.declareProtected(ModifyPortalContent, 'PUT')
   5.382 +    def PUT(self, REQUEST, RESPONSE):
   5.383 +        """ Handle HTTP (and presumably FTP?) PUT requests """
   5.384 +        self.dav__init(REQUEST, RESPONSE)
   5.385 +        self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1)
   5.386 +        body = REQUEST.get('BODY', '')
   5.387 +        headers, body, format = self.handleText(text=body)
   5.388 +        safety_belt = headers.get('SafetyBelt', '')
   5.389 +        if REQUEST.get_header('Content-Type', '') == 'text/html':
   5.390 +            text_format = 'html'
   5.391 +        else:
   5.392 +            text_format = format
   5.393 +
   5.394 +        try:
   5.395 +            self.setFormat(text_format)
   5.396 +            self.setMetadata(headers)
   5.397 +            self._edit(text=body, safety_belt=safety_belt)
   5.398 +        except EditingConflict, msg:
   5.399 +            # XXX Can we get an error msg through?  Should we be raising an
   5.400 +            #     exception, to be handled in the FTP mechanism?  Inquiring
   5.401 +            #     minds...
   5.402 +            transaction.abort()
   5.403 +            RESPONSE.setStatus(450)
   5.404 +            return RESPONSE
   5.405 +        except ResourceLockedError, msg:
   5.406 +            transaction.abort()
   5.407 +            RESPONSE.setStatus(423)
   5.408 +            return RESPONSE
   5.409 +
   5.410 +        RESPONSE.setStatus(204)
   5.411 +        self.reindexObject()
   5.412 +        return RESPONSE
   5.413 +
   5.414 +    _htmlsrc = (
   5.415 +        '<html>\n <head>\n'
   5.416 +        ' <title>%(title)s</title>\n'
   5.417 +       '%(metatags)s\n'
   5.418 +        ' </head>\n'
   5.419 +        ' <body>%(body)s</body>\n'
   5.420 +        '</html>\n'
   5.421 +        )
   5.422 +
   5.423 +    security.declareProtected(View, 'manage_FTPget')
   5.424 +    def manage_FTPget(self):
   5.425 +        "Get the document body for FTP download (also used for the WebDAV SRC)"
   5.426 +        if self.Format() == 'text/html':
   5.427 +            ti = self.getTypeInfo()
   5.428 +            method_id = ti and ti.queryMethodID('gethtml', context=self)
   5.429 +            if method_id:
   5.430 +                method = getattr(self, method_id)
   5.431 +                if getattr(aq_base(method), 'isDocTemp', 0):
   5.432 +                    bodytext = method(self, self.REQUEST)
   5.433 +                else:
   5.434 +                    bodytext = method()
   5.435 +            else:
   5.436 +                # Use the old code as fallback. May be removed some day.
   5.437 +                hdrlist = self.getMetadataHeaders()
   5.438 +                hdrtext = ''
   5.439 +                for name, content in hdrlist:
   5.440 +                    if name.lower() == 'title':
   5.441 +                        continue
   5.442 +                    else:
   5.443 +                        hdrtext = '%s\n <meta name="%s" content="%s" />' % (
   5.444 +                            hdrtext, name, content)
   5.445 +
   5.446 +                bodytext = self._htmlsrc % {
   5.447 +                    'title': self.Title(),
   5.448 +                    'metatags': hdrtext,
   5.449 +                    'body': self.EditableBody(),
   5.450 +                    }
   5.451 +        else:
   5.452 +            hdrlist = self.getMetadataHeaders()
   5.453 +            hdrtext = formatRFC822Headers( hdrlist )
   5.454 +            bodytext = '%s\r\n\r\n%s' % ( hdrtext, self.text )
   5.455 +
   5.456 +        return bodytext
   5.457 +
   5.458 +    security.declareProtected(View, 'get_size')
   5.459 +    def get_size( self ):
   5.460 +        """ Used for FTP and apparently the ZMI now too """
   5.461 +        return self._size
   5.462 +
   5.463 +InitializeClass(Document)
     6.1 new file mode 100644
     6.2 --- /dev/null
     6.3 +++ b/DublinCore.py
     6.4 @@ -0,0 +1,502 @@
     6.5 +##############################################################################
     6.6 +#
     6.7 +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
     6.8 +#
     6.9 +# This software is subject to the provisions of the Zope Public License,
    6.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
    6.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
    6.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    6.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
    6.14 +# FOR A PARTICULAR PURPOSE.
    6.15 +#
    6.16 +##############################################################################
    6.17 +""" Dublin Core support for content types.
    6.18 +
    6.19 +$Id: DublinCore.py 36913 2005-04-09 23:00:10Z slinkp $
    6.20 +"""
    6.21 +
    6.22 +from AccessControl import ClassSecurityInfo
    6.23 +from Acquisition import aq_base
    6.24 +from DateTime.DateTime import DateTime
    6.25 +from Globals import DTMLFile
    6.26 +from Globals import InitializeClass
    6.27 +from OFS.PropertyManager import PropertyManager
    6.28 +
    6.29 +from Products.CMFCore.interfaces.DublinCore import CatalogableDublinCore
    6.30 +from Products.CMFCore.interfaces.DublinCore import DublinCore
    6.31 +from Products.CMFCore.interfaces.DublinCore import MutableDublinCore
    6.32 +from Products.CMFCore.utils import getToolByName
    6.33 +
    6.34 +from permissions import ModifyPortalContent
    6.35 +from permissions import View
    6.36 +from utils import tuplize
    6.37 +from utils import _dtmldir
    6.38 +from utils import semi_split
    6.39 +
    6.40 +_marker=[]
    6.41 +
    6.42 +# For http://www.zope.org/Collectors/CMF/325
    6.43 +# We only really need this once, at startup.
    6.44 +_zone = DateTime().timezone()
    6.45 +    
    6.46 +class DefaultDublinCoreImpl( PropertyManager ):
    6.47 +    """ Mix-in class which provides Dublin Core methods.
    6.48 +    """
    6.49 +    __implements__ = DublinCore, CatalogableDublinCore, MutableDublinCore
    6.50 +
    6.51 +    security = ClassSecurityInfo()
    6.52 +
    6.53 +    def __init__( self
    6.54 +                , title=''
    6.55 +                , subject=()
    6.56 +                , description=''
    6.57 +                , contributors=()
    6.58 +                , effective_date=None
    6.59 +                , expiration_date=None
    6.60 +                , format='text/html'
    6.61 +                , language=''
    6.62 +                , rights=''
    6.63 +                ):
    6.64 +        now = DateTime()
    6.65 +        self.creation_date = now
    6.66 +        self.modification_date = now
    6.67 +        self.creators = ()
    6.68 +        self._editMetadata( title
    6.69 +                          , subject
    6.70 +                          , description
    6.71 +                          , contributors
    6.72 +                          , effective_date
    6.73 +                          , expiration_date
    6.74 +                          , format
    6.75 +                          , language
    6.76 +                          , rights
    6.77 +                          )
    6.78 +
    6.79 +    #
    6.80 +    #  Set-modification-date-related methods.
    6.81 +    #  In DefaultDublinCoreImpl for lack of a better place.
    6.82 +    #
    6.83 +
    6.84 +    # Class variable default for an upgrade.
    6.85 +    modification_date = None
    6.86 +
    6.87 +    security.declarePrivate('notifyModified')
    6.88 +    def notifyModified(self):
    6.89 +        """ Take appropriate action after the resource has been modified.
    6.90 +
    6.91 +        Update creators and modification_date.
    6.92 +        """
    6.93 +        self.addCreator()
    6.94 +        self.setModificationDate()
    6.95 +
    6.96 +    security.declareProtected(ModifyPortalContent, 'addCreator')
    6.97 +    def addCreator(self, creator=None):
    6.98 +        """ Add creator to Dublin Core creators.
    6.99 +        """
   6.100 +        if creator is None:
   6.101 +            mtool = getToolByName(self, 'portal_membership', None)
   6.102 +            creator = mtool and mtool.getAuthenticatedMember().getId()
   6.103 +
   6.104 +        # call self.listCreators() to make sure self.creators exists
   6.105 +        if creator and not creator in self.listCreators():
   6.106 +            self.creators = self.creators + (creator, )
   6.107 +
   6.108 +    security.declareProtected(ModifyPortalContent, 'setModificationDate')
   6.109 +    def setModificationDate(self, modification_date=None):
   6.110 +        """ Set the date when the resource was last modified.
   6.111 +
   6.112 +        When called without an argument, sets the date to now.
   6.113 +        """
   6.114 +        if modification_date is None:
   6.115 +            self.modification_date = DateTime()
   6.116 +        else:
   6.117 +            self.modification_date = self._datify(modification_date)
   6.118 +
   6.119 +    #
   6.120 +    #  DublinCore interface query methods
   6.121 +    #
   6.122 +    security.declareProtected(View, 'Title')
   6.123 +    def Title( self ):
   6.124 +        """ Dublin Core Title element - resource name.
   6.125 +        """
   6.126 +        return self.title
   6.127 +
   6.128 +    security.declareProtected(View, 'listCreators')
   6.129 +    def listCreators(self):
   6.130 +        """ List Dublin Core Creator elements - resource authors.
   6.131 +        """
   6.132 +        if not hasattr(aq_base(self), 'creators'):
   6.133 +            # for content created with CMF versions before 1.5
   6.134 +            owner_tuple = self.getOwnerTuple()
   6.135 +            if owner_tuple:
   6.136 +                self.creators = (owner_tuple[1],)
   6.137 +            else:
   6.138 +                self.creators = ()
   6.139 +        return self.creators
   6.140 +
   6.141 +    security.declareProtected(View, 'Creator')
   6.142 +    def Creator(self):
   6.143 +        """ Dublin Core Creator element - resource author.
   6.144 +        """
   6.145 +        creators = self.listCreators()
   6.146 +        return creators and creators[0] or ''
   6.147 +
   6.148 +    security.declareProtected(View, 'Subject')
   6.149 +    def Subject( self ):
   6.150 +        """ Dublin Core Subject element - resource keywords.
   6.151 +        """
   6.152 +        return getattr( self, 'subject', () ) # compensate for *old* content
   6.153 +
   6.154 +    security.declareProtected(View, 'Description')
   6.155 +    def Description( self ):
   6.156 +        """ Dublin Core Description element - resource summary.
   6.157 +        """
   6.158 +        return self.description
   6.159 +
   6.160 +    security.declareProtected(View, 'Publisher')
   6.161 +    def Publisher( self ):
   6.162 +        """ Dublin Core Publisher element - resource publisher.
   6.163 +        """
   6.164 +        tool = getToolByName(self, 'portal_metadata', None)
   6.165 +
   6.166 +        if tool is not None:
   6.167 +            return tool.getPublisher()
   6.168 +
   6.169 +        return 'No publisher'
   6.170 +
   6.171 +    security.declareProtected(View, 'listContributors')
   6.172 +    def listContributors(self):
   6.173 +        """ Dublin Core Contributor elements - resource collaborators.
   6.174 +        """
   6.175 +        return self.contributors
   6.176 +
   6.177 +    security.declareProtected(View, 'Contributors')
   6.178 +    def Contributors(self):
   6.179 +        """ Deprecated alias of listContributors.
   6.180 +        """
   6.181 +        return self.listContributors()
   6.182 +
   6.183 +    security.declareProtected(View, 'Date')
   6.184 +    def Date( self ):
   6.185 +        """ Dublin Core Date element - default date.
   6.186 +        """
   6.187 +        # Return effective_date if set, modification date otherwise
   6.188 +        date = getattr(self, 'effective_date', None )
   6.189 +        if date is None:
   6.190 +            date = self.modified()
   6.191 +        return date.toZone(_zone).ISO()
   6.192 +
   6.193 +    security.declareProtected(View, 'CreationDate')
   6.194 +    def CreationDate( self ):
   6.195 +        """ Dublin Core Date element - date resource created.
   6.196 +        """
   6.197 +        # return unknown if never set properly
   6.198 +        if self.creation_date:
   6.199 +            return self.creation_date.toZone(_zone).ISO()
   6.200 +        else:
   6.201 +            return 'Unknown'
   6.202 +
   6.203 +    security.declareProtected(View, 'EffectiveDate')
   6.204 +    def EffectiveDate( self ):
   6.205 +        """ Dublin Core Date element - date resource becomes effective.
   6.206 +        """
   6.207 +        ed = getattr( self, 'effective_date', None )
   6.208 +        return ed and ed.toZone(_zone).ISO() or 'None'
   6.209 +
   6.210 +    security.declareProtected(View, 'ExpirationDate')
   6.211 +    def ExpirationDate( self ):
   6.212 +        """ Dublin Core Date element - date resource expires.
   6.213 +        """
   6.214 +        ed = getattr( self, 'expiration_date', None )
   6.215 +        return ed and ed.toZone(_zone).ISO() or 'None'
   6.216 +
   6.217 +    security.declareProtected(View, 'ModificationDate')
   6.218 +    def ModificationDate( self ):
   6.219 +        """ Dublin Core Date element - date resource last modified.
   6.220 +        """
   6.221 +        return self.modified().toZone(_zone).ISO()
   6.222 +
   6.223 +    security.declareProtected(View, 'Type')
   6.224 +    def Type( self ):
   6.225 +        """ Dublin Core Type element - resource type.
   6.226 +        """
   6.227 +        if hasattr(aq_base(self), 'getTypeInfo'):
   6.228 +            ti = self.getTypeInfo()
   6.229 +            if ti is not None:
   6.230 +                return ti.Title()
   6.231 +        return self.meta_type
   6.232 +
   6.233 +    security.declareProtected(View, 'Format')
   6.234 +    def Format( self ):
   6.235 +        """ Dublin Core Format element - resource format.
   6.236 +        """
   6.237 +        return self.format
   6.238 +
   6.239 +    security.declareProtected(View, 'Identifier')
   6.240 +    def Identifier( self ):
   6.241 +        """ Dublin Core Identifier element - resource ID.
   6.242 +        """
   6.243 +        # XXX: fixme using 'portal_metadata' (we need to prepend the
   6.244 +        #      right prefix to self.getPhysicalPath().
   6.245 +        return self.absolute_url()
   6.246 +
   6.247 +    security.declareProtected(View, 'Language')
   6.248 +    def Language( self ):
   6.249 +        """ Dublin Core Language element - resource language.
   6.250 +        """
   6.251 +        return self.language
   6.252 +
   6.253 +    security.declareProtected(View, 'Rights')
   6.254 +    def Rights( self ):
   6.255 +        """ Dublin Core Rights element - resource copyright.
   6.256 +        """
   6.257 +        return self.rights
   6.258 +
   6.259 +    #
   6.260 +    #  DublinCore utility methods
   6.261 +    #
   6.262 +    def content_type( self ):
   6.263 +        """ WebDAV needs this to do the Right Thing (TM).
   6.264 +        """
   6.265 +        return self.Format()
   6.266 +
   6.267 +    __FLOOR_DATE = DateTime( 1970, 0 ) # always effective
   6.268 +
   6.269 +    security.declareProtected(View, 'isEffective')
   6.270 +    def isEffective( self, date ):
   6.271 +        """ Is the date within the resource's effective range?
   6.272 +        """
   6.273 +        pastEffective = ( self.effective_date is None
   6.274 +                       or self.effective_date <= date )
   6.275 +        beforeExpiration = ( self.expiration_date is None
   6.276 +                          or self.expiration_date >= date )
   6.277 +        return pastEffective and beforeExpiration
   6.278 +
   6.279 +    #
   6.280 +    #  CatalogableDublinCore methods
   6.281 +    #
   6.282 +    security.declareProtected(View, 'created')
   6.283 +    def created( self ):
   6.284 +        """ Dublin Core Date element - date resource created.
   6.285 +        """
   6.286 +        # allow for non-existent creation_date, existed always
   6.287 +        date = getattr( self, 'creation_date', None )
   6.288 +        return date is None and self.__FLOOR_DATE or date
   6.289 +
   6.290 +    security.declareProtected(View, 'effective')
   6.291 +    def effective( self ):
   6.292 +        """ Dublin Core Date element - date resource becomes effective.
   6.293 +        """
   6.294 +        marker = []
   6.295 +        date = getattr( self, 'effective_date', marker )
   6.296 +        if date is marker:
   6.297 +            date = getattr( self, 'creation_date', None )
   6.298 +        return date is None and self.__FLOOR_DATE or date
   6.299 +
   6.300 +    __CEILING_DATE = DateTime( 2500, 0 ) # never expires
   6.301 +
   6.302 +    security.declareProtected(View, 'expires')
   6.303 +    def expires( self ):
   6.304 +        """ Dublin Core Date element - date resource expires.
   6.305 +        """
   6.306 +        date = getattr( self, 'expiration_date', None )
   6.307 +        return date is None and self.__CEILING_DATE or date
   6.308 +
   6.309 +    security.declareProtected(View, 'modified')
   6.310 +    def modified( self ):
   6.311 +        """ Dublin Core Date element - date resource last modified.
   6.312 +        """
   6.313 +        date = self.modification_date
   6.314 +        if date is None:
   6.315 +            # Upgrade.
   6.316 +            date = self.bobobase_modification_time()
   6.317 +            self.modification_date = date
   6.318 +        return date
   6.319 +
   6.320 +    security.declareProtected(View, 'getMetadataHeaders')
   6.321 +    def getMetadataHeaders( self ):
   6.322 +        """ Return RFC-822-style headers.
   6.323 +        """
   6.324 +        hdrlist = []
   6.325 +        hdrlist.append( ( 'Title', self.Title() ) )
   6.326 +        hdrlist.append( ( 'Subject', ', '.join( self.Subject() ) ) )
   6.327 +        hdrlist.append( ( 'Publisher', self.Publisher() ) )
   6.328 +        hdrlist.append( ( 'Description', self.Description() ) )
   6.329 +        hdrlist.append( ( 'Contributors', '; '.join( self.Contributors() ) ) )
   6.330 +        hdrlist.append( ( 'Effective_date', self.EffectiveDate() ) )
   6.331 +        hdrlist.append( ( 'Expiration_date', self.ExpirationDate() ) )
   6.332 +        hdrlist.append( ( 'Type', self.Type() ) )
   6.333 +        hdrlist.append( ( 'Format', self.Format() ) )
   6.334 +        hdrlist.append( ( 'Language', self.Language() ) )
   6.335 +        hdrlist.append( ( 'Rights', self.Rights() ) )
   6.336 +        return hdrlist
   6.337 +
   6.338 +    #
   6.339 +    #  MutableDublinCore methods
   6.340 +    #
   6.341 +    security.declarePrivate( '_datify' )
   6.342 +    def _datify( self, attrib ):
   6.343 +        if attrib == 'None':
   6.344 +            attrib = None
   6.345 +        elif not isinstance( attrib, DateTime ):
   6.346 +            if attrib is not None:
   6.347 +                attrib = DateTime( attrib )
   6.348 +        return attrib
   6.349 +
   6.350 +    security.declareProtected(ModifyPortalContent, 'setTitle')
   6.351 +    def setTitle( self, title ):
   6.352 +        """ Set Dublin Core Title element - resource name.
   6.353 +        """
   6.354 +        self.title = title
   6.355 +
   6.356 +    security.declareProtected(ModifyPortalContent, 'setCreators')
   6.357 +    def setCreators(self, creators):
   6.358 +        """ Set Dublin Core Creator elements - resource authors.
   6.359 +        """
   6.360 +        self.creators = tuplize('creators', creators)
   6.361 +
   6.362 +    security.declareProtected(ModifyPortalContent, 'setSubject')
   6.363 +    def setSubject( self, subject ):
   6.364 +        """ Set Dublin Core Subject element - resource keywords.
   6.365 +        """
   6.366 +        self.subject = tuplize( 'subject', subject )
   6.367 +
   6.368 +    security.declareProtected(ModifyPortalContent, 'setDescription')
   6.369 +    def setDescription( self, description ):
   6.370 +        """ Set Dublin Core Description element - resource summary.
   6.371 +        """
   6.372 +        self.description = description
   6.373 +
   6.374 +    security.declareProtected(ModifyPortalContent, 'setContributors')
   6.375 +    def setContributors( self, contributors ):
   6.376 +        """ Set Dublin Core Contributor elements - resource collaborators.
   6.377 +        """
   6.378 +        # XXX: fixme
   6.379 +        self.contributors = tuplize('contributors', contributors, semi_split)
   6.380 +
   6.381 +    security.declareProtected(ModifyPortalContent, 'setEffectiveDate')
   6.382 +    def setEffectiveDate( self, effective_date ):
   6.383 +        """ Set Dublin Core Date element - date resource becomes effective.
   6.384 +        """
   6.385 +        self.effective_date = self._datify( effective_date )
   6.386 +
   6.387 +    security.declareProtected(ModifyPortalContent, 'setExpirationDate')
   6.388 +    def setExpirationDate( self, expiration_date ):
   6.389 +        """ Set Dublin Core Date element - date resource expires.
   6.390 +        """
   6.391 +        self.expiration_date = self._datify( expiration_date )
   6.392 +
   6.393 +    security.declareProtected(ModifyPortalContent, 'setFormat')
   6.394 +    def setFormat( self, format ):
   6.395 +        """ Set Dublin Core Format element - resource format.
   6.396 +        """
   6.397 +        self.format = format
   6.398 +
   6.399 +    security.declareProtected(ModifyPortalContent, 'setLanguage')
   6.400 +    def setLanguage( self, language ):
   6.401 +        """ Set Dublin Core Language element - resource language.
   6.402 +        """
   6.403 +        self.language = language
   6.404 +
   6.405 +    security.declareProtected(ModifyPortalContent, 'setRights')
   6.406 +    def setRights( self, rights ):
   6.407 +        """ Set Dublin Core Rights element - resource copyright.
   6.408 +        """
   6.409 +        self.rights = rights
   6.410 +
   6.411 +    #
   6.412 +    #  Management tab methods
   6.413 +    #
   6.414 +
   6.415 +    security.declarePrivate( '_editMetadata' )
   6.416 +    def _editMetadata( self
   6.417 +                     , title=_marker
   6.418 +                     , subject=_marker
   6.419 +                     , description=_marker
   6.420 +                     , contributors=_marker
   6.421 +                     , effective_date=_marker
   6.422 +                     , expiration_date=_marker
   6.423 +                     , format=_marker
   6.424 +                     , language=_marker
   6.425 +                     , rights=_marker
   6.426 +                     ):
   6.427 +        """ Update the editable metadata for this resource.
   6.428 +        """
   6.429 +        if title is not _marker:
   6.430 +            self.setTitle( title )
   6.431 +        if subject is not _marker:
   6.432 +            self.setSubject( subject )
   6.433 +        if description is not _marker:
   6.434 +            self.setDescription( description )
   6.435 +        if contributors is not _marker:
   6.436 +            self.setContributors( contributors )
   6.437 +        if effective_date is not _marker:
   6.438 +            self.setEffectiveDate( effective_date )
   6.439 +        if expiration_date is not _marker:
   6.440 +            self.setExpirationDate( expiration_date )
   6.441 +        if format is not _marker:
   6.442 +            self.setFormat( format )
   6.443 +        if language is not _marker:
   6.444 +            self.setLanguage( language )
   6.445 +        if rights is not _marker:
   6.446 +            self.setRights( rights )
   6.447 +
   6.448 +    security.declareProtected(ModifyPortalContent, 'manage_metadata')
   6.449 +    manage_metadata = DTMLFile( 'zmi_metadata', _dtmldir )
   6.450 +
   6.451 +    security.declareProtected(ModifyPortalContent, 'manage_editMetadata')
   6.452 +    def manage_editMetadata( self
   6.453 +                           , title
   6.454 +                           , subject
   6.455 +                           , description
   6.456 +                           , contributors
   6.457 +                           , effective_date
   6.458 +                           , expiration_date
   6.459 +                           , format
   6.460 +                           , language
   6.461 +                           , rights
   6.462 +                           , REQUEST
   6.463 +                           ):
   6.464 +        """ Update metadata from the ZMI.
   6.465 +        """
   6.466 +        self._editMetadata( title, subject, description, contributors
   6.467 +                          , effective_date, expiration_date
   6.468 +                          , format, language, rights
   6.469 +                          )
   6.470 +        REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
   6.471 +                                + '/manage_metadata'
   6.472 +                                + '?manage_tabs_message=Metadata+updated.' )
   6.473 +
   6.474 +    security.declareProtected(ModifyPortalContent, 'editMetadata')
   6.475 +    def editMetadata(self
   6.476 +                   , title=''
   6.477 +                   , subject=()
   6.478 +                   , description=''
   6.479 +                   , contributors=()
   6.480 +                   , effective_date=None
   6.481 +                   , expiration_date=None
   6.482 +                   , format='text/html'
   6.483 +                   , language='en-US'
   6.484 +                   , rights=''
   6.485 +                    ):
   6.486 +        """
   6.487 +        Need to add check for webDAV locked resource for TTW methods.
   6.488 +        """
   6.489 +        # as per bug #69, we cant assume they use the webdav
   6.490 +        # locking interface, and fail gracefully if they dont
   6.491 +        if hasattr(self, 'failIfLocked'):
   6.492 +            self.failIfLocked()
   6.493 +
   6.494 +        self._editMetadata(title=title
   6.495 +                     , subject=subject
   6.496 +                     , description=description
   6.497 +                     , contributors=contributors
   6.498 +                     , effective_date=effective_date
   6.499 +                     , expiration_date=expiration_date
   6.500 +                     , format=format
   6.501 +                     , language=language
   6.502 +                     , rights=rights
   6.503 +                     )
   6.504 +        self.reindexObject()
   6.505 +
   6.506 +InitializeClass(DefaultDublinCoreImpl)
     7.1 new file mode 100644
     7.2 --- /dev/null
     7.3 +++ b/Extensions/Upgrade.py
     7.4 @@ -0,0 +1,59 @@
     7.5 +##############################################################################
     7.6 +#
     7.7 +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
     7.8 +#
     7.9 +# This software is subject to the provisions of the Zope Public License,
    7.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
    7.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
    7.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    7.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
    7.14 +# FOR A PARTICULAR PURPOSE.
    7.15 +#
    7.16 +##############################################################################
    7.17 +"""
    7.18 +    Utility functions for upgrading CMFDefault-based sites.
    7.19 +"""
    7.20 +
    7.21 +from Acquisition import aq_inner
    7.22 +
    7.23 +
    7.24 +def upgrade_decor_skins( self ):
    7.25 +    """
    7.26 +        Upgrade old skin diretories loaded from 'CMFDecor' to load from
    7.27 +        'CMFDefault' (and zap the 'zpt_images' one).
    7.28 +    """
    7.29 +    log = []
    7.30 +
    7.31 +    DELETED_SKINS = ( 'zpt_images' , )
    7.32 +
    7.33 +    MOVED_SKINS = ( 'zpt_content'
    7.34 +                  , 'zpt_control'
    7.35 +                  , 'zpt_generic'
    7.36 +                  )
    7.37 +
    7.38 +    skins_tool = aq_inner( self ).portal_skins # start from CMFSite!
    7.39 +
    7.40 +    for deleted in DELETED_SKINS:
    7.41 +
    7.42 +        try:
    7.43 +
    7.44 +            skins_tool._delObject( deleted )
    7.45 +
    7.46 +        except AttributeError:
    7.47 +            pass
    7.48 +
    7.49 +        else:
    7.50 +            log.append( 'Deleted CMFDecor skin directory: %s' % deleted )
    7.51 +
    7.52 +    for moved in MOVED_SKINS:
    7.53 +
    7.54 +        skin_dir = getattr( skins_tool, moved, None )
    7.55 +
    7.56 +        if skin_dir is not None:
    7.57 +
    7.58 +            skin_dir.manage_properties(
    7.59 +                dirpath='Products/CMFDefault/skins/%s' % moved )
    7.60 +            log.append( 'Updated CMFDecor skin directory to CMFDefault: %s'
    7.61 +                      % moved )
    7.62 +
    7.63 +    return '\n'.join(log)
     8.1 new file mode 100644
     8.2 --- /dev/null
     8.3 +++ b/Extensions/fix_cmf_permissions.py
     8.4 @@ -0,0 +1,16 @@
     8.5 +
     8.6 +
     8.7 +def fix_cmf_permissions(self):
     8.8 +    '''
     8.9 +    Changes the permissions on each member folder to normal settings.
    8.10 +    '''
    8.11 +    count = 0
    8.12 +    m = self.Members
    8.13 +    for v in m.objectValues():
    8.14 +        if hasattr(v, '_View_Permission'):
    8.15 +            del v._View_Permission
    8.16 +        if hasattr(v, '_Access_contents_information_Permission'):
    8.17 +            del v._Access_contents_information_Permission
    8.18 +        if v._p_changed:
    8.19 +            count = count + 1
    8.20 +    return 'Changed permissions on %d objects.' % count
     9.1 new file mode 100644
     9.2 --- /dev/null
     9.3 +++ b/Extensions/migrate_ptk.py
     9.4 @@ -0,0 +1,351 @@
     9.5 +from Acquisition import aq_base, aq_inner, aq_parent
     9.6 +from Globals import PersistentMapping
     9.7 +import sys
     9.8 +
     9.9 +
    9.10 +#
    9.11 +# Routines generally useful for migration purposes.
    9.12 +#
    9.13 +
    9.14 +
    9.15 +class Converter:
    9.16 +    def allowDescendChildren(self): raise 'Not implemented'
    9.17 +    def convert(self, ob): raise 'Not implemented'
    9.18 +    def showDuplicationError(self): raise 'Not implemented'
    9.19 +
    9.20 +
    9.21 +class Migrator:
    9.22 +
    9.23 +    def __init__(self, conversions, skip):
    9.24 +        self.conversions = conversions
    9.25 +        self.skip = skip
    9.26 +        self.visited_folders = []
    9.27 +        self.warnings = []
    9.28 +        self.copied = []
    9.29 +        self.skipped = []
    9.30 +
    9.31 +    def migrateObjectManager(self, src_folder, dst_folder, place=()):
    9.32 +        self.visited_folders.append( '/'.join(place) )
    9.33 +        for id, s_ob in src_folder.objectItems():
    9.34 +            d_ob = getattr(dst_folder, id, None)
    9.35 +            to_store = self.migrateObject(id, s_ob, d_ob, dst_folder,
    9.36 +                                          place + (id,))
    9.37 +            if to_store is not None:
    9.38 +                owner = getattr(to_store, '_owner', None)
    9.39 +                if hasattr(dst_folder, '_setObject'):
    9.40 +                    dst_folder._setObject(id, to_store)
    9.41 +                else:
    9.42 +                    setattr(dst_folder, id, to_store)
    9.43 +                if owner is not None:
    9.44 +                    # Retain ownership.
    9.45 +                    to_store._owner = owner
    9.46 +
    9.47 +    def migrateDiscussionContainer(self, src_folder, dst_folder, place=()):
    9.48 +        self.visited_folders.append( '/'.join(place) )
    9.49 +        dst_container = getattr(dst_folder, '_container', None)
    9.50 +        if dst_container is None:
    9.51 +            dst_container = dst_folder._container = PersistentMapping()
    9.52 +        for id, s_ob in src_folder._container.items():
    9.53 +            d_ob = dst_container.get(id)
    9.54 +            to_store = self.migrateObject(id, s_ob, d_ob, dst_folder,
    9.55 +                                          place + (id,))
    9.56 +            if to_store is not None:
    9.57 +                dst_container[id] = aq_base(to_store)
    9.58 +
    9.59 +    def migratePossibleContainer(self, s_ob, d_ob, place):
    9.60 +        base_ob = aq_base(s_ob)
    9.61 +        if hasattr(base_ob, 'objectItems'):
    9.62 +            self.migrateObjectManager(s_ob, d_ob, place)
    9.63 +        elif hasattr(base_ob, '_container'):
    9.64 +            self.migrateDiscussionContainer(s_ob, d_ob, place)
    9.65 +
    9.66 +    def migrateObject(self, id, s_ob, d_ob, dst_folder, place):
    9.67 +        # Doesn't store changes, only returns the
    9.68 +        # object to store.
    9.69 +        conversions = self.conversions
    9.70 +        klass = s_ob.__class__
    9.71 +        descend_ok = 1
    9.72 +        base_ob = aq_base(s_ob)
    9.73 +        to_store = None
    9.74 +        pathname = '/'.join(place)
    9.75 +        if self.skip.has_key(id):
    9.76 +            # Don't migrate objects by this name, but we can still
    9.77 +            # migrate subobjects.
    9.78 +            descend_ok = self.skip[id]
    9.79 +            if descend_ok and d_ob is None:
    9.80 +                descend_ok = 0
    9.81 +            self.skipped.append(pathname +
    9.82 +                               (descend_ok and ' (descended)' or ' '))
    9.83 +        elif d_ob is not None:
    9.84 +            # The dest already has something with this ID.
    9.85 +            descend_ok = 1
    9.86 +            show_message = 1
    9.87 +            converter = conversions.get(klass, None)
    9.88 +            if converter is not None:
    9.89 +                descend_ok = converter.allowDescendChildren()
    9.90 +                show_message = converter.showDuplicationError()
    9.91 +            if show_message:
    9.92 +                self.warnings.append('Already existed: %s' % pathname)
    9.93 +        elif conversions.has_key(klass):
    9.94 +            # Invoke the appropriate converter.
    9.95 +            converter = conversions[klass]
    9.96 +            to_store = converter.convert(s_ob)
    9.97 +            self.copied.append(pathname)
    9.98 +        elif hasattr(base_ob, '_getCopy'):
    9.99 +            # Make a direct copy.
   9.100 +            to_store = s_ob._getCopy(dst_folder)
   9.101 +            self.warnings.append('Copied %s directly.' % pathname)
   9.102 +            descend_ok = 0
   9.103 +        else:
   9.104 +            # No way to copy.
   9.105 +            descend_ok = 0
   9.106 +            self.warnings.append('Could not copy %s' % pathname)
   9.107 +        if descend_ok:
   9.108 +            if to_store is not None:
   9.109 +                d_ob = to_store
   9.110 +            if d_ob is not None:
   9.111 +                try: d_ob._p_jar = dst_folder._p_jar
   9.112 +                except: pass
   9.113 +                self.migratePossibleContainer(s_ob, d_ob, place)
   9.114 +        return to_store
   9.115 +
   9.116 +
   9.117 +class SimpleClassConverter (Converter):
   9.118 +    def __init__(self, to_class, descend, show_dup=1):
   9.119 +        self._klass = to_class
   9.120 +        self._descend = descend
   9.121 +        self._show_dup = show_dup
   9.122 +
   9.123 +    def allowDescendChildren(self):
   9.124 +        return self._descend
   9.125 +
   9.126 +    def showDuplicationError(self):
   9.127 +        return self._show_dup
   9.128 +
   9.129 +    def convert(self, ob):
   9.130 +        # Creates a copy of ob without its children.
   9.131 +        ob = aq_base(ob)
   9.132 +        k = self._klass
   9.133 +        if hasattr(k, '__basicnew__'):
   9.134 +            newob = k.__basicnew__()
   9.135 +        else:
   9.136 +            newob = new.instance(k, {})
   9.137 +        id = ob.id
   9.138 +        if callable(id):
   9.139 +            id = id()
   9.140 +        try: newob._setId(id)
   9.141 +        except AttributeError: newob.id = id
   9.142 +        newob.__dict__.update(ob.__dict__)
   9.143 +        if hasattr(newob, '_objects'):
   9.144 +            # Clear the children.
   9.145 +            for info in newob._objects:
   9.146 +                del newob.__dict__[info['id']]
   9.147 +            newob._objects = ()
   9.148 +        if hasattr(newob, '_container'):
   9.149 +            # Clear the children.
   9.150 +            newob._container = PersistentMapping()
   9.151 +        return newob
   9.152 +
   9.153 +TupleType = type(())
   9.154 +
   9.155 +def setupDirectConversion(old_prod, new_prod, modname, classname,
   9.156 +                          conversions, descend=1, show_dup=1):
   9.157 +    try:
   9.158 +        old_module = sys.modules['Products.' + old_prod + '.' + modname]
   9.159 +        new_module = sys.modules['Products.' + new_prod + '.' + modname]
   9.160 +        old_class = getattr(old_module, classname)
   9.161 +        new_class = getattr(new_module, classname)
   9.162 +        conversions[old_class] = SimpleClassConverter(new_class, descend,
   9.163 +                                                      show_dup)
   9.164 +    except:
   9.165 +        print 'Failed to set up conversion', old_prod, new_prod, modname, classname
   9.166 +        import traceback
   9.167 +        traceback.print_exc()
   9.168 +
   9.169 +
   9.170 +def setupDirectConversions(old_prod, new_prod, modnames, conversions):
   9.171 +    for info in modnames:
   9.172 +        if type(info) is TupleType:
   9.173 +            modname, classname = info
   9.174 +        else:
   9.175 +            modname = classname = info
   9.176 +        setupDirectConversion(old_prod, new_prod, modname, classname,
   9.177 +                              conversions)
   9.178 +
   9.179 +
   9.180 +def _cleanupOwnership(ob, res, cleanup_children):
   9.181 +    '''
   9.182 +    If the user name of the owner of the referenced object
   9.183 +    is not found in its current user database but is found
   9.184 +    in the local user database, this function changes the
   9.185 +    ownership of the object to the local database.
   9.186 +    '''
   9.187 +    try: changed = ob._p_changed
   9.188 +    except: changed = 0
   9.189 +
   9.190 +    owner = getattr(ob, '_owner', None)
   9.191 +    if owner:
   9.192 +        udb, uid = owner
   9.193 +        #res.append('Owner of %s is %s!%s' % (
   9.194 +        #    '/'.join( ob.getPhysicalPath() ), '/'.join(udb), uid,))
   9.195 +        root = ob.getPhysicalRoot()
   9.196 +        try:
   9.197 +            db = root.unrestrictedTraverse(udb, None)
   9.198 +            user = db.getUserById(uid)
   9.199 +            if hasattr(ob, 'aq_inContextOf'):
   9.200 +                ucontext = aq_parent(aq_inner(db))
   9.201 +                if not ob.aq_inContextOf(ucontext):
   9.202 +                    # Not in the right context.
   9.203 +                    user = None
   9.204 +        except:
   9.205 +            user = None
   9.206 +        if user is None:
   9.207 +            # Try to change to a local database.
   9.208 +            p = ob
   9.209 +            old_udb = udb
   9.210 +            udb = None
   9.211 +            while p is not None:
   9.212 +                if hasattr(p, 'acl_users'):
   9.213 +                    acl_users = p.acl_users
   9.214 +                    try:
   9.215 +                        user = acl_users.getUserById(uid)
   9.216 +                    except:
   9.217 +                        user = None
   9.218 +                    if user is not None:
   9.219 +                        # Found the right database.
   9.220 +                        udb = acl_users.getPhysicalPath()[1:]
   9.221 +                        break
   9.222 +                p = aq_parent(aq_inner(p))
   9.223 +            if udb is not None:
   9.224 +                ob._owner = udb, uid
   9.225 +                res.append('Changed ownership of %s from %s!%s to %s!%s' %
   9.226 +                           ('/'.join( ob.getPhysicalPath() ),
   9.227 +                            '/'.join(old_udb), uid,
   9.228 +                            '/'.join(udb), uid,))
   9.229 +            else:
   9.230 +                res.append('Could not fix the ownership of %s, '
   9.231 +                           'which is set to %s!%s' %
   9.232 +                           ('/'.join( ob.getPhysicalPath() ),
   9.233 +                            '/'.join(old_udb), uid,))
   9.234 +
   9.235 +    if cleanup_children:
   9.236 +        if hasattr(ob, 'objectValues'):
   9.237 +            for subob in ob.objectValues():
   9.238 +                _cleanupOwnership(subob, res, 1)
   9.239 +
   9.240 +    # Deactivate object if possible.
   9.241 +    if changed is None: ob._p_deactivate()
   9.242 +
   9.243 +    return res
   9.244 +
   9.245 +def _copyUsers(src_folder, dst_folder):
   9.246 +    source = src_folder.acl_users
   9.247 +    target = dst_folder.acl_users
   9.248 +    for user in source.getUsers():
   9.249 +        target._addUser(name=user.name, password=user.__, confirm=user.__,
   9.250 +                        roles=user.roles, domains=user.domains, REQUEST=None)
   9.251 +
   9.252 +#
   9.253 +# PTK to CMF Migration script.
   9.254 +#
   9.255 +
   9.256 +
   9.257 +def migrate(self, src_path='', dest_path='', copy_users=0, ownership_only=0):
   9.258 +    if not src_path or not dest_path:
   9.259 +        return '''
   9.260 +        <html><body><form action="%s" method="POST">
   9.261 +        <h2>Migrate PTK content to CMF site</h2>
   9.262 +        <p>Path (not including server URL) to PTK instance (source):
   9.263 +        <input type="text" name="src_path"></p>
   9.264 +        <p>Path (not including server URL) to CMF site (destination):
   9.265 +        <input type="text" name="dest_path"></p>
   9.266 +        <p>Copy users:
   9.267 +        <input type="checkbox" name="copy_users" value="1"></p>
   9.268 +        <input type="submit" name="submit" value="Migrate">
   9.269 +        <input type="submit" name="ownership_only"
   9.270 +        value="Just clean up ownership">
   9.271 +        </form></body></html>
   9.272 +        ''' % self.REQUEST['URL']
   9.273 +    root = self.getPhysicalRoot()
   9.274 +    dst_folder = root.restrictedTraverse(dest_path)
   9.275 +    if not ownership_only:
   9.276 +        src_folder = root.restrictedTraverse(src_path)
   9.277 +        if copy_users:
   9.278 +            _copyUsers(src_folder, dst_folder)
   9.279 +        m = Migrator(ptk2cmf_conversions, ptk2cmf_skip)
   9.280 +        m.migrateObjectManager(src_folder, dst_folder)
   9.281 +    ownership_res = []
   9.282 +    _cleanupOwnership(dst_folder, ownership_res, 1)
   9.283 +    return '''
   9.284 +        <html><body><p>Finished migration.</p>
   9.285 +        <p>Warnings (if any):<ul><li>%s</li></ul></p>
   9.286 +        <p>Visited folders:<ul><li>%s</li></ul></p>
   9.287 +        <p>Skipped:<ul><li>%s</li></ul></p>
   9.288 +        <p>Converted content:</p><pre>%s</pre>
   9.289 +        <p>Fixed up ownership:</p><pre>%s</pre>
   9.290 +        </body></html>
   9.291 +        ''' % ('</li>\n<li>'.join(m.warnings),
   9.292 +               '</li>\n<li>'.join(m.visited_folders),
   9.293 +               '</li>\n<li>'.join(m.skipped),
   9.294 +               '\n'.join(m.copied),
   9.295 +               '\n'.join(ownership_res),
   9.296 +               )
   9.297 +
   9.298 +migrate_ptk = migrate
   9.299 +
   9.300 +#
   9.301 +# PTK to CMF Conversion definitions.
   9.302 +#
   9.303 +
   9.304 +
   9.305 +ptk2cmf_conversions = {}
   9.306 +
   9.307 +ptk2cmf_skip = {
   9.308 +    'portal_actions':0,
   9.309 +    'portal_catalog':0,
   9.310 +    'portal_discussion':0,
   9.311 +    'portal_memberdata':0,
   9.312 +    'portal_membership':0,
   9.313 +    'portal_properties':0,
   9.314 +    'portal_registration':0,
   9.315 +    'portal_skins':1,
   9.316 +    'portal_types':0,
   9.317 +    'portal_undo':0,
   9.318 +    'portal_url':0,
   9.319 +    'portal_workflow':0,
   9.320 +    'MailHost':0,
   9.321 +    'cookie_authentication':0,
   9.322 +    }
   9.323 +
   9.324 +demo_conversions = (
   9.325 +    'Document',
   9.326 +    'NewsItem',
   9.327 +    'Image',
   9.328 +    'File',
   9.329 +    'Link',
   9.330 +    'Favorite',
   9.331 +    'DiscussionItem',
   9.332 +    ('DiscussionItem', 'DiscussionItemContainer'),
   9.333 +    )
   9.334 +
   9.335 +
   9.336 +BEFORE_CONTENT_MOVE = 0
   9.337 +
   9.338 +if BEFORE_CONTENT_MOVE:
   9.339 +    content_product = 'PTKBase'
   9.340 +else:
   9.341 +    content_product = 'PTKDemo'
   9.342 +
   9.343 +
   9.344 +setupDirectConversions(content_product, 'CMFDefault', demo_conversions,
   9.345 +                       ptk2cmf_conversions)
   9.346 +
   9.347 +setupDirectConversion('PTKBase', 'CMFCore', 'DirectoryView', 'DirectoryView',
   9.348 +                       ptk2cmf_conversions, 0, 0)
   9.349 +
   9.350 +setupDirectConversion('PTKBase', 'CMFCore', 'PortalFolder', 'PortalFolder',
   9.351 +                       ptk2cmf_conversions, 1)
   9.352 +
   9.353 +from OFS.Folder import Folder
   9.354 +from Products.CMFCore.PortalFolder import PortalFolder
   9.355 +ptk2cmf_conversions[Folder] = SimpleClassConverter(PortalFolder, 1)
    10.1 new file mode 100644
    10.2 --- /dev/null
    10.3 +++ b/Extensions/update_catalogIndexes.py
    10.4 @@ -0,0 +1,15 @@
    10.5 +from Products.CMFCore.utils import getToolByName
    10.6 +
    10.7 +def update_catalogIndexes(self, REQUEST):
    10.8 +    '''
    10.9 +    External method to drop, re-add, and rebuild catalog Indexes for migrated 
   10.10 +    CMF sites from Zope 2.3 to 2.4+.
   10.11 +    '''
   10.12 +    rIndexes = {'allowedRolesAndUsers': 'KeywordIndex'
   10.13 +              , 'effective': 'FieldIndex'
   10.14 +              , 'expires': 'FieldIndex'}
   10.15 +    ct = getToolByName(self, 'portal_catalog')
   10.16 +    map(lambda x, ct=ct: ct.delIndex(x), rIndexes.keys())
   10.17 +    map(lambda x, ct=ct: ct.addIndex(x[0], x[1]), rIndexes.items()) 
   10.18 +    ct.manage_reindexIndex(ids=rIndexes.keys(), REQUEST=REQUEST)
   10.19 +    return 'Catalog Indexes rebuilt.'
    11.1 new file mode 100644
    11.2 --- /dev/null
    11.3 +++ b/Extensions/update_discussion.py
    11.4 @@ -0,0 +1,80 @@
    11.5 +from Products.CMFCore.TypesTool import FactoryTypeInformation
    11.6 +from Products.CMFDefault import DiscussionItem
    11.7 +
    11.8 +def update_discussion(self):
    11.9 +    """
   11.10 +     1. Install (if it isn't there already) a type information
   11.11 +        object for DiscussionItems, so that they can get actions,
   11.12 +        etc.  Erase the "(default)" workflow bound to it, to prevent
   11.13 +        showing the "Retract" options, etc.
   11.14 +
   11.15 +     2. Update all DiscussionItems to use the new marking for
   11.16 +        'in_reply_to':
   11.17 +
   11.18 +          - Items which are replies to the containing content object
   11.19 +            have None as their 'in_reply_to';
   11.20 +
   11.21 +          - Items which are replies to sibling items have the sibling's
   11.22 +            ID as their 'in_reply_to'.
   11.23 +
   11.24 +        The representation we are converting from was:
   11.25 +
   11.26 +          - Items which are replies to the containing content object
   11.27 +            have the portal-relative pathstring of the content object
   11.28 +            as their 'in_reply_to';
   11.29 +
   11.30 +          - Items which are replies to sibling items have the absolute
   11.31 +            path of the sibling as their 'in_reply_to'.
   11.32 +    """
   11.33 +
   11.34 +    log = []
   11.35 +    a = log.append
   11.36 +    types_tool = self.portal_types
   11.37 +    if not getattr( types_tool, 'Discussion Item', None ):
   11.38 +
   11.39 +        fti = FactoryTypeInformation(
   11.40 +                                **DiscussionItem.factory_type_information[0] )
   11.41 +        types_tool._setObject( 'Discussion Item', fti )
   11.42 +        a( 'Added type object for DiscussionItem' )
   11.43 +
   11.44 +        workflow_tool = self.portal_workflow
   11.45 +        workflow_tool.setChainForPortalTypes( ( 'Discussion Item', ), () )
   11.46 +        a( 'Erased workflow for DiscussionItem' )
   11.47 +
   11.48 +    items = self.portal_catalog.searchResults( meta_type='Discussion Item' )
   11.49 +    a( 'DiscussionItems updated:' )
   11.50 +
   11.51 +    for item in items:
   11.52 +
   11.53 +        object = item.getObject()
   11.54 +        talkback = object.aq_parent
   11.55 +        path = item.getPath()
   11.56 +        in_reply_to = object.in_reply_to
   11.57 +
   11.58 +        if in_reply_to is None: # we've been here already
   11.59 +            continue
   11.60 +
   11.61 +        irt_elements = in_reply_to.split('/')
   11.62 +
   11.63 +        if len( irt_elements ) == 1:
   11.64 +            if talkback._container.get( irt_elements[0] ):
   11.65 +                # we've been here already
   11.66 +                continue
   11.67 +
   11.68 +        if irt_elements[0] == '': # absolute, so we are IRT a sibling
   11.69 +            sibling_id = irt_elements[ -1 ]
   11.70 +            if talkback._container.get( sibling_id, None ):
   11.71 +                in_reply_to = sibling_id
   11.72 +            else:
   11.73 +                in_reply_to = None
   11.74 +        else:
   11.75 +            in_reply_to = None
   11.76 +
   11.77 +        object.in_reply_to = in_reply_to
   11.78 +        assert object.inReplyTo() # sanity check
   11.79 +        object.reindexObject()
   11.80 +
   11.81 +        a( path )
   11.82 +
   11.83 +    return '\n'.join(log)
   11.84 +
    12.1 new file mode 100644
    12.2 --- /dev/null
    12.3 +++ b/Favorite.py
    12.4 @@ -0,0 +1,219 @@
    12.5 +##############################################################################
    12.6 +#
    12.7 +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
    12.8 +#
    12.9 +# This software is subject to the provisions of the Zope Public License,
   12.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   12.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   12.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   12.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   12.14 +# FOR A PARTICULAR PURPOSE.
   12.15 +#
   12.16 +##############################################################################
   12.17 +""" Favorites are references to other objects within the same CMF site.
   12.18 +
   12.19 +$Id: Favorite.py 36457 2004-08-12 15:07:44Z jens $
   12.20 +"""
   12.21 +
   12.22 +import urlparse
   12.23 +
   12.24 +from Globals import InitializeClass
   12.25 +from AccessControl import ClassSecurityInfo
   12.26 +from Acquisition import aq_base
   12.27 +
   12.28 +from Products.CMFCore.utils import getToolByName
   12.29 +
   12.30 +from permissions import View
   12.31 +from permissions import ModifyPortalContent
   12.32 +from DublinCore import DefaultDublinCoreImpl
   12.33 +from Link import Link
   12.34 +
   12.35 +factory_type_information = (
   12.36 +  { 'id'             : 'Favorite'
   12.37 +  , 'meta_type'      : 'Favorite'
   12.38 +  , 'description'    : """\
   12.39 +A Favorite is a Link to an intra-portal resource.
   12.40 +"""
   12.41 +  , 'icon'           : 'link_icon.gif'
   12.42 +  , 'product'        : 'CMFDefault'
   12.43 +  , 'factory'        : 'addFavorite'
   12.44 +  , 'immediate_view' : 'metadata_edit_form'
   12.45 +  , 'aliases'        : {'(Default)':'favorite_view',
   12.46 +                        'view':'favorite_view'}
   12.47 +  , 'actions'        : ( { 'id'            : 'view'
   12.48 +                         , 'name'          : 'View'
   12.49 +                         , 'action': 'string:${object_url}/favorite_view'
   12.50 +                         , 'permissions'   : ( View, )
   12.51 +                         }
   12.52 +                       , { 'id'            : 'edit'
   12.53 +                         , 'name'          : 'Edit'
   12.54 +                         , 'action': 'string:${object_url}/link_edit_form'
   12.55 +                         , 'permissions'   : ( ModifyPortalContent, )
   12.56 +                         }
   12.57 +                       , { 'id'            : 'metadata'
   12.58 +                         , 'name'          : 'Metadata'
   12.59 +                         , 'action': 'string:${object_url}/metadata_edit_form'
   12.60 +                         , 'permissions'   : ( ModifyPortalContent, )
   12.61 +                         }
   12.62 +                       )
   12.63 +  }
   12.64 +,
   12.65 +)
   12.66 +
   12.67 +def addFavorite(self, id, title='', remote_url='', description=''):
   12.68 +    """
   12.69 +    Add a Favorite
   12.70 +    """
   12.71 +    portal_url = getToolByName(self, 'portal_url')
   12.72 +    portal_obj = portal_url.getPortalObject()
   12.73 +    content_obj = portal_obj.restrictedTraverse( remote_url )
   12.74 +    relUrl = portal_url.getRelativeUrl( content_obj )
   12.75 +    
   12.76 +    o=Favorite( id, title, relUrl, description )
   12.77 +    self._setObject(id,o)
   12.78 +
   12.79 +
   12.80 +class Favorite( Link ):
   12.81 +    """
   12.82 +        A Favorite (special kind of Link)
   12.83 +    """
   12.84 +
   12.85 +    __implements__ = Link.__implements__ # redundant, but explicit
   12.86 +
   12.87 +    meta_type='Favorite'
   12.88 +
   12.89 +    security = ClassSecurityInfo()
   12.90 +
   12.91 +    def __init__( self
   12.92 +                , id
   12.93 +                , title=''
   12.94 +                , remote_url=''
   12.95 +                , description=''
   12.96 +                ):
   12.97 +        DefaultDublinCoreImpl.__init__(self)
   12.98 +        self.id=id
   12.99 +        self.title=title
  12.100 +        self.remote_url=remote_url
  12.101 +        self.description = description
  12.102 +        
  12.103 +    def manage_afterAdd(self, item, container):
  12.104 +        """Intercept after favorite has beeing added.
  12.105 +        
  12.106 +        The tools are not reachable in '__init__' because 'self' is 
  12.107 +        not yet wrapped at this time. That's why the after add hook
  12.108 +        has to be intercepted.
  12.109 +        """
  12.110 +        # save unique id of favorite
  12.111 +        self.remote_uid = self._getUidByUrl()
  12.112 +        
  12.113 +        # do the usual stuff
  12.114 +        Link.manage_afterAdd(self, item, container)
  12.115 +        
  12.116 +    def _getUidByUrl(self):
  12.117 +        """Registers and returns the uid of the remote object if
  12.118 +        the unique id handler tool is available.
  12.119 +        """
  12.120 +        # check for unique id handler tool
  12.121 +        handler = getToolByName(self, 'portal_uidhandler', None)
  12.122 +        if handler is None or not hasattr(handler, 'register'):
  12.123 +            return
  12.124 +        
  12.125 +        portal = getToolByName(self, 'portal_url').getPortalObject()
  12.126 +        obj = portal.restrictedTraverse(self.remote_url)
  12.127 +        return handler.register(obj)
  12.128 +
  12.129 +    def _getObjectByUid(self):
  12.130 +        """Registers and returns the uid of the remote object if
  12.131 +        the unique id handler tool is available.
  12.132 +        """
  12.133 +        # check for unique id handler tool
  12.134 +        handler = getToolByName(self, 'portal_uidhandler', None)
  12.135 +        if handler is None or not hasattr(handler, 'queryObject'):
  12.136 +            return
  12.137 +        
  12.138 +        # check for remote uid info on object
  12.139 +        uid = getattr(aq_base(self), 'remote_uid', None)
  12.140 +        if uid is None:
  12.141 +            return
  12.142 +        
  12.143 +        return handler.queryObject(uid, None)
  12.144 +
  12.145 +    security.declareProtected(View, 'getRemoteUrl')
  12.146 +    def getRemoteUrl(self):
  12.147 +        """
  12.148 +            returns the remote URL of the Link
  12.149 +        """
  12.150 +        # try getting the remote object by unique id
  12.151 +        remote_url = self._getRemoteUrlTheOldWay()
  12.152 +        remote_obj = self._getObjectByUid()
  12.153 +        if remote_obj:
  12.154 +            url = remote_obj.absolute_url()
  12.155 +            # update the url when changed (avoid unnecessary ZODB writes)
  12.156 +            if url != remote_url:
  12.157 +                self.edit(url)
  12.158 +            return url
  12.159 +        
  12.160 +        return remote_url
  12.161 +
  12.162 +    def _getRemoteUrlTheOldWay(self):
  12.163 +        """Build the url without having taking the uid into account
  12.164 +        """
  12.165 +        portal_url = getToolByName(self, 'portal_url')
  12.166 +        if self.remote_url:
  12.167 +            return portal_url() + '/' + self.remote_url
  12.168 +        else:
  12.169 +            return portal_url()
  12.170 +
  12.171 +
  12.172 +    security.declareProtected(View, 'getIcon')
  12.173 +    def getIcon(self, relative_to_portal=0):
  12.174 +        """
  12.175 +        Instead of a static icon, like for Link objects, we want
  12.176 +        to display an icon based on what the Favorite links to.
  12.177 +        """
  12.178 +        try:
  12.179 +            return self.getObject().getIcon(relative_to_portal)
  12.180 +        except:
  12.181 +            return 'p_/broken'
  12.182 +
  12.183 +    security.declareProtected(View, 'getObject')
  12.184 +    def getObject(self):
  12.185 +        """
  12.186 +        Return the actual object that the Favorite is 
  12.187 +        linking to
  12.188 +        """
  12.189 +        # try getting the remote object by unique id
  12.190 +        remote_obj = self._getObjectByUid()
  12.191 +        if remote_obj is not None:
  12.192 +            return remote_obj
  12.193 +
  12.194 +        portal_url = getToolByName(self, 'portal_url')
  12.195 +        return portal_url.getPortalObject().restrictedTraverse(self.remote_url)
  12.196 +
  12.197 +    security.declarePrivate('_edit')
  12.198 +    def _edit( self, remote_url ):
  12.199 +        """
  12.200 +        Edit the Favorite. Unlike Links, Favorites have URLs that are
  12.201 +        relative to the root of the site.
  12.202 +        """
  12.203 +        # strip off scheme and machine from URL if present
  12.204 +        tokens = urlparse.urlparse( remote_url, 'http' )
  12.205 +        if tokens[1]:
  12.206 +            # There is a nethost, remove it
  12.207 +            t=('', '') + tokens[2:]
  12.208 +            remote_url=urlparse.urlunparse(t)
  12.209 +        # if URL begins with site URL, remove site URL
  12.210 +        portal_url = getToolByName(self, 'portal_url').getPortalPath()
  12.211 +        i = remote_url.find(portal_url)
  12.212 +        if i==0:
  12.213 +            remote_url=remote_url[len(portal_url):]
  12.214 +        # if site is still absolute, make it relative
  12.215 +        if remote_url[:1]=='/':
  12.216 +            remote_url=remote_url[1:]
  12.217 +        self.remote_url=remote_url
  12.218 +
  12.219 +        # save unique id of favorite
  12.220 +        self.remote_uid = self._getUidByUrl()
  12.221 +
  12.222 +
  12.223 +InitializeClass(Favorite)
    13.1 new file mode 100644
    13.2 --- /dev/null
    13.3 +++ b/File.py
    13.4 @@ -0,0 +1,264 @@
    13.5 +##############################################################################
    13.6 +#
    13.7 +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
    13.8 +#
    13.9 +# This software is subject to the provisions of the Zope Public License,
   13.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   13.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   13.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   13.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   13.14 +# FOR A PARTICULAR PURPOSE.
   13.15 +#
   13.16 +##############################################################################
   13.17 +""" This module implements a portal-managed File class.  It is based on
   13.18 +Zope's built-in File object, but modifies the behaviour slightly to
   13.19 +make it more Portal-friendly.
   13.20 +
   13.21 +$Id: File.py 37921 2005-08-13 20:36:17Z jens $
   13.22 +"""
   13.23 +
   13.24 +from Globals import InitializeClass
   13.25 +from AccessControl import ClassSecurityInfo
   13.26 +
   13.27 +from Products.CMFCore.PortalContent import PortalContent
   13.28 +
   13.29 +from DublinCore import DefaultDublinCoreImpl
   13.30 +from permissions import View
   13.31 +from permissions import ModifyPortalContent
   13.32 +
   13.33 +
   13.34 +factory_type_information = (
   13.35 +  { 'id'             : 'File'
   13.36 +  , 'meta_type'      : 'Portal File'
   13.37 +  , 'description'    : """\
   13.38 +File objects can contain arbitrary downloadable files.
   13.39 +"""
   13.40 +  , 'icon'           : 'file_icon.gif'
   13.41 +  , 'product'        : 'CMFDefault'
   13.42 +  , 'factory'        : 'addFile'
   13.43 +  , 'immediate_view' : 'metadata_edit_form'
   13.44 +  , 'aliases'        : {'(Default)':'index_html',
   13.45 +                        'view':'file_view'}
   13.46 +  , 'actions'        : ( { 'id'            : 'view'
   13.47 +                         , 'name'          : 'View'
   13.48 +                         , 'action': 'string:${object_url}/file_view'
   13.49 +                         , 'permissions'   : (View,)
   13.50 +                         }
   13.51 +                       , { 'id'            : 'download'
   13.52 +                         , 'name'          : 'Download'
   13.53 +                         , 'action': 'string:${object_url}'
   13.54 +                         , 'permissions'   : (View,)
   13.55 +                         }
   13.56 +                       , { 'id'            : 'edit'
   13.57 +                         , 'name'          : 'Edit'
   13.58 +                         , 'action': 'string:${object_url}/file_edit_form'
   13.59 +                         , 'permissions'   : (ModifyPortalContent,)
   13.60 +                         }
   13.61 +                       , { 'id'            : 'metadata'
   13.62 +                         , 'name'          : 'Metadata'
   13.63 +                         , 'action': 'string:${object_url}/metadata_edit_form'
   13.64 +                         , 'permissions'   : (ModifyPortalContent,)
   13.65 +                         }
   13.66 +                       )
   13.67 +  }
   13.68 +,
   13.69 +)
   13.70 +
   13.71 +import OFS.Image
   13.72 +
   13.73 +def addFile( self
   13.74 +           , id
   13.75 +           , title=''
   13.76 +           , file=''
   13.77 +           , content_type=''
   13.78 +           , precondition=''
   13.79 +           , subject=()
   13.80 +           , description=''
   13.81 +           , contributors=()
   13.82 +           , effective_date=None
   13.83 +           , expiration_date=None
   13.84 +           , format='text/html'
   13.85 +           , language=''
   13.86 +           , rights=''
   13.87 +           ):
   13.88 +    """
   13.89 +    Add a File
   13.90 +    """
   13.91 +
   13.92 +    # cookId sets the id and title if they are not explicity specified
   13.93 +    id, title = OFS.Image.cookId(id, title, file)
   13.94 +
   13.95 +    self=self.this()
   13.96 +
   13.97 +    # Instantiate the object and set its description.
   13.98 +    fobj = File( id, title, '', content_type, precondition, subject
   13.99 +               , description, contributors, effective_date, expiration_date
  13.100 +               , format, language, rights
  13.101 +               )
  13.102 +    
  13.103 +    # Add the File instance to self
  13.104 +    self._setObject(id, fobj)
  13.105 +
  13.106 +    # 'Upload' the file.  This is done now rather than in the
  13.107 +    # constructor because the object is now in the ZODB and
  13.108 +    # can span ZODB objects.
  13.109 +    self._getOb(id).manage_upload(file)
  13.110 +
  13.111 +
  13.112 +class File( OFS.Image.File
  13.113 +          , PortalContent
  13.114 +          , DefaultDublinCoreImpl
  13.115 +          ):
  13.116 +    """
  13.117 +        A Portal-managed File
  13.118 +    """
  13.119 +
  13.120 +    # The order of base classes is very significant in this case.
  13.121 +    # Image.File does not store it's id in it's 'id' attribute.
  13.122 +    # Rather, it has an 'id' method which returns the contents of the
  13.123 +    # instnace's __name__ attribute.  Inheriting in the other order
  13.124 +    # obscures this method, resulting in much pulling of hair and
  13.125 +    # gnashing of teeth and fraying of nerves.  Don't do it.
  13.126 +    #
  13.127 +    # Really.
  13.128 +    # 
  13.129 +    # Note that if you use getId() to retrieve an object's ID, you will avoid
  13.130 +    # this problem altogether. getId is the new way, accessing .id is
  13.131 +    # deprecated.
  13.132 +
  13.133 +    __implements__ = ( PortalContent.__implements__
  13.134 +                     , DefaultDublinCoreImpl.__implements__
  13.135 +                     )
  13.136 +    
  13.137 +    meta_type='Portal File'
  13.138 +    effective_date = expiration_date = None
  13.139 +    _isDiscussable = 1
  13.140 +    icon = PortalContent.icon
  13.141 +
  13.142 +    security = ClassSecurityInfo()
  13.143 +
  13.144 +    def __init__( self
  13.145 +                , id
  13.146 +                , title=''
  13.147 +                , file=''
  13.148 +                , content_type=''
  13.149 +                , precondition=''
  13.150 +                , subject=()
  13.151 +                , description=''
  13.152 +                , contributors=()
  13.153 +                , effective_date=None
  13.154 +                , expiration_date=None
  13.155 +                , format=None
  13.156 +                , language='en-US'
  13.157 +                , rights=''
  13.158 +                ):
  13.159 +        OFS.Image.File.__init__( self, id, title, file
  13.160 +                               , content_type, precondition )
  13.161 +
  13.162 +        # If no file format has been passed in, rely on what OFS.Image.File
  13.163 +        # detected. Unlike Images, which have code to try and pick the content
  13.164 +        # type out of the binary data, File objects only provide the correct
  13.165 +        # type if a "hint" in the form of a filename extension is given.
  13.166 +        if format is None:
  13.167 +            format = self.content_type 
  13.168 +
  13.169 +        DefaultDublinCoreImpl.__init__( self, title, subject, description
  13.170 +                               , contributors, effective_date, expiration_date
  13.171 +                               , format, language, rights )
  13.172 +
  13.173 +    security.declareProtected(View, 'SearchableText')
  13.174 +    def SearchableText(self):
  13.175 +        """
  13.176 +        SeachableText is used for full text seraches of a portal.  It
  13.177 +        should return a concatenation of all useful text.
  13.178 +        """
  13.179 +        return "%s %s" % (self.title, self.description)
  13.180 +
  13.181 +    security.declarePrivate('manage_afterAdd')
  13.182 +    def manage_afterAdd(self, item, container):
  13.183 +        """Both of my parents have an afterAdd method"""
  13.184 +        OFS.Image.File.manage_afterAdd(self, item, container)
  13.185 +        PortalContent.manage_afterAdd(self, item, container)
  13.186 +
  13.187 +    security.declarePrivate('manage_afterClone')
  13.188 +    def manage_afterClone(self, item):
  13.189 +        """Both of my parents have an afterClone method"""
  13.190 +        OFS.Image.File.manage_afterClone(self, item)
  13.191 +        PortalContent.manage_afterClone(self, item)
  13.192 +
  13.193 +    security.declarePrivate('manage_beforeDelete')
  13.194 +    def manage_beforeDelete(self, item, container):
  13.195 +        """Both of my parents have a beforeDelete method"""
  13.196 +        PortalContent.manage_beforeDelete(self, item, container)
  13.197 +        OFS.Image.File.manage_beforeDelete(self, item, container)
  13.198 +
  13.199 +    security.declarePrivate('_isNotEmpty')
  13.200 +    def _isNotEmpty(self, file):
  13.201 +        """ Do various checks on 'file' to try to determine non emptiness. """
  13.202 +        if not file:
  13.203 +            return 0                    # Catches None, Missing.Value, ''
  13.204 +        elif file and (type(file) is type('')):
  13.205 +            return 1
  13.206 +        elif getattr(file, 'filename', None):
  13.207 +            return 1
  13.208 +        elif not hasattr(file, 'read'):
  13.209 +            return 0
  13.210 +        else:
  13.211 +            file.seek(0,2)              # 0 bytes back from end of file
  13.212 +            t = file.tell()             # Report the location
  13.213 +            file.seek(0)                # and return pointer back to 0
  13.214 +            if t: return 1
  13.215 +            else: return 0
  13.216 +
  13.217 +    security.declarePrivate('_edit')
  13.218 +    def _edit(self, precondition='', file=''):
  13.219 +        """ Perform changes for user """
  13.220 +        if precondition: self.precondition = precondition
  13.221 +        elif self.precondition: del self.precondition
  13.222 +
  13.223 +        if self._isNotEmpty(file):
  13.224 +            self.manage_upload(file)
  13.225 +
  13.226 +    security.declareProtected(ModifyPortalContent, 'edit')
  13.227 +    def edit(self, precondition='', file=''):
  13.228 +        """ Update and reindex. """
  13.229 +        self._edit( precondition, file )
  13.230 +        self.reindexObject()
  13.231 +
  13.232 +    security.declareProtected(View, 'download')
  13.233 +    def download(self, REQUEST, RESPONSE):
  13.234 +        """Download this item.
  13.235 +        
  13.236 +        Calls OFS.Image.File.index_html to perform the actual transfer after
  13.237 +        first setting Content-Disposition to suggest a filename.
  13.238 +        
  13.239 +        This method is deprecated, use the URL of this object itself. Because
  13.240 +        the default view of a File object is to download, rather than view,
  13.241 +        this method is obsolete. Also note that certain browsers do not deal
  13.242 +        well with a Content-Disposition header.
  13.243 +
  13.244 +        """
  13.245 +
  13.246 +        RESPONSE.setHeader('Content-Disposition',
  13.247 +                           'attachment; filename=%s' % self.getId())
  13.248 +        return OFS.Image.File.index_html(self, REQUEST, RESPONSE)
  13.249 +
  13.250 +    security.declareProtected(View, 'Format')
  13.251 +    def Format(self):
  13.252 +        """ Dublin Core element - resource format """
  13.253 +        return self.content_type
  13.254 +
  13.255 +    security.declareProtected(ModifyPortalContent, 'setFormat')
  13.256 +    def setFormat(self, format):
  13.257 +        """ Dublin Core element - resource format """
  13.258 +        self.manage_changeProperties(content_type=format)
  13.259 +
  13.260 +    security.declareProtected(ModifyPortalContent, 'PUT')
  13.261 +    def PUT(self, REQUEST, RESPONSE):
  13.262 +        """ Handle HTTP (and presumably FTP?) PUT requests """
  13.263 +        OFS.Image.File.PUT( self, REQUEST, RESPONSE )
  13.264 +        self.reindexObject()
  13.265 +
  13.266 +
  13.267 +InitializeClass(File)
  13.268 +
    14.1 new file mode 100644
    14.2 --- /dev/null
    14.3 +++ b/Image.py
    14.4 @@ -0,0 +1,246 @@
    14.5 +##############################################################################
    14.6 +#
    14.7 +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
    14.8 +#
    14.9 +# This software is subject to the provisions of the Zope Public License,
   14.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   14.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   14.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   14.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   14.14 +# FOR A PARTICULAR PURPOSE.
   14.15 +#
   14.16 +##############################################################################
   14.17 +""" This module implements a portal-managed Image class. It is based on
   14.18 +Zope's built-in Image object.
   14.19 +
   14.20 +$Id: Image.py 37921 2005-08-13 20:36:17Z jens $
   14.21 +"""
   14.22 +
   14.23 +from Globals import InitializeClass
   14.24 +from AccessControl import ClassSecurityInfo
   14.25 +
   14.26 +from Products.CMFCore.PortalContent import PortalContent
   14.27 +
   14.28 +from DublinCore import DefaultDublinCoreImpl
   14.29 +from permissions import View
   14.30 +from permissions import ModifyPortalContent
   14.31 +
   14.32 +factory_type_information = (
   14.33 +  { 'id'             : 'Image'
   14.34 +  , 'meta_type'      : 'Portal Image'
   14.35 +  , 'description'    : """\
   14.36 +Image objects can be embedded in Portal documents.
   14.37 +"""
   14.38 +  , 'icon'           : 'image_icon.gif'
   14.39 +  , 'product'        : 'CMFDefault'
   14.40 +  , 'factory'        : 'addImage'
   14.41 +  , 'immediate_view' : 'metadata_edit_form'
   14.42 +  , 'aliases'        : {'(Default)':'index_html',
   14.43 +                        'view':'image_view'}
   14.44 +  , 'actions'        : ( { 'id'            : 'view'
   14.45 +                         , 'name'          : 'View'
   14.46 +                         , 'action': 'string:${object_url}/image_view'
   14.47 +                         , 'permissions'   : (View,)
   14.48 +                         }
   14.49 +                       , { 'id'            : 'edit'
   14.50 +                         , 'name'          : 'Edit'
   14.51 +                         , 'action': 'string:${object_url}/image_edit_form'
   14.52 +                         , 'permissions'   : (ModifyPortalContent,)
   14.53 +                         }
   14.54 +                       , { 'id'            : 'metadata'
   14.55 +                         , 'name'          : 'Metadata'
   14.56 +                         , 'action': 'string:${object_url}/metadata_edit_form'
   14.57 +                         , 'permissions'   : (ModifyPortalContent,)
   14.58 +                         }
   14.59 +                       )
   14.60 +  }
   14.61 +,
   14.62 +)
   14.63 +
   14.64 +import OFS.Image
   14.65 +
   14.66 +def addImage( self
   14.67 +            , id
   14.68 +            , title=''
   14.69 +            , file=''
   14.70 +            , content_type=''
   14.71 +            , precondition=''
   14.72 +            , subject=()
   14.73 +            , description=''
   14.74 +            , contributors=()
   14.75 +            , effective_date=None
   14.76 +            , expiration_date=None
   14.77 +            , format='image/png'
   14.78 +            , language=''
   14.79 +            , rights=''
   14.80 +            ):
   14.81 +    """
   14.82 +        Add an Image
   14.83 +    """
   14.84 +
   14.85 +    # cookId sets the id and title if they are not explicity specified
   14.86 +    id, title = OFS.Image.cookId(id, title, file)
   14.87 +
   14.88 +    self=self.this()
   14.89 +
   14.90 +    # Instantiate the object and set its description.
   14.91 +    iobj = Image( id, title, '', content_type, precondition, subject
   14.92 +                , description, contributors, effective_date, expiration_date
   14.93 +                , format, language, rights
   14.94 +                )
   14.95 +    
   14.96 +    # Add the Image instance to self
   14.97 +    self._setObject(id, iobj)
   14.98 +
   14.99 +    # 'Upload' the image.  This is done now rather than in the
  14.100 +    # constructor because it's faster (see File.py.)
  14.101 +    self._getOb(id).manage_upload(file)
  14.102 +
  14.103 +
  14.104 +class Image( OFS.Image.Image
  14.105 +           , PortalContent
  14.106 +           , DefaultDublinCoreImpl
  14.107 +           ):
  14.108 +    """
  14.109 +        A Portal-managed Image
  14.110 +    """
  14.111 +
  14.112 +    # The order of base classes is very significant in this case.
  14.113 +    # Image.Image does not store it's id in it's 'id' attribute.
  14.114 +    # Rather, it has an 'id' method which returns the contents of the
  14.115 +    # instnace's __name__ attribute.  Inheriting in the other order
  14.116 +    # obscures this method, resulting in much pulling of hair and
  14.117 +    # gnashing of teeth and fraying of nerves.  Don't do it.
  14.118 +    #
  14.119 +    # Really.
  14.120 +    # 
  14.121 +    # Note that if you use getId() to retrieve an object's ID, you will avoid
  14.122 +    # this problem altogether. getId is the new way, accessing .id is
  14.123 +    # deprecated.
  14.124 +
  14.125 +    __implements__ = ( PortalContent.__implements__
  14.126 +                     , DefaultDublinCoreImpl.__implements__
  14.127 +                     )
  14.128 +    
  14.129 +    meta_type='Portal Image'
  14.130 +    effective_date = expiration_date = None
  14.131 +    _isDiscussable = 1
  14.132 +    icon = PortalContent.icon
  14.133 +
  14.134 +    security = ClassSecurityInfo()
  14.135 +
  14.136 +    def __init__( self
  14.137 +                , id
  14.138 +                , title=''
  14.139 +                , file=''
  14.140 +                , content_type=''
  14.141 +                , precondition=''
  14.142 +                , subject=()
  14.143 +                , description=''
  14.144 +                , contributors=()
  14.145 +                , effective_date=None
  14.146 +                , expiration_date=None
  14.147 +                , format=None
  14.148 +                , language='en-US'
  14.149 +                , rights=''
  14.150 +                ):
  14.151 +        OFS.Image.File.__init__( self, id, title, file
  14.152 +                               , content_type, precondition )
  14.153 +
  14.154 +        # If no file format has been passed in, rely on what OFS.Image.File
  14.155 +        # detected.
  14.156 +        if format is None:
  14.157 +            format = self.content_type
  14.158 +
  14.159 +        DefaultDublinCoreImpl.__init__( self, title, subject, description
  14.160 +                               , contributors, effective_date, expiration_date
  14.161 +                               , format, language, rights )
  14.162 +
  14.163 +    security.declareProtected(View, 'SearchableText')
  14.164 +    def SearchableText(self):
  14.165 +        """
  14.166 +            SeachableText is used for full text seraches of a portal.
  14.167 +            It should return a concatanation of all useful text.
  14.168 +        """
  14.169 +        return "%s %s" % (self.title, self.description)
  14.170 +
  14.171 +    security.declarePrivate('manage_afterAdd')
  14.172 +    def manage_afterAdd(self, item, container):
  14.173 +        """Both of my parents have an afterAdd method"""
  14.174 +        OFS.Image.Image.manage_afterAdd(self, item, container)
  14.175 +        PortalContent.manage_afterAdd(self, item, container)
  14.176 +
  14.177 +    security.declarePrivate('manage_afterClone')
  14.178 +    def manage_afterClone(self, item):
  14.179 +        """Both of my parents have an afterClone method"""
  14.180 +        OFS.Image.Image.manage_afterClone(self, item)
  14.181 +        PortalContent.manage_afterClone(self, item)
  14.182 +
  14.183 +    security.declarePrivate('manage_beforeDelete')
  14.184 +    def manage_beforeDelete(self, item, container):
  14.185 +        """Both of my parents have a beforeDelete method"""
  14.186 +        PortalContent.manage_beforeDelete(self, item, container)
  14.187 +        OFS.Image.Image.manage_beforeDelete(self, item, container)
  14.188 +
  14.189 +    security.declarePrivate('_isNotEmpty')
  14.190 +    def _isNotEmpty(self, file):
  14.191 +        """ Do various checks on 'file' to try to determine non emptiness. """
  14.192 +        if not file:
  14.193 +            return 0                    # Catches None, Missing.Value, ''
  14.194 +        elif file and (type(file) is type('')):
  14.195 +            return 1
  14.196 +        elif getattr(file, 'filename', None):
  14.197 +            return 1
  14.198 +        elif not hasattr(file, 'read'):
  14.199 +            return 0
  14.200 +        else:
  14.201 +            file.seek(0,2)              # 0 bytes back from end of file
  14.202 +            t = file.tell()             # Report the location
  14.203 +            file.seek(0)                # and return pointer back to 0
  14.204 +            if t: return 1
  14.205 +            else: return 0
  14.206 +
  14.207 +    security.declarePrivate('_edit')
  14.208 +    def _edit(self, precondition='', file=''):
  14.209 +        """ Update image. """
  14.210 +        if precondition: self.precondition = precondition
  14.211 +        elif self.precondition: del self.precondition
  14.212 +
  14.213 +        if self._isNotEmpty(file):
  14.214 +            self.manage_upload(file)
  14.215 +
  14.216 +    security.declareProtected(ModifyPortalContent, 'edit')
  14.217 +    def edit(self, precondition='', file=''):
  14.218 +        """ Update and reindex. """
  14.219 +        self._edit( precondition, file )
  14.220 +        self.reindexObject()
  14.221 +
  14.222 +    security.declareProtected(View, 'index_html')
  14.223 +    def index_html(self, REQUEST, RESPONSE):
  14.224 +        """
  14.225 +        Display the image, with or without standard_html_[header|footer],
  14.226 +        as appropriate.
  14.227 +        """
  14.228 +        #if REQUEST['PATH_INFO'][-10:] == 'index_html':
  14.229 +        #    return self.view(self, REQUEST)
  14.230 +        return OFS.Image.Image.index_html(self, REQUEST, RESPONSE)
  14.231 +
  14.232 +    security.declareProtected(View, 'Format')
  14.233 +    def Format(self):
  14.234 +        """ Dublin Core element - resource format """
  14.235 +        return self.content_type
  14.236 +
  14.237 +    security.declareProtected(ModifyPortalContent, 'setFormat')
  14.238 +    def setFormat(self, format):
  14.239 +        """ Dublin Core element - resource format """
  14.240 +        self.manage_changeProperties(content_type=format)
  14.241 +
  14.242 +    security.declareProtected(ModifyPortalContent, 'PUT')
  14.243 +    def PUT(self, REQUEST, RESPONSE):
  14.244 +        """ Handle HTTP (and presumably FTP?) PUT requests """
  14.245 +        OFS.Image.Image.PUT( self, REQUEST, RESPONSE )
  14.246 +        self.reindexObject()
  14.247 +
  14.248 +InitializeClass(Image)
  14.249 +
  14.250 +
    15.1 new file mode 100644
    15.2 --- /dev/null
    15.3 +++ b/Link.py
    15.4 @@ -0,0 +1,236 @@
    15.5 +##############################################################################
    15.6 +#
    15.7 +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
    15.8 +#
    15.9 +# This software is subject to the provisions of the Zope Public License,
   15.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   15.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   15.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   15.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   15.14 +# FOR A PARTICULAR PURPOSE.
   15.15 +#
   15.16 +##############################################################################
   15.17 +""" Link instances represent explicit links-as-content.
   15.18 +
   15.19 +$Id: Link.py 36890 2005-04-05 10:48:10Z yuppie $
   15.20 +"""
   15.21 +
   15.22 +import urlparse
   15.23 +
   15.24 +from AccessControl import ClassSecurityInfo
   15.25 +from Globals import DTMLFile
   15.26 +from Globals import InitializeClass
   15.27 +try:
   15.28 +    import transaction
   15.29 +except ImportError:
   15.30 +    # BBB: for Zope 2.7
   15.31 +    from Products.CMFCore.utils import transaction
   15.32 +
   15.33 +from Products.CMFCore.PortalContent import PortalContent
   15.34 +from Products.CMFCore.utils import contributorsplitter
   15.35 +from Products.CMFCore.utils import keywordsplitter
   15.36 +
   15.37 +from DublinCore import DefaultDublinCoreImpl
   15.38 +from exceptions import ResourceLockedError
   15.39 +from permissions import ModifyPortalContent
   15.40 +from permissions import View
   15.41 +from utils import _dtmldir
   15.42 +from utils import formatRFC822Headers
   15.43 +from utils import parseHeadersBody
   15.44 +
   15.45 +factory_type_information = (
   15.46 +  { 'id'             : 'Link'
   15.47 +  , 'meta_type'      : 'Link'
   15.48 +  , 'description'    : """\
   15.49 +Link items are annotated URLs.
   15.50 +"""
   15.51 +  , 'icon'           : 'link_icon.gif'
   15.52 +  , 'product'        : 'CMFDefault'
   15.53 +  , 'factory'        : 'addLink'
   15.54 +  , 'immediate_view' : 'metadata_edit_form'
   15.55 +  , 'aliases'        : {'(Default)':'link_view',
   15.56 +                        'view':'link_view'}
   15.57 +  , 'actions'        : ( { 'id'            : 'view'
   15.58 +                         , 'name'          : 'View'
   15.59 +                         , 'action': 'string:${object_url}/link_view'
   15.60 +                         , 'permissions'   : (View,)
   15.61 +                         }
   15.62 +                       , { 'id'            : 'edit'
   15.63 +                         , 'name'          : 'Edit'
   15.64 +                         , 'action': 'string:${object_url}/link_edit_form'
   15.65 +                         , 'permissions'   : (ModifyPortalContent,)
   15.66 +                         }
   15.67 +                       , { 'id'            : 'metadata'
   15.68 +                         , 'name'          : 'Metadata'
   15.69 +                         , 'action': 'string:${object_url}/metadata_edit_form'
   15.70 +                         , 'permissions'   : (ModifyPortalContent,)
   15.71 +                         }
   15.72 +                       )
   15.73 +  }
   15.74 +,
   15.75 +)
   15.76 +
   15.77 +def addLink( self
   15.78 +           , id
   15.79 +           , title=''
   15.80 +           , remote_url=''
   15.81 +           , description=''
   15.82 +           ):
   15.83 +    """
   15.84 +        Add a Link instance to 'self'.
   15.85 +    """
   15.86 +    o=Link( id, title, remote_url, description )
   15.87 +    self._setObject(id,o)
   15.88 +
   15.89 +
   15.90 +class Link( PortalContent
   15.91 +          , DefaultDublinCoreImpl
   15.92 +          ):
   15.93 +    """
   15.94 +        A Link
   15.95 +    """
   15.96 +
   15.97 +    __implements__ = ( PortalContent.__implements__
   15.98 +                     , DefaultDublinCoreImpl.__implements__
   15.99 +                     )
  15.100 +
  15.101 +    meta_type = 'Link'
  15.102 +    URL_FORMAT = format = 'text/url'
  15.103 +    effective_date = expiration_date = None
  15.104 +    _isDiscussable = 1
  15.105 +
  15.106 +    security = ClassSecurityInfo()
  15.107 +
  15.108 +    def __init__( self
  15.109 +                , id
  15.110 +                , title=''
  15.111 +                , remote_url=''
  15.112 +                , description=''
  15.113 +                ):
  15.114 +        DefaultDublinCoreImpl.__init__(self)
  15.115 +        self.id=id
  15.116 +        self.title=title
  15.117 +        self.description=description
  15.118 +        self._edit(remote_url)
  15.119 +        self.format=self.URL_FORMAT
  15.120 +
  15.121 +    security.declareProtected(ModifyPortalContent, 'manage_edit')
  15.122 +    manage_edit = DTMLFile( 'zmi_editLink', _dtmldir )
  15.123 +
  15.124 +    security.declareProtected(ModifyPortalContent, 'manage_editLink')
  15.125 +    def manage_editLink( self, remote_url, REQUEST=None ):
  15.126 +        """
  15.127 +            Update the Link via the ZMI.
  15.128 +        """
  15.129 +        self._edit( remote_url )
  15.130 +        if REQUEST is not None:
  15.131 +            REQUEST['RESPONSE'].redirect( self.absolute_url()
  15.132 +                                        + '/manage_edit'
  15.133 +                                        + '?manage_tabs_message=Link+updated'
  15.134 +                                        )
  15.135 +
  15.136 +    security.declarePrivate( '_edit' )
  15.137 +    def _edit( self, remote_url ):
  15.138 +        """
  15.139 +            Edit the Link
  15.140 +        """
  15.141 +        tokens = urlparse.urlparse( remote_url, 'http' )
  15.142 +        if tokens[0] == 'http':
  15.143 +            if tokens[1]:
  15.144 +                # We have a nethost. All is well.
  15.145 +                url = urlparse.urlunparse(tokens)
  15.146 +            elif tokens[2:] == ('', '', '', ''):
  15.147 +                # Empty URL
  15.148 +                url = ''
  15.149 +            else:
  15.150 +                # Relative URL, keep it that way, without http:
  15.151 +                tokens = ('', '') + tokens[2:]
  15.152 +                url = urlparse.urlunparse(tokens)
  15.153 +        else:
  15.154 +            # Other scheme, keep original
  15.155 +            url = urlparse.urlunparse(tokens)
  15.156 +        self.remote_url = url
  15.157 +
  15.158 +    security.declareProtected(ModifyPortalContent, 'edit')
  15.159 +    def edit(self, remote_url ):
  15.160 +        """ Update and reindex. """
  15.161 +        self._edit( remote_url )
  15.162 +        self.reindexObject()
  15.163 +
  15.164 +    security.declareProtected(View, 'SearchableText')
  15.165 +    def SearchableText(self):
  15.166 +        """
  15.167 +            text for indexing
  15.168 +        """
  15.169 +        return "%s %s" % (self.title, self.description)
  15.170 +
  15.171 +    security.declareProtected(View, 'getRemoteUrl')
  15.172 +    def getRemoteUrl(self):
  15.173 +        """
  15.174 +            returns the remote URL of the Link
  15.175 +        """
  15.176 +        return self.remote_url
  15.177 +
  15.178 +    security.declarePrivate( '_writeFromPUT' )
  15.179 +    def _writeFromPUT( self, body ):
  15.180 +        headers = {}
  15.181 +        headers, body = parseHeadersBody(body, headers)
  15.182 +        lines = body.split('\n')
  15.183 +        self.edit( lines[0] )
  15.184 +        headers['Format'] = self.URL_FORMAT
  15.185 +        new_subject = keywordsplitter(headers)
  15.186 +        headers['Subject'] = new_subject or self.Subject()
  15.187 +        new_contrib = contributorsplitter(headers)
  15.188 +        headers['Contributors'] = new_contrib or self.Contributors()
  15.189 +        haveheader = headers.has_key
  15.190 +        for key, value in self.getMetadataHeaders():
  15.191 +            if not haveheader(key):
  15.192 +                headers[key] = value
  15.193 +
  15.194 +        self._editMetadata(title=headers['Title'],
  15.195 +                          subject=headers['Subject'],
  15.196 +                          description=headers['Description'],
  15.197 +                          contributors=headers['Contributors'],
  15.198 +                          effective_date=headers['Effective_date'],
  15.199 +                          expiration_date=headers['Expiration_date'],
  15.200 +                          format=headers['Format'],
  15.201 +                          language=headers['Language'],
  15.202 +                          rights=headers['Rights'],
  15.203 +                          )
  15.204 +
  15.205 +    ## FTP handlers
  15.206 +    security.declareProtected(ModifyPortalContent, 'PUT')
  15.207 +    def PUT(self, REQUEST, RESPONSE):
  15.208 +        """
  15.209 +            Handle HTTP / WebDAV / FTP PUT requests.
  15.210 +        """
  15.211 +        self.dav__init(REQUEST, RESPONSE)
  15.212 +        self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1)
  15.213 +        body = REQUEST.get('BODY', '')
  15.214 +        try:
  15.215 +            self._writeFromPUT( body )
  15.216 +            RESPONSE.setStatus(204)
  15.217 +            return RESPONSE
  15.218 +        except ResourceLockedError, msg:
  15.219 +            transaction.abort()
  15.220 +            RESPONSE.setStatus(423)
  15.221 +            return RESPONSE
  15.222 +
  15.223 +    security.declareProtected(View, 'manage_FTPget')
  15.224 +    def manage_FTPget(self):
  15.225 +        """
  15.226 +            Get the link as text for WebDAV src / FTP download.
  15.227 +        """
  15.228 +        hdrlist = self.getMetadataHeaders()
  15.229 +        hdrtext = formatRFC822Headers( hdrlist )
  15.230 +        bodytext = '%s\n\n%s' % ( hdrtext, self.getRemoteUrl() )
  15.231 +
  15.232 +        return bodytext
  15.233 +
  15.234 +    security.declareProtected(View, 'get_size')
  15.235 +    def get_size( self ):
  15.236 +        """ Used for FTP and apparently the ZMI now too.
  15.237 +        """
  15.238 +        return len(self.manage_FTPget())
  15.239 +
  15.240 +InitializeClass( Link )
    16.1 new file mode 100644
    16.2 --- /dev/null
    16.3 +++ b/MembershipTool.py
    16.4 @@ -0,0 +1,297 @@
    16.5 +##############################################################################
    16.6 +#
    16.7 +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
    16.8 +#
    16.9 +# This software is subject to the provisions of the Zope Public License,
   16.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   16.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   16.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   16.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   16.14 +# FOR A PARTICULAR PURPOSE.
   16.15 +#
   16.16 +##############################################################################
   16.17 +""" CMFDefault portal_membership tool.
   16.18 +
   16.19 +$Id: MembershipTool.py 37067 2005-06-16 10:05:38Z yuppie $
   16.20 +"""
   16.21 +
   16.22 +from AccessControl import ClassSecurityInfo
   16.23 +from Acquisition import aq_base
   16.24 +from Acquisition import aq_inner
   16.25 +from Acquisition import aq_parent
   16.26 +from Globals import DTMLFile
   16.27 +from Globals import InitializeClass
   16.28 +
   16.29 +from Products.CMFCore.ActionInformation import ActionInformation as AI
   16.30 +from Products.CMFCore.ActionProviderBase import ActionProviderBase
   16.31 +from Products.CMFCore.Expression import Expression
   16.32 +from Products.CMFCore.MembershipTool import MembershipTool as BaseTool
   16.33 +from Products.CMFCore.utils import _checkPermission
   16.34 +from Products.CMFCore.utils import _getAuthenticatedUser
   16.35 +from Products.CMFCore.utils import getToolByName
   16.36 +
   16.37 +from Document import addDocument
   16.38 +from interfaces.portal_membership \
   16.39 +        import portal_membership as IMembershipTool
   16.40 +from permissions import ListPortalMembers
   16.41 +from permissions import ManagePortal
   16.42 +from permissions import ManageUsers
   16.43 +from permissions import View
   16.44 +from utils import _dtmldir
   16.45 +
   16.46 +
   16.47 +DEFAULT_MEMBER_CONTENT = """\
   16.48 +Default page for %s
   16.49 +
   16.50 +  This is the default document created for you when
   16.51 +  you joined this community.
   16.52 +
   16.53 +  To change the content just select "Edit"
   16.54 +  in the Tool Box on the left.
   16.55 +"""
   16.56 +
   16.57 +
   16.58 +class MembershipTool( BaseTool ):
   16.59 +    """ Implement 'portal_membership' interface using "stock" policies.
   16.60 +    """
   16.61 +
   16.62 +    __implements__ = (IMembershipTool, ActionProviderBase.__implements__)
   16.63 +
   16.64 +    meta_type = 'Default Membership Tool'
   16.65 +    _actions = (
   16.66 +      AI( id='login'
   16.67 +        , title='Login'
   16.68 +        , description='Click here to Login'
   16.69 +        , action=Expression(text='string:${portal_url}/login_form')
   16.70 +        , permissions=(View,)
   16.71 +        , category='user'
   16.72 +        , condition=Expression(text='not: member')
   16.73 +        , visible=1
   16.74 +        )
   16.75 +    , AI( id='preferences'
   16.76 +        , title='Preferences'
   16.77 +        , description='Change your user preferences'
   16.78 +        , action=Expression(text='string:${portal_url}/personalize_form')
   16.79 +        , permissions=(View,)
   16.80 +        , category='user'
   16.81 +        , condition=Expression(text='member')
   16.82 +        , visible=1
   16.83 +        )
   16.84 +    , AI( id='logout'
   16.85 +        , title='Log out'
   16.86 +        , description='Click here to logout'
   16.87 +        , action=Expression(text='string:${portal_url}/logout')
   16.88 +        , permissions=(View,)
   16.89 +        , category='user'
   16.90 +        , condition=Expression(text='member')
   16.91 +        , visible=1
   16.92 +        )
   16.93 +    , AI( id='addFavorite'
   16.94 +        , title='Add to favorites'
   16.95 +        , description='Add this item to your favorites'
   16.96 +        , action=Expression(text='string:${object_url}/addtoFavorites')
   16.97 +        , permissions=(View,)
   16.98 +        , category='user'
   16.99 +        , condition=Expression(text= 'portal/portal_membership'
  16.100 +                                   + '/getHomeFolder')
  16.101 +        , visible=1
  16.102 +        )
  16.103 +    , AI( id='mystuff'
  16.104 +        , title='My stuff'
  16.105 +        , description='Goto your home folder'
  16.106 +        , action=Expression(text='string:${portal/portal_membership'
  16.107 +                               + '/getHomeUrl}/folder_contents')
  16.108 +        , permissions=(View,)
  16.109 +        , category='user'
  16.110 +        , condition=Expression( text='python: member and '
  16.111 +                              + 'portal.portal_membership.getHomeFolder()')
  16.112 +        , visible=1
  16.113 +        )
  16.114 +    , AI( id='favorites'
  16.115 +        , title='My favorites'
  16.116 +        , description='Browse your favorites'
  16.117 +        , action=Expression(text='string:${portal/portal_membership'
  16.118 +                               + '/getHomeUrl}/Favorites/folder_contents')
  16.119 +        , permissions=(View,)
  16.120 +        , category='user'
  16.121 +        , condition=Expression( text='python: member and '
  16.122 +                                   + 'hasattr(portal.portal_membership.'
  16.123 +                                   +  'getHomeFolder(), "Favorites")')
  16.124 +        , visible=1
  16.125 +        )
  16.126 +    , AI( id='manage_members'
  16.127 +        , title='Manage members'
  16.128 +        , description='Manage portal members'
  16.129 +        , action=Expression(text='string:${portal_url}/members_manage_form')
  16.130 +        , permissions=(ManageUsers,)
  16.131 +        , category='global'
  16.132 +        , condition=None
  16.133 +        , visible=1
  16.134 +        )
  16.135 +    , AI( id='logged_in'
  16.136 +        , title='Logged in'
  16.137 +        , description='Used by scripts'
  16.138 +        , action=Expression(text='string:${portal_url}/logged_in')
  16.139 +        , permissions=(View,)
  16.140 +        , category='user'
  16.141 +        , condition=None
  16.142 +        , visible=0
  16.143 +        )
  16.144 +    )
  16.145 +
  16.146 +    membersfolder_id = 'Members'
  16.147 +
  16.148 +    security = ClassSecurityInfo()
  16.149 +
  16.150 +    #
  16.151 +    #   ZMI methods
  16.152 +    #
  16.153 +    security.declareProtected( ManagePortal, 'manage_overview' )
  16.154 +    manage_overview = DTMLFile( 'explainMembershipTool', _dtmldir )
  16.155 +
  16.156 +    security.declareProtected(ManagePortal, 'manage_mapRoles')
  16.157 +    manage_mapRoles = DTMLFile('membershipRolemapping', _dtmldir )
  16.158 +
  16.159 +    security.declareProtected(ManagePortal, 'manage_setMembersFolderById')
  16.160 +    def manage_setMembersFolderById(self, id='', REQUEST=None):
  16.161 +        """ ZMI method to set the members folder object by its id.
  16.162 +        """
  16.163 +        self.setMembersFolderById(id)
  16.164 +        if REQUEST is not None:
  16.165 +            REQUEST['RESPONSE'].redirect( self.absolute_url()
  16.166 +                    + '/manage_mapRoles'
  16.167 +                    + '?manage_tabs_message=Members+folder+changed.'
  16.168 +                    )
  16.169 +
  16.170 +    #
  16.171 +    #   'portal_membership' interface methods
  16.172 +    #
  16.173 +    security.declareProtected( ListPortalMembers, 'getRoster' )
  16.174 +    def getRoster(self):
  16.175 +        """ Return a list of mappings for 'listed' members.
  16.176 +
  16.177 +        If Manager, return a list of all usernames.  The mapping
  16.178 +        contains the id and listed variables.
  16.179 +        """
  16.180 +        isUserManager = _checkPermission(ManageUsers, self)
  16.181 +        roster = []
  16.182 +        for member in self.listMembers():
  16.183 +            if isUserManager or member.listed:
  16.184 +                roster.append({'id':member.getId(),
  16.185 +                               'listed':member.listed})
  16.186 +        return roster
  16.187 +
  16.188 +    security.declareProtected(ManagePortal, 'setMembersFolderById')
  16.189 +    def setMembersFolderById(self, id=''):
  16.190 +        """ Set the members folder object by its id.
  16.191 +        """
  16.192 +        self.membersfolder_id = id.strip()
  16.193 +
  16.194 +    security.declarePublic('getMembersFolder')
  16.195 +    def getMembersFolder(self):
  16.196 +        """ Get the members folder object.
  16.197 +        """
  16.198 +        parent = aq_parent( aq_inner(self) )
  16.199 +        members = getattr(parent, self.membersfolder_id, None)
  16.200 +        return members
  16.201 +
  16.202 +    security.declarePublic('createMemberArea')
  16.203 +    def createMemberArea(self, member_id=''):
  16.204 +        """ Create a member area for 'member_id' or authenticated user.
  16.205 +        """
  16.206 +        if not self.getMemberareaCreationFlag():
  16.207 +            return None
  16.208 +        members = self.getMembersFolder()
  16.209 +        if not members:
  16.210 +            return None
  16.211 +        if self.isAnonymousUser():
  16.212 +            return None
  16.213 +        # Note: We can't use getAuthenticatedMember() and getMemberById()
  16.214 +        # because they might be wrapped by MemberDataTool.
  16.215 +        user = _getAuthenticatedUser(self)
  16.216 +        user_id = user.getId()
  16.217 +        if member_id in ('', user_id):
  16.218 +            member = user
  16.219 +            member_id = user_id
  16.220 +        else:
  16.221 +            if _checkPermission(ManageUsers, self):
  16.222 +                member = self.acl_users.getUserById(member_id, None)
  16.223 +                if member:
  16.224 +                    member = member.__of__(self.acl_users)
  16.225 +                else:
  16.226 +                    raise ValueError, 'Member %s does not exist' % member_id
  16.227 +            else:
  16.228 +                return None
  16.229 +        if hasattr( aq_base(members), member_id ):
  16.230 +            return None
  16.231 +
  16.232 +        # Note: We can't use invokeFactory() to add folder and content because
  16.233 +        # the user might not have the necessary permissions.
  16.234 +
  16.235 +        # Create Member's home folder.
  16.236 +        members.manage_addPortalFolder(id=member_id,
  16.237 +                                       title="%s's Home" % member_id)
  16.238 +        f = members._getOb(member_id)
  16.239 +
  16.240 +        # Grant Ownership and Owner role to Member
  16.241 +        f.changeOwnership(member)
  16.242 +        f.__ac_local_roles__ = None
  16.243 +        f.manage_setLocalRoles(member_id, ['Owner'])
  16.244 +
  16.245 +        # Create Member's initial content.
  16.246 +        if hasattr(self, 'createMemberContent'):
  16.247 +            self.createMemberContent(member=member,
  16.248 +                                   member_id=member_id,
  16.249 +                                   member_folder=f)
  16.250 +        else:
  16.251 +            addDocument( f
  16.252 +                       , 'index_html'
  16.253 +                       , member_id+"'s Home"
  16.254 +                       , member_id+"'s front page"
  16.255 +                       , "structured-text"
  16.256 +                       , (DEFAULT_MEMBER_CONTENT % member_id)
  16.257 +                       )
  16.258 +
  16.259 +            # Grant Ownership and Owner role to Member
  16.260 +            f.index_html.changeOwnership(member)
  16.261 +            f.index_html.__ac_local_roles__ = None
  16.262 +            f.index_html.manage_setLocalRoles(member_id, ['Owner'])
  16.263 +
  16.264 +            f.index_html._setPortalTypeName( 'Document' )
  16.265 +            f.index_html.reindexObject()
  16.266 +            f.index_html.notifyWorkflowCreated()
  16.267 +        return f
  16.268 +
  16.269 +    security.declarePublic('createMemberarea')
  16.270 +    createMemberarea = createMemberArea
  16.271 +
  16.272 +    def getHomeFolder(self, id=None, verifyPermission=0):
  16.273 +        """ Return a member's home folder object, or None.
  16.274 +        """
  16.275 +        if id is None:
  16.276 +            member = self.getAuthenticatedMember()
  16.277 +            if not hasattr(member, 'getMemberId'):
  16.278 +                return None
  16.279 +            id = member.getMemberId()
  16.280 +        members = self.getMembersFolder()
  16.281 +        if members:
  16.282 +            try:
  16.283 +                folder = members._getOb(id)
  16.284 +                if verifyPermission and not _checkPermission(View, folder):
  16.285 +                    # Don't return the folder if the user can't get to it.
  16.286 +                    return None
  16.287 +                return folder
  16.288 +            except (AttributeError, TypeError, KeyError):
  16.289 +                pass
  16.290 +        return None
  16.291 +
  16.292 +    def getHomeUrl(self, id=None, verifyPermission=0):
  16.293 +        """ Return the URL to a member's home folder, or None.
  16.294 +        """
  16.295 +        home = self.getHomeFolder(id, verifyPermission)
  16.296 +        if home is not None:
  16.297 +            return home.absolute_url()
  16.298 +        else:
  16.299 +            return None
  16.300 +
  16.301 +InitializeClass(MembershipTool)
    17.1 new file mode 100644
    17.2 --- /dev/null
    17.3 +++ b/MetadataTool.py
    17.4 @@ -0,0 +1,549 @@
    17.5 +##############################################################################
    17.6 +#
    17.7 +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
    17.8 +#
    17.9 +# This software is subject to the provisions of the Zope Public License,
   17.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   17.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   17.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   17.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   17.14 +# FOR A PARTICULAR PURPOSE.
   17.15 +#
   17.16 +##############################################################################
   17.17 +""" CMFDefault portal_metadata tool.
   17.18 +
   17.19 +$Id: MetadataTool.py 37005 2005-05-04 21:33:28Z jens $
   17.20 +"""
   17.21 +
   17.22 +from AccessControl import ClassSecurityInfo
   17.23 +from AccessControl import getSecurityManager
   17.24 +from Globals import DTMLFile
   17.25 +from Globals import InitializeClass
   17.26 +from Globals import PersistentMapping
   17.27 +from OFS.SimpleItem import SimpleItem
   17.28 +
   17.29 +from Products.CMFCore.ActionProviderBase import ActionProviderBase
   17.30 +from Products.CMFCore.interfaces.portal_metadata \
   17.31 +        import portal_metadata as IMetadataTool
   17.32 +from Products.CMFCore.utils import UniqueObject
   17.33 +
   17.34 +from exceptions import MetadataError
   17.35 +from permissions import ManagePortal
   17.36 +from permissions import ModifyPortalContent
   17.37 +from permissions import View
   17.38 +from utils import _dtmldir
   17.39 +
   17.40 +
   17.41 +class MetadataElementPolicy( SimpleItem ):
   17.42 +    """
   17.43 +        Represent a type-specific policy about a particular DCMI element.
   17.44 +    """
   17.45 +
   17.46 +    security = ClassSecurityInfo()
   17.47 +    #
   17.48 +    #   Default values.
   17.49 +    #
   17.50 +    is_required         = 0
   17.51 +    supply_default      = 0
   17.52 +    default_value       = ''
   17.53 +    enforce_vocabulary  = 0
   17.54 +    allowed_vocabulary  = ()
   17.55 +
   17.56 +    def __init__(self, is_multi_valued=False):
   17.57 +        self.is_multi_valued = bool(is_multi_valued)
   17.58 +
   17.59 +    #
   17.60 +    #   Mutator.
   17.61 +    #
   17.62 +    security.declareProtected(ManagePortal , 'edit')
   17.63 +    def edit( self
   17.64 +            , is_required
   17.65 +            , supply_default
   17.66 +            , default_value
   17.67 +            , enforce_vocabulary
   17.68 +            , allowed_vocabulary
   17.69 +            ):
   17.70 +        self.is_required        = bool(is_required)
   17.71 +        self.supply_default     = bool(supply_default)
   17.72 +        self.default_value      = default_value
   17.73 +        self.enforce_vocabulary = bool(enforce_vocabulary)
   17.74 +        self.allowed_vocabulary = tuple(allowed_vocabulary)
   17.75 +
   17.76 +    #
   17.77 +    #   Query interface
   17.78 +    #
   17.79 +    security.declareProtected(View , 'isMultiValued')
   17.80 +    def isMultiValued( self ):
   17.81 +        """
   17.82 +            Can this element hold multiple values?
   17.83 +        """
   17.84 +        return self.is_multi_valued
   17.85 +
   17.86 +    security.declareProtected(View , 'isRequired')
   17.87 +    def isRequired( self ):
   17.88 +        """
   17.89 +            Must this element be supplied?
   17.90 +        """
   17.91 +        return self.is_required
   17.92 +
   17.93 +    security.declareProtected(View , 'supplyDefault')
   17.94 +    def supplyDefault( self ):
   17.95 +        """
   17.96 +            Should the tool supply a default?
   17.97 +        """
   17.98 +        return self.supply_default
   17.99 +
  17.100 +    security.declareProtected(View , 'defaultValue')
  17.101 +    def defaultValue( self ):
  17.102 +        """
  17.103 +            If so, what is the default?
  17.104 +        """
  17.105 +        return self.default_value
  17.106 +
  17.107 +    security.declareProtected(View , 'enforceVocabulary')
  17.108 +    def enforceVocabulary( self ):
  17.109 +        """
  17.110 +        """
  17.111 +        return self.enforce_vocabulary
  17.112 +
  17.113 +    security.declareProtected(View , 'allowedVocabulary')
  17.114 +    def allowedVocabulary( self ):
  17.115 +        """
  17.116 +        """
  17.117 +        return self.allowed_vocabulary
  17.118 +
  17.119 +InitializeClass( MetadataElementPolicy )
  17.120 +
  17.121 +
  17.122 +DEFAULT_ELEMENT_SPECS = ( ( 'Title', 0 )
  17.123 +                        , ( 'Description', 0 )
  17.124 +                        , ( 'Subject', 1 )
  17.125 +                        , ( 'Format', 0 )
  17.126 +                        , ( 'Language', 0 )
  17.127 +                        , ( 'Rights', 0 )
  17.128 +                        )
  17.129 +
  17.130 +
  17.131 +class ElementSpec( SimpleItem ):
  17.132 +    """
  17.133 +        Represent all the tool knows about a single metadata element.
  17.134 +    """
  17.135 +    security = ClassSecurityInfo()
  17.136 +
  17.137 +    #
  17.138 +    #   Default values.
  17.139 +    #
  17.140 +    is_multi_valued = 0
  17.141 +
  17.142 +    def __init__( self, is_multi_valued ):
  17.143 +        self.is_multi_valued  = is_multi_valued
  17.144 +        self.policies         = PersistentMapping()
  17.145 +        self.policies[ None ] = self._makePolicy()  # set default policy
  17.146 +
  17.147 +    security.declarePrivate( '_makePolicy' )
  17.148 +    def _makePolicy( self ):
  17.149 +        return MetadataElementPolicy( self.is_multi_valued )
  17.150 +
  17.151 +    security.declareProtected(View , 'isMultiValued')
  17.152 +    def isMultiValued( self ):
  17.153 +        """
  17.154 +            Is this element multi-valued?
  17.155 +        """
  17.156 +        return self.is_multi_valued
  17.157 +
  17.158 +    security.declareProtected(View , 'getPolicy')
  17.159 +    def getPolicy( self, typ=None ):
  17.160 +        """
  17.161 +            Find the policy this element for objects whose type
  17.162 +            object name is 'typ';  return a default, if none found.
  17.163 +        """
  17.164 +        try:
  17.165 +            return self.policies[ typ ].__of__(self)
  17.166 +        except KeyError:
  17.167 +            return self.policies[ None ].__of__(self)
  17.168 +
  17.169 +    security.declareProtected(View , 'listPolicies')
  17.170 +    def listPolicies( self ):
  17.171 +        """
  17.172 +            Return a list of all policies for this element.
  17.173 +        """
  17.174 +        res = []
  17.175 +        for k, v in self.policies.items():
  17.176 +            res.append((k, v.__of__(self)))
  17.177 +        return res
  17.178 +
  17.179 +    security.declareProtected(ManagePortal , 'addPolicy')
  17.180 +    def addPolicy( self, typ ):
  17.181 +        """
  17.182 +            Add a policy to this element for objects whose type
  17.183 +            object name is 'typ'.
  17.184 +        """
  17.185 +        if typ is None:
  17.186 +            raise MetadataError, "Can't replace default policy."
  17.187 +
  17.188 +        if self.policies.has_key( typ ):
  17.189 +            raise MetadataError, "Existing policy for content type:" + typ
  17.190 +
  17.191 +        self.policies[ typ ] = self._makePolicy()
  17.192 +
  17.193 +    security.declareProtected(ManagePortal, 'removePolicy')
  17.194 +    def removePolicy( self, typ ):
  17.195 +        """
  17.196 +            Remove the policy from this element for objects whose type
  17.197 +            object name is 'typ' (*not* the default, however).
  17.198 +        """
  17.199 +        if typ is None:
  17.200 +            raise MetadataError, "Can't remove default policy."
  17.201 +        del self.policies[ typ ]
  17.202 +
  17.203 +InitializeClass( ElementSpec )
  17.204 +
  17.205 +
  17.206 +class MetadataTool( UniqueObject, SimpleItem, ActionProviderBase ):
  17.207 +
  17.208 +    __implements__ = (IMetadataTool, ActionProviderBase.__implements__)
  17.209 +
  17.210 +    id = 'portal_metadata'
  17.211 +    meta_type = 'Default Metadata Tool'
  17.212 +    _actions = ()
  17.213 +
  17.214 +    #
  17.215 +    #   Default values.
  17.216 +    #
  17.217 +    publisher           = ''
  17.218 +    element_specs       = None
  17.219 +    #initial_values_hook = None
  17.220 +    #validation_hook     = None
  17.221 +
  17.222 +    security = ClassSecurityInfo()
  17.223 +
  17.224 +    def __init__( self
  17.225 +                , publisher=None
  17.226 +               #, initial_values_hook=None
  17.227 +               #, validation_hook=None
  17.228 +                , element_specs=DEFAULT_ELEMENT_SPECS
  17.229 +                ):
  17.230 +
  17.231 +        self.editProperties( publisher
  17.232 +                          #, initial_values_hook
  17.233 +                          #, validation_hook
  17.234 +                           )
  17.235 +
  17.236 +        self.element_specs = PersistentMapping()
  17.237 +
  17.238 +        for name, is_multi_valued in element_specs:
  17.239 +            self.element_specs[ name ] = ElementSpec( is_multi_valued )
  17.240 +
  17.241 +    #
  17.242 +    #   ZMI methods
  17.243 +    #
  17.244 +    manage_options = ( ActionProviderBase.manage_options +
  17.245 +                     ( { 'label'      : 'Overview'
  17.246 +                         , 'action'     : 'manage_overview'
  17.247 +                         }
  17.248 +                       , { 'label'      : 'Properties'
  17.249 +                         , 'action'     : 'propertiesForm'
  17.250 +                         }
  17.251 +                       , { 'label'      : 'Elements'
  17.252 +                         , 'action'     : 'elementPoliciesForm'
  17.253 +                         }
  17.254 +            # TODO     , { 'label'      : 'Types'
  17.255 +            #            , 'action'     : 'typesForm'
  17.256 +            #            }
  17.257 +                       )
  17.258 +                     + SimpleItem.manage_options
  17.259 +                     )
  17.260 +
  17.261 +    security.declareProtected(ManagePortal, 'manage_overview')
  17.262 +    manage_overview = DTMLFile( 'explainMetadataTool', _dtmldir )
  17.263 +
  17.264 +    security.declareProtected(ManagePortal, 'propertiesForm')
  17.265 +    propertiesForm = DTMLFile( 'metadataProperties', _dtmldir )
  17.266 +
  17.267 +    security.declareProtected(ManagePortal, 'editProperties')
  17.268 +    def editProperties( self
  17.269 +                      , publisher=None
  17.270 +               # TODO , initial_values_hook=None
  17.271 +               # TODO , validation_hook=None
  17.272 +                      , REQUEST=None
  17.273 +                      ):
  17.274 +        """
  17.275 +            Form handler for "tool-wide" properties (including list of
  17.276 +            metadata elements).
  17.277 +        """
  17.278 +        if publisher is not None:
  17.279 +            self.publisher = publisher
  17.280 +
  17.281 +        # TODO self.initial_values_hook = initial_values_hook
  17.282 +        # TODO self.validation_hook = validation_hook
  17.283 +
  17.284 +        if REQUEST is not None:
  17.285 +            REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
  17.286 +                                        + '/propertiesForm'
  17.287 +                                        + '?manage_tabs_message=Tool+updated.'
  17.288 +                                        )
  17.289 +
  17.290 +    security.declareProtected(ManagePortal, 'elementPoliciesForm')
  17.291 +    elementPoliciesForm = DTMLFile( 'metadataElementPolicies', _dtmldir )
  17.292 +
  17.293 +    security.declareProtected(ManagePortal, 'addElementPolicy')
  17.294 +    def addElementPolicy( self
  17.295 +                        , element
  17.296 +                        , content_type
  17.297 +                        , is_required
  17.298 +                        , supply_default
  17.299 +                        , default_value
  17.300 +                        , enforce_vocabulary
  17.301 +                        , allowed_vocabulary
  17.302 +                        , REQUEST=None
  17.303 +                        ):
  17.304 +        """
  17.305 +            Add a type-specific policy for one of our elements.
  17.306 +        """
  17.307 +        if content_type == '<default>':
  17.308 +            content_type = None
  17.309 +
  17.310 +        spec = self.getElementSpec( element )
  17.311 +        spec.addPolicy( content_type )
  17.312 +        policy = spec.getPolicy( content_type )
  17.313 +        policy.edit( is_required
  17.314 +                   , supply_default
  17.315 +                   , default_value
  17.316 +                   , enforce_vocabulary
  17.317 +                   , allowed_vocabulary
  17.318 +                   )
  17.319 +        if REQUEST is not None:
  17.320 +            REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
  17.321 +               + '/elementPoliciesForm'
  17.322 +               + '?element=' + element
  17.323 +               + '&manage_tabs_message=Policy+added.'
  17.324 +               )
  17.325 +
  17.326 +    security.declareProtected(ManagePortal, 'removeElementPolicy')
  17.327 +    def removeElementPolicy( self
  17.328 +                           , element
  17.329 +                           , content_type
  17.330 +                           , REQUEST=None
  17.331 +                           ):
  17.332 +        """
  17.333 +            Remvoe a type-specific policy for one of our elements.
  17.334 +        """
  17.335 +        if content_type == '<default>':
  17.336 +            content_type = None
  17.337 +
  17.338 +        spec = self.getElementSpec( element )
  17.339 +        spec.removePolicy( content_type )
  17.340 +        if REQUEST is not None:
  17.341 +            REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
  17.342 +               + '/elementPoliciesForm'
  17.343 +               + '?element=' + element
  17.344 +               + '&manage_tabs_message=Policy+removed.'
  17.345 +               )
  17.346 +
  17.347 +    security.declareProtected(ManagePortal, 'updateElementPolicy')
  17.348 +    def updateElementPolicy( self
  17.349 +                           , element
  17.350 +                           , content_type
  17.351 +                           , is_required
  17.352 +                           , supply_default
  17.353 +                           , default_value
  17.354 +                           , enforce_vocabulary
  17.355 +                           , allowed_vocabulary
  17.356 +                           , REQUEST=None
  17.357 +                           ):
  17.358 +        """
  17.359 +            Update a policy for one of our elements ('content_type'
  17.360 +            will be '<default>' when we edit the default).
  17.361 +        """
  17.362 +        if content_type == '<default>':
  17.363 +            content_type = None
  17.364 +        spec = self.getElementSpec( element )
  17.365 +        policy = spec.getPolicy( content_type )
  17.366 +        policy.edit( is_required
  17.367 +                   , supply_default
  17.368 +                   , default_value
  17.369 +                   , enforce_vocabulary
  17.370 +                   , allowed_vocabulary
  17.371 +                   )
  17.372 +        if REQUEST is not None:
  17.373 +            REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
  17.374 +               + '/elementPoliciesForm'
  17.375 +               + '?element=' + element
  17.376 +               + '&manage_tabs_message=Policy+updated.'
  17.377 +               )
  17.378 +
  17.379 +
  17.380 +    #
  17.381 +    #   Element spec manipulation.
  17.382 +    #
  17.383 +    security.declareProtected(ManagePortal, 'listElementSpecs')
  17.384 +    def listElementSpecs( self ):
  17.385 +        """
  17.386 +            Return a list of ElementSpecs representing
  17.387 +            the elements managed by the tool.
  17.388 +        """
  17.389 +        res = []
  17.390 +        for k, v in self.element_specs.items():
  17.391 +            res.append((k, v.__of__(self)))
  17.392 +        return res
  17.393 +
  17.394 +    security.declareProtected(ManagePortal, 'getElementSpec')
  17.395 +    def getElementSpec( self, element ):
  17.396 +        """
  17.397 +            Return an ElementSpec representing the tool's knowledge
  17.398 +            of 'element'.
  17.399 +        """
  17.400 +        return self.element_specs[ element ].__of__( self )
  17.401 +
  17.402 +    security.declareProtected(ManagePortal, 'addElementSpec')
  17.403 +    def addElementSpec( self, element, is_multi_valued, REQUEST=None ):
  17.404 +        """
  17.405 +            Add 'element' to our list of managed elements.
  17.406 +        """
  17.407 +        # Don't replace.
  17.408 +        if self.element_specs.has_key( element ):
  17.409 +           return
  17.410 +
  17.411 +        self.element_specs[ element ] = ElementSpec( is_multi_valued )
  17.412 +
  17.413 +        if REQUEST is not None:
  17.414 +            REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
  17.415 +               + '/propertiesForm'
  17.416 +               + '?manage_tabs_message=Element+' + element + '+added.'
  17.417 +               )
  17.418 +
  17.419 +    security.declareProtected(ManagePortal, 'removeElementSpec')
  17.420 +    def removeElementSpec( self, element, REQUEST=None ):
  17.421 +        """
  17.422 +            Remove 'element' from our list of managed elements.
  17.423 +        """
  17.424 +        del self.element_specs[ element ]
  17.425 +
  17.426 +        if REQUEST is not None:
  17.427 +            REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
  17.428 +               + '/propertiesForm'
  17.429 +               + '?manage_tabs_message=Element+' + element + '+removed.'
  17.430 +               )
  17.431 +
  17.432 +    security.declareProtected(ManagePortal, 'listPolicies')
  17.433 +    def listPolicies( self, typ=None ):
  17.434 +        """
  17.435 +            Show all policies for a given content type, or the default
  17.436 +            if None.
  17.437 +        """
  17.438 +        result = []
  17.439 +        for element, spec in self.listElementSpecs():
  17.440 +            result.append( ( element, spec.getPolicy( typ ) ) )
  17.441 +        return result
  17.442 +
  17.443 +    #
  17.444 +    #   'portal_metadata' interface
  17.445 +    #
  17.446 +    security.declarePrivate( 'getFullName' )
  17.447 +    def getFullName( self, userid ):
  17.448 +        """
  17.449 +            Convert an internal userid to a "formal" name, if
  17.450 +            possible, perhaps using the 'portal_membership' tool.
  17.451 +
  17.452 +            Used to map userid's for Creator, Contributor DCMI
  17.453 +            queries.
  17.454 +        """
  17.455 +        return userid   # TODO: do lookup here
  17.456 +
  17.457 +    security.declarePublic( 'getPublisher' )
  17.458 +    def getPublisher( self ):
  17.459 +        """
  17.460 +            Return the "formal" name of the publisher of the
  17.461 +            portal.
  17.462 +        """
  17.463 +        return self.publisher
  17.464 +
  17.465 +    security.declarePublic( 'listAllowedVocabulary' )
  17.466 +    def listAllowedVocabulary( self, element, content=None, content_type=None ):
  17.467 +        """
  17.468 +            List allowed keywords for a given portal_type, or all
  17.469 +            possible keywords if none supplied.
  17.470 +        """
  17.471 +        spec = self.getElementSpec( element )
  17.472 +        if content_type is None and content:
  17.473 +            content_type = content.getPortalTypeName()
  17.474 +        return spec.getPolicy( content_type ).allowedVocabulary()
  17.475 +
  17.476 +    security.declarePublic( 'listAllowedSubjects' )
  17.477 +    def listAllowedSubjects( self, content=None, content_type=None ):
  17.478 +        """
  17.479 +            List allowed keywords for a given portal_type, or all
  17.480 +            possible keywords if none supplied.
  17.481 +        """
  17.482 +        return self.listAllowedVocabulary( 'Subject', content, content_type )
  17.483 +
  17.484 +    security.declarePublic( 'listAllowedFormats' )
  17.485 +    def listAllowedFormats( self, content=None, content_type=None ):
  17.486 +        """
  17.487 +            List the allowed 'Content-type' values for a particular
  17.488 +            portal_type, or all possible formats if none supplied.
  17.489 +        """
  17.490 +        return self.listAllowedVocabulary( 'Format', content, content_type )
  17.491 +
  17.492 +    security.declarePublic( 'listAllowedLanguages' )
  17.493 +    def listAllowedLanguages( self, content=None, content_type=None ):
  17.494 +        """
  17.495 +            List the allowed language values.
  17.496 +        """
  17.497 +        return self.listAllowedVocabulary( 'Language', content, content_type )
  17.498 +
  17.499 +    security.declarePublic( 'listAllowedRights' )
  17.500 +    def listAllowedRights( self, content=None, content_type=None ):
  17.501 +        """
  17.502 +            List the allowed values for a "Rights"
  17.503 +            selection list;  this gets especially important where
  17.504 +            syndication is involved.
  17.505 +        """
  17.506 +        return self.listAllowedVocabulary( 'Rights', content, content_type )
  17.507 +
  17.508 +    security.declareProtected(ModifyPortalContent, 'setInitialMetadata')
  17.509 +    def setInitialMetadata( self, content ):
  17.510 +        """
  17.511 +            Set initial values for content metatdata, supplying
  17.512 +            any site-specific defaults.
  17.513 +        """
  17.514 +        for element, policy in self.listPolicies(content.getPortalTypeName()):
  17.515 +
  17.516 +            if not getattr( content, element )():
  17.517 +
  17.518 +                if policy.supplyDefault():
  17.519 +                    setter = getattr( content, 'set%s' % element )
  17.520 +                    setter( policy.defaultValue() )
  17.521 +                elif policy.isRequired():
  17.522 +                    raise MetadataError, \
  17.523 +                          'Metadata element %s is required.' % element
  17.524 +
  17.525 +        # TODO:  Call initial_values_hook, if present
  17.526 +
  17.527 +
  17.528 +    security.declareProtected(View, 'validateMetadata')
  17.529 +    def validateMetadata( self, content ):
  17.530 +        """
  17.531 +            Enforce portal-wide policies about DCI, e.g.,
  17.532 +            requiring non-empty title/description, etc.  Called
  17.533 +            by the CMF immediately before saving changes to the
  17.534 +            metadata of an object.
  17.535 +        """
  17.536 +        for element, policy in self.listPolicies(content.getPortalTypeName()):
  17.537 +
  17.538 +            value = getattr( content, element )()
  17.539 +            if not value and policy.isRequired():
  17.540 +                raise MetadataError, \
  17.541 +                        'Metadata element %s is required.' % element
  17.542 +
  17.543 +            if value and policy.enforceVocabulary():
  17.544 +                values = policy.isMultiValued() and value or [ value ]
  17.545 +                for value in values:
  17.546 +                    if not value in policy.allowedVocabulary():
  17.547 +                        raise MetadataError, \
  17.548 +                        'Value %s is not in allowed vocabulary for ' \
  17.549 +                        'metadata element %s.' % ( value, element )
  17.550 +
  17.551 +        # TODO:  Call validation_hook, if present
  17.552 +
  17.553 +InitializeClass( MetadataTool )
    18.1 new file mode 100644
    18.2 --- /dev/null
    18.3 +++ b/NewsItem.py
    18.4 @@ -0,0 +1,101 @@
    18.5 +##############################################################################
    18.6 +#
    18.7 +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
    18.8 +#
    18.9 +# This software is subject to the provisions of the Zope Public License,
   18.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   18.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   18.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   18.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   18.14 +# FOR A PARTICULAR PURPOSE.
   18.15 +#
   18.16 +##############################################################################
   18.17 +""" News content object.
   18.18 +
   18.19 +$Id: NewsItem.py 36457 2004-08-12 15:07:44Z jens $
   18.20 +"""
   18.21 +
   18.22 +from AccessControl import ClassSecurityInfo
   18.23 +from Globals import InitializeClass
   18.24 +
   18.25 +from Document import Document
   18.26 +from permissions import ModifyPortalContent
   18.27 +from permissions import View
   18.28 +
   18.29 +factory_type_information = (
   18.30 +  { 'id'             : 'News Item'
   18.31 +  , 'meta_type'      : 'News Item'
   18.32 +  , 'description'    : """\
   18.33 +News Items contain short text articles and carry a title as well as
   18.34 +an optional description.
   18.35 +"""
   18.36 +  , 'icon'           : 'newsitem_icon.gif'
   18.37 +  , 'product'        : 'CMFDefault'
   18.38 +  , 'factory'        : 'addNewsItem'
   18.39 +  , 'immediate_view' : 'metadata_edit_form'
   18.40 +  , 'aliases'        : {'(Default)':'newsitem_view',
   18.41 +                        'view':'newsitem_view',
   18.42 +                        'gethtml':'source_html'}
   18.43 +  , 'actions'        : ( { 'id'            : 'view'
   18.44 +                         , 'name'          : 'View'
   18.45 +                         , 'action': 'string:${object_url}/newsitem_view'
   18.46 +                         , 'permissions'   : (View,)
   18.47 +                         }
   18.48 +                       , { 'id'            : 'edit'
   18.49 +                         , 'name'          : 'Edit'
   18.50 +                         , 'action': 'string:${object_url}/newsitem_edit_form'
   18.51 +                         , 'permissions'   : (ModifyPortalContent,)
   18.52 +                         }
   18.53 +                       , { 'id'            : 'metadata'
   18.54 +                         , 'name'          : 'Metadata'
   18.55 +                         , 'action': 'string:${object_url}/metadata_edit_form'
   18.56 +                         , 'permissions'   : (ModifyPortalContent,)
   18.57 +                         }
   18.58 +                       )
   18.59 +  }
   18.60 +,
   18.61 +)
   18.62 +
   18.63 +def addNewsItem( self
   18.64 +               , id
   18.65 +               , title=''
   18.66 +               , description=''
   18.67 +               , text=''
   18.68 +               , text_format=''
   18.69 +               ):
   18.70 +    """
   18.71 +        Add a NewsItem
   18.72 +    """
   18.73 +    o=NewsItem( id=id
   18.74 +              , title=title
   18.75 +              , description=description
   18.76 +              , text=text
   18.77 +              , text_format=text_format
   18.78 +              )
   18.79 +    self._setObject(id, o)
   18.80 +
   18.81 +
   18.82 +class NewsItem( Document ):
   18.83 +    """
   18.84 +        A News Item
   18.85 +    """
   18.86 +
   18.87 +    __implements__ = Document.__implements__  # redundant, but explicit
   18.88 +
   18.89 +    meta_type='News Item'
   18.90 +    text_format = 'html'
   18.91 +
   18.92 +    security = ClassSecurityInfo()
   18.93 +
   18.94 +    security.declareProtected(ModifyPortalContent, 'edit')
   18.95 +    def edit( self, text, description=None, text_format=None ):
   18.96 +        """
   18.97 +            Edit the News Item
   18.98 +        """
   18.99 +        if text_format is None:
  18.100 +            text_format = getattr(self, 'text_format', 'structured-text')
  18.101 +        if description is not None:
  18.102 +            self.setDescription( description )
  18.103 +        Document.edit( self, text_format, text )
  18.104 +
  18.105 +InitializeClass( NewsItem )
    19.1 new file mode 100644
    19.2 --- /dev/null
    19.3 +++ b/Portal.py
    19.4 @@ -0,0 +1,381 @@
    19.5 +##############################################################################
    19.6 +#
    19.7 +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
    19.8 +#
    19.9 +# This software is subject to the provisions of the Zope Public License,
   19.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   19.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   19.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   19.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   19.14 +# FOR A PARTICULAR PURPOSE.
   19.15 +#
   19.16 +##############################################################################
   19.17 +""" Portal class
   19.18 +
   19.19 +$Id: Portal.py 36699 2004-12-14 17:45:52Z yuppie $
   19.20 +"""
   19.21 +
   19.22 +from Globals import HTMLFile
   19.23 +from Globals import InitializeClass
   19.24 +
   19.25 +from Products.CMFCore.PortalObject import PortalObjectBase
   19.26 +from Products.CMFCore import PortalFolder
   19.27 +from Products.CMFCore.TypesTool import FactoryTypeInformation
   19.28 +from Products.CMFCore.utils import getToolByName
   19.29 +from Products.CMFTopic import Topic
   19.30 +from Products.CMFTopic import topic_globals
   19.31 +
   19.32 +from DublinCore import DefaultDublinCoreImpl
   19.33 +from permissions import AccessFuturePortalContent
   19.34 +from permissions import AddPortalContent
   19.35 +from permissions import AddPortalFolders
   19.36 +from permissions import DeleteObjects
   19.37 +from permissions import FTPAccess
   19.38 +from permissions import ListPortalMembers
   19.39 +from permissions import ListUndoableChanges
   19.40 +from permissions import ManagePortal
   19.41 +from permissions import ManageProperties
   19.42 +from permissions import ReplyToItem
   19.43 +from permissions import ReviewPortalContent
   19.44 +from permissions import SetOwnPassword
   19.45 +from permissions import SetOwnProperties
   19.46 +from permissions import UndoChanges
   19.47 +from permissions import View
   19.48 +from permissions import ViewManagementScreens
   19.49 +
   19.50 +import Document
   19.51 +import Image
   19.52 +import File
   19.53 +import Link
   19.54 +import NewsItem
   19.55 +import Favorite
   19.56 +import DiscussionItem
   19.57 +import SkinnedFolder
   19.58 +
   19.59 +factory_type_information = ( Document.factory_type_information
   19.60 +                           + Image.factory_type_information
   19.61 +                           + File.factory_type_information
   19.62 +                           + Link.factory_type_information
   19.63 +                           + NewsItem.factory_type_information
   19.64 +                           + Favorite.factory_type_information
   19.65 +                           + DiscussionItem.factory_type_information
   19.66 +                           + SkinnedFolder.factory_type_information
   19.67 +                           )
   19.68 +
   19.69 +
   19.70 +class CMFSite ( PortalObjectBase
   19.71 +              , DefaultDublinCoreImpl
   19.72 +              ):
   19.73 +    """
   19.74 +        The *only* function this class should have is to help in the setup
   19.75 +        of a new CMFSite.  It should not assist in the functionality at all.
   19.76 +    """
   19.77 +    meta_type = 'CMF Site'
   19.78 +
   19.79 +    _properties = (
   19.80 +        {'id':'title', 'type':'string', 'mode': 'w'},
   19.81 +        {'id':'description', 'type':'text', 'mode': 'w'},
   19.82 +        )
   19.83 +    title = ''
   19.84 +    description = ''
   19.85 +
   19.86 +    __ac_permissions__=( ( ManagePortal, ('manage_migrate_content',) )
   19.87 +                       , ( AddPortalContent, () )
   19.88 +                       , ( AddPortalFolders, () )
   19.89 +                       , ( ListPortalMembers, () )
   19.90 +                       , ( ReplyToItem, () )
   19.91 +                       , ( View, ('isEffective',) )
   19.92 +                       )
   19.93 +
   19.94 +    def __init__( self, id, title='' ):
   19.95 +        PortalObjectBase.__init__( self, id, title )
   19.96 +        DefaultDublinCoreImpl.__init__( self )
   19.97 +
   19.98 +    def isEffective( self, date ):
   19.99 +        """
  19.100 +            Override DefaultDublinCoreImpl's test, since we are always viewable.
  19.101 +        """
  19.102 +        return 1
  19.103 +
  19.104 +    def reindexObject( self, idxs=[] ):
  19.105 +        """
  19.106 +            Override DefaultDublinCoreImpl's method (so that we can play
  19.107 +            in 'editMetadata').
  19.108 +        """
  19.109 +        pass
  19.110 +
  19.111 +    #
  19.112 +    #   The following two methods allow conversion of portal content from
  19.113 +    #   PTK version 0.7.1.
  19.114 +    #   DO NOT REMOVE!!!
  19.115 +    #
  19.116 +    if 0:
  19.117 +        def manage_migrate_content(self, REQUEST):
  19.118 +            """
  19.119 +                Converts instances of Products.PTKBase.<content> to
  19.120 +                instances of Products.PTKDemo.<content>.
  19.121 +            """
  19.122 +            import Products.PTKBase.Document
  19.123 +            import Products.PTKBase.File
  19.124 +            import Products.PTKBase.Image
  19.125 +            import Products.PTKBase.Link
  19.126 +            import Products.PTKBase.NewsItem
  19.127 +            import NewsItem, Link, Document, File, Image
  19.128 +
  19.129 +            migrations = {
  19.130 +                Products.PTKBase.Document.Document : Document.Document,
  19.131 +                Products.PTKBase.File.File         : File.File,
  19.132 +                Products.PTKBase.Image.Image       : Image.Image,
  19.133 +                Products.PTKBase.Link.Link         : Link.Link,
  19.134 +                Products.PTKBase.NewsItem.NewsItem : NewsItem.NewsItem,
  19.135 +                }
  19.136 +
  19.137 +            visited = []
  19.138 +            migrated = []
  19.139 +            self.__migrate_branches(migrations, self, migrated, visited)
  19.140 +            return 'Converted:\n%s\n\nDone.' % '\n'.join(migrated)
  19.141 +
  19.142 +        def __migrate_branches(self, migrations, branch, migrated, visited):
  19.143 +            base = getattr(branch, 'aq_base', branch)
  19.144 +            if base in visited:
  19.145 +                # Don't visit again!
  19.146 +                return
  19.147 +            visited.append(base)
  19.148 +
  19.149 +            try: changed = branch._p_changed
  19.150 +            except: changed = 1
  19.151 +            for id in branch.objectIds():
  19.152 +                obj = branch._getOb(id)
  19.153 +                obase = getattr(obj, 'aq_base', obj)
  19.154 +                klass = obase.__class__
  19.155 +                if migrations.has_key(klass):
  19.156 +                    # Replace this object.
  19.157 +                    changed = 1
  19.158 +                    newob = migrations[klass](obase.id)
  19.159 +                    newob.id = obase.id   # This line activates obase.
  19.160 +                    newob.__dict__.update(obase.__dict__)
  19.161 +                    setattr(branch, id, newob)
  19.162 +                    migrated.append(obj.absolute_url())
  19.163 +                elif hasattr(obase, 'objectIds'):
  19.164 +                    # Enter a sub-branch.
  19.165 +                    self.__migrate_branches(migrations, obj, migrated, visited)
  19.166 +                else:
  19.167 +                    # Unload this object if it has not been changed.
  19.168 +                    try:
  19.169 +                        if obj._p_changed is None:
  19.170 +                            obj._p_deactivate()
  19.171 +                    except: pass
  19.172 +            if changed is None:
  19.173 +                # Unload this branch.
  19.174 +                object._p_deactivate()
  19.175 +
  19.176 +        del visited[-1]
  19.177 +
  19.178 +    else:   # placeholder
  19.179 +        def manage_migrate_content( self, REQUEST ):
  19.180 +            pass
  19.181 +
  19.182 +InitializeClass(CMFSite)
  19.183 +
  19.184 +
  19.185 +class PortalGenerator:
  19.186 +
  19.187 +    klass = CMFSite
  19.188 +
  19.189 +    def setupTools(self, p):
  19.190 +        """Set up initial tools"""
  19.191 +
  19.192 +        addCMFCoreTool = p.manage_addProduct['CMFCore'].manage_addTool
  19.193 +        addCMFCoreTool('CMF Actions Tool', None)
  19.194 +        addCMFCoreTool('CMF Catalog', None)
  19.195 +        addCMFCoreTool('CMF Member Data Tool', None)
  19.196 +        addCMFCoreTool('CMF Skins Tool', None)
  19.197 +        addCMFCoreTool('CMF Types Tool', None)
  19.198 +        addCMFCoreTool('CMF Undo Tool', None)
  19.199 +        addCMFCoreTool('CMF URL Tool', None)
  19.200 +        addCMFCoreTool('CMF Workflow Tool', None)
  19.201 +
  19.202 +        addCMFDefaultTool = p.manage_addProduct['CMFDefault'].manage_addTool
  19.203 +        addCMFDefaultTool('Default Discussion Tool', None)
  19.204 +        addCMFDefaultTool('Default Membership Tool', None)
  19.205 +        addCMFDefaultTool('Default Registration Tool', None)
  19.206 +        addCMFDefaultTool('Default Properties Tool', None)
  19.207 +        addCMFDefaultTool('Default Metadata Tool', None)
  19.208 +        addCMFDefaultTool('Default Syndication Tool', None)
  19.209 +
  19.210 +        # try to install CMFUid without raising exceptions if not available
  19.211 +        try:
  19.212 +            addCMFUidTool = p.manage_addProduct['CMFUid'].manage_addTool
  19.213 +        except AttributeError:
  19.214 +            pass
  19.215 +        else:
  19.216 +            addCMFUidTool('Unique Id Annotation Tool', None)
  19.217 +            addCMFUidTool('Unique Id Generator Tool', None)
  19.218 +            addCMFUidTool('Unique Id Handler Tool', None)
  19.219 +
  19.220 +    def setupMailHost(self, p):
  19.221 +        p.manage_addProduct['MailHost'].manage_addMailHost(
  19.222 +            'MailHost', smtp_host='localhost')
  19.223 +
  19.224 +    def setupUserFolder(self, p):
  19.225 +        p.manage_addProduct['OFSP'].manage_addUserFolder()
  19.226 +
  19.227 +    def setupCookieAuth(self, p):
  19.228 +        p.manage_addProduct['CMFCore'].manage_addCC(
  19.229 +            id='cookie_authentication')
  19.230 +
  19.231 +    def setupMembersFolder(self, p):
  19.232 +        PortalFolder.manage_addPortalFolder(p, 'Members')
  19.233 +        p.Members.manage_addProduct['OFSP'].manage_addDTMLMethod(
  19.234 +            'index_html', 'Member list', '<dtml-return roster>')
  19.235 +
  19.236 +    def setupRoles(self, p):
  19.237 +        # Set up the suggested roles.
  19.238 +        p.__ac_roles__ = ('Member', 'Reviewer',)
  19.239 +
  19.240 +    def setupPermissions(self, p):
  19.241 +        # Set up some suggested role to permission mappings.
  19.242 +        mp = p.manage_permission
  19.243 +
  19.244 +        mp(AccessFuturePortalContent, ['Reviewer','Manager',], 1)
  19.245 +        mp(AddPortalContent,          ['Owner','Manager',],    1)
  19.246 +        mp(AddPortalFolders,          ['Owner','Manager',],    1)
  19.247 +        mp(ListPortalMembers,         ['Member','Manager',],   1)
  19.248 +        mp(ListUndoableChanges,       ['Member','Manager',],   1)
  19.249 +        mp(ReplyToItem,               ['Member','Manager',],   1)
  19.250 +        mp(ReviewPortalContent,       ['Reviewer','Manager',], 1)
  19.251 +        mp(SetOwnPassword,            ['Member','Manager',],   1)
  19.252 +        mp(SetOwnProperties,          ['Member','Manager',],   1)
  19.253 +
  19.254 +        # Add some other permissions mappings that may be helpful.
  19.255 +        mp(DeleteObjects,             ['Owner','Manager',],    1)
  19.256 +        mp(FTPAccess,                 ['Owner','Manager',],    1)
  19.257 +        mp(ManageProperties,          ['Owner','Manager',],    1)
  19.258 +        mp(UndoChanges,               ['Owner','Manager',],    1)
  19.259 +        mp(ViewManagementScreens,     ['Owner','Manager',],    1)
  19.260 +
  19.261 +    def setupDefaultSkins(self, p):
  19.262 +        from Products.CMFCore.DirectoryView import addDirectoryViews
  19.263 +        ps = getToolByName(p, 'portal_skins')
  19.264 +        addDirectoryViews(ps, 'skins', globals())
  19.265 +        addDirectoryViews(ps, 'skins', topic_globals)
  19.266 +        ps.manage_addProduct['OFSP'].manage_addFolder(id='custom')
  19.267 +        ps.addSkinSelection('Basic',
  19.268 +            'custom, zpt_topic, zpt_content, zpt_generic,'
  19.269 +            + 'zpt_control, Images',
  19.270 +            make_default=1)
  19.271 +        ps.addSkinSelection('Nouvelle',
  19.272 +            'nouvelle, custom, topic, content, generic, control, Images')
  19.273 +        ps.addSkinSelection('No CSS',
  19.274 +            'no_css, custom, topic, content, generic, control, Images')
  19.275 +        p.setupCurrentSkin()
  19.276 +
  19.277 +    def setupTypes(self, p, initial_types=factory_type_information):
  19.278 +        tool = getToolByName(p, 'portal_types', None)
  19.279 +        if tool is None:
  19.280 +            return
  19.281 +        for t in initial_types:
  19.282 +            fti = FactoryTypeInformation(**t)
  19.283 +            tool._setObject(t['id'], fti)
  19.284 +
  19.285 +    def setupMimetypes(self, p):
  19.286 +        p.manage_addProduct[ 'CMFCore' ].manage_addRegistry()
  19.287 +        reg = p.content_type_registry
  19.288 +
  19.289 +        reg.addPredicate( 'link', 'extension' )
  19.290 +        reg.getPredicate( 'link' ).edit( extensions="url, link" )
  19.291 +        reg.assignTypeName( 'link', 'Link' )
  19.292 +
  19.293 +        reg.addPredicate( 'news', 'extension' )
  19.294 +        reg.getPredicate( 'news' ).edit( extensions="news" )
  19.295 +        reg.assignTypeName( 'news', 'News Item' )
  19.296 +
  19.297 +        reg.addPredicate( 'document', 'major_minor' )
  19.298 +        reg.getPredicate( 'document' ).edit( major="text", minor="" )
  19.299 +        reg.assignTypeName( 'document', 'Document' )
  19.300 +
  19.301 +        reg.addPredicate( 'image', 'major_minor' )
  19.302 +        reg.getPredicate( 'image' ).edit( major="image", minor="" )
  19.303 +        reg.assignTypeName( 'image', 'Image' )
  19.304 +
  19.305 +        reg.addPredicate( 'file', 'major_minor' )
  19.306 +        reg.getPredicate( 'file' ).edit( major="application", minor="" )
  19.307 +        reg.assignTypeName( 'file', 'File' )
  19.308 +
  19.309 +    def setupWorkflow(self, p):
  19.310 +        wftool = getToolByName(p, 'portal_workflow', None)
  19.311 +        if wftool is None:
  19.312 +            return
  19.313 +        try:
  19.314 +            from Products.DCWorkflow.Default \
  19.315 +                    import createDefaultWorkflowClassic
  19.316 +        except ImportError:
  19.317 +            return
  19.318 +        id = 'default_workflow'
  19.319 +        wftool._setObject( id, createDefaultWorkflowClassic(id) )
  19.320 +
  19.321 +        #   These objects don't participate in workflow by default.
  19.322 +        wftool.setChainForPortalTypes( ('Folder', 'Topic'), () )
  19.323 +
  19.324 +    def setup(self, p, create_userfolder):
  19.325 +        self.setupTools(p)
  19.326 +        self.setupMailHost(p)
  19.327 +        if int(create_userfolder) != 0:
  19.328 +            self.setupUserFolder(p)
  19.329 +        self.setupCookieAuth(p)
  19.330 +        self.setupMembersFolder(p)
  19.331 +        self.setupRoles(p)
  19.332 +        self.setupPermissions(p)
  19.333 +        self.setupDefaultSkins(p)
  19.334 +
  19.335 +        #   SkinnedFolders are only for customization;
  19.336 +        #   they aren't a default type.
  19.337 +        default_types = tuple( filter( lambda x: x['id'] != 'Skinned Folder'
  19.338 +                                     , factory_type_information ) )
  19.339 +        self.setupTypes(p, default_types )
  19.340 +
  19.341 +        self.setupTypes(p, PortalFolder.factory_type_information)
  19.342 +        self.setupTypes(p, Topic.factory_type_information)
  19.343 +        self.setupMimetypes(p)
  19.344 +        self.setupWorkflow(p)
  19.345 +
  19.346 +    def create(self, parent, id, create_userfolder):
  19.347 +        id = str(id)
  19.348 +        portal = self.klass(id=id)
  19.349 +        parent._setObject(id, portal)
  19.350 +        # Return the fully wrapped object.
  19.351 +        p = parent.this()._getOb(id)
  19.352 +        self.setup(p, create_userfolder)
  19.353 +        return p
  19.354 +
  19.355 +    def setupDefaultProperties(self, p, title, description,
  19.356 +                               email_from_address, email_from_name,
  19.357 +                               validate_email, default_charset=''):
  19.358 +        p._setProperty('email_from_address', email_from_address, 'string')
  19.359 +        p._setProperty('email_from_name', email_from_name, 'string')
  19.360 +        p._setProperty('validate_email', validate_email and 1 or 0, 'boolean')
  19.361 +        p._setProperty('default_charset', default_charset, 'string')
  19.362 +        p._setProperty('enable_permalink', 0, 'boolean')
  19.363 +        p.title = title
  19.364 +        p.description = description
  19.365 +
  19.366 +
  19.367 +manage_addCMFSiteForm = HTMLFile('dtml/addPortal', globals())
  19.368 +manage_addCMFSiteForm.__name__ = 'addPortal'
  19.369 +
  19.370 +def manage_addCMFSite(self, id, title='Portal', description='',
  19.371 +                         create_userfolder=1,
  19.372 +                         email_from_address='postmaster@localhost',
  19.373 +                         email_from_name='Portal Administrator',
  19.374 +                         validate_email=0, default_charset='',
  19.375 +                         RESPONSE=None):
  19.376 +    """ Adds a portal instance.
  19.377 +    """
  19.378 +    gen = PortalGenerator()
  19.379 +    id = id.strip()
  19.380 +    p = gen.create(self, id, create_userfolder)
  19.381 +    gen.setupDefaultProperties(p, title, description,
  19.382 +                               email_from_address, email_from_name,
  19.383 +                               validate_email, default_charset)
  19.384 +    if RESPONSE is not None:
  19.385 +        RESPONSE.redirect(p.absolute_url() + '/finish_portal_construction')
    20.1 new file mode 100644
    20.2 --- /dev/null
    20.3 +++ b/PropertiesTool.py
    20.4 @@ -0,0 +1,86 @@
    20.5 +##############################################################################
    20.6 +#
    20.7 +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
    20.8 +#
    20.9 +# This software is subject to the provisions of the Zope Public License,
   20.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   20.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   20.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   20.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   20.14 +# FOR A PARTICULAR PURPOSE.
   20.15 +#
   20.16 +##############################################################################
   20.17 +""" CMFDefault portal_properties tool.
   20.18 +
   20.19 +$Id: PropertiesTool.py 36457 2004-08-12 15:07:44Z jens $
   20.20 +"""
   20.21 +
   20.22 +from OFS.SimpleItem import SimpleItem
   20.23 +from Acquisition import aq_inner, aq_parent
   20.24 +from Globals import InitializeClass, DTMLFile
   20.25 +from AccessControl import ClassSecurityInfo
   20.26 +
   20.27 +from Products.CMFCore.utils import UniqueObject
   20.28 +from Products.CMFCore.ActionProviderBase import ActionProviderBase
   20.29 +from Products.CMFCore.ActionInformation import ActionInformation
   20.30 +from Products.CMFCore.Expression import Expression
   20.31 +from Products.CMFCore.interfaces.portal_properties \
   20.32 +        import portal_properties as IPropertiesTool
   20.33 +
   20.34 +from permissions import ManagePortal
   20.35 +from utils import _dtmldir
   20.36 +
   20.37 +class PropertiesTool(UniqueObject, SimpleItem, ActionProviderBase):
   20.38 +
   20.39 +    __implements__ = (IPropertiesTool, ActionProviderBase.__implements__)
   20.40 +
   20.41 +    id = 'portal_properties'
   20.42 +    meta_type = 'Default Properties Tool'
   20.43 +    _actions = (ActionInformation(id='configPortal'
   20.44 +                            , title='Reconfigure Portal'
   20.45 +                            , description='Reconfigure the portal'
   20.46 +                            , action=Expression(
   20.47 +            text='string:${portal_url}/reconfig_form')
   20.48 +                            , permissions=(ManagePortal,)
   20.49 +                            , category='global'
   20.50 +                            , condition=None
   20.51 +                            , visible=1
   20.52 +                             )
   20.53 +               ,
   20.54 +               )
   20.55 +
   20.56 +    security = ClassSecurityInfo()
   20.57 +
   20.58 +    manage_options = ( ActionProviderBase.manage_options +
   20.59 +                      ({ 'label' : 'Overview', 'action' : 'manage_overview' }
   20.60 +                     , 
   20.61 +                     ) + SimpleItem.manage_options
   20.62 +                     )
   20.63 +
   20.64 +    #
   20.65 +    #   ZMI methods
   20.66 +    #
   20.67 +    security.declareProtected(ManagePortal, 'manage_overview')
   20.68 +    manage_overview = DTMLFile( 'explainPropertiesTool', _dtmldir )
   20.69 +
   20.70 +    #
   20.71 +    #   'portal_properties' interface methods
   20.72 +    #
   20.73 +    security.declareProtected(ManagePortal, 'editProperties')
   20.74 +    def editProperties(self, props):
   20.75 +        '''Change portal settings'''
   20.76 +        aq_parent(aq_inner(self)).manage_changeProperties(props)
   20.77 +        self.MailHost.smtp_host = props['smtp_server']
   20.78 +        if hasattr(self, 'propertysheets'):
   20.79 +            ps = self.propertysheets
   20.80 +            if hasattr(ps, 'props'):
   20.81 +                ps.props.manage_changeProperties(props)
   20.82 +
   20.83 +    def title(self):
   20.84 +        return self.aq_inner.aq_parent.title
   20.85 +
   20.86 +    def smtp_server(self):
   20.87 +        return self.MailHost.smtp_host
   20.88 +
   20.89 +
   20.90 +InitializeClass(PropertiesTool)
    21.1 new file mode 100644
    21.2 --- /dev/null
    21.3 +++ b/README.txt
    21.4 @@ -0,0 +1,6 @@
    21.5 +CMFDefault
    21.6 +
    21.7 +  This product declares basic content objects and provides
    21.8 +  default implementation of some of the framework services for
    21.9 +  the Zope Content Management Framework (CMF).  Please see the
   21.10 +  CMF "dogbowl site":http://cmf.zope.org, for more details.
    22.1 new file mode 100644
    22.2 --- /dev/null
    22.3 +++ b/RegistrationTool.py
    22.4 @@ -0,0 +1,256 @@
    22.5 +##############################################################################
    22.6 +#
    22.7 +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
    22.8 +#
    22.9 +# This software is subject to the provisions of the Zope Public License,
   22.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   22.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   22.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   22.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   22.14 +# FOR A PARTICULAR PURPOSE.
   22.15 +#
   22.16 +##############################################################################
   22.17 +""" CMFDefault portal_registration tool.
   22.18 +
   22.19 +$Id: RegistrationTool.py 36777 2005-03-07 21:50:28Z jens $
   22.20 +"""
   22.21 +import re
   22.22 +
   22.23 +from Globals import InitializeClass, DTMLFile
   22.24 +from AccessControl import ClassSecurityInfo
   22.25 +
   22.26 +from Products.CMFCore.utils import _checkPermission
   22.27 +from Products.CMFCore.utils import getToolByName
   22.28 +from Products.CMFCore.ActionInformation import ActionInformation
   22.29 +from Products.CMFCore.Expression import Expression
   22.30 +from Products.CMFCore.ActionProviderBase import ActionProviderBase
   22.31 +from Products.CMFCore.RegistrationTool import RegistrationTool as BaseTool
   22.32 +
   22.33 +from permissions import AddPortalMember
   22.34 +from permissions import ManagePortal
   22.35 +from utils import _dtmldir
   22.36 +
   22.37 +
   22.38 +class RegistrationTool(BaseTool):
   22.39 +    """ Manage through-the-web signup policies.
   22.40 +    """
   22.41 +
   22.42 +    __implements__ = BaseTool.__implements__
   22.43 +
   22.44 +    meta_type = 'Default Registration Tool'
   22.45 +    _actions = ( ActionInformation( id='join'
   22.46 +                                  , title='Join'
   22.47 +                                  , description='Click here to Join'
   22.48 +                                  , action=Expression(
   22.49 +                                     text='string:${portal_url}/join_form')
   22.50 +                                  , permissions=(AddPortalMember,)
   22.51 +                                  , category='user'
   22.52 +                                  , condition=Expression(text='not: member')
   22.53 +                                  , visible=1
   22.54 +                                  )
   22.55 +               ,
   22.56 +               )
   22.57 +
   22.58 +    security = ClassSecurityInfo()
   22.59 +
   22.60 +    #
   22.61 +    #   'portal_registration' interface
   22.62 +    #
   22.63 +    security.declarePublic( 'testPasswordValidity' )
   22.64 +    def testPasswordValidity(self, password, confirm=None):
   22.65 +
   22.66 +        """ Verify that the password satisfies the portal's requirements.
   22.67 +
   22.68 +        o If the password is valid, return None.
   22.69 +        o If not, return a string explaining why.
   22.70 +        """
   22.71 +        if not password:
   22.72 +            return 'You must enter a password.'
   22.73 +
   22.74 +        if len(password) < 5 and not _checkPermission(ManagePortal, self):
   22.75 +            return 'Your password must contain at least 5 characters.'
   22.76 +
   22.77 +        if confirm is not None and confirm != password:
   22.78 +            return ( 'Your password and confirmation did not match. '
   22.79 +                   + 'Please try again.' )
   22.80 +
   22.81 +        return None
   22.82 +
   22.83 +    security.declarePublic( 'testPropertiesValidity' )
   22.84 +    def testPropertiesValidity(self, props, member=None):
   22.85 +
   22.86 +        """ Verify that the properties supplied satisfy portal's requirements.
   22.87 +
   22.88 +        o If the properties are valid, return None.
   22.89 +        o If not, return a string explaining why.
   22.90 +        """
   22.91 +        if member is None: # New member.
   22.92 +
   22.93 +            username = props.get('username', '')
   22.94 +            if not username:
   22.95 +                return 'You must enter a valid name.'
   22.96 +
   22.97 +            if not self.isMemberIdAllowed(username):
   22.98 +                return ('The login name you selected is already '
   22.99 +                        'in use or is not valid. Please choose another.')
  22.100 +
  22.101 +            email = props.get('email')
  22.102 +            if email is None:
  22.103 +                return 'You must enter an email address.'
  22.104 +
  22.105 +            ok, message =  _checkEmail( email )
  22.106 +            if not ok:
  22.107 +                return 'You must enter a valid email address.'
  22.108 +
  22.109 +        else: # Existing member.
  22.110 +            email = props.get('email')
  22.111 +
  22.112 +            if email is not None:
  22.113 +
  22.114 +                ok, message =  _checkEmail( email )
  22.115 +                if not ok:
  22.116 +                    return 'You must enter a valid email address.'
  22.117 +
  22.118 +            # Not allowed to clear an existing non-empty email.
  22.119 +            existing = member.getProperty('email')
  22.120 +            
  22.121 +            if existing and email == '':
  22.122 +                return 'You must enter a valid email address.'
  22.123 +
  22.124 +        return None
  22.125 +
  22.126 +    security.declarePublic( 'mailPassword' )
  22.127 +    def mailPassword(self, forgotten_userid, REQUEST):
  22.128 +        """ Email a forgotten password to a member.
  22.129 +
  22.130 +        o Raise an exception if user ID is not found.
  22.131 +        """
  22.132 +        membership = getToolByName(self, 'portal_membership')
  22.133 +        member = membership.getMemberById(forgotten_userid)
  22.134 +
  22.135 +        if member is None:
  22.136 +            raise ValueError('The username you entered could not be found.')
  22.137 +
  22.138 +        # assert that we can actually get an email address, otherwise
  22.139 +        # the template will be made with a blank To:, this is bad
  22.140 +        if not member.getProperty('email'):
  22.141 +            raise ValueError('That user does not have an email address.')
  22.142 +
  22.143 +        check, msg = _checkEmail(member.getProperty('email'))
  22.144 +        if not check:
  22.145 +            raise ValueError, msg
  22.146 +
  22.147 +        # Rather than have the template try to use the mailhost, we will
  22.148 +        # render the message ourselves and send it from here (where we
  22.149 +        # don't need to worry about 'UseMailHost' permissions).
  22.150 +        mail_text = self.mail_password_template( self
  22.151 +                                               , REQUEST
  22.152 +                                               , member=member
  22.153 +                                               , password=member.getPassword()
  22.154 +                                               )
  22.155 +
  22.156 +        host = self.MailHost
  22.157 +        host.send( mail_text )
  22.158 +
  22.159 +        return self.mail_password_response( self, REQUEST )
  22.160 +
  22.161 +    security.declarePublic( 'registeredNotify' )
  22.162 +    def registeredNotify( self, new_member_id, password=None ):
  22.163 +        """ Handle mailing the registration / welcome message.
  22.164 +        """
  22.165 +        membership = getToolByName( self, 'portal_membership' )
  22.166 +        member = membership.getMemberById( new_member_id )
  22.167 +
  22.168 +        if member is None:
  22.169 +            raise ValueError('The username you entered could not be found.')
  22.170 +
  22.171 +        if password is None:
  22.172 +            password = member.getPassword()
  22.173 +
  22.174 +        email = member.getProperty( 'email' )
  22.175 +
  22.176 +        if email is None:
  22.177 +            raise ValueError( 'No email address is registered for member: %s'
  22.178 +                            % new_member_id )
  22.179 +
  22.180 +        check, msg = _checkEmail(email)
  22.181 +        if not check:
  22.182 +            raise ValueError, msg
  22.183 +
  22.184 +        # Rather than have the template try to use the mailhost, we will
  22.185 +        # render the message ourselves and send it from here (where we
  22.186 +        # don't need to worry about 'UseMailHost' permissions).
  22.187 +        mail_text = self.registered_notify_template( self
  22.188 +                                                   , self.REQUEST
  22.189 +                                                   , member=member
  22.190 +                                                   , password=password
  22.191 +                                                   , email=email
  22.192 +                                                   )
  22.193 +
  22.194 +        host = self.MailHost
  22.195 +        host.send( mail_text )
  22.196 +
  22.197 +        return self.mail_password_response( self, self.REQUEST )
  22.198 +
  22.199 +    security.declareProtected(ManagePortal, 'editMember')
  22.200 +    def editMember( self
  22.201 +                  , member_id
  22.202 +                  , properties=None
  22.203 +                  , password=None
  22.204 +                  , roles=None
  22.205 +                  , domains=None
  22.206 +                  ):
  22.207 +        """ Edit a user's properties and security settings
  22.208 +
  22.209 +        o Checks should be done before this method is called using
  22.210 +          testPropertiesValidity and testPasswordValidity
  22.211 +        """
  22.212 +
  22.213 +        mtool = getToolByName(self, 'portal_membership')
  22.214 +        member = mtool.getMemberById(member_id)
  22.215 +        member.setMemberProperties(properties)
  22.216 +        member.setSecurityProfile(password,roles,domains)
  22.217 +
  22.218 +        return member
  22.219 +
  22.220 +InitializeClass(RegistrationTool)
  22.221 +
  22.222 +# See URL: http://www.zopelabs.com/cookbook/1033402597
  22.223 +
  22.224 +_TESTS = ( ( re.compile("^[0-9a-zA-Z\.\-\_\+]+\@[0-9a-zA-Z\.\-]+$")
  22.225 +           , True
  22.226 +           , "Failed a"
  22.227 +           )
  22.228 +         , ( re.compile("^[^0-9a-zA-Z]|[^0-9a-zA-Z]$")
  22.229 +           , False
  22.230 +           , "Failed b"
  22.231 +           )
  22.232 +         , ( re.compile("([0-9a-zA-Z_]{1})\@.")
  22.233 +           , True
  22.234 +           , "Failed c"
  22.235 +           )
  22.236 +         , ( re.compile(".\@([0-9a-zA-Z]{1})")
  22.237 +           , True
  22.238 +           , "Failed d"
  22.239 +           )
  22.240 +         , ( re.compile(".\.\-.|.\-\..|.\.\..|.\-\-.")
  22.241 +           , False
  22.242 +           , "Failed e"
  22.243 +           )
  22.244 +         , ( re.compile(".\.\_.|.\-\_.|.\_\..|.\_\-.|.\_\_.")
  22.245 +           , False
  22.246 +           , "Failed f"
  22.247 +           )
  22.248 +         , ( re.compile(".\.([a-zA-Z]{2,3})$|.\.([a-zA-Z]{2,4})$")
  22.249 +           , True
  22.250 +           , "Failed g"
  22.251 +           )
  22.252 +         )
  22.253 +
  22.254 +def _checkEmail( address ):
  22.255 +    for pattern, expected, message in _TESTS:
  22.256 +        matched = pattern.search( address ) is not None
  22.257 +        if matched != expected:
  22.258 +            return False, message
  22.259 +    return True, ''
  22.260 +
    23.1 new file mode 100644
    23.2 --- /dev/null
    23.3 +++ b/SkinnedFolder.py
    23.4 @@ -0,0 +1,135 @@
    23.5 +##############################################################################
    23.6 +#
    23.7 +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
    23.8 +#
    23.9 +# This software is subject to the provisions of the Zope Public License,
   23.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   23.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   23.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   23.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   23.14 +# FOR A PARTICULAR PURPOSE.
   23.15 +#
   23.16 +##############################################################################
   23.17 +""" Allow the "view" of a folder to be skinned by type.
   23.18 +
   23.19 +$Id: SkinnedFolder.py 37990 2005-08-18 16:43:36Z jens $
   23.20 +"""
   23.21 +
   23.22 +from AccessControl import ClassSecurityInfo
   23.23 +from Acquisition import aq_base
   23.24 +from Globals import InitializeClass
   23.25 +
   23.26 +from Products.CMFCore.CMFCatalogAware import CMFCatalogAware
   23.27 +from Products.CMFCore.PortalFolder import PortalFolder
   23.28 +from Products.CMFCore.utils import _getViewFor
   23.29 +
   23.30 +from DublinCore import DefaultDublinCoreImpl
   23.31 +from permissions import AddPortalContent
   23.32 +from permissions import ListFolderContents
   23.33 +from permissions import ManageProperties
   23.34 +from permissions import ModifyPortalContent
   23.35 +from permissions import View
   23.36 +
   23.37 +factory_type_information = (
   23.38 +  { 'id'             : 'Skinned Folder'
   23.39 +  , 'meta_type'      : 'Skinned Folder'
   23.40 +  , 'description'    : """\
   23.41 +Skinned folders can define custom 'view' actions.
   23.42 +"""
   23.43 +  , 'icon'           : 'folder_icon.gif'
   23.44 +  , 'product'        : 'CMFDefault'
   23.45 +  , 'factory'        : 'addSkinnedFolder'
   23.46 +  , 'filter_content_types' : 0
   23.47 +  , 'immediate_view' : 'folder_edit_form'
   23.48 +  , 'aliases'        : {'(Default)': 'folder_view',
   23.49 +                        'view': 'folder_view'}
   23.50 +  , 'actions'        : ( { 'id'            : 'view'
   23.51 +                         , 'name'          : 'View'
   23.52 +                         , 'action': 'string:${object_url}/folder_view'
   23.53 +                         , 'permissions'   : (View,)
   23.54 +                         }
   23.55 +                       , { 'id'            : 'edit'
   23.56 +                         , 'name'          : 'Edit'
   23.57 +                         , 'action': 'string:${object_url}/folder_edit_form'
   23.58 +                         , 'permissions'   : (ManageProperties,)
   23.59 +                         }
   23.60 +                       , { 'id'            : 'folderContents'
   23.61 +                         , 'name'          : 'Folder contents'
   23.62 +                         , 'action': 'string:${object_url}/folder_contents'
   23.63 +                         , 'permissions'   : (ListFolderContents,)
   23.64 +                         }
   23.65 +                       , { 'id'            : 'new'
   23.66 +                         , 'name'          : 'New...'
   23.67 +                         , 'action': 'string:${object_url}/folder_factories'
   23.68 +                         , 'permissions'   : (AddPortalContent,)
   23.69 +                         , 'visible'       : 0
   23.70 +                         }
   23.71 +                       , { 'id'            : 'rename_items'
   23.72 +                         , 'name'          : 'Rename items'
   23.73 +                         , 'action': 'string:${object_url}/folder_rename_form'
   23.74 +                         , 'permissions'   : (AddPortalContent,)
   23.75 +                         , 'visible'       : 0
   23.76 +                         }
   23.77 +                       )
   23.78 +  }
   23.79 +,
   23.80 +)
   23.81 +
   23.82 +
   23.83 +class SkinnedFolder(CMFCatalogAware, PortalFolder):
   23.84 +    """ Skinned Folder class. 
   23.85 +    """
   23.86 +    meta_type = 'Skinned Folder'
   23.87 +
   23.88 +    security = ClassSecurityInfo()
   23.89 +
   23.90 +    manage_options = PortalFolder.manage_options
   23.91 +
   23.92 +    def __call__(self):
   23.93 +        '''
   23.94 +        Invokes the default view.
   23.95 +        '''
   23.96 +        view = _getViewFor(self)
   23.97 +        if getattr(aq_base(view), 'isDocTemp', 0):
   23.98 +            return view(self, self.REQUEST, self.REQUEST['RESPONSE'])
   23.99 +        else:
  23.100 +            return view()
  23.101 +
  23.102 +    security.declareProtected(View, 'view')
  23.103 +    view = __call__
  23.104 +
  23.105 +    index_html = None  # This special value informs ZPublisher to use __call__
  23.106 +
  23.107 +    # XXX: maybe we should subclass from DefaultDublinCoreImpl or refactor it
  23.108 +
  23.109 +    security.declarePrivate('notifyModified')
  23.110 +    def notifyModified(self):
  23.111 +        """ Take appropriate action after the resource has been modified.
  23.112 +
  23.113 +        Update creators.
  23.114 +        """
  23.115 +        self.addCreator()
  23.116 +
  23.117 +    security.declareProtected(ModifyPortalContent, 'addCreator')
  23.118 +    addCreator = DefaultDublinCoreImpl.addCreator.im_func
  23.119 +
  23.120 +    security.declareProtected(View, 'listCreators')
  23.121 +    listCreators = DefaultDublinCoreImpl.listCreators.im_func
  23.122 +
  23.123 +    security.declareProtected(View, 'Creator')
  23.124 +    Creator = DefaultDublinCoreImpl.Creator.im_func
  23.125 +
  23.126 +    # We derive from CMFCatalogAware first, so we are cataloged too.
  23.127 +
  23.128 +InitializeClass( SkinnedFolder )
  23.129 +
  23.130 +
  23.131 +def addSkinnedFolder( self, id, title='', description='', REQUEST=None ):
  23.132 +    """
  23.133 +    """
  23.134 +    sf = SkinnedFolder( id, title )
  23.135 +    sf.description = description
  23.136 +    self._setObject( id, sf )
  23.137 +    sf = self._getOb( id )
  23.138 +    if REQUEST is not None:
  23.139 +        REQUEST['RESPONSE'].redirect( sf.absolute_url() + '/manage_main' )
    24.1 new file mode 100644
    24.2 --- /dev/null
    24.3 +++ b/SyndicationInfo.py
    24.4 @@ -0,0 +1,5 @@
    24.5 +from OFS.SimpleItem import SimpleItem
    24.6 +
    24.7 +class SyndicationInformation(SimpleItem):
    24.8 +        id='syndication_information'
    24.9 +        meta_type='SyndicationInformation'
    25.1 new file mode 100644
    25.2 --- /dev/null
    25.3 +++ b/SyndicationTool.py
    25.4 @@ -0,0 +1,401 @@
    25.5 +##############################################################################
    25.6 +#
    25.7 +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
    25.8 +#
    25.9 +# This software is subject to the provisions of the Zope Public License,
   25.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   25.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   25.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   25.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   25.14 +# FOR A PARTICULAR PURPOSE.
   25.15 +#
   25.16 +##############################################################################
   25.17 +""" CMFDefault portal_syndication tool.
   25.18 +
   25.19 +Manage outbound RSS syndication of folder content.
   25.20 +
   25.21 +$Id: SyndicationTool.py 37759 2005-08-06 13:48:57Z jens $
   25.22 +"""
   25.23 +
   25.24 +from AccessControl import ClassSecurityInfo
   25.25 +from Acquisition import aq_base
   25.26 +from DateTime import DateTime
   25.27 +from Globals import HTMLFile
   25.28 +from Globals import InitializeClass
   25.29 +from OFS.SimpleItem import SimpleItem
   25.30 +
   25.31 +from Products.CMFCore.ActionInformation import ActionInformation
   25.32 +from Products.CMFCore.ActionProviderBase import ActionProviderBase
   25.33 +from Products.CMFCore.Expression import Expression
   25.34 +from Products.CMFCore.PortalFolder import PortalFolderBase
   25.35 +from Products.CMFCore.utils import _checkPermission
   25.36 +from Products.CMFCore.utils import UniqueObject
   25.37 +
   25.38 +from exceptions import AccessControl_Unauthorized
   25.39 +from permissions import ManagePortal
   25.40 +from permissions import ManageProperties
   25.41 +from SyndicationInfo import SyndicationInformation
   25.42 +from utils import _dtmldir
   25.43 +
   25.44 +
   25.45 +class SyndicationTool (UniqueObject, SimpleItem, ActionProviderBase):
   25.46 +    """
   25.47 +        The syndication tool manages the site-wide policy for
   25.48 +        syndication of folder content as RSS.
   25.49 +    """
   25.50 +
   25.51 +    __implements__ = ActionProviderBase.__implements__
   25.52 +
   25.53 +    id = 'portal_syndication'
   25.54 +    meta_type = 'Default Syndication Tool'
   25.55 +    _actions = ( ActionInformation(
   25.56 +                    id='syndication'
   25.57 +                  , title='Syndication'
   25.58 +                  , action=Expression(
   25.59 +                        text='string:${folder_url}/synPropertiesForm')
   25.60 +                  , condition=Expression(
   25.61 +                        text='python: folder is object')
   25.62 +                  , permissions=(ManageProperties,)
   25.63 +                  , category='object'
   25.64 +                  , visible=1
   25.65 +                  )
   25.66 +               ,
   25.67 +               )
   25.68 +
   25.69 +    #Default Sitewide Values
   25.70 +    isAllowed = 0
   25.71 +    syUpdatePeriod = 'daily'
   25.72 +    syUpdateFrequency = 1
   25.73 +    syUpdateBase = DateTime()
   25.74 +    max_items = 15
   25.75 +
   25.76 +    security = ClassSecurityInfo()
   25.77 +
   25.78 +    #ZMI Methods
   25.79 +    manage_options = ( ActionProviderBase.manage_options
   25.80 +                     + ( { 'label'  : 'Overview'
   25.81 +                         , 'action' : 'overview'
   25.82 +                         , 'help'   : ( 'CMFDefault'
   25.83 +                                      , 'Syndication-Tool_Overview.stx' )
   25.84 +                         }
   25.85 +                        ,{ 'label'  : 'Properties'
   25.86 +                         , 'action' : 'propertiesForm'
   25.87 +                         , 'help'   : ( 'CMFDefault'
   25.88 +                                      , 'Syndication-Tool_Properties.stx' )
   25.89 +                         }
   25.90 +                        ,{ 'label'  : 'Policies'
   25.91 +                         , 'action' : 'policiesForm'
   25.92 +                         , 'help'   : ( 'CMFDefault'
   25.93 +                                      , 'Syndication-Tool_Policies.stx' )
   25.94 +                         }
   25.95 +                        ,{ 'label'  : 'Reports'
   25.96 +                         , 'action' : 'reportForm'
   25.97 +                         , 'help'   : ( 'CMFDefault'
   25.98 +                                      , 'Syndication-Tool_Reporting.stx' )
   25.99 +                         }
  25.100 +                        )
  25.101 +                     )
  25.102 +
  25.103 +    security.declareProtected(ManagePortal, 'overview')
  25.104 +    overview = HTMLFile('synOverview', _dtmldir)
  25.105 +
  25.106 +    security.declareProtected(ManagePortal, 'propertiesForm')
  25.107 +    propertiesForm = HTMLFile('synProps', _dtmldir)
  25.108 +
  25.109 +    security.declareProtected(ManagePortal, 'policiesForm')
  25.110 +    policiesForm = HTMLFile('synPolicies', _dtmldir)
  25.111 +
  25.112 +    security.declareProtected(ManagePortal, 'reportForm')
  25.113 +    reportForm = HTMLFile('synReports', _dtmldir)
  25.114 +
  25.115 +    security.declareProtected(ManagePortal, 'editProperties')
  25.116 +    def editProperties( self
  25.117 +                      , updatePeriod=None
  25.118 +                      , updateFrequency=None
  25.119 +                      , updateBase=None
  25.120 +                      , isAllowed=None
  25.121 +                      , max_items=None
  25.122 +                      , REQUEST=None
  25.123 +                      ):
  25.124 +        """
  25.125 +        Edit the properties for the SystemWide defaults on the
  25.126 +        SyndicationTool.
  25.127 +        """
  25.128 +        if isAllowed is not None:
  25.129 +            self.isAllowed = isAllowed
  25.130 +
  25.131 +        if updatePeriod is not None:
  25.132 +            self.syUpdatePeriod = updatePeriod
  25.133 +        else:
  25.134 +            try:
  25.135 +                del self.syUpdatePeriod
  25.136 +            except (AttributeError, KeyError):
  25.137 +                pass
  25.138 +
  25.139 +        if updateFrequency is not None:
  25.140 +            self.syUpdateFrequency = int(updateFrequency)
  25.141 +        else:
  25.142 +            try:
  25.143 +                del self.syUpdateFrequency
  25.144 +            except (AttributeError, KeyError):
  25.145 +                pass
  25.146 +
  25.147 +        if updateBase is not None:
  25.148 +            if type( updateBase ) is type( '' ):
  25.149 +                updateBase = DateTime( updateBase )
  25.150 +            self.syUpdateBase = updateBase
  25.151 +        else:
  25.152 +            try:
  25.153 +                del self.syUpdateBase
  25.154 +            except (AttributeError, KeyError):
  25.155 +                pass
  25.156 +
  25.157 +        if max_items is not None:
  25.158 +            self.max_items = int(max_items)
  25.159 +        else:
  25.160 +            try:
  25.161 +                del self.max_items
  25.162 +            except (AttributeError, KeyError):
  25.163 +                pass
  25.164 +
  25.165 +        if REQUEST is not None:
  25.166 +            REQUEST['RESPONSE'].redirect( self.absolute_url()
  25.167 +                                        + '/propertiesForm'
  25.168 +                                        + '?manage_tabs_message=Tool+Updated.'
  25.169 +                                        )
  25.170 +
  25.171 +    security.declarePublic( 'editSyInformationProperties' )
  25.172 +    def editSyInformationProperties( self
  25.173 +                                   , obj
  25.174 +                                   , updatePeriod=None
  25.175 +                                   , updateFrequency=None
  25.176 +                                   , updateBase=None
  25.177 +                                   , max_items=None
  25.178 +                                   , REQUEST=None
  25.179 +                                   ):
  25.180 +        """
  25.181 +        Edit syndication properties for the obj being passed in.
  25.182 +        These are held on the syndication_information object.
  25.183 +        Not Sitewide Properties.
  25.184 +        """
  25.185 +        if not _checkPermission( ManageProperties, obj ):
  25.186 +            raise AccessControl_Unauthorized
  25.187 +
  25.188 +        syInfo = getattr(obj, 'syndication_information', None)
  25.189 +
  25.190 +        if syInfo is None:
  25.191 +            raise 'Syndication is Disabled'
  25.192 +
  25.193 +        if updatePeriod is not None:
  25.194 +            syInfo.syUpdatePeriod = updatePeriod
  25.195 +        else:
  25.196 +            syInfo.syUpdatePeriod = self.syUpdatePeriod
  25.197 +
  25.198 +        if updateFrequency is not None:
  25.199 +            syInfo.syUpdateFrequency = int(updateFrequency)
  25.200 +        else:
  25.201 +            syInfo.syUpdateFrequency = self.syUpdateFrequency
  25.202 +
  25.203 +        if updateBase is not None:
  25.204 +            if type( updateBase ) is type( '' ):
  25.205 +                updateBase = DateTime( updateBase )
  25.206 +            syInfo.syUpdateBase = updateBase
  25.207 +        else:
  25.208 +            syInfo.syUpdateBase = self.syUpdateBase
  25.209 +
  25.210 +        if max_items is not None:
  25.211 +            syInfo.max_items = int(max_items)
  25.212 +        else:
  25.213 +            syInfo.max_items = self.max_items
  25.214 +
  25.215 +    security.declarePublic('enableSyndication')
  25.216 +    def enableSyndication(self, obj):
  25.217 +        """
  25.218 +        Enable syndication for the obj
  25.219 +        """
  25.220 +        if not self.isSiteSyndicationAllowed():
  25.221 +            raise 'Syndication is Disabled'
  25.222 +
  25.223 +        if hasattr(aq_base(obj), 'syndication_information'):
  25.224 +            raise 'Syndication Information Exists'
  25.225 +
  25.226 +        syInfo = SyndicationInformation()
  25.227 +        obj._setObject('syndication_information', syInfo)
  25.228 +        syInfo = obj._getOb('syndication_information')
  25.229 +        syInfo.syUpdatePeriod = self.syUpdatePeriod
  25.230 +        syInfo.syUpdateFrequency = self.syUpdateFrequency
  25.231 +        syInfo.syUpdateBase = self.syUpdateBase
  25.232 +        syInfo.max_items = self.max_items
  25.233 +        syInfo.description = "Channel Description"
  25.234 +
  25.235 +    security.declarePublic('disableSyndication')
  25.236 +    def disableSyndication(self, obj):
  25.237 +        """
  25.238 +        Disable syndication for the obj; and remove it.
  25.239 +        """
  25.240 +        syInfo = getattr(obj, 'syndication_information', None)
  25.241 +
  25.242 +        if syInfo is None:
  25.243 +            raise 'This object does not have Syndication Information'
  25.244 +
  25.245 +        obj._delObject('syndication_information')
  25.246 +
  25.247 +    security.declarePublic('getSyndicatableContent')
  25.248 +    def getSyndicatableContent(self, obj):
  25.249 +        """
  25.250 +        An interface for allowing folderish items to implement an
  25.251 +        equivalent of PortalFolderBase.contentValues()
  25.252 +        """
  25.253 +        if hasattr(obj, 'synContentValues'):
  25.254 +            values = obj.synContentValues()
  25.255 +        else:
  25.256 +            values = PortalFolderBase.contentValues(obj)
  25.257 +        return values
  25.258 +
  25.259 +    security.declarePublic('buildUpdatePeriods')
  25.260 +    def buildUpdatePeriods(self):
  25.261 +        """
  25.262 +        Return a list of possible update periods for the xmlns: sy
  25.263 +        """
  25.264 +        updatePeriods = ( ('hourly',  'Hourly')
  25.265 +                        , ('daily',   'Daily')
  25.266 +                        , ('weekly',  'Weekly')
  25.267 +                        , ('monthly', 'Monthly')
  25.268 +                        , ('yearly',  'Yearly')
  25.269 +                        )
  25.270 +        return updatePeriods
  25.271 +
  25.272 +    security.declarePublic('isSiteSyndicationAllowed')
  25.273 +    def isSiteSyndicationAllowed(self):
  25.274 +        """
  25.275 +        Return sitewide syndication policy
  25.276 +        """
  25.277 +        return self.isAllowed
  25.278 +
  25.279 +    security.declarePublic('isSyndicationAllowed')
  25.280 +    def isSyndicationAllowed(self, obj=None):
  25.281 +        """
  25.282 +        Check whether syndication is enabled for the site.  This
  25.283 +        provides for extending the method to check for whether a
  25.284 +        particular obj is enabled, allowing for turning on only
  25.285 +        specific folders for syndication.
  25.286 +        """
  25.287 +        syInfo = getattr(aq_base(obj), 'syndication_information',
  25.288 +                         None)
  25.289 +        if syInfo is None:
  25.290 +            return 0
  25.291 +        else:
  25.292 +            return self.isSiteSyndicationAllowed()
  25.293 +
  25.294 +    security.declarePublic('getUpdatePeriod')
  25.295 +    def getUpdatePeriod( self, obj=None ):
  25.296 +        """
  25.297 +        Return the update period for the RSS syn namespace.
  25.298 +        This is either on the object being passed or the
  25.299 +        portal_syndication tool (if a sitewide value or default
  25.300 +        is set)
  25.301 +
  25.302 +        NOTE:  Need to add checks for sitewide policies!!!
  25.303 +        """
  25.304 +        if not self.isSiteSyndicationAllowed():
  25.305 +            raise 'Syndication is Not Allowed'
  25.306 +
  25.307 +        if obj is None:
  25.308 +            return self.syUpdatePeriod
  25.309 +
  25.310 +        syInfo = getattr(obj, 'syndication_information', None)
  25.311 +
  25.312 +        if syInfo is not None:
  25.313 +            return syInfo.syUpdatePeriod
  25.314 +        else:
  25.315 +            return 'Syndication is Not Allowed'
  25.316 +
  25.317 +    security.declarePublic('getUpdateFrequency')
  25.318 +    def getUpdateFrequency(self, obj=None):
  25.319 +        """
  25.320 +        Return the update frequency (as a positive integer) for
  25.321 +        the syn namespace.  This is either on the object being
  25.322 +        pass or the portal_syndication tool (if a sitewide value
  25.323 +        or default is set).
  25.324 +
  25.325 +        Note:  Need to add checks for sitewide policies!!!
  25.326 +        """
  25.327 +        if not self.isSiteSyndicationAllowed():
  25.328 +            raise 'Syndication is not Allowed'
  25.329 +
  25.330 +        if obj is None:
  25.331 +            return self.syUpdateFrequency
  25.332 +
  25.333 +        syInfo = getattr(obj, 'syndication_information',
  25.334 +                            None)
  25.335 +        if syInfo is not None:
  25.336 +            return syInfo.syUpdateFrequency
  25.337 +        else:
  25.338 +            return 'Syndication is not Allowed'
  25.339 +
  25.340 +    security.declarePublic('getUpdateBase')
  25.341 +    def getUpdateBase(self, obj=None):
  25.342 +        """
  25.343 +        Return the base date to be used with the update frequency
  25.344 +        and the update period to calculate a publishing schedule.
  25.345 +
  25.346 +        Note:  I'm not sure what's best here, creation date, last
  25.347 +        modified date (of the folder being syndicated) or some
  25.348 +        arbitrary date.  For now, I'm going to build a updateBase
  25.349 +        time from zopetime and reformat it to meet the W3CDTF.
  25.350 +        Additionally, sitewide policy checks might have a place
  25.351 +        here...
  25.352 +        """
  25.353 +        if not self.isSiteSyndicationAllowed():
  25.354 +            raise 'Syndication is not Allowed'
  25.355 +
  25.356 +        if obj is None:
  25.357 +            when = self.syUpdateBase
  25.358 +            return when.ISO()
  25.359 +
  25.360 +        syInfo = getattr(obj, 'syndication_information',
  25.361 +                            None)
  25.362 +        if syInfo is not None:
  25.363 +                when = syInfo.syUpdateBase
  25.364 +                return when.ISO()
  25.365 +        else:
  25.366 +            return 'Syndication is not Allowed'
  25.367 +
  25.368 +    security.declarePublic('getHTML4UpdateBase')
  25.369 +    def getHTML4UpdateBase(self, obj):
  25.370 +        """
  25.371 +        Return HTML4 formated UpdateBase DateTime
  25.372 +        """
  25.373 +        if not self.isSiteSyndicationAllowed():
  25.374 +            raise 'Syndication is not Allowed'
  25.375 +
  25.376 +        if obj is None:
  25.377 +            when = syUpdateBase
  25.378 +            return when.HTML4()
  25.379 +
  25.380 +        syInfo = getattr(obj, 'syndication_information',
  25.381 +                            None)
  25.382 +        if syInfo is not None:
  25.383 +            when = syInfo.syUpdateBase
  25.384 +            return when.HTML4()
  25.385 +        else:
  25.386 +            return 'Syndication is not Allowed'
  25.387 +
  25.388 +    def getMaxItems(self, obj=None):
  25.389 +        """
  25.390 +        Return the max_items to be displayed in the syndication
  25.391 +        """
  25.392 +        if not self.isSiteSyndicationAllowed():
  25.393 +            raise 'Syndication is not Allowed'
  25.394 +
  25.395 +        if obj is None:
  25.396 +            return self.max_items
  25.397 +
  25.398 +        syInfo = getattr(obj, 'syndication_information',
  25.399 +                            None)
  25.400 +        if syInfo is not None:
  25.401 +            return syInfo.max_items
  25.402 +        else:
  25.403 +            return 'Syndication is not Allowed'
  25.404 +
  25.405 +InitializeClass(SyndicationTool)
    26.1 new file mode 100644
    26.2 --- /dev/null
    26.3 +++ b/URLTool.py
    26.4 @@ -0,0 +1,19 @@
    26.5 +##############################################################################
    26.6 +#
    26.7 +# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
    26.8 +#
    26.9 +# This software is subject to the provisions of the Zope Public License,
   26.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   26.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   26.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   26.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   26.14 +# FOR A PARTICULAR PURPOSE.
   26.15 +#
   26.16 +##############################################################################
   26.17 +""" CMFDefault portal_url tool.
   26.18 +
   26.19 +$Id: URLTool.py 36457 2004-08-12 15:07:44Z jens $
   26.20 +"""
   26.21 +
   26.22 +# for URL Tools created with CMF versions before 1.4
   26.23 +from Products.CMFCore.URLTool import URLTool
    27.1 new file mode 100644
    27.2 --- /dev/null
    27.3 +++ b/__init__.py
    27.4 @@ -0,0 +1,178 @@
    27.5 +##############################################################################
    27.6 +#
    27.7 +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
    27.8 +#
    27.9 +# This software is subject to the provisions of the Zope Public License,
   27.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   27.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   27.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   27.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   27.14 +# FOR A PARTICULAR PURPOSE.
   27.15 +#
   27.16 +##############################################################################
   27.17 +""" Default implementation of CMFCore.
   27.18 +
   27.19 +$Id: __init__.py 37118 2005-07-07 16:52:34Z regebro $
   27.20 +"""
   27.21 +
   27.22 +from Products.CMFCore.DirectoryView import registerDirectory
   27.23 +from Products.CMFCore.utils import initializeBasesPhase1
   27.24 +from Products.CMFCore.utils import initializeBasesPhase2
   27.25 +from Products.CMFCore.utils import ToolInit
   27.26 +from Products.CMFCore.utils import ContentInit
   27.27 +from Products.CMFCore.utils import registerIcon
   27.28 +try:
   27.29 +    from Products.CMFSetup import BASE
   27.30 +    from Products.CMFSetup import profile_registry
   27.31 +    has_profile_registry = True
   27.32 +except ImportError:
   27.33 +    has_profile_registry = False
   27.34 +
   27.35 +import utils
   27.36 +from permissions import AddPortalContent
   27.37 +
   27.38 +import Portal
   27.39 +import Document
   27.40 +import Link
   27.41 +import NewsItem
   27.42 +import File
   27.43 +import Image
   27.44 +import Favorite
   27.45 +import SkinnedFolder
   27.46 +
   27.47 +import DiscussionItem
   27.48 +import PropertiesTool
   27.49 +import MembershipTool
   27.50 +import MetadataTool
   27.51 +import RegistrationTool
   27.52 +import DublinCore
   27.53 +import DiscussionTool
   27.54 +import SyndicationTool
   27.55 +import DefaultWorkflow
   27.56 +
   27.57 +
   27.58 +# Old name that some third-party packages may need.
   27.59 +ADD_CONTENT_PERMISSION = AddPortalContent
   27.60 +
   27.61 +
   27.62 +#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
   27.63 +#   N.B.:  The following symbol controls whether we "inject" the
   27.64 +#          content types which formerly lived in CMFCore back into
   27.65 +#          it.  While it is initially true (to allow existing portal
   27.66 +#          content to load), in a future release it will be set to
   27.67 +#          false;  the behavior it governs will eventually be removed
   27.68 +#          altogether.  YOU HAVE BEEN WARNED!!!
   27.69 +#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
   27.70 +SUPPLY_DEPRECATED_PTK_BASE_ALIASES = 0
   27.71 +
   27.72 +if SUPPLY_DEPRECATED_PTK_BASE_ALIASES:
   27.73 +
   27.74 +    #   Get the old module names aliased into sys.modules...
   27.75 +    __module_aliases__ = ( ( 'Products.PTKBase.Document', Document )
   27.76 +                         , ( 'Products.PTKBase.File', File )
   27.77 +                         , ( 'Products.PTKBase.Image', Image )
   27.78 +                         , ( 'Products.PTKBase.Link', Link )
   27.79 +                         , ( 'Products.PTKBase.NewsItem', NewsItem )
   27.80 +                         )
   27.81 +
   27.82 +    #   ...and make sure we can find them in PTKBase when we do
   27.83 +    #   'manage_migrate_content()'.
   27.84 +    Products.PTKBase.Document = Document
   27.85 +    Products.PTKBase.File = File
   27.86 +    Products.PTKBase.Image = Image
   27.87 +    Products.PTKBase.Link = Link
   27.88 +    Products.PTKBase.NewsItem = NewsItem
   27.89 +
   27.90 +contentClasses = ( Document.Document
   27.91 +                 , File.File
   27.92 +                 , Image.Image
   27.93 +                 , Link.Link
   27.94 +                 , Favorite.Favorite
   27.95 +                 , NewsItem.NewsItem
   27.96 +                 , SkinnedFolder.SkinnedFolder
   27.97 +                 )
   27.98 +
   27.99 +contentConstructors = ( Document.addDocument
  27.100 +                      , File.addFile
  27.101 +                      , Image.addImage
  27.102 +                      , Link.addLink
  27.103 +                      , Favorite.addFavorite
  27.104 +                      , NewsItem.addNewsItem
  27.105 +                      , SkinnedFolder.addSkinnedFolder
  27.106 +                      )
  27.107 +
  27.108 +bases = ( ( Portal.CMFSite
  27.109 +          , DublinCore.DefaultDublinCoreImpl
  27.110 +          , DiscussionItem.DiscussionItem
  27.111 +          )
  27.112 +          + contentClasses
  27.113 +        )
  27.114 +
  27.115 +tools = ( DiscussionTool.DiscussionTool
  27.116 +        , MembershipTool.MembershipTool
  27.117 +        , RegistrationTool.RegistrationTool
  27.118 +        , PropertiesTool.PropertiesTool
  27.119 +        , MetadataTool.MetadataTool
  27.120 +        , SyndicationTool.SyndicationTool
  27.121 +        )
  27.122 +
  27.123 +import sys
  27.124 +this_module = sys.modules[ __name__ ]
  27.125 +
  27.126 +z_bases = initializeBasesPhase1( bases, this_module )
  27.127 +z_tool_bases = initializeBasesPhase1( tools, this_module )
  27.128 +
  27.129 +cmfdefault_globals=globals()
  27.130 +
  27.131 +# Make the skins available as DirectoryViews.
  27.132 +registerDirectory('skins', globals())
  27.133 +registerDirectory('help', globals())
  27.134 +
  27.135 +def initialize( context ):
  27.136 +
  27.137 +    initializeBasesPhase2( z_bases, context )
  27.138 +    initializeBasesPhase2( z_tool_bases, context )
  27.139 +
  27.140 +    ToolInit( 'CMF Default Tool'
  27.141 +            , tools=tools
  27.142 +            , icon='tool.gif'
  27.143 +            ).initialize( context )
  27.144 +
  27.145 +    ContentInit( 'CMF Default Content'
  27.146 +               , content_types=contentClasses
  27.147 +               , permission=AddPortalContent
  27.148 +               , extra_constructors=contentConstructors
  27.149 +               , fti=Portal.factory_type_information
  27.150 +               ).initialize( context )
  27.151 +
  27.152 +    if has_profile_registry:
  27.153 +        profile_registry.registerProfile('default',
  27.154 +                                         'CMFDefault Site',
  27.155 +                                         'Profile for a default CMFSite.',
  27.156 +                                         'profiles/default',
  27.157 +                                         'CMFDefault',
  27.158 +                                         BASE)
  27.159 +
  27.160 +    context.registerClass( Portal.CMFSite
  27.161 +                         , constructors=( Portal.manage_addCMFSiteForm
  27.162 +                                        , Portal.manage_addCMFSite
  27.163 +                                        )
  27.164 +                         , icon='portal.gif'
  27.165 +                         )
  27.166 +
  27.167 +    registerIcon( DefaultWorkflow.DefaultWorkflowDefinition
  27.168 +                , 'images/workflow.gif'
  27.169 +                , globals()
  27.170 +                )
  27.171 +
  27.172 +    # make registerHelp work with 2 directories
  27.173 +    help = context.getProductHelp()
  27.174 +    lastRegistered = help.lastRegistered
  27.175 +    context.registerHelp(directory='help', clear=1)
  27.176 +    context.registerHelp(directory='interfaces', clear=1)
  27.177 +    if help.lastRegistered != lastRegistered:
  27.178 +        help.lastRegistered = None
  27.179 +        context.registerHelp(directory='help', clear=1)
  27.180 +        help.lastRegistered = None
  27.181 +        context.registerHelp(directory='interfaces', clear=0)
  27.182 +    context.registerHelpTitle('CMF Default Help')
    28.1 new file mode 100644
    28.2 --- /dev/null
    28.3 +++ b/bridge.zcml
    28.4 @@ -0,0 +1,23 @@
    28.5 +<configure
    28.6 +    xmlns="http://namespaces.zope.org/five"
    28.7 +    >
    28.8 +
    28.9 +  <bridge
   28.10 +      zope2=".interfaces.Document.IDocument"
   28.11 +      package=".interfaces"
   28.12 +      name="IDocument"
   28.13 +      />
   28.14 +
   28.15 +  <bridge
   28.16 +      zope2=".interfaces.Document.IMutableDocument"
   28.17 +      package=".interfaces"
   28.18 +      name="IMutableDocument"
   28.19 +      />
   28.20 +
   28.21 +  <bridge
   28.22 +      zope2=".interfaces.portal_membership.portal_membership"
   28.23 +      package=".interfaces"
   28.24 +      name="IMembershipTool"
   28.25 +      />
   28.26 +
   28.27 +</configure>
    29.1 new file mode 100644
    29.2 --- /dev/null
    29.3 +++ b/browser/__init__.py
    29.4 @@ -0,0 +1,16 @@
    29.5 +##############################################################################
    29.6 +#
    29.7 +# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
    29.8 +#
    29.9 +# This software is subject to the provisions of the Zope Public License,
   29.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   29.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   29.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   29.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   29.14 +# FOR A PARTICULAR PURPOSE.
   29.15 +#
   29.16 +##############################################################################
   29.17 +""" CMFDefault.browser package.
   29.18 +
   29.19 +$Id: __init__.py 37087 2005-06-22 13:24:44Z yuppie $
   29.20 +"""
    30.1 new file mode 100644
    30.2 --- /dev/null
    30.3 +++ b/browser/configure.zcml
    30.4 @@ -0,0 +1,12 @@
    30.5 +<configure
    30.6 +    xmlns="http://namespaces.zope.org/browser"
    30.7 +    >
    30.8 +
    30.9 +  <page
   30.10 +      name="five_template"
   30.11 +      for="Products.CMFCore.interfaces.IDynamicType"
   30.12 +      template="five_template.pt"
   30.13 +      permission="zope2.View"
   30.14 +      />
   30.15 +
   30.16 +</configure>
    31.1 new file mode 100644
    31.2 --- /dev/null
    31.3 +++ b/browser/five_template.pt
    31.4 @@ -0,0 +1,11 @@
    31.5 +<metal:macro metal:define-macro="page"
    31.6 +><html metal:use-macro="context/main_template/macros/master">
    31.7 +<head>
    31.8 +<metal:slot metal:fill-slot="style_slot"
    31.9 +><metal:slot metal:define-slot="style_slot" /></metal:slot>
   31.10 +</head>
   31.11 +<body>
   31.12 +<metal:slot metal:fill-slot="main"
   31.13 +><metal:slot metal:define-slot="body" /></metal:slot>
   31.14 +</body>
   31.15 +</html></metal:macro>
    32.1 new file mode 100644
    32.2 --- /dev/null
    32.3 +++ b/configure.zcml
    32.4 @@ -0,0 +1,11 @@
    32.5 +<configure
    32.6 +    xmlns="http://namespaces.zope.org/zope"
    32.7 +    >
    32.8 +
    32.9 +  <include file="bridge.zcml"/>
   32.10 +
   32.11 +  <include file="implements.zcml"/>
   32.12 +
   32.13 +  <include package=".browser"/>
   32.14 +
   32.15 +</configure>
    33.1 new file mode 100644
    33.2 --- /dev/null
    33.3 +++ b/dtml/addPortal.dtml
    33.4 @@ -0,0 +1,126 @@
    33.5 +<dtml-let form_title="'Add Portal'">
    33.6 +<dtml-if manage_page_header>
    33.7 +  <dtml-var manage_page_header>
    33.8 +  <dtml-var manage_form_title>
    33.9 +<dtml-else>
   33.10 +  <html><head><title>&dtml-form_title;</title></head>
   33.11 +  <body>
   33.12 +  <h2>&dtml-form_title;</h2>
   33.13 +</dtml-if>
   33.14 +</dtml-let>
   33.15 +
   33.16 +<form action="manage_addCMFSite" method="POST">
   33.17 +<table cellspacing="0" cellpadding="2" border="0">
   33.18 +  <tr>
   33.19 +    <td align="left" valign="top" colspan="2">
   33.20 +    <div class="form-help">
   33.21 +    Enter an ID and click the button below to create a new CMF site.
   33.22 +    </div>
   33.23 +    </td>
   33.24 +  </tr>
   33.25 +
   33.26 +  <tr>
   33.27 +    <td align="left" valign="top">
   33.28 +    <div class="form-label">
   33.29 +    Id
   33.30 +    </div>
   33.31 +    </td>
   33.32 +    <td align="left" valign="top">
   33.33 +    <input type="text" name="id" size="40" />
   33.34 +    </td>
   33.35 +  </tr>
   33.36 +
   33.37 +  <tr>
   33.38 +    <td align="left" valign="top">
   33.39 +    <div class="form-label">
   33.40 +    Title
   33.41 +    </div>
   33.42 +    </td>
   33.43 +    <td align="left" valign="top">
   33.44 +    <input type="text" name="title" size="40" value="Portal" />
   33.45 +    </td>
   33.46 +  </tr>
   33.47 +
   33.48 +  <tr>
   33.49 +    <td align="left" valign="top">
   33.50 +    <div class="form-label">
   33.51 +    Membership source 
   33.52 +    </div>
   33.53 +    </td>
   33.54 +    <td align="left" valign="top">
   33.55 +      <select name="create_userfolder">
   33.56 +        <option value="1">Create a new user folder in the portal</option>
   33.57 +        <option value="0">I have an existing user folder and want to use it instead</option>
   33.58 +      </select>
   33.59 +    </td>
   33.60 +  </tr>
   33.61 +
   33.62 +  <tr>
   33.63 +    <td align="left" valign="top">
   33.64 +    <div class="form-label">
   33.65 +    Description
   33.66 +    </div>
   33.67 +    </td>
   33.68 +    <td align="left" valign="top">
   33.69 +    <textarea name="description" cols="60" rows="10"
   33.70 +     style="width: 100%"></textarea>
   33.71 +    </td>
   33.72 +  </tr>
   33.73 +
   33.74 +<!-- This may be used in the future.
   33.75 +
   33.76 +  <tr>
   33.77 +    <td align="left" valign="top">
   33.78 +    <div class="form-label">
   33.79 +    Portal administrator name
   33.80 +    </div>
   33.81 +    </td>
   33.82 +    <td align="left" valign="top">
   33.83 +    <input type="text" name="email_from_name" size="40"
   33.84 +     value="Portal Administrator" />
   33.85 +    </td>
   33.86 +  </tr>
   33.87 +
   33.88 +  <tr>
   33.89 +    <td align="left" valign="top">
   33.90 +    <div class="form-label">
   33.91 +    Portal administrator e-mail address
   33.92 +    </div>
   33.93 +    </td>
   33.94 +    <td align="left" valign="top">
   33.95 +    <input type="text" name="email_from_address" size="40"
   33.96 +     value="postmaster@localhost"/>
   33.97 +    </td>
   33.98 +  </tr>
   33.99 +
  33.100 +  <tr>
  33.101 +    <td align="left" valign="top">
  33.102 +    <div class="form-label"><label for="cb_valemail">
  33.103 +    Validate e-mail addresses
  33.104 +    </label></div>
  33.105 +    </td>
  33.106 +    <td align="left" valign="top">
  33.107 +    <input type="checkbox" name="validate_email" value="1" id="cb_valemail" />
  33.108 +    </td>
  33.109 +  </tr>
  33.110 +
  33.111 +-->
  33.112 +
  33.113 +  <tr>
  33.114 +    <td align="left" valign="top">
  33.115 +    </td>
  33.116 +    <td align="left" valign="top">
  33.117 +    <div class="form-element">
  33.118 +    <input class="form-element" type="submit" name="submit" 
  33.119 +     value=" Add " />
  33.120 +    </div>
  33.121 +    </td>
  33.122 +  </tr>
  33.123 +</table>
  33.124 +</form>
  33.125 +
  33.126 +<dtml-if manage_page_footer>
  33.127 +  <dtml-var manage_page_footer>
  33.128 +<dtml-else>
  33.129 +  </body></html>
  33.130 +</dtml-if>
    34.1 new file mode 100644
    34.2 --- /dev/null
    34.3 +++ b/dtml/discussionEdit.dtml
    34.4 @@ -0,0 +1,77 @@
    34.5 +<dtml-var standard_html_header>
    34.6 +
    34.7 +<div class="Desktop">
    34.8 +
    34.9 +<dtml-if message>
   34.10 + <p>&dtml-message;</p>
   34.11 + <hr>
   34.12 +</dtml-if>
   34.13 +
   34.14 +<div class="DiscussionItem">
   34.15 +
   34.16 +<h2>Edit &dtml-getId;</h2>
   34.17 +
   34.18 +<form action="edit" method="post" enctype="multipart/form-data">
   34.19 +<table class="FormLayout">
   34.20 + <tr>
   34.21 +  <th>
   34.22 +   Title
   34.23 +  </th>
   34.24 +  <td>
   34.25 +   <dtml-var name="title">
   34.26 +  </td>
   34.27 + </tr>
   34.28 + <tr>
   34.29 +  <th>
   34.30 +   Description
   34.31 +  </th>
   34.32 +  <td>
   34.33 +    <dtml-var name="description">
   34.34 +  </td>
   34.35 + </tr>
   34.36 + <tr>
   34.37 +  <th>
   34.38 +   Format
   34.39 +  </th>
   34.40 +  <td> 
   34.41 +   <input type="radio" name="text_format" value="structured-text"
   34.42 +        <dtml-if "text_format=='structured-text'">checked
   34.43 +        </dtml-if> id="cb_structuredtext" /> 
   34.44 +        <label for="cb_structuredtext">structured-text</label>
   34.45 +   <input type="radio" name="text_format" value="html"
   34.46 +        <dtml-if "text_format=='html'">checked
   34.47 +        </dtml-if> id="cb_html" />
   34.48 +	<label for="cb_html">html</label>
   34.49 +  </td>
   34.50 + </tr>
   34.51 + <tr>
   34.52 +  <th>
   34.53 +   Upload File
   34.54 +  </th>
   34.55 +  <td>
   34.56 +   <input type="file" name="file" size="25">
   34.57 +  </td>
   34.58 + </tr>
   34.59 + <tr>
   34.60 +  <th>
   34.61 +   Content
   34.62 +  </th>
   34.63 +  <td>
   34.64 +   <textarea name="text:text"
   34.65 +             rows="20" cols="80"><dtml-var text></textarea>
   34.66 +  </td>
   34.67 + </tr>
   34.68 + <tr>
   34.69 +  <td> <br> </td>
   34.70 +  <td>
   34.71 +    <input type="submit" value=" Change ">
   34.72 +  </td>
   34.73 + </tr>
   34.74 +</table>
   34.75 +</form>
   34.76 +
   34.77 +</div>
   34.78 +
   34.79 +</div>
   34.80 +
   34.81 +<dtml-var standard_html_footer>
    35.1 new file mode 100644
    35.2 --- /dev/null
    35.3 +++ b/dtml/discussionView.dtml
    35.4 @@ -0,0 +1,23 @@
    35.5 +<dtml-let relative_to_content="1">
    35.6 +<dtml-var standard_html_header>
    35.7 +</dtml-let>
    35.8 +
    35.9 +<div class="Desktop">
   35.10 +
   35.11 +<div class="DiscussionItem">
   35.12 +
   35.13 +<dtml-var content_byline>
   35.14 +
   35.15 +<dtml-var cooked_text>
   35.16 +
   35.17 +<div class="Discussion">
   35.18 +
   35.19 +<dtml-var viewThreadsAtBottom>
   35.20 +
   35.21 +</div>
   35.22 +
   35.23 +</div>
   35.24 +
   35.25 +</div>
   35.26 +
   35.27 +<dtml-var standard_html_footer>
    36.1 new file mode 100644
    36.2 --- /dev/null
    36.3 +++ b/dtml/explainDiscussionTool.dtml
    36.4 @@ -0,0 +1,13 @@
    36.5 +<dtml-var manage_page_header>
    36.6 +<dtml-var manage_tabs>
    36.7 +
    36.8 +<h3> <code>portal_discussion</code> Tool </h3>
    36.9 +
   36.10 +<p> This tool embodies the policies for a given CMFSite concerning
   36.11 +    the storage mechanisms for discussion about content.  In particular,
   36.12 +    this version implements the policy that replies to a piece of content
   36.13 +    are stored on a special "talkback" subobject of the content, and are
   36.14 +    threaded by it.
   36.15 +</p>
   36.16 +
   36.17 +<dtml-var manage_page_footer>
    37.1 new file mode 100644
    37.2 --- /dev/null
    37.3 +++ b/dtml/explainMembershipTool.dtml
    37.4 @@ -0,0 +1,15 @@
    37.5 +<dtml-var manage_page_header>
    37.6 +<dtml-var manage_tabs>
    37.7 +
    37.8 +<h3> <code>portal_membership</code> Tool </h3>
    37.9 +
   37.10 +<p> This tool encapsulates the authentication mechanism (the user folder)
   37.11 +    in a more usable, "member-centric" interface, insulating other CMF
   37.12 +    objects from changes or idiosyncracies in the particular member folder.
   37.13 +</p>
   37.14 +
   37.15 +<p>If you want to customize the content created by 'createMemberArea', add a
   37.16 +  'createMemberContent' script with the parameters "member, member_id,
   37.17 +  member_folder" to the Contents tab of this tool.</p>
   37.18 +
   37.19 +<dtml-var manage_page_footer>
    38.1 new file mode 100644
    38.2 --- /dev/null
    38.3 +++ b/dtml/explainMetadataTool.dtml
    38.4 @@ -0,0 +1,11 @@
    38.5 +<dtml-var manage_page_header>
    38.6 +<dtml-var manage_tabs>
    38.7 +
    38.8 +<h3> <code>portal_metadata</code> Tool </h3>
    38.9 +
   38.10 +<p> This tool embodies site-wide policies concerning required metadata
   38.11 +    for each content type, as well as default values and controlled
   38.12 +    vocabularies.
   38.13 +</p>
   38.14 +
   38.15 +<dtml-var manage_page_footer>
    39.1 new file mode 100644
    39.2 --- /dev/null
    39.3 +++ b/dtml/explainPropertiesTool.dtml
    39.4 @@ -0,0 +1,10 @@
    39.5 +<dtml-var manage_page_header>
    39.6 +<dtml-var manage_tabs>
    39.7 +
    39.8 +<h3> <code>portal_properties</code> Tool </h3>
    39.9 +
   39.10 +<p> This tool provides a common interface for accessing "portal-wide"
   39.11 +    properties.
   39.12 +</p>
   39.13 +
   39.14 +<dtml-var manage_page_footer>
    40.1 new file mode 100644
    40.2 --- /dev/null
    40.3 +++ b/dtml/membershipRolemapping.dtml
    40.4 @@ -0,0 +1,93 @@
    40.5 +<dtml-let form_title="'Membership Tool Role Mappings'">
    40.6 +<dtml-if manage_page_header>
    40.7 + <dtml-var manage_page_header>
    40.8 +<dtml-else>
    40.9 + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
   40.10 + <html lang="en">
   40.11 + <head>
   40.12 + <title>&dtml-form_title;</title>
   40.13 + </head>
   40.14 + <body bgcolor="#FFFFFF" link="#000099" vlink="#555555">
   40.15 + <h3>&dtml-form_title;</h3>
   40.16 +</dtml-if>
   40.17 +</dtml-let>
   40.18 +
   40.19 +<dtml-var name="manage_tabs">
   40.20 +  
   40.21 +<h2>Membership role mappings</h2>
   40.22 +
   40.23 +<p>Use this screen if you are using a userfolder other than the built-in folder to map 
   40.24 +existing role names to roles understood by the CMF.</p>
   40.25 +
   40.26 +<dtml-in expr="getPortalRoles()">
   40.27 +  <dtml-if name="sequence-start">
   40.28 +    <table border="1" cellpadding="3">
   40.29 +      <tr>
   40.30 +        <td class="form-label"><b>Portal Role</b></td>
   40.31 +        <td class="form-label"><b>User Folder-defined Role</b></td>
   40.32 +        <td>&nbsp;</td>
   40.33 +      </tr>
   40.34 +  </dtml-if>
   40.35 +
   40.36 +  <tr>
   40.37 +    <form method="post" action="setRoleMapping">
   40.38 +    <td class="form-label"><dtml-var name="sequence-item"><input type="hidden" name="portal_role" value="&dtml-sequence-item;"></td>
   40.39 +    <td class="form-element"><input type="text" size="30" name="userfolder_role" 
   40.40 +              value="<dtml-var expr="getMappedRole(_['sequence-item'])">"></td>
   40.41 +    <td><input type="submit" value="Set Mapping"></td>
   40.42 +    </form>
   40.43 +  </tr>
   40.44 +
   40.45 +  <dtml-if name="sequence-end">
   40.46 +    </table>
   40.47 +  </dtml-if>
   40.48 +</dtml-in>
   40.49 +
   40.50 +<p><hr></p>
   40.51 +
   40.52 +<h2>Control creation of member areas</h2>
   40.53 +
   40.54 +<p>This feature controls whether users coming from an outside user source (such as an underlying 
   40.55 +user folder) will have their own folder created upon first login or not</p>
   40.56 +
   40.57 +<form method="post" action="setMemberareaCreationFlag">
   40.58 +
   40.59 +<dtml-if expr="getMemberareaCreationFlag() == 1">
   40.60 +  <p><b>Folders are created upon first login.</b> 
   40.61 +  <input type="submit" value=" Turn folder creation off ">
   40.62 +<dtml-else>
   40.63 +  <p><b>No Folders are created.</b> 
   40.64 +  <input type="submit" value=" Turn folder creation on ">
   40.65 +</dtml-if>
   40.66 +
   40.67 +</form>
   40.68 +
   40.69 +<p><hr></p>
   40.70 +
   40.71 +<h2>Set members folder</h2>
   40.72 +
   40.73 +<p>The members folder has to be in the same container as the membership tool.</p>
   40.74 +
   40.75 +<form action="manage_setMembersFolderById" method="post">
   40.76 +<table cellspacing="2">
   40.77 +<tr>
   40.78 +  <td align="left" valign="top">
   40.79 +    <div class="form-label">Members folder id</div>
   40.80 +  </td>
   40.81 +  <td align="left" valign="top">
   40.82 +    <input class="form-element" type="text" name="id"
   40.83 +        value="&dtml-membersfolder_id;">
   40.84 +  </td>
   40.85 +</tr>
   40.86 +<tr>
   40.87 +  <td>
   40.88 +  </td>
   40.89 +  <td align="left" valign="top">
   40.90 +    <input class="form-element" type="submit" value=" Change ">
   40.91 +  </td>
   40.92 +</tr>
   40.93 +</table>
   40.94 +</form>
   40.95 +
   40.96 +</body>
   40.97 +</html>
    41.1 new file mode 100644
    41.2 --- /dev/null
    41.3 +++ b/dtml/metadataElementPolicies.dtml
    41.4 @@ -0,0 +1,176 @@
    41.5 +<dtml-var manage_page_header>
    41.6 +<dtml-var manage_tabs>
    41.7 +
    41.8 +<dtml-unless expr="REQUEST.has_key( 'element' )">
    41.9 +<dtml-call expr="REQUEST.set( 'element', listElementSpecs()[0][0] )">
   41.10 +</dtml-unless>
   41.11 +
   41.12 +<h3> Update Element Metadata Policies </h3>
   41.13 +
   41.14 +<table class="FormLayout">
   41.15 +
   41.16 + <tr>
   41.17 +  <th> Element: </th>
   41.18 +  <td colspan="3"> 
   41.19 +   <dtml-in listElementSpecs>
   41.20 +    <dtml-let key=sequence-key>
   41.21 +     <dtml-if expr="key == REQUEST[ 'element' ]">
   41.22 +     &dtml-key;  &nbsp;
   41.23 +     <dtml-else>
   41.24 +      <a href="&dtml-URL;?element=&dtml-key;"> &dtml-key; </a> &nbsp;
   41.25 +     </dtml-if>
   41.26 +    </dtml-let>
   41.27 +   </dtml-in>
   41.28 +  </td>
   41.29 + </tr>
   41.30 +
   41.31 + <dtml-let spec="getElementSpec( element=REQUEST[ 'element' ] )"
   41.32 +           multi="spec.isMultiValued()"
   41.33 +           tokenz="multi and ':tokens' or ''"
   41.34 + >
   41.35 +
   41.36 + <dtml-in expr="spec.listPolicies()" sort>
   41.37 + <dtml-let element="REQUEST[ 'element']"
   41.38 +           key=sequence-key
   41.39 +           typ="key or '<default>'"
   41.40 +           policy=sequence-item
   41.41 +           rqd="policy.isRequired() and 'checked' or ''"
   41.42 +           canRemove="key is not _.None"
   41.43 +           supply="policy.supplyDefault() and 'checked' or ''"
   41.44 +           rawdef="policy.defaultValue()"
   41.45 +           defval="(multi and ( _.string.join( rawdef ), ) or ( rawdef, ))[0]"
   41.46 +           enforce="policy.enforceVocabulary() and 'checked' or ''"
   41.47 +           vocab="_.string.join( policy.allowedVocabulary(), '\n' )"
   41.48 + >
   41.49 +
   41.50 + <form action="&dtml-absolute_url;" method="POST">
   41.51 + <input type="hidden" name="element" value="&dtml-element;"> 
   41.52 + <input type="hidden" name="content_type" value="&dtml-typ;"> 
   41.53 +
   41.54 + <tr style="background-color: DarkGray; color: DarkBlue">
   41.55 +  <th colspan="4"> <br> </th>
   41.56 + </tr>
   41.57 +
   41.58 + <tr valign="top">
   41.59 +  <th> Content type </th>
   41.60 +  <td>
   41.61 +    <dtml-if "typ == '<default>'">
   41.62 +      &dtml-typ;
   41.63 +    <dtml-else>
   41.64 +      <dtml-let typeinfo="portal_types.getTypeInfo(typ)">
   41.65 +        <dtml-if typeinfo>
   41.66 +          <dtml-var "typeinfo.Title()" html_quote>
   41.67 +        <dtml-else>
   41.68 +          &dtml-typ; (deleted)
   41.69 +        </dtml-if>
   41.70 +      </dtml-let>
   41.71 +    </dtml-if>
   41.72 +  </td>
   41.73 +  <th> Required? </th>
   41.74 +  <td>
   41.75 +    <input type="checkbox" name="is_required:boolean" &dtml-rqd;>
   41.76 +    <input type="hidden" name="is_required:int:default" value="0">
   41.77 +  </td>
   41.78 + </tr>
   41.79 + 
   41.80 + <tr valign="top">
   41.81 +  <th> Supply default? </th>
   41.82 +  <td>
   41.83 +    <input type="checkbox" name="supply_default:boolean" &dtml-supply;>
   41.84 +    <input type="hidden" name="supply_default:int:default" value="0">
   41.85 +  </td>
   41.86 +  <th> Default </th>
   41.87 +  <td> <input type="text" name="default_value&dtml-tokenz;"
   41.88 +              value="&dtml-defval;" size="40"> </td>
   41.89 + </tr>
   41.90 + 
   41.91 + <tr valign="top">
   41.92 +  <th> Enforce vocabulary? </th>
   41.93 +  <td>
   41.94 +    <input type="checkbox" name="enforce_vocabulary:boolean" &dtml-enforce;>
   41.95 +    <input type="hidden" name="enforce_vocabulary:int:default" value="0">
   41.96 +  </td>
   41.97 +  <th> Vocabulary </th>
   41.98 +  <td> <textarea name="allowed_vocabulary:lines"
   41.99 +                 rows="5" cols="20">&dtml-vocab;</textarea>
  41.100 + </tr>
  41.101 + 
  41.102 + <tr valign="top">
  41.103 +  <td> <br> </td>
  41.104 +  <td colspan="3">
  41.105 +   <input type="submit" name="updateElementPolicy:method" value=" Update ">
  41.106 +   <dtml-if canRemove>
  41.107 +    <input type="submit" name="removeElementPolicy:method" value=" Remove ">
  41.108 +   </dtml-if>
  41.109 +  </td>
  41.110 + </tr>
  41.111 +
  41.112 + </form>
  41.113 + </dtml-let>
  41.114 + </dtml-in>
  41.115 +
  41.116 + <form action="&dtml-absolute_url;" method="POST">
  41.117 + <input type="hidden" name="element" value="&dtml-element;"> 
  41.118 +
  41.119 + <tr style="background-color: DarkGray; color: DarkBlue">
  41.120 +  <th colspan="4"> &lt;new type&gt; </th>
  41.121 + </tr>
  41.122 +
  41.123 + <tr valign="top">
  41.124 +  <th> Content type </th>
  41.125 +
  41.126 +  <dtml-let typeinfos="portal_types.listTypeInfo()">
  41.127 +  <td>
  41.128 +   <select name="content_type">
  41.129 +    <dtml-in typeinfos>
  41.130 +     <option value="&dtml-getId;"> &dtml-Title; </option>
  41.131 +    </dtml-in>
  41.132 +   </select>
  41.133 +  </td>
  41.134 +  </dtml-let>
  41.135 +
  41.136 +  <th> Required? </th>
  41.137 +  <td>
  41.138 +    <input type="checkbox" name="is_required:boolean">
  41.139 +    <input type="hidden" name="is_required:int:default" value="0">
  41.140 +  </td>
  41.141 + </tr>
  41.142 + 
  41.143 + <tr valign="top">
  41.144 +  <th> Supply default? </th>
  41.145 +  <td>
  41.146 +    <input type="checkbox" name="supply_default:boolean">
  41.147 +    <input type="hidden" name="supply_default:int:default" value="0">
  41.148 +  </td>
  41.149 +  <th> Default </th>
  41.150 +  <td> <input type="text" name="default_value&dtml-tokenz;" size="40"> </td>
  41.151 + </tr>
  41.152 + 
  41.153 + <tr valign="top">
  41.154 +  <th> Enforce vocabulary? </th>
  41.155 +  <td>
  41.156 +    <input type="checkbox" name="enforce_vocabulary:boolean">
  41.157 +    <input type="hidden" name="enforce_vocabulary:int:default" value="0">
  41.158 +  </td>
  41.159 +  <th> Vocabulary </th>
  41.160 +  <td> <textarea name="allowed_vocabulary:lines"
  41.161 +                 rows="5" cols="20"></textarea>
  41.162 + </tr>
  41.163 + 
  41.164 + <tr valign="top">
  41.165 +  <td> <br> </td>
  41.166 +  <td>
  41.167 +   <input type="submit" name="addElementPolicy:method" value=" Add ">
  41.168 +  </td>
  41.169 + </tr>
  41.170 +
  41.171 + </form>
  41.172 +
  41.173 + <tr style="background-color: DarkGray; color: DarkBlue">
  41.174 +  <th colspan="4"> <br> </th>
  41.175 + </tr>
  41.176 +
  41.177 + </dtml-let>
  41.178 +</table>
  41.179 +
  41.180 +<dtml-var manage_page_footer>
    42.1 new file mode 100644
    42.2 --- /dev/null
    42.3 +++ b/dtml/metadataProperties.dtml
    42.4 @@ -0,0 +1,74 @@
    42.5 +<dtml-var manage_page_header>
    42.6 +<dtml-var manage_tabs>
    42.7 +
    42.8 +<h3> Update Metadata Tool Properties </h3>
    42.9 +
   42.10 +<form action="editProperties" method="POST">
   42.11 +<table>
   42.12 +
   42.13 + <tr valign="middle">
   42.14 +  <th width="100" align="right"> Publisher: </th>
   42.15 +  <td> <input type="text" name="publisher"
   42.16 +              value="&dtml-getPublisher;" size="40"> </td>
   42.17 + </tr>
   42.18 +
   42.19 + <tr valign="middle">
   42.20 +  <td> <br> </td>
   42.21 +  <td> <input type="submit" value=" Change "> </td>
   42.22 + </tr>
   42.23 +
   42.24 +</table>
   42.25 +</form>
   42.26 +
   42.27 +<h3> Add Metadata Element </h3>
   42.28 +
   42.29 +<form action="&dtml-absolute_url;/addElementSpec" method="POST">
   42.30 +<input type="hidden" name="is_multi_valued:int:default" value="0">
   42.31 +<table>
   42.32 +
   42.33 + <tr valign="middle">
   42.34 +  <th width="100" align="right"> Element: </th>
   42.35 +  <td> <input type="text" name="element" size="20"> </td>
   42.36 + </tr>
   42.37 +
   42.38 + <tr valign="middle">
   42.39 +  <th width="100" align="right"> Multi-valued? </th>
   42.40 +  <td> <input type="checkbox" name="is_multi_valued:boolean"> </td>
   42.41 + </tr>
   42.42 +
   42.43 + <tr valign="middle">
   42.44 +  <td> <br> </td>
   42.45 +  <td> <input type="submit" value=" Add "> </td>
   42.46 + </tr>
   42.47 +
   42.48 +</table>
   42.49 +</form>
   42.50 +
   42.51 +<h3> Remove Metadata Element </h3>
   42.52 +
   42.53 +<form action="&dtml-absolute_url;/removeElementSpec" method="POST">
   42.54 +<table>
   42.55 +
   42.56 + <tr valign="middle">
   42.57 +  <th width="100" align="right"> Element: </th>
   42.58 +  <td> <dtml-in listElementSpecs>
   42.59 +       <dtml-if sequence-start>
   42.60 +       <select name="element">
   42.61 +       </dtml-if>
   42.62 +       <option value="&dtml-sequence-key;"> &dtml-sequence-key; </option>
   42.63 +       <dtml-if sequence-end>
   42.64 +       </select>
   42.65 +       </dtml-if>
   42.66 +       </dtml-in>
   42.67 +  </td>
   42.68 + </tr>
   42.69 +
   42.70 + <tr valign="middle">
   42.71 +  <td> <br> </td>
   42.72 +  <td> <input type="submit" value=" Remove "> </td>
   42.73 + </tr>
   42.74 +
   42.75 +</table>
   42.76 +</form>
   42.77 +
   42.78 +<dtml-var manage_page_footer>
    43.1 new file mode 100644
    43.2 --- /dev/null
    43.3 +++ b/dtml/synOverview.dtml
    43.4 @@ -0,0 +1,26 @@
    43.5 +<dtml-let form_title="'Portal Syndication Overview'">
    43.6 +<dtml-if manage_page_header>
    43.7 + <dtml-var manage_page_header>
    43.8 +<dtml-else>
    43.9 + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
   43.10 + <html lang="en">
   43.11 + <head>
   43.12 + <title>&dtml-form_title;</title>
   43.13 + </head>
   43.14 + <body bgcolor="#FFFFFF" link="#000099" vlink="#555555">
   43.15 + <h3>&dtml-form_title;</h3>
   43.16 +</dtml-if>
   43.17 +</dtml-let>
   43.18 +
   43.19 +<dtml-var manage_tabs>
   43.20 +
   43.21 +<h4>Syndication Tool Overview</h4>
   43.22 +
   43.23 +    See the online help by clicking the 'Help' link on the Management Forms.
   43.24 +    To turn on syndication, visit the Properties Tab in the management interface.
   43.25 +    <br>
   43.26 +    More online documentation is available at:  
   43.27 +    <a href="http://cmf.zope.org/doc/admin/syndication/">Syndication Administration</a>
   43.28 +
   43.29 +</body>
   43.30 +</html>
    44.1 new file mode 100644
    44.2 --- /dev/null
    44.3 +++ b/dtml/synPolicies.dtml
    44.4 @@ -0,0 +1,27 @@
    44.5 +<body bgcolor="#ffffff">
    44.6 +<dtml-var manage_tabs>
    44.7 +<h2>Default Syndication Policies</h2>
    44.8 +<form action="editSyPolicies" method="POST">
    44.9 +<table width="100%" border="0">
   44.10 +  <tr>
   44.11 +     <th valign="top" align="left">Sy Module Policies</th>
   44.12 +  </tr>
   44.13 +  <tr>
   44.14 +     <td colspan="2">
   44.15 +       <table width="90%" border="1"><tr><td>
   44.16 +       <table>
   44.17 +         <tr>
   44.18 +           <th>Policies are not yet implemented</th>
   44.19 +           <th>&nbsp;</th>
   44.20 +         </tr>
   44.21 +       </table>
   44.22 +       </td></tr></table>
   44.23 +     </td>
   44.24 +  </tr>
   44.25 +  <tr>
   44.26 +   <td> <br /> </td>
   44.27 +  </tr>
   44.28 +</table>
   44.29 +</form>
   44.30 +</body>
   44.31 +</html>
    45.1 new file mode 100644
    45.2 --- /dev/null
    45.3 +++ b/dtml/synProps.dtml
    45.4 @@ -0,0 +1,88 @@
    45.5 +<html><head><title>portal_syndication properties</title></head>
    45.6 +<body bgcolor="#ffffff">
    45.7 +<dtml-var manage_tabs>
    45.8 +<h2>Sitewide Default Syndication Properties</h2>
    45.9 +<dtml-if expr="portal_syndication.isSiteSyndicationAllowed()"> 
   45.10 +<form action="editProperties" method="POST">
   45.11 +<table width="100%" border="0">
   45.12 +  <tr>
   45.13 +     <th valign="top" align="left">Sy Module Properties</th>
   45.14 +  </tr>
   45.15 +  <tr>
   45.16 +     <td colspan="2">
   45.17 +       <table width="90%" border="1"><tr><td>
   45.18 +       <table>
   45.19 +         <tr>
   45.20 +           <th>Element</th>
   45.21 +           <th>Default Value</th>
   45.22 +         </tr>
   45.23 +	<dtml-with portal_syndication>
   45.24 +         <tr>
   45.25 +           <td>
   45.26 +             UpdatePeriod
   45.27 +           </td>
   45.28 +           <td>
   45.29 + <select name="updatePeriod">
   45.30 +	<dtml-in buildUpdatePeriods>
   45.31 +	<option value="&dtml-sequence-key;"
   45.32 +	<dtml-if expr="_['sequence-key'] == getUpdatePeriod()">
   45.33 +		selected="selected"</dtml-if>>&dtml-sequence-item;
   45.34 +	</option>
   45.35 +</dtml-in>
   45.36 +</select>
   45.37 +           </td>
   45.38 +         </tr>
   45.39 +         <tr>
   45.40 +           <td>
   45.41 +             UpdateFrequency
   45.42 +           </td>
   45.43 +           <td>
   45.44 +             <input type="text" name="updateFrequency"
   45.45 +              value="&dtml-getUpdateFrequency;" size="3">
   45.46 +           </td>
   45.47 +         </tr>
   45.48 +		 <tr>
   45.49 +           <td>
   45.50 +             UpdateBase
   45.51 +           </td>
   45.52 +           <td>
   45.53 +             <input type="text" name="updateBase:date"
   45.54 +              value="&dtml-getUpdateBase;" size="70">
   45.55 +           </td>
   45.56 +         </tr>
   45.57 +           <tr>
   45.58 +           <td>
   45.59 +             Max Syndicated Items
   45.60 +           </td>
   45.61 +           <td>
   45.62 +             <input type="text" name="max_items"
   45.63 +              value="&dtml-getMaxItems;" size="3">
   45.64 +           </td>
   45.65 +         </tr>
   45.66 +	</dtml-with>
   45.67 +         <tr>
   45.68 +           <td colspan="2">
   45.69 +             <input type="submit" name="edit" value=" Save ">
   45.70 +           </td>
   45.71 +         </tr>
   45.72 +
   45.73 +       </table>
   45.74 +       </td></tr></table>
   45.75 +     </td>
   45.76 +  </tr>
   45.77 +  <tr>
   45.78 +   <td> <br /> </td>
   45.79 +  </tr>
   45.80 +</table>
   45.81 +</form>
   45.82 +<form action="editProperties" method="post">
   45.83 +<input type="submit" value="Disable Syndication"></input>
   45.84 +<input type="hidden" name="isAllowed:int" value="0"></input>
   45.85 +</form>
   45.86 +<dtml-else>
   45.87 +<form action="editProperties" method="post">
   45.88 +<input type="submit" value="Enable Syndication">
   45.89 +<input type="hidden" name="isAllowed:int" value="1">
   45.90 +</dtml-if>
   45.91 +</body>
   45.92 +</html>
    46.1 new file mode 100644
    46.2 --- /dev/null
    46.3 +++ b/dtml/synReports.dtml
    46.4 @@ -0,0 +1,29 @@
    46.5 +<body bgcolor="#ffffff">
    46.6 +<dtml-var manage_tabs>
    46.7 +<h2>Sitewide Syndication Reporting Facility</h2>
    46.8 +<form action="generateSyReport" method="POST">
    46.9 +<table width="100%" border="0">
   46.10 +  <tr>
   46.11 +     <th valign="top" align="left">Syndication Reports</th>
   46.12 +  </tr>
   46.13 +
   46.14 +
   46.15 +  <tr>
   46.16 +     <td colspan="2">
   46.17 +       <table width="90%" border="1"><tr><td>
   46.18 +       <table>
   46.19 +         <tr>
   46.20 +           <th>Reports are not yet implemented</th>
   46.21 +           <th>&nbsp;</th>
   46.22 +         </tr>
   46.23 +       </table>
   46.24 +       </td></tr></table>
   46.25 +     </td>
   46.26 +  </tr>
   46.27 +  <tr>
   46.28 +   <td> <br /> </td>
   46.29 +  </tr>
   46.30 +</table>
   46.31 +</form>
   46.32 +</body>
   46.33 +</html>
    47.1 new file mode 100644
    47.2 --- /dev/null
    47.3 +++ b/dtml/zmi_editDocument.dtml
    47.4 @@ -0,0 +1,70 @@
    47.5 +<dtml-var manage_page_header>
    47.6 +<dtml-var manage_tabs>
    47.7 +
    47.8 +<h2>Edit &dtml-getId;</h2>
    47.9 +
   47.10 +<form action="manage_editDocument" method="post" enctype="multipart/form-data">
   47.11 +<table>
   47.12 + <tr>
   47.13 +  <th>
   47.14 +    Title
   47.15 +  </th>
   47.16 +  <td>
   47.17 +   <dtml-var Title>
   47.18 +  </td>
   47.19 + </tr>
   47.20 +
   47.21 + <tr>
   47.22 +  <th>
   47.23 +    Description
   47.24 +  </th>
   47.25 +  <td>
   47.26 +   <dtml-var description>
   47.27 +  </td>
   47.28 + </tr>
   47.29 +
   47.30 + <tr>
   47.31 +  <th>
   47.32 +   Format
   47.33 +  </th>
   47.34 +  <td>
   47.35 +   <input type="radio" name="text_format" value="structured-text"
   47.36 +          id="cb_structuredtext"
   47.37 +          <dtml-if "text_format=='structured-text'">checked</dtml-if> />
   47.38 +          <label for="cb_structuredtext">structured-text</label>
   47.39 +   <input type="radio" name="text_format" value="html"
   47.40 +          id="cb_html"
   47.41 +          <dtml-if "text_format=='html'">checked</dtml-if> />
   47.42 +	  <label for="cb_html">html</label>
   47.43 +   <input type="radio" name="text_format" value="plain"
   47.44 +          id="cb_plain"
   47.45 +          <dtml-if "text_format=='plain'">checked</dtml-if> />
   47.46 +          <label for="cb_plain">plain text</label>
   47.47 +  </td>
   47.48 + </tr>
   47.49 +
   47.50 + <tr>
   47.51 +  <th> Upload </th>
   47.52 +  <td>
   47.53 +   <input type="file" name="file" size="25">
   47.54 +  </td>
   47.55 + </tr>
   47.56 +
   47.57 + <tr>
   47.58 +  <th class="TextField"> Edit </th>
   47.59 +  <td class="TextField">
   47.60 +   <textarea name="text:text"
   47.61 +             rows="20" cols="80"><dtml-var text html_quote></textarea>
   47.62 +  </td>
   47.63 + </tr>
   47.64 +
   47.65 + <tr>
   47.66 +  <td> <br> </td>
   47.67 +  <td>
   47.68 +    <input type="submit" value=" Change ">
   47.69 +  </td>
   47.70 + </tr>
   47.71 +</table>
   47.72 +</form>
   47.73 +
   47.74 +<dtml-var manage_page_footer>
    48.1 new file mode 100644
    48.2 --- /dev/null
    48.3 +++ b/dtml/zmi_editLink.dtml
    48.4 @@ -0,0 +1,26 @@
    48.5 +<dtml-var manage_page_header>
    48.6 +<dtml-var manage_tabs>
    48.7 +
    48.8 +<h2>Edit &dtml-getId;</h2>
    48.9 +
   48.10 +<form action="manage_editLink" method="post">
   48.11 +<table>
   48.12 + <tr>
   48.13 +  <th>
   48.14 +    Remote Url
   48.15 +  </th>
   48.16 +  <td>
   48.17 +   <input type="text" name="remote_url" value="<dtml-var remote_url html_quote>" size="30">
   48.18 +  </td>
   48.19 + </tr>
   48.20 +
   48.21 + <tr>
   48.22 +  <td> <br> </td>
   48.23 +  <td>
   48.24 +    <input type="submit" value=" Change ">
   48.25 +  </td>
   48.26 + </tr>
   48.27 +</table>
   48.28 +</form>
   48.29 +
   48.30 +<dtml-var manage_page_footer>
    49.1 new file mode 100644
    49.2 --- /dev/null
    49.3 +++ b/dtml/zmi_metadata.dtml
    49.4 @@ -0,0 +1,114 @@
    49.5 +<dtml-var manage_page_header>
    49.6 +<dtml-var manage_tabs>
    49.7 +
    49.8 +<h2>Standard Resource Metadata </h2>
    49.9 +
   49.10 +
   49.11 +<dtml-let effectiveString="effective_date and effective_date.ISO() or 'None'"
   49.12 +          expirationString="expiration_date and expiration_date.ISO() or 'None'"
   49.13 +>
   49.14 +
   49.15 +<form action="manage_editMetadata" method="post">
   49.16 +<table>
   49.17 +
   49.18 + <tr valign="top">
   49.19 +  <th align="right"> Identifier
   49.20 +  </th>
   49.21 +  <td colspan="3"> <dtml-var Identifier>
   49.22 +  </td>
   49.23 + </tr>
   49.24 +
   49.25 + <tr valign="top">
   49.26 +  <th align="right"> Title
   49.27 +  </th>
   49.28 +  <td colspan="3">
   49.29 +   <input type="text"
   49.30 +          name="title"
   49.31 +          value="&dtml-Title;"
   49.32 +          size="65">
   49.33 +  </td>
   49.34 + </tr>
   49.35 +
   49.36 + <tr valign="top">
   49.37 +  <th align="right"> Description
   49.38 +  </th>
   49.39 +  <td colspan="3">
   49.40 +   <textarea name="description:text" rows="5"
   49.41 +             cols="65">&dtml-Description;</textarea>
   49.42 +  </td>
   49.43 + </tr>
   49.44 +
   49.45 + <tr valign="top">
   49.46 +  <th align="right"> Subject
   49.47 +  </th>
   49.48 +  <td>
   49.49 +   <textarea name="subject:lines" rows="5"
   49.50 +             cols="30"><dtml-in Subject><dtml-var sequence-item>
   49.51 +</dtml-in></textarea>
   49.52 +  </td>
   49.53 +  <th align="right"> Contributors
   49.54 +  </th>
   49.55 +  <td>
   49.56 +   <textarea name="contributors:lines" rows="5"
   49.57 +             cols="30"><dtml-in Contributors><dtml-var sequence-item>
   49.58 +</dtml-in></textarea>
   49.59 +  </td>
   49.60 + </tr>
   49.61 +
   49.62 + <tr valign="top">
   49.63 +  <th align="right"> Creation Date
   49.64 +  </th>
   49.65 +  <td> <dtml-var CreationDate>
   49.66 +  </td>
   49.67 +  <th align="right"> Last Modified Date
   49.68 +  </th>
   49.69 +  <td> <dtml-var ModificationDate>
   49.70 +  </td>
   49.71 + </tr>
   49.72 +
   49.73 + <tr valign="top">
   49.74 +  <th align="right"> Effective Date
   49.75 +  </th>
   49.76 +  <td> <input type="text" name="effective_date"
   49.77 +                          value="&dtml-effectiveString;">
   49.78 +  </td>
   49.79 +  <th align="right"> Expiration Date
   49.80 +  </th>
   49.81 +  <td> <input type="text" name="expiration_date"
   49.82 +                          value="&dtml-expirationString;">
   49.83 +  </td>
   49.84 + </tr>
   49.85 +
   49.86 + <tr valign="top">
   49.87 +  <th align="right"> Format
   49.88 +  </th>
   49.89 +  <td> <input type="text" name="format" value="&dtml-Format;">
   49.90 +  </td>
   49.91 + </tr>
   49.92 +
   49.93 + <tr valign="top">
   49.94 +  <th align="right"> Language
   49.95 +  </th>
   49.96 +  <td> <input type="text" name="language" value="&dtml-Language;">
   49.97 +  </td>
   49.98 + </tr>
   49.99 +
  49.100 + <tr valign="top">
  49.101 +  <th align="right"> Rights
  49.102 +  </th>
  49.103 +  <td> <input type="text" name="rights" value="&dtml-Rights;">
  49.104 +  </td>
  49.105 + </tr>
  49.106 +
  49.107 + <tr valign="top">
  49.108 +  <td> <br> </td>
  49.109 +  <td>
  49.110 +   <input type="submit" value=" Change ">
  49.111 +  </td>
  49.112 + </tr>
  49.113 +</table>
  49.114 +</form>
  49.115 +
  49.116 +</dtml-let>
  49.117 +
  49.118 +<dtml-var manage_page_footer>
    50.1 new file mode 100644
    50.2 --- /dev/null
    50.3 +++ b/exceptions.py
    50.4 @@ -0,0 +1,58 @@
    50.5 +##############################################################################
    50.6 +#
    50.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
    50.8 +#
    50.9 +# This software is subject to the provisions of the Zope Public License,
   50.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   50.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   50.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   50.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   50.14 +# FOR A PARTICULAR PURPOSE.
   50.15 +#
   50.16 +##############################################################################
   50.17 +""" CMFDefault product exceptions.
   50.18 +
   50.19 +$Id: exceptions.py 36896 2005-04-05 15:17:18Z yuppie $
   50.20 +"""
   50.21 +
   50.22 +from AccessControl import ModuleSecurityInfo
   50.23 +security = ModuleSecurityInfo('Products.CMFDefault.exceptions')
   50.24 +
   50.25 +security.declarePublic('AccessControl_Unauthorized')
   50.26 +from Products.CMFCore.exceptions import AccessControl_Unauthorized
   50.27 +
   50.28 +security.declarePublic('BadRequest')
   50.29 +from Products.CMFCore.exceptions import BadRequest
   50.30 +
   50.31 +security.declarePublic('CopyError')
   50.32 +from Products.CMFCore.exceptions import CopyError
   50.33 +
   50.34 +security.declarePublic('ResourceLockedError')
   50.35 +from Products.CMFCore.exceptions import ResourceLockedError
   50.36 +
   50.37 +security.declarePublic('zExceptions_Unauthorized')
   50.38 +from Products.CMFCore.exceptions import zExceptions_Unauthorized
   50.39 +
   50.40 +
   50.41 +security.declarePublic('EditingConflict')
   50.42 +class EditingConflict(Exception):
   50.43 +    """ Editing conflict error.
   50.44 +    """
   50.45 +
   50.46 +
   50.47 +security.declarePublic('DiscussionNotAllowed')
   50.48 +class DiscussionNotAllowed(Exception):
   50.49 +    """ Discussion not allowed error.
   50.50 +    """
   50.51 +
   50.52 +
   50.53 +security.declarePublic('IllegalHTML')
   50.54 +class IllegalHTML(ValueError):
   50.55 +    """ Illegal HTML error.
   50.56 +    """
   50.57 +
   50.58 +
   50.59 +security.declarePublic('MetadataError')
   50.60 +class MetadataError(Exception):
   50.61 +    """ Metadata error.
   50.62 +    """
    51.1 new file mode 100644
    51.2 --- /dev/null
    51.3 +++ b/help/ActorDefinitions.stx
    51.4 @@ -0,0 +1,61 @@
    51.5 +CMF Actors
    51.6 +
    51.7 +  **Site Manager** --
    51.8 +    This actor is responsible for implementing site policies
    51.9 +    such as security, workflow associations, metadata and
   51.10 +    syndication policies. The Site Manager is also responsible
   51.11 +    for the overall organizational structure of the site.  See
   51.12 +      "Actor: Site Manager":Actor_SiteManager.
   51.13 +
   51.14 +  **Membership Manager** --
   51.15 +    This actor is responsible for managing who has access to a
   51.16 +    site (particularly back-end line of business users), and
   51.17 +    controls the privileges and properties of users.  See
   51.18 +    "Actor: Membership Manager":Actor_MembershipManager.
   51.19 +
   51.20 +  **Site Developer** --
   51.21 +    This actor is responsible for implementing new
   51.22 +    functionality for a site and making changes to existing
   51.23 +    site capabilities. This is a "programmer" type of role, and
   51.24 +    users acting the Site Developer capacity are technical
   51.25 +    people.  See "Actor: Site Developer":Actor_SiteDeveloper.
   51.26 +
   51.27 +  **Add-on Developer** --
   51.28 +    This actor is responsible for implementing new
   51.29 +    functionality that is suitable for distribution to one or
   51.30 +    more sites. **  See "Actor: Add-On
   51.31 +    Developer":Actor_AddOnDeveloper.
   51.32 +
   51.33 +  **Site Designer** --
   51.34 +    The Site Designer is responsible for producing and
   51.35 +    maintaining the "look and feel" of a site. This includes
   51.36 +    graphics, layout, navigation and other human factors.  See
   51.37 +    "Actor: Site Designer":Actor_SiteDesigner.
   51.38 +
   51.39 +  **Workflow Designer** --
   51.40 +    The Workflow Designer is responsible for defining new
   51.41 +    workflows and customizing existing workflows to meet
   51.42 +    business goals.  See "Actor: Workflow
   51.43 +    Designer":Actor_WorkflowDesigner.
   51.44 +
   51.45 +  **Content Creator** --
   51.46 +    Content Creators are responsible for producing and
   51.47 +    maintaining the actual content of a site.  See "Actor:
   51.48 +    Content Creator":Actor_ContentCreator.
   51.49 +
   51.50 +  **Reviewer** --
   51.51 +    This actor is responsible for ensuring the quality and
   51.52 +    correctness of site content.  See "Actor:
   51.53 +    Reviewer":Actor_Reviewer.
   51.54 +
   51.55 +  **Site Visitor** --
   51.56 +    A Site Visitor is an "end user" of the site. The visitor
   51.57 +    may or may not have an identity known to the system.
   51.58 +    Visitors with a known identity are referred to as "Members"
   51.59 +    of the site, and often can do more on a site than visitors
   51.60 +    without a known identity ("Guests"). Member visitors often
   51.61 +    have a participatory role on the site. Site Visitors have
   51.62 +    some general goals that are applicable to most sites, but
   51.63 +    many of the specific goals and expectations of Site
   51.64 +    Visitors are dependent upon the specific CMF site.  See
   51.65 +    "Actor: Site Visitor":Actor_SiteVisitor.
    52.1 new file mode 100644
    52.2 --- /dev/null
    52.3 +++ b/help/Actor_ContentCreator.stx
    52.4 @@ -0,0 +1,59 @@
    52.5 +Content Creator Goals
    52.6 +
    52.7 +  * Make information available to the end users of the site.
    52.8 +
    52.9 +    - "Create a content object":CreateNewContent
   52.10 +
   52.11 +      o Add Content form with a slightly more involved
   52.12 +        description of each content type that comes with the CMF.
   52.13 +
   52.14 +      o Next form in succession is the "Standard Resource
   52.15 +        Metadata" form which is now a separate use case.  Since
   52.16 +        this is a succession of three steps ("Add Content",
   52.17 +        "Metadata", "Edit") "Define content metadata" use case
   52.18 +        should be merged in with this one.
   52.19 +
   52.20 +      o "Edit" form.
   52.21 +
   52.22 +    - **XXX** "Define content metadata":ChangeMetadata
   52.23 +
   52.24 +      This should mainly be pointers back to "Create a content object".  
   52.25 +
   52.26 +    - "Submit content for publication":SubmitContentForPublication
   52.27 +
   52.28 +      o Brief discussion of workflow with references back to
   52.29 +        workflow use cases. 
   52.30 +
   52.31 +      o Submit for the default workflow
   52.32 +
   52.33 +  * Ensure that information is up-to-date and accurate.
   52.34 +
   52.35 +    - "View personally authored content":ViewMyContent
   52.36 +
   52.37 +    - "Update existing content":ChangeContent
   52.38 +
   52.39 +    - "Remove unneeded content":RemoveContent
   52.40 +
   52.41 +    - "Undo changes to content":UndoChanges
   52.42 +
   52.43 +
   52.44 +  * Improve content quality over time using end user
   52.45 +          feedback.
   52.46 +
   52.47 +    - **XXX** "Make a content object discussable":EnableDiscussion
   52.48 +
   52.49 +          *Note:  this is not a per-object option in stock CMF.*
   52.50 +
   52.51 +  * Collaborate with other content creators.
   52.52 +
   52.53 +    - "Give local roles to other users":ManageLocalRoles
   52.54 +
   52.55 +
   52.56 +  * Modify content organization to improve maintainability
   52.57 +          or navigation.
   52.58 +
   52.59 +    - "Add content folders":AddContentFolders
   52.60 +
   52.61 +    - "Move / copy content between folders":MoveCopyContent
   52.62 +
   52.63 +    - "Rename content object":RenameContent
    53.1 new file mode 100644
    53.2 --- /dev/null
    53.3 +++ b/help/Actor_MembershipManager.stx
    53.4 @@ -0,0 +1,17 @@
    53.5 +Membership Manager Goals
    53.6 +
    53.7 +  * Empower many users to collaborate on content production
    53.8 +
    53.9 +    - **XXX** "Add a new member to the site using a
   53.10 +       standard user folder":AddMemberToUserFolder
   53.11 +        
   53.12 +  * Delegate responsibilities to site members
   53.13 +
   53.14 +    - **XXX** "Change member information and abilities":ChangeMemberInformation
   53.15 +
   53.16 +  * Ensure that only appropriate users have access to the site
   53.17 +
   53.18 +    - **XXX** "Browse member roster":BrowseMemberRoster
   53.19 +
   53.20 +    - **XXX** "Remove a member from the site":RemoveMemberFromSite
   53.21 +
    54.1 new file mode 100644
    54.2 --- /dev/null
    54.3 +++ b/help/Actor_Reviewer.stx
    54.4 @@ -0,0 +1,22 @@
    54.5 +Reviewer Goals
    54.6 +
    54.7 +  * Collaborate with content creators to ensure the quality and 
    54.8 +          timeliness of site content.
    54.9 +
   54.10 +    - "Browse content submitted for review and
   54.11 +       publication":BrowseSubmittedForReview
   54.12 +
   54.13 +       o Brief description of the Action box, with pointers back to
   54.14 +         where this is set up in the skin.
   54.15 +
   54.16 +       o Pending lists
   54.17 +
   54.18 +    - "Approve content for publication":ApproveForPublication
   54.19 +
   54.20 +        This should mainly be a pointer back into "Browse content
   54.21 +        submitted for review and publication"
   54.22 +
   54.23 +  * React quickly to resolve issues with published content.
   54.24 +
   54.25 +    - "Remove content from public site":UnpublishContent
   54.26 +
    55.1 new file mode 100644
    55.2 --- /dev/null
    55.3 +++ b/help/Actor_SiteDesigner.stx
    55.4 @@ -0,0 +1,44 @@
    55.5 +Site Designer Goals
    55.6 +
    55.7 +  * Provide an integrated look and feel for site content.
    55.8 +
    55.9 +    - **XXX** "Create new skin for the site":CreateNewSkin
   55.10 +
   55.11 +      o portal_skins Properties tab.  Discussion of Layers and their
   55.12 +        order of precedence.  
   55.13 +            
   55.14 +      o Creating a new layer.  Difference between file system layers
   55.15 +        vs TTW layers.  Don't document actually creating a file system
   55.16 +        layer, this should likely be a separate use case that
   55.17 +        refers back to this one.
   55.18 +
   55.19 +      o The theory behind customizing methods, but don't refer to
   55.20 +        specific layers or specific methods/images.
   55.21 + 
   55.22 +    - **XXX** "Modify skin appearance":ChangeSkinLookAndFeel
   55.23 +
   55.24 +      o Brief description of customizing methods.  Refer back to
   55.25 +        "Create new skin for the site".
   55.26 +
   55.27 +      o Document each layer that deals with appearance with a
   55.28 +        description of each constituent object (method, image, ...)
   55.29 + 
   55.30 +  * Give end users an effective way to navigate the site.
   55.31 +
   55.32 +    - **XXX** "Change skin behavior":ChangeSkinBehavior
   55.33 +
   55.34 +      o Brief description of customizing methods.  Refer back to
   55.35 +        "Create new skin for the site description".
   55.36 +
   55.37 +      o Document each layer that deals with behavior with a
   55.38 +        description of each method.
   55.39 + 
   55.40 +  * Keep the site fresh and interesting for end users.
   55.41 +
   55.42 +    - **XXX** "Change the default skin for the site":ChangeDefaultSiteSkin
   55.43 +
   55.44 +      o Brief description of skins.  Refer back to other skin use
   55.45 +        cases. 
   55.46 +
   55.47 +      o portal_skin Properties tab, bottom half.  Very simple form.
   55.48 +
    56.1 new file mode 100644
    56.2 --- /dev/null
    56.3 +++ b/help/Actor_SiteManager.stx
    56.4 @@ -0,0 +1,40 @@
    56.5 +Site Manager Goals
    56.6 +
    56.7 +  * Provide an online collaboration environment for an organization
    56.8 +    or community.
    56.9 +
   56.10 +    - "Create a CMF site":CreateCMFSite 
   56.11 +
   56.12 +    - "Configure CMF site":ConfigureCMFSite 
   56.13 +
   56.14 +  * Maintain an overall site structure and organization.
   56.15 +
   56.16 +    - "Create CMF Folder":CreateCMFFolder 
   56.17 +
   56.18 +    - "Create CMF Topic":CreateCMFTopic 
   56.19 +
   56.20 +    - **XXX** "Configure CMF Topic":ConfigureCMFTopic 
   56.21 +
   56.22 +    - **XXX** "Configure what types of content can be created by
   56.23 +       members":ConfigureAllowedContentTypes 
   56.24 +
   56.25 +  * Implement security policies for the site.
   56.26 +
   56.27 +    - **XXX** "Configure security for a content object
   56.28 +       or folder":ConfigureObjectSecurity 
   56.29 +
   56.30 +  * Implement workflow policies for the site.
   56.31 +
   56.32 +    - **XXX** "Associate a workflow with a content
   56.33 +       type":AssociateWorkflowWithContentType 
   56.34 +
   56.35 +  * Implement metadata policies for the site.
   56.36 +
   56.37 +    - **XXX** "Configure required metadata for a content
   56.38 +       type":ConfigureRequiredMetadata 
   56.39 +
   56.40 +  * Implement syndication policies for the site.
   56.41 +
   56.42 +    - **XXX** "Configure what content is released via
   56.43 +       syndication":ConfigureContentSyndication 
   56.44 +
    57.1 new file mode 100644
    57.2 --- /dev/null
    57.3 +++ b/help/Actor_SiteVisitor.stx
    57.4 @@ -0,0 +1,30 @@
    57.5 +Site Vistor Goals
    57.6 +
    57.7 +  * Participate in the community or operations of the site
    57.8 +
    57.9 +    - "Become a member of the site":BecomeAMember
   57.10 +
   57.11 +    - "Log into the site":LoginAsMember
   57.12 +
   57.13 +    - "Browse community news":BrowseNewsItems
   57.14 +
   57.15 +    - **XXX** "Submit a news item":SubmitNewsItem
   57.16 +
   57.17 +      o Not a stock use case for anonymous visitors;  see
   57.18 +        "Create Content":CreateNewContent and "Submit
   57.19 +        Content":SubmitContentForPublication for Content
   57.20 +        Creator's take.
   57.21 +
   57.22 +  * Quickly find content that is important to him
   57.23 +
   57.24 +    - "Browse the site homepage":BrowseCMFSiteHomepage
   57.25 +
   57.26 +    - **XXX** "Browse a portal topic":BrowsePortalTopic
   57.27 +
   57.28 +    - "Search site for information":SearchCMFSite
   57.29 +
   57.30 +  * Improve relevance and usability of site information
   57.31 +
   57.32 +    - "Configure personalization options":ConfigurePersonalization
   57.33 +
   57.34 +    - "Provide feedback on content":ProvideFeedback
    58.1 new file mode 100644
    58.2 --- /dev/null
    58.3 +++ b/help/AddContentFolders.stx
    58.4 @@ -0,0 +1,48 @@
    58.5 +Use Case: Add content folders
    58.6 +
    58.7 +  Actor
    58.8 +
    58.9 +    - Content Creator
   58.10 +
   58.11 +  Overview
   58.12 +
   58.13 +    As with directories on a filesystem, foldersin a CMF Site allow
   58.14 +    Content Creators to partition their content into manageable groups.
   58.15 +
   58.16 +  Assumptions
   58.17 +
   58.18 +    - Content Creator has logged into the CMF (see "Login to the
   58.19 +      Site":LoginAsMember).
   58.20 +
   58.21 +  Procedure
   58.22 +
   58.23 +    1.  Navigate to the folder in which you would like to create sub-folders.
   58.24 +
   58.25 +    2.  In the "Folder contents" view of the folder, select the "New" button.
   58.26 +
   58.27 +    3.  From the list of addable portal types, select "Folder" by clicking
   58.28 +        the adjacent radio button.  Supply an ID [1] for the new folder in the
   58.29 +        input field at the bottom of the page, and click the "Add" button.
   58.30 +
   58.31 +    4.  The system will create the new folder using the ID you supplied,
   58.32 +        and present you with a form for editing the folder's properties.
   58.33 +
   58.34 +    5.  Supply appropriate values as follows:
   58.35 +
   58.36 +        **Title** --  a "human-readable" title for the folder.
   58.37 +
   58.38 +        **Description** -- a brief paragraph summarizing the use to which
   58.39 +          the folder is put.
   58.40 +
   58.41 +    6.  Click the "Change" button.  The system will update the folder's
   58.42 +        metadata using the values you supply.
   58.43 +
   58.44 +  Notes
   58.45 +
   58.46 +  ..[1] Don't confuse the folder's ID with the its Title. ID's cannot
   58.47 +        contain special characters (e.g., comma, asterisk, brackets,
   58.48 +        parentheses, etc.)  A good practise is not to use spaces in
   58.49 +        an ID either. The ID is used in the url to reach the folder's
   58.50 +        content, so any character which is not allowed in a URI is not
   58.51 +        allowed in the id (see: "URI RFC",
   58.52 +        http://www.ietf.org/rfc/rfc2396.txt).
    59.1 new file mode 100644
    59.2 --- /dev/null
    59.3 +++ b/help/ApproveForPublication.stx
    59.4 @@ -0,0 +1,37 @@
    59.5 +Use Case: Approve content for publication
    59.6 +
    59.7 +  Actor
    59.8 +
    59.9 +    - Reviewer
   59.10 +
   59.11 +  Overview
   59.12 +
   59.13 +    The Reviewer's job is to enforce the site's policies with respect
   59.14 +    to the quality and appropriateness of content published by Content
   59.15 +    Creators.
   59.16 +
   59.17 +  Assumptions
   59.18 +
   59.19 +    - Reviewer has logged into the CMF (see "Login to the
   59.20 +      Site":LoginAsMember).
   59.21 +    
   59.22 +    - Content has been submitted for review (see "Submit content for
   59.23 +      publication":SubmitContentForPublication).
   59.24 +      
   59.25 +    - Reviewer has completed the Use Case:  "Browse for content submitted
   59.26 +      for review and publication":BrowseSubmittedForReview.
   59.27 +
   59.28 +  Procedure
   59.29 +
   59.30 +    1.  Select the item from the list of content pending review.
   59.31 +
   59.32 +    2.  From the actions box, select the 'Publish' link.
   59.33 +
   59.34 +    3.  Enter appropriate comments.
   59.35 +
   59.36 +    4.  Select the Publish this item button.
   59.37 +
   59.38 +    5.  The item has been published.
   59.39 +
   59.40 +    6.  You can now "Browse for content submitted for review and
   59.41 +        publication":BrowseSubmittedForReview to repeat this process.
    60.1 new file mode 100644
    60.2 --- /dev/null
    60.3 +++ b/help/BecomeAMember.stx
    60.4 @@ -0,0 +1,42 @@
    60.5 +Use Case: Become A Site Member
    60.6 +
    60.7 +  Actor
    60.8 +
    60.9 +    - Site Visitor
   60.10 +
   60.11 +  Overview
   60.12 +
   60.13 +    Becoming a member of a site allows you to access the additional 
   60.14 +    services of the site. Often this includes a personal online 
   60.15 +    work area (your "desktop"), the ability create and submit your 
   60.16 +    own content for publication and the ability to personalize the
   60.17 +    look and behavior of the site to better meet your needs. 
   60.18 +
   60.19 +    Note that different sites have different purposes, and the specific
   60.20 +    services available to site members depends on the choices of the 
   60.21 +    site administrators. By default, a CMF site provides members with a
   60.22 +    private "desktop", the ability to create certain types of basic 
   60.23 +    content and the ability to select the visual style of the site that
   60.24 +    they see.
   60.25 +
   60.26 +  Procedure
   60.27 +
   60.28 +    1. To become a member of a site, visit the site homepage and click on 
   60.29 +      the "Join" link in the menu located on the left side of the page.
   60.30 +
   60.31 +    2. Clicking the "Join" link will take you to a form. Complete
   60.32 +      the fields on the form and click the "Register" button to
   60.33 +      become a registered member of the site.
   60.34 +
   60.35 +    3. The exact information required by the registration form will
   60.36 +      vary from site to site. The form for a default CMF site
   60.37 +      requires visitors to provide at least a login name, a
   60.38 +      password and a valid email address to become a member. The
   60.39 +      default form also gives you an option to have the password
   60.40 +      you provided at registration time emailed to you for future
   60.41 +      reference.
   60.42 +
   60.43 +    4. After submitting the member registration form, you should
   60.44 +      see a page informing you that you have successfully been
   60.45 +      registered as a site member. This page also provides a link
   60.46 +      that you can use to log into the site immediately.
    61.1 new file mode 100644
    61.2 --- /dev/null
    61.3 +++ b/help/BrowseCMFSiteHomepage.stx
    61.4 @@ -0,0 +1,29 @@
    61.5 +Use Case:  Browse the site homepage
    61.6 +
    61.7 +  Actor
    61.8 +
    61.9 +    - Site Visitor
   61.10 +
   61.11 +  Assumptions
   61.12 +
   61.13 +    - Site Visitor has already navigated to a page within the CMF
   61.14 +      site[1].
   61.15 +
   61.16 +  Procedure
   61.17 +
   61.18 +    1.  Selecting the 'home' link or the site logo
   61.19 +        from the top navigation bar.
   61.20 +
   61.21 +    2.  Browse the content published there (e.g. 10 most recent
   61.22 +        News Announcements, etc.)
   61.23 +
   61.24 +  Notes
   61.25 +
   61.26 +  ..[1] Site Visitors may get to the site initially through several
   61.27 +        mechanisms:
   61.28 +
   61.29 +          - Links on another site, such as a search engine
   61.30 +
   61.31 +          - Mailed URLs
   61.32 +
   61.33 +          - Typing the URL directly
    62.1 new file mode 100644
    62.2 --- /dev/null
    62.3 +++ b/help/BrowseNewsItems.stx
    62.4 @@ -0,0 +1,26 @@
    62.5 +Use Case:  Browse community news
    62.6 +
    62.7 +  Actor
    62.8 +
    62.9 +    - Site Visitor
   62.10 +
   62.11 +  Overview
   62.12 +
   62.13 +    Visitors to a CMF Site will typically return to the site after
   62.14 +    an initial visit only if the site's content was interesting or
   62.15 +    valuable to them.  They will return often to the site only if
   62.16 +    they perceive that this interesting and valuable content is
   62.17 +    being frequently updated.  This use case deals with the most
   62.18 +    common "dynamic" feature of a CMF Site:  its list of published
   62.19 +    News Items.
   62.20 +
   62.21 +  Procedure
   62.22 +
   62.23 +    1.  Select the 'news' link from the navigation bar.  The system
   62.24 +        will display the ten most recent News Items, sorted in
   62.25 +        descending date order.  If there are more than ten
   62.26 +        published News Items, the system will display a link to
   62.27 +        the next batch at the bottom of the page.
   62.28 +
   62.29 +    2.  Navigate between batches of News Items by clicking the
   62.30 +        "10 older artcles" and "10 newer articles" links.
    63.1 new file mode 100644
    63.2 --- /dev/null
    63.3 +++ b/help/BrowseSubmittedForReview.stx
    63.4 @@ -0,0 +1,26 @@
    63.5 +Use Case:  Browse for content submitted for review and publication
    63.6 +
    63.7 +  Actor
    63.8 +