Repository: plone/Products.CMFPlone

Issue:
traversal fails with portal from getPortalObject()
In Plone 5.2 with CMF 2.4 traversal with a portal returned by `url_tool.getPortalObject()` fails since it can not acquire the request. `unrestrictedTraverse` needs that though. Found when debugging `test ./bin/test -t testIconLookupForUnknownMimeType`

```
>>> from zope.component import getUtility
>>> from Products.CMFCore.interfaces import IURLTool
>>> portal_url = getUtility(IURLTool)
>>> portal = portal.getPortalObject()
>>> portal.aq_chain
[<PloneSite at /Plone>, <Application at >]

>>> portal.unrestrictedTraverse('foo')
*** AttributeError: REQUEST

>>> from plone import api
>>> portal = api.portal.get()
>>> portal.aq_chain
[<PloneSite at /Plone>, <Application at >, <ZPublisher.BaseRequest.RequestContainer object at 0x10acc66d0>]

>>> portal.unrestrictedTraverse('foo')
*** KeyError: 'foo'
```



Discussion:
You have to get portal_url using getToolByName in order for it to include the request in the acquisition chain. five.localsitemanager removes the request for utility lookups so that it doesn't get cached by the ZCA

David Glick
(mobile)

> On Oct 27, 2017, at 7:48 AM, Philip Bauer <notifications@github.com> wrote:
> 
> In Plone 5.2 with CMF 2.4 traversal with a portal returned by url_tool.getPortalObject() fails since it can not acquire the request. unrestrictedTraverse needs that though. Found when debugging test ./bin/test -t testIconLookupForUnknownMimeType
> 
> >>> from zope.component import getUtility
> >>> from Products.CMFCore.interfaces import IURLTool
> >>> portal_url = getUtility(IURLTool)
> >>> portal = portal.getPortalObject()
> >>> portal.aq_chain
> [<PloneSite at /Plone>, <Application at >]
> 
> >>> portal.unrestrictedTraverse('foo')
> *** AttributeError: REQUEST
> 
> >>> from plone import api
> >>> portal = api.portal.get()
> >>> portal.aq_chain
> [<PloneSite at /Plone>, <Application at >, <ZPublisher.BaseRequest.RequestContainer object at 0x10acc66d0>]
> 
> >>> portal.unrestrictedTraverse('foo')
> *** KeyError: 'foo'
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub, or mute the thread.
> 

Yes, but the lookup happens in `DynamicType` which is in CMF when doing `portal['some_file'].getIcon()` as done in the test `testIconLookupForUnknownMimeType`.
Okay. Then I think my `getPortalObject` which I added to CMFPlone's URLTool is not correct. We should go back to the CMFCore version, but I'll need to follow up with a small fix to that for one of the other failures.
@davisagli any idea when you might get around to doing that?
I'll try to take a look tomorrow.
I took a look but haven't been able to get it right yet.

Resolving patch:
diff --git a/Products/CMFPlone/URLTool.py b/Products/CMFPlone/URLTool.py
--- a/Products/CMFPlone/URLTool.py
+++ b/Products/CMFPlone/URLTool.py
@@ -4,9 +4,11 @@
 from App.class_init import InitializeClass
 from plone.registry.interfaces import IRegistry
 from posixpath import normpath
+from Products.CMFCore.interfaces import ISiteRoot
 from Products.CMFCore.URLTool import URLTool as BaseTool
 from Products.CMFPlone.interfaces import ILoginSchema
 from Products.CMFPlone.PloneBaseTool import PloneBaseTool
+from Products.CMFPlone.patches.gtbn import rewrap_in_request_container
 from urlparse import urlparse, urljoin
 from zope.component import getUtility
 import re
@@ -84,8 +86,11 @@ def isURLInPortal(self, url, context=None):
         return False
 
     def getPortalObject(self):
-        # Make sure portal is wrapped properly
-        return aq_parent(aq_inner(self))
+        portal = aq_parent(aq_inner(self))
+        if portal is None:
+            portal = getUtility(ISiteRoot)
+        # Make sure portal can acquire REQUEST
+        return rewrap_in_request_container(portal, context=self)
 
 
 URLTool.__doc__ = BaseTool.__doc__
diff --git a/Products/CMFPlone/patches/gtbn.py b/Products/CMFPlone/patches/gtbn.py
--- a/Products/CMFPlone/patches/gtbn.py
+++ b/Products/CMFPlone/patches/gtbn.py
@@ -1,5 +1,27 @@
 # -*- coding: utf-8 -*-
+from Acquisition import aq_parent, aq_base, aq_inner
+from Acquisition import IAcquirer
 from Products.CMFCore import utils
+from zope.globalrequest import getRequest
+from ZPublisher.BaseRequest import RequestContainer
+
+
+def rewrap_in_request_container(obj, context=None):
+    '''Fix an object's acquisition wrapper to be able to acquire the REQUEST.'''
+    request = getattr(context, 'REQUEST', None) or getRequest()
+    if IAcquirer.providedBy(obj) and request is not None:
+        chain = []
+        parent = obj
+        while 1:
+            chain.append(parent)
+            parent = aq_parent(aq_inner(parent))
+            if parent in chain or parent is None or isinstance(
+                    parent, RequestContainer):
+                break
+        obj = RequestContainer(REQUEST=request)
+        for ob in reversed(chain):
+            obj = aq_base(ob).__of__(obj)
+    return obj
 
 # Check return value of getToolByName
 # this used to be step 5 in earlypatches, but was moved to avoid
@@ -8,12 +30,8 @@
 # rather than the entire function, to make sure that aliases to the function
 # that were imported prior to this patch will still run the patched code.
 code = """
-from Acquisition.interfaces import IAcquirer
-from Acquisition import aq_base, aq_chain, aq_inner
 from persistent.interfaces import IPersistent
 from OFS.interfaces import IItem
-from ZPublisher.BaseRequest import RequestContainer
-from zope.globalrequest import getRequest
 try:
     from Products.ATContentTypes.tool.factory import FauxArchetypeTool
 except ImportError:
@@ -32,22 +50,7 @@ def check_getToolByName(obj, name, default=_marker):
             result.__class__.__module__ == 'mock' or \
             result is _marker or \
             result is default:
-
-        # Rewrap in request container
-        request = getattr(obj, 'REQUEST', None) or getRequest()
-        if IAcquirer.providedBy(result) and request is not None:
-            chain = []
-            parent = result
-            while 1:
-                chain.append(parent)
-                parent = aq_parent(aq_inner(parent))
-                if parent in chain or parent is None or isinstance(parent, RequestContainer):
-                    break
-            result = RequestContainer(REQUEST=request)
-            for ob in reversed(chain):
-                result = aq_base(ob).__of__(result)
-
-        return result
+        return rewrap_in_request_container(result, context=obj)
     else:
         raise TypeError("Object found is not a portal tool (%s)" % (name,))
     return result
@@ -57,6 +60,8 @@ def check_getToolByName(obj, name, default=_marker):
                     "with Products.PloneHotfix20121106, the fixes are "
                     "included already in Products.CMFPlone, please remove "
                     "the hotfix")
+utils.getToolByName.func_globals[
+    'rewrap_in_request_container'] = rewrap_in_request_container
 exec code in utils.getToolByName.func_globals
 utils._getToolByName.func_code = utils.getToolByName.func_code
 utils.getToolByName.func_code = utils.check_getToolByName.func_code


Test patch: