Back to index

plone3  3.1.7
check_id.py
Go to the documentation of this file.
00001 ## Script (Python) "check_id"
00002 ##bind container=container
00003 ##bind context=context
00004 ##bind namespace=
00005 ##bind script=script
00006 ##bind subpath=traverse_subpath
00007 ##parameters=id=None,required=0,alternative_id=None,contained_by=None
00008 ##title=Check an id's validity
00009 """
00010 This script tests an id to make sure it is valid.
00011 
00012 Returns an error message if the id is bad or None if the id is good. Parameters
00013 are as follows:
00014 
00015     id - the id to check
00016 
00017     required - if False, id can be the empty string
00018 
00019     alternative_id - an alternative value to use for the id if the id is empty
00020     or autogenerated
00021 
00022 Note: The reason the id is included is to handle id error messages for such
00023 objects as files and images that supply an alternative id when an id is
00024 autogenerated. If you say "There is already an item with this name in this folder"
00025 for an image that has the Name field populated with an autogenerated id, it can
00026 cause some confusion, since the real problem is the name of the image file name,
00027 not in the name of the autogenerated id.
00028 """
00029 
00030 from AccessControl import Unauthorized
00031 from ZODB.POSException import ConflictError
00032 from Products.CMFCore.utils import getToolByName
00033 from Products.CMFPlone import PloneMessageFactory as _
00034 from Products.CMFPlone.utils import base_hasattr
00035 
00036 # if an alternative id has been supplied, see if we need to use it
00037 
00038 if alternative_id and not id:
00039     id = alternative_id
00040 
00041 # make sure we have an id if one is required
00042 
00043 if not id:
00044     if required:
00045         return _(u'Please enter a name.')
00046 
00047     # Id is not required and no alternative was specified, so assume the
00048     # object's id will be context.getId(). We still should check to make sure
00049     # context.getId() is OK to handle the case of pre-created objects
00050     # constructed via portal_factory.  The main potential problem is an id
00051     # collision, e.g. if portal_factory autogenerates an id that already exists.
00052 
00053     id = context.getId()
00054 
00055 # do basic id validation
00056 
00057 # check for reserved names
00058 if id in [ 'login', 'layout' ]:
00059     return _(u'${name} is reserved.', mapping={u'name' : id})
00060 
00061 # check for bad characters
00062 plone_utils = getToolByName(container, 'plone_utils', None)
00063 if plone_utils is not None:
00064     bad_chars = plone_utils.bad_chars(id)
00065     if len(bad_chars) > 0:
00066         return _(u'${name} is not a legal name. The following characters are invalid: ${characters}',
00067                  mapping={u'name' : id, u'characters' : ''.join(bad_chars)})
00068 
00069 # check for a catalog index
00070 portal_catalog = getToolByName(container, 'portal_catalog', None)
00071 if portal_catalog is not None:
00072     try:
00073         if id in portal_catalog.indexes() + portal_catalog.schema():
00074             return _(u'${name} is reserved.', mapping={u'name' : id})
00075     except Unauthorized:
00076         pass # ignore if we don't have permission; will get picked up at the end
00077 
00078 # id is good; decide if we should check for id collisions
00079 portal_factory = getToolByName(container, 'portal_factory', None)
00080 if contained_by is not None:
00081     # always check for collisions if a container was passed
00082     checkForCollision = True
00083 elif portal_factory is not None and portal_factory.isTemporary(context):
00084     # always check for collisions if we are creating a new object
00085     checkForCollision = True
00086     try:
00087         # XXX this can't really be necessary, can it!?
00088         contained_by = context.aq_inner.aq_parent.aq_parent.aq_parent
00089     except Unauthorized:
00090         pass
00091 else:
00092     # if we have an existing object, only check for collisions if we are
00093     # changing the id
00094     checkForCollision = (context.getId() != id)
00095 
00096 # check for id collisions
00097 if checkForCollision:
00098     # handles two use cases:
00099     # 1. object has not yet been created and we don't know where it will be
00100     # 2. object has been created and checking validity of id within container
00101     if contained_by is None:
00102         try:
00103             contained_by = context.getParentNode()
00104         except Unauthorized:
00105             return # nothing we can do
00106 
00107     # Check for an existing object.  If it is a content object, then we don't
00108     # try to replace it; there may be other attributes we shouldn't replace,
00109     # but because there are some always replaceable attributes, this is the
00110     # only type of filter we can reasonably expect to work.
00111     exists = False
00112     # Optimization for BTreeFolders
00113     if base_hasattr(contained_by, 'has_key'):
00114         exists = contained_by.has_key(id)
00115     # Otherwise check object ids (using getattr can trigger Unauth exceptions)
00116     elif base_hasattr(contained_by, 'objectIds'):
00117         exists = id in contained_by.objectIds()
00118     if exists:
00119         try:
00120             existing_obj = getattr(contained_by, id, None)
00121             if base_hasattr(existing_obj, 'portal_type'):
00122                 return _(u'There is already an item named ${name} in this folder.',
00123                      mapping={u'name' : id})
00124         except Unauthorized:
00125             # If we cannot access the object it is safe to assume we cannot
00126             # replace it
00127             return _(u'There is already an item named ${name} in this folder.',
00128                      mapping={u'name' : id})
00129 
00130     if hasattr(contained_by, 'checkIdAvailable'):
00131         try:
00132             if not contained_by.checkIdAvailable(id):
00133                 return _(u'${name} is reserved.', mapping={u'name' : id})
00134         except Unauthorized:
00135             pass # ignore if we don't have permission
00136 
00137     # containers may implement this hook to further restrict ids
00138     if hasattr(contained_by, 'checkValidId'):
00139         try:
00140             contained_by.checkValidId(id)
00141         except Unauthorized:
00142             raise
00143         except ConflictError:
00144             raise
00145         except:
00146             return _(u'${name} is reserved.', mapping={u'name' : id})
00147 
00148     # make sure we don't collide with any parent method aliases
00149     portal_types = getToolByName(context, 'portal_types', None)
00150     if plone_utils is not None and portal_types is not None:
00151         parentFti = portal_types.getTypeInfo(contained_by)
00152         if parentFti is not None:
00153             aliases = plone_utils.getMethodAliases(parentFti)
00154             if aliases is not None:
00155                 for alias in aliases.keys():
00156                     if id == alias:
00157                         return _(u'${name} is reserved.', mapping={u'name' : id})
00158 
00159     # Lastly, we want to disallow the id of any of the tools in the portal root,
00160     # as well as any object that can be acquired via portal_skins. However, we
00161     # do want to allow overriding of *content* in the object's parent path,
00162     # including the portal root.
00163 
00164     if id != 'index_html': # always allow index_html
00165         portal = context.portal_url.getPortalObject()
00166         if id not in portal.contentIds(): # can override root *content*
00167             try:
00168                 if getattr(portal, id, None) is not None: # but not other things
00169                     return _(u'${name} is reserved.', mapping={u'name' : id})
00170             except Unauthorized:
00171                 pass # ignore if we don't have permission