Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions news/+move-moved.internal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Remove utility functions which were already moved to plone.base.

The following utility functions were already moved to plone.base.utils and are
removed from CMFPlone:

- `transaction_note`
- `check_id`
- `_check_for_collision`

[thet]
2 changes: 1 addition & 1 deletion src/Products/CMFPlone/tests/testCheckId.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from AccessControl import Unauthorized
from plone.app.testing.bbb import PloneTestCase
from plone.base.utils import check_id
from Products.CMFCore.utils import getToolByName
from Products.CMFPlone.tests import dummy
from Products.CMFPlone.utils import check_id
from ZODB.POSException import ConflictError


Expand Down
233 changes: 3 additions & 230 deletions src/Products/CMFPlone/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,17 @@
from os.path import abspath
from os.path import join
from os.path import split
from plone.base import PloneMessageFactory as _
from plone.base import utils as base_utils
from plone.base.interfaces.siteroot import IPloneSiteRoot
from plone.i18n.normalizer.interfaces import IIDNormalizer
from plone.registry.interfaces import IRegistry
from Products.CMFCore.permissions import AddPortalContent
from Products.CMFCore.permissions import ManageUsers
from Products.CMFCore.utils import getToolByName
from Products.CMFCore.utils import ToolInit as CMFCoreToolInit
from Products.CMFPlone import bbb
from Products.CMFPlone.log import log # noqa: F401 - for python scripts
from Products.CMFPlone.log import log_deprecated # noqa: F401 - for python scripts
from Products.CMFPlone.log import log_exc # noqa: F401 - for python scripts
from ZODB.POSException import ConflictError
from zope.component import getMultiAdapter
from zope.component import getUtility
from zope.component import providedBy
Expand All @@ -43,7 +40,6 @@
import OFS
import re
import sys
import transaction
import zope.interface


Expand All @@ -70,6 +66,9 @@
get_top_site_from_url="plone.base.utils:get_top_site_from_url",
pretty_title_or_id="plone.base.utils:pretty_title_or_id",
_createObjectByType="plone.base.utils:unrestricted_construct_instance",
transaction_note="plone.base.utils:transaction_note",
check_id="plone.base.utils:check_id",
_check_for_collision="plone.base.utils:_check_for_collision",
)

deprecated_import(
Expand Down Expand Up @@ -107,7 +106,6 @@ def safe_nativestring(value, encoding="utf-8"):
security.declarePrivate("package_home")
security.declarePrivate("ImageFile")
security.declarePrivate("CMFCoreToolInit")
security.declarePrivate("transaction")
security.declarePrivate("zope")

# Canonical way to get at CMFPlone directory
Expand Down Expand Up @@ -316,15 +314,6 @@ def getFSVersionTuple():
return versionTupleFromString(version)


def transaction_note(note):
"""Write human legible note"""
T = transaction.get()
if (len(T.description) + len(note)) >= 65533:
log("Transaction note too large omitting %s" % str(note))
else:
T.note(base_utils.safe_text(note))


def tuplize(value):
if isinstance(value, tuple):
return value
Expand Down Expand Up @@ -526,219 +515,3 @@ def _safe_format(inst, method):
as we do in CMFPlone/__init__.py.
"""
return SafeFormatter(inst).safe_format


def check_id(
context, id=None, required=0, alternative_id=None, contained_by=None, **kwargs
):
"""Test an id to make sure it is valid.

This used to be in Products/CMFPlone/skins/plone_scripts/check_id.py.

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

Accept keyword arguments for compatibility with the fallback
in Products.validation.

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 auto-generated.
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.
"""

def xlate(message):
ts = getToolByName(context, "translation_service", None)
if ts is None:
return message
return ts.translate(message, context=context.REQUEST)

# 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 xlate(_("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 reserved names
if id in (
"login",
"layout",
"plone",
"zip",
"properties",
):
return xlate(_("${name} is reserved.", mapping={"name": id}))

# check for bad characters
plone_utils = getToolByName(context, "plone_utils", None)
if plone_utils is not None:
bad_chars = plone_utils.bad_chars(id)
if len(bad_chars) > 0:
bad_chars = "".join(bad_chars).decode("utf-8")
decoded_id = id.decode("utf-8")
return xlate(
_(
"${name} is not a legal name. The following characters are "
"invalid: ${characters}",
mapping={"name": decoded_id, "characters": bad_chars},
)
)

# check for a catalog index
portal_catalog = getToolByName(context, "portal_catalog", None)
if portal_catalog is not None:
if id in list(portal_catalog.indexes()) + list(portal_catalog.schema()):
return xlate(_("${name} is reserved.", mapping={"name": id}))

# id is good; decide if we should check for id collisions
if contained_by is not None:
# Always check for collisions if a container was passed
# via the contained_by parameter.
checkForCollision = True
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 not checkForCollision:
# We are done.
return
# 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
try:
result = _check_for_collision(contained_by, id, **kwargs)
except Unauthorized:
# There is a permission problem. Safe to assume we can't use this id.
return xlate(_("${name} is reserved.", mapping={"name": id}))
if result is not None:
result = xlate(
result,
)
return result


def _check_for_collision(contained_by, id, **kwargs):
"""Check for collisions of an object id in a container.

Accept keyword arguments for compatibility with the fallback
in Products.validation.

When this was still a Python skin script, some security checks
would have been done automatically and caught by some
'except Unauthorized' lines. Now, in unrestricted Python
code, we explicitly check. But not all checks make sense. If you don't
have the 'Access contents information' permission, theoretically
you should not be able to check for an existing conflicting id,
but it seems silly to then pretend that there is no conflict.

For safety, we let the check_id
function do a try/except Unauthorized when calling us.
"""
secman = getSecurityManager()
# if not secman.checkPermission(
# 'Access contents information', contained_by):
# # We cannot check. Do not complain.
# return

# Check for an existing object.
if id in contained_by:
existing_obj = getattr(contained_by, id, None)
if base_utils.base_hasattr(existing_obj, "portal_type"):
return _(
"There is already an item named ${name} in this folder.",
mapping={"name": id},
)

if base_utils.base_hasattr(contained_by, "checkIdAvailable"):
# This used to be called from the check_id skin script,
# which would check the permission automatically,
# and the code would catch the Unauthorized exception.
if secman.checkPermission(AddPortalContent, contained_by):
if not contained_by.checkIdAvailable(id):
return _("${name} is reserved.", mapping={"name": id})

# containers may implement this hook to further restrict ids
if base_utils.base_hasattr(contained_by, "checkValidId"):
try:
contained_by.checkValidId(id)
except ConflictError:
raise
except: # noqa: E722
return _("${name} is reserved.", mapping={"name": id})

# make sure we don't collide with any parent method aliases
plone_utils = getToolByName(contained_by, "plone_utils", None)
portal_types = getToolByName(contained_by, "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:
if id in aliases.keys():
return _("${name} is reserved.", mapping={"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
return
portal_url = getToolByName(contained_by, "portal_url", None)
if portal_url is None:
# Probably a test.
# All other code needs the portal, so there is nothing left to check.
return
portal = portal_url.getPortalObject()
if id in portal.contentIds():
# Fine to use the same id as a *content* item from the root.
return
# It is allowed to give an object the same id as another
# container in it's acquisition path as long as the
# object is outside the portal.
outsideportal = getattr(aq_parent(portal), id, None)
insideportal = getattr(portal, id, None)
if (
insideportal is not None
and outsideportal is not None
and aq_base(outsideportal) == aq_base(insideportal)
):
return
# but not other things
if getattr(portal, id, None) is not None:
return _("${name} is reserved.", mapping={"name": id})