import os, string from Acquisition import aq_base from zExceptions import BadRequest from Products.CMFCore.permissions import AccessInactivePortalContent, \ DeleteObjects, ListFolderContents, ManagePortal, ModifyPortalContent, View from Products.CMFCore.utils import getToolByName from Products.CMFCore.Expression import Expression from Products.CMFPlone.migrations.migration_util import installOrReinstallProduct, \ safeGetMemberDataTool, safeEditProperty, cleanupSkinPath from Products.CMFCore.DirectoryView import createDirectoryView from alphas import convertPloneFTIToCMFDynamicViewFTI from Products.CMFPlone.PloneTool import AllowSendto from Products.CMFPlone.migrations.v2_1.alphas import migrateResourceRegistries def alpha2_beta1(portal): """2.1-alpha2 -> 2.1-beta1 """ out = [] reindex = 0 #Make object paste action work with all default pages. fixObjectPasteActionForDefaultPages(portal, out) # Make batch action a toggle by using a pair of actions fixBatchActionToggle(portal, out) # Update the 'my folder' action to not use folder_contents fixMyFolderAction(portal, out) # Migrate ResourceRegistries # This is also done in alpha migrations, but that was introduced later, so # it has to be called here as well or migrations from alphas might break. migrateResourceRegistries(portal, out) # Bring ploneRTL back to the nearly-top of the stack reorderStylesheets(portal, out) # Grant Access inactive portal content to Owner allowOwnerToAccessInactiveContent(portal, out) # Add criteria to News and Events topics to restrict to published restrictNewsTopicToPublished(portal, out) restrictEventsTopicToPublished(portal, out) # Install new login skins and scripts installLogin(portal, out) # Install kupu installKupu(portal, out) # Add the stylesheets for font size the selector addFontSizeStylesheets(portal, out) # Add cssQuery.js addCssQueryJS(portal, out) # Exchange plone_menu.js with dropdown.js exchangePloneMenuWithDropDown(portal, out) # Remove plone prefix of stylesheet files removePlonePrefixFromStylesheets(portal, out) # Convert navtree whitelist to blacklist convertNavTreeWhitelistToBlacklist(portal, out) # Add Index for is_default_page reindex += addIsDefaultPageIndex(portal, out) # Add Index for is_foldersh and remove corresponding metadata reindex += addIsFolderishIndex(portal, out) # Add the plone_3rdParty to the skin layers add3rdPartySkinPath(portal, out) # Add deprecated and portlet style sheets addDeprecatedAndPortletStylesheets(portal, out) # Add LiveSearch site property addEnableLivesearchProperty(portal, out) # Add icon for search settings configlet addIconForSearchSettingsConfiglet(portal,out) # CMF 1.5 Cookie Crumbler has new properties sanitizeCookieCrumbler(portal, out) # Fix folderlisting action for portal fixFolderlistingAction(portal, out) # Rebuild catalog if reindex: migtool = getToolByName(portal, 'portal_migration') migtool._needRecatalog = True return out def beta1_beta2(portal): """2.1-beta1 -> 2.1-beta2 """ out = [] # Convert Plone Site FTI to CMFDynamicViewFTI again, for people already on # an alpha or beta. convertPloneFTIToCMFDynamicViewFTI(portal, out) # Fix folderlisting action for portal fixFolderContentsActionAgain(portal, out) # Change the actions on the Portal FTI to category 'object' in order to # get consistent tab ordering across the portal. changePortalActionCategory(portal, out) # Add method aliases for PloneSite addMethodAliasesForPloneSite(portal, out) # Add 'Large Plone Folder' to the list of types not to query for the # navtree updateParentMetaTypesNotToQuery(portal, out) # Fix condition on External Editor action fixExtEditAction(portal, out) # Disable external editor member property by default changeMemberdataExtEditor(portal, out) # Fix Workflow state titles fixWorkflowStateTitles(portal, out) # Remove the text size actions, add contact and accessibility actions, and # move site setup to site_actions changeSiteActions(portal, out) # Remove the oddly located plone_membership plone_setup action removePloneSetupActionFromPortalMembership(portal, out) # Make sure our 'view' alises are (selected layout) fixViewMethodAliases(portal, out) # Make portal root use /edit and /sharing URLs on actions fixPortalEditAndSharingActions(portal, out) # Add CMFUid tools addCMFUidTools(portal, out) # Correct CSS media types fixCSSMediaTypes(portal,out) # Add properties for filtering the navtree/sitemap by workflow state addWFStateFilteringToNavTree(portal, out) # Add icon for navigation settings configlet addIconForNavigationSettingsConfiglet(portal,out) # Be sure that Search and Navigation panels are installed addSearchAndNavigationConfiglets(portal, out) # add formsubmithelpers.js to ResourceRegistries addFormSubmitHelpersJS(portal, out) # Re-Add the memberdata property for visible ids readdVisibleIdsMemberProperty(portal, out) # Add CMF types to blacklists addCMFTypesToSearchBlackList(portal, out) # Convert default page types to whitelist convertDefaultPageTypesToWhitelist(portal, out) # setup new Allow Sendto permission setupAllowSendtoPermission(portal, out) return out def installLogin(portal, out): # register new login scripts jsreg = getToolByName(portal, 'portal_javascripts', None) if jsreg is not None: if not 'login.js' in jsreg.getResourceIds(): jsreg.registerScript('login.js') out.append('Registered login.js') # register login skin st = getToolByName(portal, 'portal_skins') if not hasattr(aq_base(st), 'plone_login'): createDirectoryView(st, os.path.join('CMFPlone', 'skins', 'plone_login')) out.append('Added directory view for plone_login') # add login skin to Plone Default, Plone Tableless skins skins = ['Plone Default', 'Plone Tableless'] selections = st._getSelections() for s in skins: if not selections.has_key(s): continue cleanupSkinPath(portal, s) path = st.getSkinPath(s) path = map(string.strip, string.split(path,',')) if not 'plone_login' in path: if 'cmf_legacy' in path: path.insert(path.index('cmf_legacy'), 'plone_login') else: path.append('plone_login') st.addSkinSelection(s, ','.join(path)) out.append('Added plone_login to %s' % s) # add a property to control whether or not "user name not found" message # should be displayed on login failure propTool = getToolByName(portal, 'portal_properties', None) if propTool is not None: propSheet = getattr(propTool, 'site_properties', None) if propSheet is not None: if not propSheet.hasProperty('verify_login_name'): propSheet.manage_addProperty('verify_login_name', 1, 'boolean') out.append("Added 'verify_login_name' property to site_properties.") def fixObjectPasteActionForDefaultPages(portal, out): """ Fix the object paste action so that paste into default pages pastes into the parent. """ newaction = {'id' : 'paste', 'name' : 'Paste', 'action' : 'python:"%s/object_paste"%(object.isDefaultPageInFolder() and object.getParentNode().absolute_url() or object_url)', 'condition' : 'folder/cb_dataValid', 'permission': View, 'category': 'object_buttons', } exists = False actionsTool = getToolByName(portal, 'portal_actions', None) if actionsTool is not None: new_actions = actionsTool._cloneActions() for action in new_actions: if action.getId() == newaction['id'] and action.category == newaction['category']: exists = True action.setActionExpression(Expression(newaction['action'])) out.append('Modified existing object paste action for folderish default pages') if exists: actionsTool._actions = new_actions else: actionsTool.addAction(newaction['id'], name=newaction['name'], action=newaction['action'], condition=newaction['condition'], permission=newaction['permission'], category=newaction['category'], visible=1) out.append("Added missing object paste action") def fixBatchActionToggle(portal, out): """Fix batch actions so as to function as a toggle """ ACTIONS = ( {'id' : 'batch', 'name' : 'Contents', 'action' : "python:((object.isDefaultPageInFolder() and object.getParentNode().absolute_url()) or folder_url)+'/folder_contents'", 'condition' : "python:portal.portal_membership.checkPermission('View',folder) and folder.displayContentsTab() and object.REQUEST['ACTUAL_URL'] != object.absolute_url() + '/folder_contents'", 'permission': View, 'category' : 'batch', }, {'id' : 'nobatch', 'name' : 'Default view', 'action' : "string:${folder_url}/view", 'condition' : "python:portal.portal_membership.checkPermission('View',folder) and folder.displayContentsTab() and object.REQUEST['ACTUAL_URL'] == object.absolute_url() + '/folder_contents'", 'permission': View, 'category' : 'batch', }, ) actionsTool = getToolByName(portal, 'portal_actions', None) if actionsTool is not None: # update/add actions for newaction in ACTIONS: idx = 0 for action in actionsTool.listActions(): # if action exists, remove and re-add if action.getId() == newaction['id'] \ and action.getCategory() == newaction['category']: actionsTool.deleteActions((idx,)) break idx += 1 actionsTool.addAction(newaction['id'], name=newaction['name'], action=newaction['action'], condition=newaction['condition'], permission=newaction['permission'], category=newaction['category'], visible=1) out.append("Added '%s' contentmenu action to actions tool." % newaction['name']) def fixMyFolderAction(portal, out): """Fix my folder action to point to folder w/o folder_contents """ actionsTool = getToolByName(portal, 'portal_membership', None) if actionsTool is not None: for action in actionsTool.listActions(): if action.getId() == 'mystuff' and action.getCategory() == 'user': action.setActionExpression(Expression('string:${portal/portal_membership/getHomeUrl}')) out.append("Made the 'mystuff' action point to folder listing instead of folder_contents") break def reorderStylesheets(portal, out): """ Fix the position of the ploneRTL and member.css stylesheet After the 'alphas' migration, ploneRTL was at the bottom of the pile - it should be near the top in order to overwrite common plone stuff (which is left-to-right) for right-to-left (hebrew, arabic, etc.) usage. ploneMember.css breaks some stylesheet-combining order, so we're moving it to the bottom of the list. """ qi = getToolByName(portal, 'portal_quickinstaller', None) if qi is not None: if not qi.isProductInstalled('ResourceRegistries'): qi.installProduct('ResourceRegistries', locked=0) cssreg = getToolByName(portal, 'portal_css', None) if cssreg is not None: stylesheet_ids = cssreg.getResourceIds() # Failsafe: first make sure the two stylesheets exist in the list if 'ploneRTL.css' not in stylesheet_ids: cssreg.registerStylesheet('ploneRTL.css', expression="python:object.isRightToLeft(domain='plone')") if 'ploneCustom.css' not in stylesheet_ids: cssreg.registerStylesheet('ploneCustom.css') if 'ploneMember.css' not in stylesheet_ids: cssreg.registerStylesheet('ploneMember.css', expression='not: portal/portal_membership/isAnonymousUser') # Now move 'em cssreg.moveResourceBefore('ploneRTL.css', 'ploneCustom.css') cssreg.moveResourceToTop('ploneMember.css') def addCssQueryJS(portal, out): jsreg = getToolByName(portal, 'portal_javascripts', None) if jsreg is not None: script_ids = jsreg.getResourceIds() # Failsafe: first make sure the stylesheet doesn't exist in the list if 'cssQuery.js' not in script_ids: jsreg.registerScript('cssQuery.js') try: jsreg.moveResourceBefore('cssQuery.js', 'plone_javascript_variables.js') except ValueError: try: jsreg.moveResourceBefore('cssQuery.js', 'register_function.js') except ValueError: # ok put to the top jsreg.moveResourceToTop('cssQuery.js') def exchangePloneMenuWithDropDown(portal, out): qi = getToolByName(portal, 'portal_quickinstaller', None) if qi is not None: if not qi.isProductInstalled('ResourceRegistries'): qi.installProduct('ResourceRegistries', locked=0) jsreg = getToolByName(portal, 'portal_javascripts', None) if jsreg is not None: script_ids = jsreg.getResourceIds() # Failsafe: first make sure the stylesheet doesn't exist in the list if 'dropdown.js' not in script_ids: jsreg.registerScript('dropdown.js') if 'plone_menu.js' in script_ids: jsreg.moveResourceBefore('dropdown.js', 'plone_menu.js') jsreg.unregisterResource('plone_menu.js') elif 'sarissa.js' in script_ids: jsreg.moveResourceBefore('dropdown.js', 'sarissa.js') else: # ok we stay at the bottom pass def removePlonePrefixFromStylesheets(portal, out): out.append("Removing plone prefix from stylesheets.") names = [ ('ploneAuthoring.css', 'authoring.css'), ('ploneBase.css', 'base.css'), ('ploneColumns.css', 'columns.css'), ('ploneDeprecated.css', 'deprecated.css'), ('ploneGenerated.css', 'generated.css'), ('ploneIEFixes.css', 'IEFixes.css'), ('ploneMember.css', 'member.css'), ('ploneMobile.css', 'mobile.css'), ('ploneNS4.css', 'NS4.css'), ('plonePresentation.css', 'presentation.css'), ('plonePrint.css', 'print.css'), ('plonePublic.css', 'public.css'), ('ploneRTL.css', 'RTL.css'), ('ploneTextHuge.css', 'textHuge.css'), ('ploneTextLarge.css', 'textLarge.css'), ('ploneTextSmall.css', 'textSmall.css'), ] cssreg = getToolByName(portal, 'portal_css', None) if cssreg is not None: stylesheet_ids = cssreg.getResourceIds() for old, new in names: if old in stylesheet_ids: if new in stylesheet_ids: # delete the old name cssreg.unregisterResource(old) else: # rename cssreg.renameResource(old, new) else: out.append("No CSSRegistry found.") out.append("Finished removing plone prefix from stylesheets.") def addDeprecatedAndPortletStylesheets(portal, out): out.append("Adding Portlet and Deprecated stylesheets.") cssreg = getToolByName(portal, 'portal_css', None) if cssreg is not None: stylesheet_ids = cssreg.getResourceIds() stylesheets_to_move_after = ('base.css', 'public.css', 'authoring.css', 'member.css') if 'deprecated.css' not in stylesheet_ids: cssreg.registerStylesheet('deprecated.css', media="screen") for style_id in stylesheets_to_move_after: if style_id in stylesheet_ids: # found an existing stylesheet, place the new one below cssreg.moveResourceAfter('deprecated.css', style_id) break # we are done if 'portlets.css' not in stylesheet_ids: cssreg.registerStylesheet('portlets.css', media="screen") cssreg.moveResourceAfter('portlets.css', 'deprecated.css') else: out.append("No CSSRegistry found.") out.append("Finished adding Portlet and Deprecated stylesheets.") def allowOwnerToAccessInactiveContent(portal, out): permission = AccessInactivePortalContent cur_perms=portal.permission_settings(permission)[0] roles = portal.valid_roles() if 'Owner' in roles: cur_allowed = [roles[i] for i in range(len(cur_perms['roles'])) if cur_perms['roles'][i]['checked']] if 'Owner' not in cur_allowed: cur_allowed.append('Owner') acquire = cur_perms['acquire'] and 1 or 0 portal.manage_permission(permission, tuple(cur_allowed), acquire=acquire) out.append('Granted "Access inactive portal content" permission to Owner role') def restrictNewsTopicToPublished(portal, out): news = getattr(portal,'news', None) topic = getattr(news,'news_topic', None) if topic is not None: crit = getattr(topic, 'crit__review_state_ATSimpleStringCriterion', None) if crit is None: state_crit = topic.addCriterion('review_state', 'ATSimpleStringCriterion') state_crit.setValue('published') out.append('Added published criterion to news topic.') else: out.append('News topic already restricted to published.') else: out.append('News topic view not in place!') def restrictEventsTopicToPublished(portal, out): events = getattr(portal,'events', None) topic = getattr(events,'events_topic', None) if topic is not None: crit = getattr(topic, 'crit__review_state_ATSimpleStringCriterion', None) if crit is None: state_crit = topic.addCriterion('review_state', 'ATSimpleStringCriterion') state_crit.setValue('published') out.append('Added published criterion to events topic.') else: out.append('Events topic already restricted to published.') else: out.append('Events topic view not in place!') def installKupu(portal, out): """Quickinstalls Kupu if not installed yet.""" # Kupu is optional try: import Products.kupu except ImportError: pass else: # Kupu is not installed by e.g. tests if 'kupu' in portal.Control_Panel.Products.objectIds(): installOrReinstallProduct(portal, 'kupu', out) # Make kupu the default md = safeGetMemberDataTool(portal) if md and not hasattr(md, 'wysiwyg_editor'): safeEditProperty(md, 'wysiwyg_editor', 'Kupu', 'string') out.append('Set Kupu as default WYSIWYG editor.') def addFontSizeStylesheets(portal, out): """Add the stylesheets for font size the selector.""" cssreg = getToolByName(portal, 'portal_css', None) if cssreg is not None: stylesheet_ids = cssreg.getResourceIds() # Failsafe: first make sure the stylesheets don't exist in the list if 'ploneTextSmall.css' not in stylesheet_ids: cssreg.registerStylesheet('ploneTextSmall.css', media='screen', rel='alternate stylesheet', title='Small Text', rendering='link') if 'ploneRTL.css' in stylesheet_ids: cssreg.moveResourceBefore('ploneTextSmall.css', 'ploneRTL.css') out.append('Added ploneTextSmall.css to CSSRegistry.') if 'ploneTextLarge.css' not in stylesheet_ids: cssreg.registerStylesheet('ploneTextLarge.css', media='screen', rel='alternate stylesheet', title='Large Text', rendering='link') if 'ploneRTL.css' in stylesheet_ids: cssreg.moveResourceBefore('ploneTextLarge.css', 'ploneRTL.css') out.append('Added ploneTextLarge.css to CSSRegistry.') def add3rdPartySkinPath(portal, out): """Add the plone_3rdParty to the skin layers.""" skinsTool = getToolByName(portal, 'portal_skins', None) if skinsTool is not None: skins = ['Plone Default', 'Plone Tableless'] selections = skinsTool._getSelections() for skin in skins: if not selections.has_key(skin): continue cleanupSkinPath(portal, skin) path = skinsTool.getSkinPath(skin) path = map(string.strip, string.split(path,',')) if not 'plone_3rdParty' in path: if 'cmf_legacy' in path: path.insert(path.index('cmf_legacy'), 'plone_3rdParty') else: path.append('plone_3rdParty') skinsTool.addSkinSelection(skin, ','.join(path)) out.append('Added plone_3rdParty to %s.' % skin) def addEnableLivesearchProperty(portal, out): """Adds sitewide config for Livesearch.""" propTool = getToolByName(portal, 'portal_properties', None) if propTool is not None: propSheet = getattr(aq_base(propTool), 'site_properties', None) if propSheet is not None: if not propSheet.hasProperty('enable_livesearch'): propSheet.manage_addProperty('enable_livesearch', 1, 'boolean') out.append("Added 'enable_livesearch' property to site_properties.") def addIconForSearchSettingsConfiglet(portal, out): """Adds an icon for the search settings configlet. """ iconsTool = getToolByName(portal, 'portal_actionicons', None) if iconsTool is not None: for icon in iconsTool.listActionIcons(): if icon.getActionId() == 'SearchSettings': break # We already have the icon else: iconsTool.addActionIcon( category='controlpanel', action_id='SearchSettings', icon_expr='search_icon.gif', title='Search Settings', ) out.append("Added 'search' icon to actionicons tool.") def sanitizeCookieCrumbler(portal, out): """CMF 1.5 Cookie Crumbler handles the require login nonsense just fine.""" cc = getToolByName(portal, 'cookie_authentication', None) if cc is not None: if cc.hasProperty('unauth_page'): cc._updateProperty('auto_login_page', 'login_form') out.append("Set 'Login page ID' of Cookie Crumbler to 'login_form'.") cc._updateProperty('unauth_page', 'insufficient_privileges') out.append("Set 'Failed authorization page ID' of Cookie Crumbler to 'insufficient_privileges'.") def convertNavTreeWhitelistToBlacklist(portal, out): """Makes navtree_properties typesToList typesNotToList with appropriate conversion.""" bl = ['ATBooleanCriterion', 'ATCurrentAuthorCriterion', 'ATPathCriterion', 'ATDateCriteria', 'ATDateRangeCriterion', 'ATListCriterion', 'ATPortalTypeCriterion', 'ATReferenceCriterion', 'ATSelectionCriterion', 'ATSimpleIntCriterion', 'ATSimpleStringCriterion', 'ATSortCriterion', 'Discussion Item', 'Plone Site', 'TempFolder'] types_dict={} propTool = getToolByName(portal, 'portal_properties', None) typesTool = getToolByName(portal, 'portal_types', None) if propTool is not None: propSheet = getattr(aq_base(propTool), 'navtree_properties', None) sitepropSheet = getattr(aq_base(propTool), 'site_properties', None) if propSheet is not None: if propSheet.hasProperty('typesToList'): propSheet.manage_delProperties(['typesToList']) out.append('Removed navtree whitelist') if propSheet.hasProperty('typesNotToList'): propSheet.manage_delProperties(['typesNotToList']) out.append('Removed old navtree blacklist') if sitepropSheet is not None: bl = sitepropSheet.getProperty('types_not_searched', bl) # Let's add the two new criteria to the not searched list as well if 'ATCurrentAuthorCriterion' not in bl: bl= bl + ('ATCurrentAuthorCriterion','ATPathCriterion') sitepropSheet.manage_changeProperties(types_not_searched=bl) out.append('Added new entries to "types_not_searched" site_property') if not propSheet.hasProperty('metaTypesNotToList'): propSheet._setProperty('metaTypesNotToList', bl, 'lines') out.append('Added navtree blacklist') else: # Combine existing values with not searchable types bl2 = propSheet.getProperty('metaTypesNotToList') if isinstance(bl2,basestring): bl2 = [bl2] for i in tuple(bl)+tuple(bl2): types_dict[i] = True propSheet.manage_delProperties(['metaTypesNotToList']) propSheet._setProperty('metaTypesNotToList', types_dict.keys(),'lines') out.append('Updated navtree blacklist') def addIsDefaultPageIndex(portal, out): """Adds the is_default_page FieldIndex.""" catalog = getToolByName(portal, 'portal_catalog', None) if catalog is not None: try: index = catalog._catalog.getIndex('is_default_page') except KeyError: pass else: indextype = index.__class__.__name__ if indextype == 'FieldIndex': return 0 catalog.delIndex('is_default_page') out.append("Deleted %s 'is_default_page' from portal_catalog." % indextype) catalog.addIndex('is_default_page', 'FieldIndex') out.append("Added FieldIndex 'is_default_page' to portal_catalog.") return 1 # Ask for reindexing return 0 # now used in rcs def fixContentActionConditions(portal,out): """Don't use aq_parent in action conditions directly, as it will fail if we don't have permissions on the parent""" ACTIONS = ( {'id' : 'cut', 'name' : 'Cut', 'action' : 'python:"%s/object_cut"%(object.isDefaultPageInFolder() and object.getParentNode().absolute_url() or object_url)', 'condition' : 'python:portal.portal_membership.checkPermission("Delete objects", object.aq_inner.getParentNode()) and portal.portal_membership.checkPermission("Copy or Move", object) and object is not portal and not (object.isDefaultPageInFolder() and object.getParentNode() is portal)', 'permission': DeleteObjects, 'category' : 'object_buttons', }, {'id' : 'paste', 'name' : 'Paste', 'action' : 'python:"%s/object_paste"%((object.isDefaultPageInFolder() or not object.is_folderish()) and object.getParentNode().absolute_url() or object_url)', 'condition' : 'folder/cb_dataValid|nothing', 'permission': View, 'category' : 'object_buttons', }, {'id' : 'delete', 'name' : 'Delete', 'action' : 'python:"%s/object_delete"%(object.isDefaultPageInFolder() and object.getParentNode().absolute_url() or object_url)', 'condition' : 'python:portal.portal_membership.checkPermission("Delete objects", object.aq_inner.getParentNode()) and object is not portal and not (object.isDefaultPageInFolder() and object.getParentNode() is portal)', 'permission': DeleteObjects, 'category' : 'object_buttons', }, {'id' : 'copy', 'name' : 'Copy', 'action' : 'python:"%s/object_copy"%(object.isDefaultPageInFolder() and object.getParentNode().absolute_url() or object_url)', 'condition' : 'python: portal.portal_membership.checkPermission("Copy or Move", object) and object is not portal and not (object.isDefaultPageInFolder() and object.getParentNode() is portal)', 'permission': View, 'category' : 'object_buttons', },) actionsTool = getToolByName(portal, 'portal_actions', None) if actionsTool is not None: # update/add actions for newaction in ACTIONS: idx = 0 for action in actionsTool.listActions(): # if action exists, remove and re-add if action.getId() == newaction['id'] \ and action.getCategory() == newaction['category']: actionsTool.deleteActions((idx,)) break idx += 1 actionsTool.addAction(newaction['id'], name=newaction['name'], action=newaction['action'], condition=newaction['condition'], permission=newaction['permission'], category=newaction['category'], visible=1) out.append("Added '%s' contentmenu action to actions tool." % newaction['name']) def addIsFolderishIndex(portal, out): """Adds the is_folderish FieldIndex and removes the metadata.""" catalog = getToolByName(portal, 'portal_catalog', None) if catalog is not None: if 'is_folderish' in catalog.schema(): catalog.delColumn('is_folderish') out.append("Removed metadata 'is_folderish' from portal_catalog.") try: index = catalog._catalog.getIndex('is_folderish') except KeyError: pass else: indextype = index.__class__.__name__ if indextype == 'FieldIndex': return 0 catalog.delIndex('is_folderish') out.append("Deleted %s 'is_folderish' from portal_catalog." % indextype) catalog.addIndex('is_folderish', 'FieldIndex') out.append("Added FieldIndex 'is_folderish' to portal_catalog.") return 1 # Ask for reindexing return 0 def fixFolderlistingAction(portal, out): """Fixes the folder listing action for portal to make it work properly with the new browser default magic """ typesTool = getToolByName(portal, 'portal_types', None) if typesTool is not None: siteFTI = getattr(typesTool, 'Plone Site', None) if siteFTI is not None: haveFolderListing = False for action in siteFTI.listActions(): if action.getId() == 'folderlisting': action.setActionExpression(Expression('string:${folder_url}/view')) action.condition = '' haveFolderListing = True break if not haveFolderListing: siteFTI.addAction('folderlisting', 'Folder view', 'string:${folder_url}/view', '', 'View', 'folder', visible=0) out.append("Set target expresion of folderlisting action for 'Plone Site' to 'view'") def fixFolderContentsActionAgain(portal, out): """Remove batch toggle actions and replace folder_contents action """ REMOVE_ACTIONS=('batch','nobatch') REMOVE_CATEGORY='batch' ACTIONS = ( {'id' : 'folderContents', 'name' : 'Contents', 'action' : "python:((object.isDefaultPageInFolder() and object.getParentNode().absolute_url()) or folder_url)+'/folder_contents'", 'condition' : "object/displayContentsTab", 'permission': ListFolderContents, 'category' : 'object', }, ) actionsTool = getToolByName(portal, 'portal_actions', None) if actionsTool is not None: # First delete the batch toggle actions idx = 0 del_idxs=[] for action in actionsTool.listActions(): if action.getId() in REMOVE_ACTIONS \ and action.getCategory() == REMOVE_CATEGORY: del_idxs.append(idx) idx += 1 actionsTool.deleteActions(del_idxs) # Now Remove and readd the contents action for newaction in ACTIONS: idx = 0 del_idxs=[] for action in actionsTool.listActions(): # if action exists, remove (including duplicates) if action.getId() == newaction['id'] \ and action.getCategory() in (newaction['category'], 'folder'): del_idxs.append(idx) idx += 1 actionsTool.deleteActions(del_idxs) #Add the action actionsTool.addAction(newaction['id'], name=newaction['name'], action=newaction['action'], condition=newaction['condition'], permission=newaction['permission'], category=newaction['category'], visible=1) out.append("Fixed '%s' action on actions tool." % newaction['name']) def changePortalActionCategory(portal, out): """Change the category for the Plone Site type actions to 'object'""" EDIT_ACTIONS=('view','edit') OLD_CATEGORY='folder' NEW_CATEGORY='object' ttool = getToolByName(portal, 'portal_types', None) if ttool is not None: fti = getattr(ttool, 'Plone Site', None) if fti is not None: current_actions = fti._cloneActions() for action in current_actions: if action.getId() in EDIT_ACTIONS and action.category == OLD_CATEGORY: action.category = 'object' out.append("Changed category of Plone Site %s action to 'object'"%action.getId()) fti._actions = current_actions def addMethodAliasesForPloneSite(portal, out): """Add standard method aliases to Plone Site FTI""" aliases = { '(Default)' : '(dynamic view)', 'view' : '(selected layout)', 'index.html' : '(dynamic view)', 'edit' : 'folder_edit_form', 'properties' : '', 'sharing' : 'folder_localrole_form', 'gethtml' : '', 'mkdir' : '', } ttool = getToolByName(portal, 'portal_types', None) if ttool is not None: fti = getattr(ttool, 'Plone Site', None) if fti is not None: cur_aliases = fti.getMethodAliases() if cur_aliases.get('view', None) != '(selected layout)': fti.setMethodAliases(aliases) out.append("Added method aliases to Plone Site FTI") def updateParentMetaTypesNotToQuery(portal, out): """Add Large Plone Folder to the list of types not to query""" props = getattr(portal, 'portal_properties', None) if props is not None: ntp = getattr(props, 'navtree_properties', None) if ntp is not None: val = ntp.getProperty('parentMetaTypesNotToQuery',None) if val is None: ntp.manage_addProperty('parentMetaTypesNotToQuery', ['Large Plone Folder'], 'lines') out.append("Added missing 'parentMetaTypesNotToQuery' property to navtree_properties") elif 'Large Plone Folder' not in val: ntp.manage_changeProperties(parentMetaTypesNotToQuery=list(val)+['Large Plone Folder']) out.append("Added 'Large Plone Folder' to 'parentMetaTypesNotToQuery' in navtree_properties") def fixCutActionPermission(portal,out): """Use more a reasonable permission for the cut action""" ACTIONS = ( {'id' : 'cut', 'name' : 'Cut', 'action' : 'python:"%s/object_cut"%(object.isDefaultPageInFolder() and object.getParentNode().absolute_url() or object_url)', 'condition' : 'python:portal.portal_membership.checkPermission("Delete objects", object.aq_inner.getParentNode()) and portal.portal_membership.checkPermission("Copy or Move", object) and object is not portal and not (object.getParentNode() is portal and object.isDefaultPageInFolder())', 'permission': DeleteObjects, 'category' : 'object_buttons', }, ) actionsTool = getToolByName(portal, 'portal_actions', None) if actionsTool is not None: # update/add actions for newaction in ACTIONS: idx = 0 for action in actionsTool.listActions(): # if action exists, remove and re-add if action.getId() == newaction['id'] \ and action.getCategory() == newaction['category']: actionsTool.deleteActions((idx,)) break idx += 1 actionsTool.addAction(newaction['id'], name=newaction['name'], action=newaction['action'], condition=newaction['condition'], permission=newaction['permission'], category=newaction['category'], visible=1) out.append("Added '%s' contentmenu action to actions tool." % newaction['name']) def fixExtEditAction(portal,out): """Make the external editor action appear in a reasonable way""" ACTIONS = ( {'id' : 'extedit', 'name' : 'Edit this file in an external application (Requires Zope ExternalEditor installed)', 'action' : 'string:$object_url/external_edit', 'condition' : 'object/externalEditorEnabled', 'permission': ModifyPortalContent, 'category' : 'document_actions' },) actionsTool = getToolByName(portal, 'portal_actions', None) if actionsTool is not None: # update/add actions for newaction in ACTIONS: idx = 0 for action in actionsTool.listActions(): # if action exists, remove and re-add if action.getId() == newaction['id'] \ and action.getCategory() == newaction['category']: actionsTool.deleteActions((idx,)) break idx += 1 actionsTool.addAction(newaction['id'], name=newaction['name'], action=newaction['action'], condition=newaction['condition'], permission=newaction['permission'], category=newaction['category'], visible=1) out.append("Added '%s' action to actions tool." % newaction['name']) def changeMemberdataExtEditor(portal, out): memberdata_tool = safeGetMemberDataTool(portal) if memberdata_tool is not None: safeEditProperty(memberdata_tool, 'ext_editor', 0, 'boolean') out.append("Turned off 'ext_editor' property on portal_memberdata by default.") def fixWorkflowStateTitles(portal, out): """Fix titles for default workflow states""" wftool = getattr(portal, 'portal_workflow', None) wfs = ('plone_workflow','folder_workflow') state_titles = { 'private': 'Private', 'visible': 'Public Draft', 'pending': 'Pending', 'published': 'Published' } for wfid in wfs: wf = getattr(wftool, wfid, None) wf_states = getattr(wf, 'states', None) if wf_states is not None: for state, title in state_titles.items(): wf_state = getattr(wf.states, state, None) if wf_state is not None and wf_state.title != title: wf_state.title = title out.append("Updated workflow titles for state %s"%state) def changeSiteActions(portal, out): """Remove text size actions, add accessibility and contact actions, move site setup to site_actions""" REMOVE_ACTIONS=('small_text','normal_text','large_text') REMOVE_CATEGORY='site_actions' ACTIONS=( {'id': 'accessibility', 'name': 'Accessibility', 'action': 'string:${portal_url}/accessibility-info', 'condition': '', 'permission': View, 'category': 'site_actions', 'visible': 1}, {'id': 'contact', 'name': 'Contact', 'action': 'string:${portal_url}/contact-info', 'condition': '', 'permission': View, 'category': 'site_actions', 'visible': 1}, {'id': 'plone_setup', 'name': 'Site Setup', 'action': 'string:${portal_url}/plone_control_panel', 'condition': '', 'permission': ManagePortal, 'category': 'site_actions', 'visible': 1} ) actionsTool = getToolByName(portal, 'portal_actions', None) if actionsTool is not None: # First delete the text size toggle actions idx = 0 del_idxs=[] for action in actionsTool.listActions(): if action.getId() in REMOVE_ACTIONS \ and action.getCategory() == REMOVE_CATEGORY: del_idxs.append(idx) idx += 1 actionsTool.deleteActions(del_idxs) # Now Remove and readd the contents action for newaction in ACTIONS: idx = 0 del_idxs=[] for action in actionsTool.listActions(): # if action exists, remove (including duplicates from any category) if action.getId() == newaction['id']: del_idxs.append(idx) idx += 1 actionsTool.deleteActions(del_idxs) #Add the action actionsTool.addAction(newaction['id'], name=newaction['name'], action=newaction['action'], condition=newaction['condition'], permission=newaction['permission'], category=newaction['category'], visible=1) out.append("Fixed '%s' action on actions tool." % newaction['name']) def removePloneSetupActionFromPortalMembership(portal, out): """Remove plone_setup action from portal_membership""" REMOVE_ACTIONS=('plone_setup',) actionsTool = getToolByName(portal, 'portal_membership', None) if actionsTool is not None: # First delete the text size toggle actions idx = 0 del_idxs=[] for action in actionsTool.listActions(): if action.getId() in REMOVE_ACTIONS: del_idxs.append(idx) out.append("Removed action %s from portal_membership"%action.getId()) idx += 1 actionsTool.deleteActions(del_idxs) def fixViewMethodAliases(portal, out): """The 'view' method alias should be (selected layout), not (default view), because you should be able to use /view to avoid any default-page being displayed. Fix all ATCT types and Plone Site. """ types = ('Document', 'Event', 'Favorite', 'File', 'Folder', 'Image', 'Link', 'News Item', 'Topic', 'Plone Site') ttool = getToolByName(portal, 'portal_types', None) if ttool is not None: for typeName in types: fti = getattr(ttool, typeName, None) if fti is not None: aliases = fti.getMethodAliases().copy() if aliases.get('view', None) != '(selected layout)': aliases['view'] = '(selected layout)' fti.setMethodAliases(aliases) out.append("Fixed 'view' method alias for %s FTI" % (typeName,)) def fixPortalEditAndSharingActions(portal, out): """The 'edit' and 'sharing' actions of the portal should use the method aliases /edit and /sharing. """ ttool = getToolByName(portal, 'portal_types', None) if ttool is not None: fti = getattr(ttool, 'Plone Site', None) if fti is not None: for action in fti.listActions(): if action.getId() == 'edit': action.setActionExpression('string:${object_url}/edit') out.append('Set portal root edit action to use /edit method alias') elif action.getId() == 'local_roles': action.setActionExpression('string:${object_url}/sharing') out.append('Set portal root sharing action to use /sharing method alias') def addCMFUidTools(portal, out): """Adds CMFUid tools to the plone instance CMFUid is an new CMF core product to handle uids the CMF way. CMF uids are different to AT uuids but can be used in parallel. """ tools = (('Unique Id Annotation Tool', 'portal_uidannotation'), ('Unique Id Generator Tool', 'portal_uidgenerator'), ('Unique Id Handler Tool', 'portal_uidhandler') ) added = [] try: addCMFUidTool = portal.manage_addProduct['CMFUid'].manage_addTool except AttributeError: pass else: for tool, id in tools: try: addCMFUidTool(tool, None) except BadRequest: # already there pass else: tool = getattr(portal, id) tool.title = "CMF %s" % id added.append(id) if added: out.append('Added CMFUid tool(s) %s' % ', '.join(added)) def fixCSSMediaTypes(portal,out): """Fixes some css media types in the Resource Registry, like missing media type or wrong 'presentation' media type """ cssreg = getToolByName(portal, 'portal_css', None) stylesheet_ids = cssreg.getResourceIds() cssmediatypes = [ ('member.css', 'screen'), ('RTL.css', 'screen'), ('presentation.css', 'projection'), ('ploneCustom.css', 'all'), ] changed=[] if cssreg is not None: for stylesheet,cssmediatype in cssmediatypes: if stylesheet in stylesheet_ids: cssresource=cssreg.getResource(stylesheet) cssresource.setMedia(cssmediatype) changed.append(stylesheet) out.append('Set media type for %s to %s' % (stylesheet,cssmediatype)) if changed: out.append('Corrected CSS media types') def addWFStateFilteringToNavTree(portal,out): """ Adds enable_wf_state_filtering and wf_states_to_show properties to the navtree_properties """ props = getToolByName(portal, 'portal_properties', None) propSheet = getattr(props, 'navtree_properties', None) if propSheet is not None: if not propSheet.hasProperty('enable_wf_state_filtering'): propSheet.manage_addProperty('enable_wf_state_filtering', 0, 'boolean') out.append("Added 'enable_wf_state_filtering' property to navtree_properties.") if not propSheet.hasProperty('wf_states_to_show'): propSheet.manage_addProperty('wf_states_to_show', [], 'lines') out.append("Added 'wf_states_to_show' property to navtree_properties.") def addIconForNavigationSettingsConfiglet(portal, out): """Adds an icon for the navigation settings configlet. """ iconsTool = getToolByName(portal, 'portal_actionicons', None) if iconsTool is not None: for icon in iconsTool.listActionIcons(): if icon.getActionId() == 'NavigationSettings': break # We already have the icon else: iconsTool.addActionIcon( category='controlpanel', action_id='NavigationSettings', icon_expr='navigation_icon.gif', title='Navigation Settings', ) out.append("Added 'navigation' icon to actionicons tool.") def addSearchAndNavigationConfiglets(portal, out): """Add the configlets for the search and navigation settings""" controlPanel = getToolByName(portal, 'portal_controlpanel', None) if controlPanel is not None: haveSearch = False haveNavigation = False for configlet in controlPanel.listActions(): if configlet.getId() == 'SearchSettings': haveSearch = True if configlet.getId() == 'NavigationSettings': haveNavigation = True if not haveSearch: controlPanel.registerConfiglet(id = 'SearchSettings', appId = 'Plone', name = 'Search Settings', action = 'string:${portal_url}/prefs_search_form', category = 'Plone', permission = ManagePortal,) out.append("Added search settings to the control panel") if not haveNavigation: controlPanel.registerConfiglet(id = 'NavigationSettings', appId = 'Plone', name = 'Navigation Settings', action = 'string:${portal_url}/prefs_navigation_form', category = 'Plone', permission = ManagePortal,) out.append("Added navigation settings to the control panel") def addFormSubmitHelpersJS(portal, out): """Add formsubmithelpers.js to ResourceRegistries. """ jsreg = getToolByName(portal, 'portal_javascripts', None) if jsreg is not None: if not 'formsubmithelpers.js' in jsreg.getResourceIds(): jsreg.registerScript('formsubmithelpers.js') out.append('Registered formsubmithelpers.js') def setupAllowSendtoPermission(portal, out): """Setup and configure new Allow Sendto permission """ portal.manage_permission(AllowSendto, ('Anonymous', 'Manager', 'Member',), acquire=1) atool = getToolByName(portal, 'portal_actions', None) if atool is None: return actions = atool._cloneActions() for action in actions: if action.getId() == "sendto": action.permissions = (AllowSendto,) atool._actions = actions out.append("Changed sendto action permission to Allow Sendto") def readdVisibleIdsMemberProperty(portal, out): """Re-Adds member config for editable short names.""" mpropTool = getToolByName(portal, 'portal_memberdata', None) if mpropTool is not None: if not mpropTool.hasProperty('visible_ids'): mpropTool.manage_addProperty('visible_ids', 0, 'boolean') out.append("Added 'visible_ids' property to portal_memberdata.") out.append("Added 'visible_ids' property to portal_memberdata.") def addCMFTypesToSearchBlackList(portal, out): """Adds CMF types to types_not_searched site property.""" BASE_TYPES_NOT_SEARCHED = ['ATBooleanCriterion', 'ATDateCriteria', 'ATDateRangeCriterion', 'ATListCriterion', 'ATPortalTypeCriterion', 'ATReferenceCriterion', 'ATSelectionCriterion', 'ATSimpleIntCriterion', 'ATSimpleStringCriterion', 'ATSortCriterion', 'Discussion Item', 'Plone Site', 'TempFolder'] MORE_TYPES_NOT_SEARCHED = ['CMF Document', 'CMF Event', 'CMF Favorite', 'CMF File', 'CMF Folder', 'CMF Image', 'CMF Large Plone Folder', 'CMF Link', 'CMF News Item', 'CMF Topic'] propTool = getToolByName(portal, 'portal_properties', None) propSheet = getattr(propTool, 'site_properties', None) if propSheet is not None: old_val = propSheet.getProperty('types_not_searched', None) if old_val is None: propSheet.manage_addProperty('types_not_searched', BASE_TYPES_NOT_SEARCHED + MORE_TYPES_NOT_SEARCHED, 'lines') out.append("Added 'types_not_searched' property to site_properties.") elif 'CMF Document' not in old_val: propSheet.manage_changeProperties(types_not_searched=list(old_val) + MORE_TYPES_NOT_SEARCHED) out.append("Added CMF types to 'types_not_searched' property.") def convertDefaultPageTypesToWhitelist(portal, out): """Adds non_default_page_types site property.""" # Types which are not selectable as a default_page DEFAULT_PAGE_TYPES = ('Document', 'Event', 'Link', 'News Item', 'Topic') propTool = getToolByName(portal, 'portal_properties', None) propSheet = getattr(propTool, 'site_properties', None) if propSheet is not None: if propSheet.hasProperty('non_default_page_types'): propSheet.manage_delProperties(['non_default_page_types']) out.append("Removed 'non_default_page_types' property to site_properties.") if not propSheet.hasProperty('default_page_types'): propSheet.manage_addProperty('default_page_types', DEFAULT_PAGE_TYPES, 'lines') out.append("Added 'default_page_types' property to site_properties.")