## Script (Python) "check_id" ##bind container=container ##bind context=context ##bind namespace= ##bind script=script ##bind subpath=traverse_subpath ##parameters=id=None,required=0,alternative_id=None,contained_by=None ##title=Check an id's validity """ This script tests an id to make sure it is valid. Returns an error message if the id is bad or None if the id is good. Parameters are as follows: id - the id to check required - if False, id can be the empty string alternative_id - an alternative value to use for the id if the id is empty or autogenerated Note: The reason the id is included is to handle id error messages for such objects as files and images that supply an alternative id when an id is autogenerated. If you say "There is already an item with this name in this folder" for an image that has the Name field populated with an autogenerated id, it can cause some confusion, since the real problem is the name of the image file name, not in the name of the autogenerated id. """ from AccessControl import Unauthorized from ZODB.POSException import ConflictError from Products.CMFCore.utils import getToolByName from Products.CMFPlone import PloneMessageFactory as _ from Products.CMFPlone.utils import base_hasattr # if an alternative id has been supplied, see if we need to use it if alternative_id and not id: id = alternative_id # make sure we have an id if one is required if not id: if required: return _(u'Please enter a name.') # Id is not required and no alternative was specified, so assume the # object's id will be context.getId(). We still should check to make sure # context.getId() is OK to handle the case of pre-created objects # constructed via portal_factory. The main potential problem is an id # collision, e.g. if portal_factory autogenerates an id that already exists. id = context.getId() # do basic id validation # check for bad characters plone_utils = getToolByName(container, 'plone_utils', None) if plone_utils is not None: bad_chars = plone_utils.bad_chars(id) if len(bad_chars) > 0: return _(u'${name} is not a legal name. The following characters are invalid: ${characters}', mapping={u'name' : id, u'characters' : ''.join(bad_chars)}) # check for a catalog index portal_catalog = getToolByName(container, 'portal_catalog', None) if portal_catalog is not None: try: if id in portal_catalog.indexes() + portal_catalog.schema(): return _(u'${name} is reserved.', mapping={u'name' : id}) except Unauthorized: pass # ignore if we don't have permission; will get picked up at the end # id is good; decide if we should check for id collisions portal_factory = getToolByName(container, 'portal_factory', None) if contained_by is not None: # always check for collisions if a container was passed checkForCollision = True elif portal_factory is not None and portal_factory.isTemporary(context): # always check for collisions if we are creating a new object checkForCollision = True try: # XXX this can't really be necessary, can it!? contained_by = context.aq_inner.aq_parent.aq_parent.aq_parent except Unauthorized: pass else: # if we have an existing object, only check for collisions if we are # changing the id checkForCollision = (context.getId() != id) # check for id collisions if checkForCollision: # handles two use cases: # 1. object has not yet been created and we don't know where it will be # 2. object has been created and checking validity of id within container if contained_by is None: try: contained_by = context.getParentNode() except Unauthorized: return # nothing we can do # Check for an existing object. If it is a content object, then we don't # try to replace it; there may be other attributes we shouldn't replace, # but because there are some always replaceable attributes, this is the # only type of filter we can reasonably expect to work. exists = False # Optimization for BTreeFolders if base_hasattr(contained_by, 'has_key'): exists = contained_by.has_key(id) # Otherwise check object ids (using getattr can trigger Unauth exceptions) elif base_hasattr(contained_by, 'objectIds'): exists = id in contained_by.objectIds() if exists: try: existing_obj = getattr(contained_by, id, None) if base_hasattr(existing_obj, 'portal_type'): return _(u'There is already an item named ${name} in this folder.', mapping={u'name' : id}) except Unauthorized: # If we cannot access the object it is safe to assume we cannot # replace it return _(u'There is already an item named ${name} in this folder.', mapping={u'name' : id}) if hasattr(contained_by, 'checkIdAvailable'): try: if not contained_by.checkIdAvailable(id): return _(u'${name} is reserved.', mapping={u'name' : id}) except Unauthorized: pass # ignore if we don't have permission # containers may implement this hook to further restrict ids if hasattr(contained_by, 'checkValidId'): try: contained_by.checkValidId(id) except Unauthorized: raise except ConflictError: raise except: return _(u'${name} is reserved.', mapping={u'name' : id}) # make sure we don't collide with any parent method aliases portal_types = getToolByName(context, 'portal_types', None) if plone_utils is not None and portal_types is not None: parentFti = portal_types.getTypeInfo(contained_by) if parentFti is not None: aliases = plone_utils.getMethodAliases(parentFti) if aliases is not None: for alias in aliases.keys(): if id == alias: return _(u'${name} is reserved.', mapping={u'name' : id}) # Lastly, we want to disallow the id of any of the tools in the portal root, # as well as any object that can be acquired via portal_skins. However, we # do want to allow overriding of *content* in the object's parent path, # including the portal root. if id != 'index_html': # always allow index_html portal = context.portal_url.getPortalObject() if id not in portal.contentIds(): # can override root *content* try: if getattr(portal, id, None) is not None: # but not other things return _(u'${name} is reserved.', mapping={u'name' : id}) except Unauthorized: pass # ignore if we don't have permission