From 68d8943db455fe28cc9cee8cfb38f653d6a3f841 Mon Sep 17 00:00:00 2001 From: mauritsvanrees Date: Thu, 14 Dec 2023 12:29:54 +0100 Subject: [PATCH] [fc] Repository: Products.CMFPlone Branch: refs/heads/5.2.x Date: 2023-09-25T16:19:08+02:00 Author: szakitibi (szakitibi) Commit: https://github.com/plone/Products.CMFPlone/commit/1155ffc82d45fc411bd3279a74918746e220c8be Improve check on created content ID The fix is already there for Plone 6. Applied same fix for 5.2 Files changed: M Products/CMFPlone/utils.py Repository: Products.CMFPlone Branch: refs/heads/5.2.x Date: 2023-09-25T16:28:16+02:00 Author: szakitibi (szakitibi) Commit: https://github.com/plone/Products.CMFPlone/commit/7c3611447f022440b9f4e98fcd33a2a52bf839fd Create 3847.bugfix Files changed: A news/3847.bugfix Repository: Products.CMFPlone Branch: refs/heads/5.2.x Date: 2023-12-14T12:29:54+01:00 Author: Maurits van Rees (mauritsvanrees) Commit: https://github.com/plone/Products.CMFPlone/commit/6bfa16edfa245bff80b8dd9e972a3cb0c1fa703f Merge pull request #3847 from szakitibi/patch-1 Content ID check should be more strict (it allows to break objects) Files changed: A news/3847.bugfix M Products/CMFPlone/utils.py --- last_commit.txt | 159 ++++++++++++++---------------------------------- 1 file changed, 47 insertions(+), 112 deletions(-) diff --git a/last_commit.txt b/last_commit.txt index 783f36c169..e8351d4c6f 100644 --- a/last_commit.txt +++ b/last_commit.txt @@ -1,115 +1,50 @@ -Repository: plone.restapi - - -Branch: refs/heads/7.x.x -Date: 2023-10-28T14:24:29-07:00 -Author: Mikel Larreategi (erral) -Commit: https://github.com/plone/plone.restapi/commit/79cce5e89c62500e1879041387dabef4d8e7e1e2 - -backport #1465: @site and @navroot endpoints (#1702) - -* backport #1465: @site and @navroot endpoints - -* fix - -* black - -* fix - -* remove duplicated import - -* fixes - -* black - -* f-strings are not available in python2 - -* adapt for Plone 4.3 - -* adapt for Plone 4.3 - -* adapt for Plone 4.3 - -* adapt for Plone 4.3 - -* skip multilingual tests when not installed - -* skip multilingual tests when not installed - -* remove unneeded - -* make tests for Plone 5 - -* make tests for Plone 5 - -* make tests for Plone 5 - ---------- - -Co-authored-by: David Glick <david@glicksoftware.com> +Repository: Products.CMFPlone + + +Branch: refs/heads/5.2.x +Date: 2023-09-25T16:19:08+02:00 +Author: szakitibi (szakitibi) +Commit: https://github.com/plone/Products.CMFPlone/commit/1155ffc82d45fc411bd3279a74918746e220c8be + +Improve check on created content ID + +The fix is already there for Plone 6. Applied same fix for 5.2 + +Files changed: +M Products/CMFPlone/utils.py + +b'diff --git a/Products/CMFPlone/utils.py b/Products/CMFPlone/utils.py\nindex 930aa3df67..fe9ee6a602 100644\n--- a/Products/CMFPlone/utils.py\n+++ b/Products/CMFPlone/utils.py\n@@ -1030,6 +1030,11 @@ def _check_for_collision(contained_by, id, **kwargs):\n u\'There is already an item named ${name} in this folder.\',\n mapping={u\'name\': id})\n \n+ # containers may have a field / attribute of the same name\n+ # see original fix in https://github.com/plone/plone.base/issues/35\n+ if base_hasattr(contained_by, id):\n+ return _("${name} is reserved.", mapping={"name": id})\n+ \n if base_hasattr(contained_by, \'checkIdAvailable\'):\n # This used to be called from the check_id skin script,\n # which would check the permission automatically,\n' + +Repository: Products.CMFPlone + + +Branch: refs/heads/5.2.x +Date: 2023-09-25T16:28:16+02:00 +Author: szakitibi (szakitibi) +Commit: https://github.com/plone/Products.CMFPlone/commit/7c3611447f022440b9f4e98fcd33a2a52bf839fd + +Create 3847.bugfix + +Files changed: +A news/3847.bugfix + +b'diff --git a/news/3847.bugfix b/news/3847.bugfix\nnew file mode 100644\nindex 0000000000..b59ef54f88\n--- /dev/null\n+++ b/news/3847.bugfix\n@@ -0,0 +1,2 @@\n+Check for container field / attribute when trying to create content with same id\n+[laulaz]\n' + +Repository: Products.CMFPlone + + +Branch: refs/heads/5.2.x +Date: 2023-12-14T12:29:54+01:00 +Author: Maurits van Rees (mauritsvanrees) +Commit: https://github.com/plone/Products.CMFPlone/commit/6bfa16edfa245bff80b8dd9e972a3cb0c1fa703f + +Merge pull request #3847 from szakitibi/patch-1 + +Content ID check should be more strict (it allows to break objects) Files changed: -A docs/source/endpoints/index.md -A docs/source/endpoints/navroot.md -A docs/source/endpoints/site.md -A docs/source/glossary.md -A docs/source/index.md -A news/1464.feature -A src/plone/restapi/services/navroot/__init__.py -A src/plone/restapi/services/navroot/configure.zcml -A src/plone/restapi/services/navroot/get.py -A src/plone/restapi/services/site/__init__.py -A src/plone/restapi/services/site/configure.zcml -A src/plone/restapi/services/site/get.py -A src/plone/restapi/tests/http-examples/navroot_lang_content_get.req -A src/plone/restapi/tests/http-examples/navroot_lang_content_get.resp -A src/plone/restapi/tests/http-examples/navroot_lang_folder_get.req -A src/plone/restapi/tests/http-examples/navroot_lang_folder_get.resp -A src/plone/restapi/tests/http-examples/navroot_site_get.req -A src/plone/restapi/tests/http-examples/navroot_site_get.resp -A src/plone/restapi/tests/http-examples/navroot_standard_site_content_get.req -A src/plone/restapi/tests/http-examples/navroot_standard_site_content_get.resp -A src/plone/restapi/tests/http-examples/navroot_standard_site_content_get_expansion.req -A src/plone/restapi/tests/http-examples/navroot_standard_site_content_get_expansion.resp -A src/plone/restapi/tests/http-examples/navroot_standard_site_get.req -A src/plone/restapi/tests/http-examples/navroot_standard_site_get.resp -A src/plone/restapi/tests/http-examples/navroot_standard_site_get_expansion.req -A src/plone/restapi/tests/http-examples/navroot_standard_site_get_expansion.resp -A src/plone/restapi/tests/http-examples/site_get.req -A src/plone/restapi/tests/http-examples/site_get.resp -A src/plone/restapi/tests/http-examples/site_get_expand_lang_folder.req -A src/plone/restapi/tests/http-examples/site_get_expand_lang_folder.resp -A src/plone/restapi/tests/http-examples/site_get_expand_lang_folder_content.req -A src/plone/restapi/tests/http-examples/site_get_expand_lang_folder_content.resp -A src/plone/restapi/tests/http-examples/site_get_expand_navroot.req -A src/plone/restapi/tests/http-examples/site_get_expand_navroot.resp -A src/plone/restapi/tests/test_services_navroot.py -A src/plone/restapi/tests/test_services_site.py -M src/plone/restapi/services/configure.zcml -M src/plone/restapi/testing.py -M src/plone/restapi/tests/http-examples/collection.resp -M src/plone/restapi/tests/http-examples/collection_fullobjects.resp -M src/plone/restapi/tests/http-examples/content_get.resp -M src/plone/restapi/tests/http-examples/content_get_folder.resp -M src/plone/restapi/tests/http-examples/content_patch_representation.resp -M src/plone/restapi/tests/http-examples/content_post.resp -M src/plone/restapi/tests/http-examples/document.resp -M src/plone/restapi/tests/http-examples/event.resp -M src/plone/restapi/tests/http-examples/expansion.resp -M src/plone/restapi/tests/http-examples/expansion_expanded.resp -M src/plone/restapi/tests/http-examples/expansion_expanded_full.resp -M src/plone/restapi/tests/http-examples/file.resp -M src/plone/restapi/tests/http-examples/folder.resp -M src/plone/restapi/tests/http-examples/image.resp -M src/plone/restapi/tests/http-examples/jwt_logged_in.resp -M src/plone/restapi/tests/http-examples/link.resp -M src/plone/restapi/tests/http-examples/newsitem.resp -M src/plone/restapi/tests/http-examples/search_fullobjects.resp -M src/plone/restapi/tests/http-examples/siteroot.resp -M src/plone/restapi/tests/http-examples/translations_link_on_post.resp -M src/plone/restapi/tests/http-examples/workingcopy_baseline_get.resp -M src/plone/restapi/tests/http-examples/workingcopy_wc_get.resp -M src/plone/restapi/tests/test_documentation.py - -b'diff --git a/docs/source/endpoints/index.md b/docs/source/endpoints/index.md\nnew file mode 100644\nindex 0000000000..239b6577ad\n--- /dev/null\n+++ b/docs/source/endpoints/index.md\n@@ -0,0 +1,61 @@\n+---\n+myst:\n+ html_meta:\n+ "description": "Usage of the Plone REST API."\n+ "property=og:description": "Usage of the Plone REST API."\n+ "property=og:title": "Usage of the Plone REST API"\n+ "keywords": "Plone, plone.restapi, REST, API, Usage"\n+---\n+\n+(restapi-endpoints)=\n+\n+# Endpoints\n+\n+This part of the documentation explains the endpoints of Plone REST API.\n+\n+```{toctree}\n+:caption: Table of Contents\n+:maxdepth: 2\n+\n+addons\n+aliases\n+breadcrumbs\n+comments\n+content-types\n+content-rules\n+contextnavigation\n+controlpanels\n+copymove\n+database\n+email-notification\n+email-send\n+expansion\n+groups\n+history\n+linkintegrity\n+locking\n+navigation\n+navroot\n+actions\n+portrait\n+principals\n+querystring\n+querystringsearch\n+registry\n+relations\n+roles\n+searching\n+site\n+system\n+tiles\n+transactions\n+translations\n+tusupload\n+types\n+upgrade\n+users\n+userschema\n+vocabularies\n+workflow\n+workingcopy\n+```\ndiff --git a/docs/source/endpoints/navroot.md b/docs/source/endpoints/navroot.md\nnew file mode 100644\nindex 0000000000..30fd610177\n--- /dev/null\n+++ b/docs/source/endpoints/navroot.md\n@@ -0,0 +1,158 @@\n+---\n+html_meta:\n+ "description": "Navigation root is a concept that provides a way to root catalog queries, searches, and breadcrumbs in Plone."\n+ "property=og:description": "Navigation root is a concept that provides a way to root catalog queries, searches, and breadcrumbs in Plone."\n+ "property=og:title": "Navigation Root"\n+ "keywords": "Plone, plone.restapi, REST, API, site, navigation root"\n+---\n+\n+(navigation-root-label)=\n+\n+# Navigation root\n+\n+Plone has a concept called {term}`navigation root` which provides a way to root catalog queries, searches, breadcrumbs, and so on in a given section of the site.\n+This feature is useful when working with subsites or multilingual sites, because it allows the site manager to restrict searches or navigation queries to a specific location in the site.\n+\n+This navigation root information is different depending on the context of the request.\n+For instance, in a default multilingual site when browsing the contents inside a language folder such as `www.domain.com/en`, the context is `en` and its navigation root will be `/en/`.\n+In a non-multilingual site, the context is the root of the site such as `www.domain.com` and the navigation root will be `/`.\n+\n+To get the information about the navigation root, the REST API has a `@navroot` contextual endpoint.\n+For instance, send a `GET` request to the `@navroot` endpoint at the root of the site:\n+\n+```{eval-rst}\n+.. http:example:: curl httpie python-requests\n+ :request: ../../../src/plone/restapi/tests/http-examples/navroot_standard_site_get.req\n+```\n+\n+The response will contain the navigation root information for the site:\n+\n+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/navroot_standard_site_get.resp\n+:language: http\n+```\n+\n+If you request the `@navroot` of a given content item in the site:\n+\n+```{eval-rst}\n+.. http:example:: curl httpie python-requests\n+ :request: ../../../src/plone/restapi/tests/http-examples/navroot_standard_site_content_get.req\n+```\n+\n+The response will contain the navigation root information in the context of that content item:\n+\n+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/navroot_standard_site_content_get.resp\n+:language: http\n+```\n+\n+In a multilingual site, the root of the site will work as usual:\n+\n+```{eval-rst}\n+.. http:example:: curl httpie python-requests\n+ :request: ../../../src/plone/restapi/tests/http-examples/navroot_site_get.req\n+```\n+\n+The response will contain the navigation root information of the root of the site:\n+\n+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/navroot_site_get.resp\n+:language: http\n+```\n+\n+In a multilingual site where the language folder is the navigation root, the response has the language folder information:\n+\n+```{eval-rst}\n+.. http:example:: curl httpie python-requests\n+ :request: ../../../src/plone/restapi/tests/http-examples/navroot_lang_folder_get.req\n+```\n+\n+The response will contain the navigation root information for the language folder:\n+\n+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/navroot_lang_folder_get.resp\n+:language: http\n+```\n+\n+In a multilingual site, if the navigation root is requested for content inside a language folder:\n+\n+```{eval-rst}\n+.. http:example:: curl httpie python-requests\n+ :request: ../../../src/plone/restapi/tests/http-examples/navroot_lang_content_get.req\n+```\n+\n+The response has the language folder information as a navigation root:\n+\n+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/navroot_lang_content_get.resp\n+:language: http\n+```\n+\n+(navigation-root-expansion-label)=\n+\n+## Expansion\n+\n+This endpoint can be used with the {doc}`expansion` mechanism which allows getting more information about a content item in one query, avoiding unnecessary requests.\n+\n+If a simple `GET` request is made on the content item, a new entry will be shown on the `@components` entry with the URL of the `@navroot` endpoint.\n+\n+In a standard site when querying the site root:\n+\n+```{eval-rst}\n+.. http:example:: curl httpie python-requests\n+ :request: ../../../src/plone/restapi/tests/http-examples/navroot_standard_site_get_expansion.req\n+```\n+\n+The response will contain information of the site root with the navigation expanded:\n+\n+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/navroot_standard_site_get_expansion.resp\n+:language: http\n+```\n+\n+When querying a content item inside the root:\n+\n+```{eval-rst}\n+.. http:example:: curl httpie python-requests\n+ :request: ../../../src/plone/restapi/tests/http-examples/navroot_standard_site_content_get_expansion.req\n+```\n+\n+The response will contain the information of that content item with its navigation root information expanded:\n+\n+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/navroot_standard_site_content_get_expansion.resp\n+:language: http\n+```\n+\n+In a multilingual site, it works the same.\n+Use the request:\n+\n+```{eval-rst}\n+.. http:example:: curl httpie python-requests\n+ :request: ../../../src/plone/restapi/tests/http-examples/site_get_expand_navroot.req\n+```\n+\n+And the response will contain the navigation root information pointing to the root of the site:\n+\n+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/site_get_expand_navroot.resp\n+:language: http\n+```\n+\n+It will also work with language root folders that are navigation roots:\n+\n+```{eval-rst}\n+.. http:example:: curl httpie python-requests\n+ :request: ../../../src/plone/restapi/tests/http-examples/site_get_expand_lang_folder.req\n+```\n+\n+The response will contain the navigation root information expanded:\n+\n+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/site_get_expand_lang_folder.resp\n+:language: http\n+```\n+\n+And also for content inside the language root folders:\n+\n+```{eval-rst}\n+.. http:example:: curl httpie python-requests\n+ :request: ../../../src/plone/restapi/tests/http-examples/site_get_expand_lang_folder_content.req\n+```\n+\n+The response will include the expanded information pointing to the language root:\n+\n+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/site_get_expand_lang_folder_content.resp\n+:language: http\n+```\ndiff --git a/docs/source/endpoints/site.md b/docs/source/endpoints/site.md\nnew file mode 100644\nindex 0000000000..40e76bf37c\n--- /dev/null\n+++ b/docs/source/endpoints/site.md\n@@ -0,0 +1,25 @@\n+---\n+html_meta:\n+ "description": "Site endpoint for Plone REST API"\n+ "property=og:title": "Site endpoint for Plone REST API"\n+ "property=og:description": "Site endpoint for Plone REST API"\n+ "keywords": "Plone, plone.restapi, REST, API, site, navigation root"\n+---\n+\n+# Site\n+\n+The `@site` endpoint provides general site-wide information, such as the site title, logo, and other information.\n+It uses the `zope2.View` permission, which requires appropriate authorization.\n+\n+Send a `GET` request to the `@site` endpoint:\n+\n+```{eval-rst}\n+.. http:example:: curl httpie python-requests\n+ :request: ../../../src/plone/restapi/tests/http-examples/site_get.req\n+```\n+\n+The response will contain the site information:\n+\n+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/site_get.resp\n+:language: http\n+```\ndiff --git a/docs/source/glossary.md b/docs/source/glossary.md\nnew file mode 100644\nindex 0000000000..74adaaa75b\n--- /dev/null\n+++ b/docs/source/glossary.md\n@@ -0,0 +1,67 @@\n+---\n+myst:\n+ html_meta:\n+ "description": "plone.restapi Glossary"\n+ "property=og:description": "plone.restapi Glossary"\n+ "property=og:title": "Glossary"\n+ "keywords": "Plone, plone.restapi, REST, API, Glossary"\n+---\n+\n+# Glossary\n+\n+```{glossary}\n+:sorted: true\n+\n+REST\n+ REST stands for [Representational State Transfer](https://en.wikipedia.org/wiki/Representational_state_transfer). It is a software architectural principle to create loosely coupled web APIs.\n+\n+workflow\n+ A concept in Plone (and other CMS\'s) whereby a content object can be in a number of states (private, public, etcetera) and uses transitions to change between them (e.g. "publish", "approve", "reject", "retract"). See the [Plone docs on Workflow](https://5.docs.plone.org/working-with-content/collaboration-and-workflow/)\n+\n+HTTP-Request\n+HTTP Request\n+Request\n+Requests\n+ The initial action performed by a web client to communicate with a server. The {term}`Request` is usually followed by a {term}`Response` by the server, either synchronous or asynchronous (which is more complex to handle on the user side)\n+\n+HTTP-Response\n+HTTP Response\n+Response\n+ Answer of or action by the server that is executed or send to the client after the {term}`Request` is processed.\n+\n+HTTP-Header\n+HTTP Header\n+Header\n+ The part of the communication of the client with the server that provides the initialisation of the communication of a {term}`Request`.\n+\n+HTTP-Verb\n+HTTP Verb\n+Verb\n+ One of the basic actions that can be requested to be executed by the server (on an object) based on the {term}`Request`.\n+\n+Object URL\n+ The target object of the {term}`Request`\n+\n+Authorization Header\n+ Part of the {term}`Request` that is responsible for the authentication related to the right user or service to ask for a {term}`Response`.\n+\n+Accept Header\n+ Part of the {term}`Request` that is responsible to define the expected type of data to be accepted by the client in the {term}`Response`.\n+\n+Authentication Method\n+ Access restriction provided by the connection chain to the server exposed to the client.\n+\n+Basic Auth\n+ A simple {term}`Authentication Method` referenced in the {term}`Authorization Header` that needs to be provided by the server.\n+ \n+content rule\n+ A content rule will automatically perform an action when a certain event, known as a {term}`trigger`, takes place.\n+\n+trigger\n+ A trigger is an event in Plone that causes the execution of defined actions.\n+ Example triggers include object modified, user logged in, and workflow state changed.\n+\n+navigation root\n+ An object marked as a navigation root provides a way to root catalog queries, searches, breadcrumbs, and so on, into that object.\n+\n+```\ndiff --git a/docs/source/index.md b/docs/source/index.md\nnew file mode 100644\nindex 0000000000..1560bc7bd4\n--- /dev/null\n+++ b/docs/source/index.md\n@@ -0,0 +1,43 @@\n+---\n+myst:\n+ html_meta:\n+ "description": "A RESTful API for Plone."\n+ "property=og:description": "A RESTful API for Plone."\n+ "property=og:title": "REST API"\n+ "keywords": "Plone, plone.restapi, REST, API"\n+---\n+\n+% plone.restapi documentation master file, created by\n+% sphinx-quickstart on Mon Apr 28 13:04:12 2014.\n+% You can adapt this file completely to your liking, but it should at least\n+% contain the root `toctree` directive.\n+\n+# REST API\n+\n+A RESTful API for Plone.\n+\n+```{toctree}\n+:caption: Table of Contents\n+:maxdepth: 2\n+\n+introduction\n+usage/index\n+endpoints/index\n+upgrade-guide\n+contributing/index\n+```\n+\n+```{eval-rst}\n+.. include:: ../../README.rst\n+```\n+\n+## Appendix and Glossary\n+\n+```{toctree}\n+http-status-codes\n+/glossary\n+```\n+\n+## Index\n+\n+- {ref}`genindex`\ndiff --git a/news/1464.feature b/news/1464.feature\nnew file mode 100644\nindex 0000000000..5e52088d8b\n--- /dev/null\n+++ b/news/1464.feature\n@@ -0,0 +1 @@\n+Added `@site` and `@navroot` endpoints. @erral \ndiff --git a/src/plone/restapi/services/configure.zcml b/src/plone/restapi/services/configure.zcml\nindex b9f4fac9db..0a526c7690 100644\n--- a/src/plone/restapi/services/configure.zcml\n+++ b/src/plone/restapi/services/configure.zcml\n@@ -23,6 +23,7 @@\n \n \n \n+ \n \n \n \n@@ -30,6 +31,7 @@\n \n \n \n+ \n \n \n \ndiff --git a/src/plone/restapi/services/navroot/__init__.py b/src/plone/restapi/services/navroot/__init__.py\nnew file mode 100644\nindex 0000000000..e69de29bb2\ndiff --git a/src/plone/restapi/services/navroot/configure.zcml b/src/plone/restapi/services/navroot/configure.zcml\nnew file mode 100644\nindex 0000000000..a9a8451388\n--- /dev/null\n+++ b/src/plone/restapi/services/navroot/configure.zcml\n@@ -0,0 +1,23 @@\n+\n+\n+ \n+\n+\n+ \n+\n+\n+\ndiff --git a/src/plone/restapi/services/navroot/get.py b/src/plone/restapi/services/navroot/get.py\nnew file mode 100644\nindex 0000000000..bb65295387\n--- /dev/null\n+++ b/src/plone/restapi/services/navroot/get.py\n@@ -0,0 +1,39 @@\n+# -*- coding: utf-8 -*-\n+from plone.restapi.interfaces import IExpandableElement, ISerializeToJson\n+from plone.restapi.services import Service\n+from zope.component import adapter\n+from zope.component import getMultiAdapter\n+from zope.interface import implementer\n+from zope.interface import Interface\n+\n+\n+@implementer(IExpandableElement)\n+@adapter(Interface, Interface)\n+class Navroot:\n+ def __init__(self, context, request):\n+ self.context = context\n+ self.request = request\n+\n+ def __call__(self, expand=False):\n+ result = {"navroot": {"@id": "{}/@navroot".format(self.context.absolute_url())}}\n+ if not expand:\n+ return result\n+\n+ portal_state = getMultiAdapter(\n+ (self.context, self.request), name="plone_portal_state"\n+ )\n+ # We need to unset expansion here, otherwise we get infinite recursion\n+ self.request.form["expand"] = ""\n+\n+ result["navroot"]["navroot"] = getMultiAdapter(\n+ (portal_state.navigation_root(), self.request),\n+ ISerializeToJson,\n+ )()\n+\n+ return result\n+\n+\n+class NavrootGet(Service):\n+ def reply(self):\n+ navroot = Navroot(self.context, self.request)\n+ return navroot(expand=True)["navroot"]\ndiff --git a/src/plone/restapi/services/site/__init__.py b/src/plone/restapi/services/site/__init__.py\nnew file mode 100644\nindex 0000000000..e69de29bb2\ndiff --git a/src/plone/restapi/services/site/configure.zcml b/src/plone/restapi/services/site/configure.zcml\nnew file mode 100644\nindex 0000000000..76bc963b44\n--- /dev/null\n+++ b/src/plone/restapi/services/site/configure.zcml\n@@ -0,0 +1,14 @@\n+\n+\n+ \n+\n+\ndiff --git a/src/plone/restapi/services/site/get.py b/src/plone/restapi/services/site/get.py\nnew file mode 100644\nindex 0000000000..fdff8d46f7\n--- /dev/null\n+++ b/src/plone/restapi/services/site/get.py\n@@ -0,0 +1,77 @@\n+# -*- coding: utf-8 -*-\n+from plone.registry.interfaces import IRegistry\n+from plone.restapi.interfaces import IExpandableElement\n+from plone.restapi.services import Service\n+from zope.component import adapter\n+from zope.component import getMultiAdapter\n+from zope.component import getUtility\n+from zope.interface import implementer\n+from zope.interface import Interface\n+\n+try:\n+ from Products.CMFPlone.utils import getSiteLogo\n+except ImportError:\n+ getSiteLogo = None\n+try:\n+ from Products.CMFPlone.interfaces import IImagingSchema\n+except ImportError:\n+ IImagingSchema = None\n+try:\n+ from Products.CMFPlone.interfaces import ISiteSchema\n+except ImportError:\n+ ISiteSchema = None\n+\n+\n+@implementer(IExpandableElement)\n+@adapter(Interface, Interface)\n+class Site:\n+ def __init__(self, context, request):\n+ self.context = context\n+ self.request = request\n+\n+ def __call__(self, expand=False):\n+ result = {"site": {"@id": "{}/@site".format(self.context.absolute_url())}}\n+ if not expand:\n+ return result\n+\n+ portal_state = getMultiAdapter(\n+ (self.context, self.request), name="plone_portal_state"\n+ )\n+ registry = getUtility(IRegistry)\n+\n+ if ISiteSchema is not None:\n+ site_settings = registry.forInterface(\n+ ISiteSchema, prefix="plone", check=False\n+ )\n+ result["site"].update(\n+ {\n+ "plone.site_logo": site_settings.site_logo\n+ and getSiteLogo()\n+ or None,\n+ "plone.robots_txt": site_settings.robots_txt,\n+ }\n+ )\n+\n+ if IImagingSchema is not None:\n+ image_settings = registry.forInterface(\n+ IImagingSchema, prefix="plone", check=False\n+ )\n+ result["site"].update(\n+ {\n+ "plone.allowed_sizes": image_settings.allowed_sizes,\n+ }\n+ )\n+\n+ result["site"].update(\n+ {\n+ "plone.site_title": portal_state.portal_title(),\n+ }\n+ )\n+\n+ return result\n+\n+\n+class SiteGet(Service):\n+ def reply(self):\n+ site = Site(self.context, self.request)\n+ return site(expand=True)["site"]\ndiff --git a/src/plone/restapi/testing.py b/src/plone/restapi/testing.py\nindex 9e35799b83..57530693e2 100644\n--- a/src/plone/restapi/testing.py\n+++ b/src/plone/restapi/testing.py\n@@ -32,6 +32,7 @@\n from zope.configuration import xmlconfig\n from zope.interface import implementer\n \n+\n import collective.MockMailHost\n import os\n import pkg_resources\n@@ -253,7 +254,8 @@ def setUpPloneSite(self, portal):\n \n PLONE_RESTAPI_DX_PAM_FIXTURE = PloneRestApiDXPAMLayer()\n PLONE_RESTAPI_DX_PAM_INTEGRATION_TESTING = IntegrationTesting(\n- bases=(PLONE_RESTAPI_DX_PAM_FIXTURE,), name="PloneRestApiDXPAMLayer:Integration"\n+ bases=(PLONE_RESTAPI_DX_PAM_FIXTURE,),\n+ name="PloneRestApiDXPAMLayer:Integration",\n )\n PLONE_RESTAPI_DX_PAM_FUNCTIONAL_TESTING = FunctionalTesting(\n bases=(PLONE_RESTAPI_DX_PAM_FIXTURE, z2.ZSERVER_FIXTURE),\n@@ -339,7 +341,8 @@ def setUpPloneSite(self, portal):\n \n PLONE_RESTAPI_BLOCKS_FIXTURE = PloneRestApIBlocksLayer()\n PLONE_RESTAPI_BLOCKS_INTEGRATION_TESTING = IntegrationTesting(\n- bases=(PLONE_RESTAPI_BLOCKS_FIXTURE,), name="PloneRestApIBlocksLayer:Integration"\n+ bases=(PLONE_RESTAPI_BLOCKS_FIXTURE,),\n+ name="PloneRestApIBlocksLayer:Integration",\n )\n PLONE_RESTAPI_BLOCKS_FUNCTIONAL_TESTING = FunctionalTesting(\n bases=(PLONE_RESTAPI_BLOCKS_FIXTURE, z2.ZSERVER_FIXTURE),\ndiff --git a/src/plone/restapi/tests/http-examples/collection.resp b/src/plone/restapi/tests/http-examples/collection.resp\nindex 8af2a37629..5b16ad41fb 100644\n--- a/src/plone/restapi/tests/http-examples/collection.resp\n+++ b/src/plone/restapi/tests/http-examples/collection.resp\n@@ -15,6 +15,9 @@ Content-Type: application/json\n "navigation": {\n "@id": "http://localhost:55001/plone/collection/@navigation"\n },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/collection/@navroot"\n+ },\n "types": {\n "@id": "http://localhost:55001/plone/collection/@types"\n },\ndiff --git a/src/plone/restapi/tests/http-examples/collection_fullobjects.resp b/src/plone/restapi/tests/http-examples/collection_fullobjects.resp\nindex b924bf7145..80231930e4 100644\n--- a/src/plone/restapi/tests/http-examples/collection_fullobjects.resp\n+++ b/src/plone/restapi/tests/http-examples/collection_fullobjects.resp\n@@ -15,6 +15,9 @@ Content-Type: application/json\n "navigation": {\n "@id": "http://localhost:55001/plone/collection/@navigation"\n },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/collection/@navroot"\n+ },\n "types": {\n "@id": "http://localhost:55001/plone/collection/@types"\n },\n@@ -71,6 +74,9 @@ Content-Type: application/json\n "navigation": {\n "@id": "http://localhost:55001/plone/front-page/@navigation"\n },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/front-page/@navroot"\n+ },\n "types": {\n "@id": "http://localhost:55001/plone/front-page/@types"\n },\n@@ -142,6 +148,9 @@ Content-Type: application/json\n "navigation": {\n "@id": "http://localhost:55001/plone/doc1/@navigation"\n },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/doc1/@navroot"\n+ },\n "types": {\n "@id": "http://localhost:55001/plone/doc1/@types"\n },\n@@ -214,6 +223,9 @@ Content-Type: application/json\n "navigation": {\n "@id": "http://localhost:55001/plone/doc2/@navigation"\n },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/doc2/@navroot"\n+ },\n "types": {\n "@id": "http://localhost:55001/plone/doc2/@types"\n },\ndiff --git a/src/plone/restapi/tests/http-examples/content_get.resp b/src/plone/restapi/tests/http-examples/content_get.resp\nindex 561934faf5..f320965547 100644\n--- a/src/plone/restapi/tests/http-examples/content_get.resp\n+++ b/src/plone/restapi/tests/http-examples/content_get.resp\n@@ -15,6 +15,9 @@ Content-Type: application/json\n "navigation": {\n "@id": "http://localhost:55001/plone/folder/my-document/@navigation"\n },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/folder/my-document/@navroot"\n+ },\n "types": {\n "@id": "http://localhost:55001/plone/folder/my-document/@types"\n },\ndiff --git a/src/plone/restapi/tests/http-examples/content_get_folder.resp b/src/plone/restapi/tests/http-examples/content_get_folder.resp\nindex 3366895925..c64dd798e6 100644\n--- a/src/plone/restapi/tests/http-examples/content_get_folder.resp\n+++ b/src/plone/restapi/tests/http-examples/content_get_folder.resp\n@@ -15,6 +15,9 @@ Content-Type: application/json\n "navigation": {\n "@id": "http://localhost:55001/plone/folder/@navigation"\n },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/folder/@navroot"\n+ },\n "types": {\n "@id": "http://localhost:55001/plone/folder/@types"\n },\ndiff --git a/src/plone/restapi/tests/http-examples/content_patch_representation.resp b/src/plone/restapi/tests/http-examples/content_patch_representation.resp\nindex 6c88900792..f601f0d4d1 100644\n--- a/src/plone/restapi/tests/http-examples/content_patch_representation.resp\n+++ b/src/plone/restapi/tests/http-examples/content_patch_representation.resp\n@@ -15,6 +15,9 @@ Content-Type: application/json\n "navigation": {\n "@id": "http://localhost:55001/plone/folder/my-document/@navigation"\n },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/folder/my-document/@navroot"\n+ },\n "types": {\n "@id": "http://localhost:55001/plone/folder/my-document/@types"\n },\ndiff --git a/src/plone/restapi/tests/http-examples/content_post.resp b/src/plone/restapi/tests/http-examples/content_post.resp\nindex febf385b50..542cc3c9e0 100644\n--- a/src/plone/restapi/tests/http-examples/content_post.resp\n+++ b/src/plone/restapi/tests/http-examples/content_post.resp\n@@ -16,6 +16,9 @@ Location: http://localhost:55001/plone/folder/my-document\n "navigation": {\n "@id": "http://localhost:55001/plone/folder/my-document/@navigation"\n },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/folder/my-document/@navroot"\n+ },\n "types": {\n "@id": "http://localhost:55001/plone/folder/my-document/@types"\n },\ndiff --git a/src/plone/restapi/tests/http-examples/document.resp b/src/plone/restapi/tests/http-examples/document.resp\nindex 783586cada..0e23e1c1ae 100644\n--- a/src/plone/restapi/tests/http-examples/document.resp\n+++ b/src/plone/restapi/tests/http-examples/document.resp\n@@ -15,6 +15,9 @@ Content-Type: application/json\n "navigation": {\n "@id": "http://localhost:55001/plone/front-page/@navigation"\n },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/front-page/@navroot"\n+ },\n "types": {\n "@id": "http://localhost:55001/plone/front-page/@types"\n },\ndiff --git a/src/plone/restapi/tests/http-examples/event.resp b/src/plone/restapi/tests/http-examples/event.resp\nindex b4765e4e69..aad5e18d0b 100644\n--- a/src/plone/restapi/tests/http-examples/event.resp\n+++ b/src/plone/restapi/tests/http-examples/event.resp\n@@ -15,6 +15,9 @@ Content-Type: application/json\n "navigation": {\n "@id": "http://localhost:55001/plone/event/@navigation"\n },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/event/@navroot"\n+ },\n "types": {\n "@id": "http://localhost:55001/plone/event/@types"\n },\ndiff --git a/src/plone/restapi/tests/http-examples/expansion.resp b/src/plone/restapi/tests/http-examples/expansion.resp\nindex 783586cada..0e23e1c1ae 100644\n--- a/src/plone/restapi/tests/http-examples/expansion.resp\n+++ b/src/plone/restapi/tests/http-examples/expansion.resp\n@@ -15,6 +15,9 @@ Content-Type: application/json\n "navigation": {\n "@id": "http://localhost:55001/plone/front-page/@navigation"\n },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/front-page/@navroot"\n+ },\n "types": {\n "@id": "http://localhost:55001/plone/front-page/@types"\n },\ndiff --git a/src/plone/restapi/tests/http-examples/expansion_expanded.resp b/src/plone/restapi/tests/http-examples/expansion_expanded.resp\nindex b554f1f8c2..acdbd61bdf 100644\n--- a/src/plone/restapi/tests/http-examples/expansion_expanded.resp\n+++ b/src/plone/restapi/tests/http-examples/expansion_expanded.resp\n@@ -22,6 +22,9 @@ Content-Type: application/json\n "navigation": {\n "@id": "http://localhost:55001/plone/front-page/@navigation"\n },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/front-page/@navroot"\n+ },\n "types": {\n "@id": "http://localhost:55001/plone/front-page/@types"\n },\ndiff --git a/src/plone/restapi/tests/http-examples/expansion_expanded_full.resp b/src/plone/restapi/tests/http-examples/expansion_expanded_full.resp\nindex e7b16baeaf..22fa73c0e6 100644\n--- a/src/plone/restapi/tests/http-examples/expansion_expanded_full.resp\n+++ b/src/plone/restapi/tests/http-examples/expansion_expanded_full.resp\n@@ -138,6 +138,9 @@ Content-Type: application/json\n }\n ]\n },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/front-page/@navroot"\n+ },\n "types": [\n {\n "@id": "http://localhost:55001/plone/@types/Collection",\ndiff --git a/src/plone/restapi/tests/http-examples/file.resp b/src/plone/restapi/tests/http-examples/file.resp\nindex 47556c6c91..30f8f8e02d 100644\n--- a/src/plone/restapi/tests/http-examples/file.resp\n+++ b/src/plone/restapi/tests/http-examples/file.resp\n@@ -15,6 +15,9 @@ Content-Type: application/json\n "navigation": {\n "@id": "http://localhost:55001/plone/file/@navigation"\n },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/file/@navroot"\n+ },\n "types": {\n "@id": "http://localhost:55001/plone/file/@types"\n },\ndiff --git a/src/plone/restapi/tests/http-examples/folder.resp b/src/plone/restapi/tests/http-examples/folder.resp\nindex 5f6af7eeab..7984400c4f 100644\n--- a/src/plone/restapi/tests/http-examples/folder.resp\n+++ b/src/plone/restapi/tests/http-examples/folder.resp\n@@ -15,6 +15,9 @@ Content-Type: application/json\n "navigation": {\n "@id": "http://localhost:55001/plone/folder/@navigation"\n },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/folder/@navroot"\n+ },\n "types": {\n "@id": "http://localhost:55001/plone/folder/@types"\n },\ndiff --git a/src/plone/restapi/tests/http-examples/image.resp b/src/plone/restapi/tests/http-examples/image.resp\nindex 12d9158fd7..dd2d91e4f2 100644\n--- a/src/plone/restapi/tests/http-examples/image.resp\n+++ b/src/plone/restapi/tests/http-examples/image.resp\n@@ -15,6 +15,9 @@ Content-Type: application/json\n "navigation": {\n "@id": "http://localhost:55001/plone/image/@navigation"\n },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/image/@navroot"\n+ },\n "types": {\n "@id": "http://localhost:55001/plone/image/@types"\n },\ndiff --git a/src/plone/restapi/tests/http-examples/jwt_logged_in.resp b/src/plone/restapi/tests/http-examples/jwt_logged_in.resp\nindex 7e8dca4f6c..dd45f70974 100644\n--- a/src/plone/restapi/tests/http-examples/jwt_logged_in.resp\n+++ b/src/plone/restapi/tests/http-examples/jwt_logged_in.resp\n@@ -14,6 +14,9 @@ Content-Type: application/json\n },\n "navigation": {\n "@id": "http://localhost:55001/plone/@navigation"\n+ },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/@navroot"\n }\n },\n "@id": "http://localhost:55001/plone/",\ndiff --git a/src/plone/restapi/tests/http-examples/link.resp b/src/plone/restapi/tests/http-examples/link.resp\nindex 7913206eab..3b5fc65eeb 100644\n--- a/src/plone/restapi/tests/http-examples/link.resp\n+++ b/src/plone/restapi/tests/http-examples/link.resp\n@@ -15,6 +15,9 @@ Content-Type: application/json\n "navigation": {\n "@id": "http://localhost:55001/plone/link/@navigation"\n },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/link/@navroot"\n+ },\n "types": {\n "@id": "http://localhost:55001/plone/link/@types"\n },\ndiff --git a/src/plone/restapi/tests/http-examples/navroot_lang_content_get.req b/src/plone/restapi/tests/http-examples/navroot_lang_content_get.req\nnew file mode 100644\nindex 0000000000..a16879755c\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/navroot_lang_content_get.req\n@@ -0,0 +1,3 @@\n+GET /plone/en/test-document/@navroot HTTP/1.1\n+Accept: application/json\n+Authorization: Basic YWRtaW46c2VjcmV0\ndiff --git a/src/plone/restapi/tests/http-examples/navroot_lang_content_get.resp b/src/plone/restapi/tests/http-examples/navroot_lang_content_get.resp\nnew file mode 100644\nindex 0000000000..e02abd7099\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/navroot_lang_content_get.resp\n@@ -0,0 +1,91 @@\n+HTTP/1.1 200 OK\n+Content-Type: application/json\n+\n+{\n+ "@id": "http://localhost:55001/plone/en/test-document/@navroot",\n+ "navroot": {\n+ "@components": {\n+ "actions": {\n+ "@id": "http://localhost:55001/plone/en/@actions"\n+ },\n+ "breadcrumbs": {\n+ "@id": "http://localhost:55001/plone/en/@breadcrumbs"\n+ },\n+ "contextnavigation": {\n+ "@id": "http://localhost:55001/plone/en/@contextnavigation"\n+ },\n+ "navigation": {\n+ "@id": "http://localhost:55001/plone/en/@navigation"\n+ },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/en/@navroot"\n+ },\n+ "translations": {\n+ "@id": "http://localhost:55001/plone/en/@translations"\n+ },\n+ "types": {\n+ "@id": "http://localhost:55001/plone/en/@types"\n+ },\n+ "workflow": {\n+ "@id": "http://localhost:55001/plone/en/@workflow"\n+ }\n+ },\n+ "@id": "http://localhost:55001/plone/en",\n+ "@type": "LRF",\n+ "UID": "00000000000000000000000000000001",\n+ "allow_discussion": false,\n+ "contributors": [],\n+ "created": "1995-07-31T13:45:00",\n+ "creators": [\n+ "admin"\n+ ],\n+ "description": "",\n+ "effective": null,\n+ "exclude_from_nav": true,\n+ "expires": null,\n+ "id": "en",\n+ "is_folderish": true,\n+ "items": [\n+ {\n+ "@id": "http://localhost:55001/plone/en/assets",\n+ "@type": "LIF",\n+ "description": "",\n+ "review_state": "published",\n+ "title": "Assets"\n+ },\n+ {\n+ "@id": "http://localhost:55001/plone/en/test-document",\n+ "@type": "Document",\n+ "description": "",\n+ "review_state": "private",\n+ "title": "Test document"\n+ }\n+ ],\n+ "items_total": 2,\n+ "language": {\n+ "title": "English",\n+ "token": "en"\n+ },\n+ "layout": "folder_listing",\n+ "lock": {},\n+ "modified": "1995-07-31T17:30:00",\n+ "next_item": {\n+ "@id": "http://localhost:55001/plone/de",\n+ "@type": "LRF",\n+ "description": "",\n+ "title": "Deutsch"\n+ },\n+ "parent": {\n+ "@id": "http://localhost:55001/plone",\n+ "@type": "Plone Site",\n+ "description": "",\n+ "title": "Plone site"\n+ },\n+ "previous_item": {},\n+ "review_state": "published",\n+ "rights": "",\n+ "subjects": [],\n+ "title": "English",\n+ "version": "current"\n+ }\n+}\ndiff --git a/src/plone/restapi/tests/http-examples/navroot_lang_folder_get.req b/src/plone/restapi/tests/http-examples/navroot_lang_folder_get.req\nnew file mode 100644\nindex 0000000000..5bbb15c017\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/navroot_lang_folder_get.req\n@@ -0,0 +1,3 @@\n+GET /plone/en/@navroot HTTP/1.1\n+Accept: application/json\n+Authorization: Basic YWRtaW46c2VjcmV0\ndiff --git a/src/plone/restapi/tests/http-examples/navroot_lang_folder_get.resp b/src/plone/restapi/tests/http-examples/navroot_lang_folder_get.resp\nnew file mode 100644\nindex 0000000000..bc30c5d934\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/navroot_lang_folder_get.resp\n@@ -0,0 +1,91 @@\n+HTTP/1.1 200 OK\n+Content-Type: application/json\n+\n+{\n+ "@id": "http://localhost:55001/plone/en/@navroot",\n+ "navroot": {\n+ "@components": {\n+ "actions": {\n+ "@id": "http://localhost:55001/plone/en/@actions"\n+ },\n+ "breadcrumbs": {\n+ "@id": "http://localhost:55001/plone/en/@breadcrumbs"\n+ },\n+ "contextnavigation": {\n+ "@id": "http://localhost:55001/plone/en/@contextnavigation"\n+ },\n+ "navigation": {\n+ "@id": "http://localhost:55001/plone/en/@navigation"\n+ },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/en/@navroot"\n+ },\n+ "translations": {\n+ "@id": "http://localhost:55001/plone/en/@translations"\n+ },\n+ "types": {\n+ "@id": "http://localhost:55001/plone/en/@types"\n+ },\n+ "workflow": {\n+ "@id": "http://localhost:55001/plone/en/@workflow"\n+ }\n+ },\n+ "@id": "http://localhost:55001/plone/en",\n+ "@type": "LRF",\n+ "UID": "00000000000000000000000000000001",\n+ "allow_discussion": false,\n+ "contributors": [],\n+ "created": "1995-07-31T13:45:00",\n+ "creators": [\n+ "admin"\n+ ],\n+ "description": "",\n+ "effective": null,\n+ "exclude_from_nav": true,\n+ "expires": null,\n+ "id": "en",\n+ "is_folderish": true,\n+ "items": [\n+ {\n+ "@id": "http://localhost:55001/plone/en/assets",\n+ "@type": "LIF",\n+ "description": "",\n+ "review_state": "published",\n+ "title": "Assets"\n+ },\n+ {\n+ "@id": "http://localhost:55001/plone/en/test-document",\n+ "@type": "Document",\n+ "description": "",\n+ "review_state": "private",\n+ "title": "Test document"\n+ }\n+ ],\n+ "items_total": 2,\n+ "language": {\n+ "title": "English",\n+ "token": "en"\n+ },\n+ "layout": "folder_listing",\n+ "lock": {},\n+ "modified": "1995-07-31T17:30:00",\n+ "next_item": {\n+ "@id": "http://localhost:55001/plone/de",\n+ "@type": "LRF",\n+ "description": "",\n+ "title": "Deutsch"\n+ },\n+ "parent": {\n+ "@id": "http://localhost:55001/plone",\n+ "@type": "Plone Site",\n+ "description": "",\n+ "title": "Plone site"\n+ },\n+ "previous_item": {},\n+ "review_state": "published",\n+ "rights": "",\n+ "subjects": [],\n+ "title": "English",\n+ "version": "current"\n+ }\n+}\ndiff --git a/src/plone/restapi/tests/http-examples/navroot_site_get.req b/src/plone/restapi/tests/http-examples/navroot_site_get.req\nnew file mode 100644\nindex 0000000000..cc9c5ca1e8\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/navroot_site_get.req\n@@ -0,0 +1,3 @@\n+GET /plone/@navroot HTTP/1.1\n+Accept: application/json\n+Authorization: Basic YWRtaW46c2VjcmV0\ndiff --git a/src/plone/restapi/tests/http-examples/navroot_site_get.resp b/src/plone/restapi/tests/http-examples/navroot_site_get.resp\nnew file mode 100644\nindex 0000000000..5879b552e3\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/navroot_site_get.resp\n@@ -0,0 +1,65 @@\n+HTTP/1.1 200 OK\n+Content-Type: application/json\n+\n+{\n+ "@id": "http://localhost:55001/plone/@navroot",\n+ "navroot": {\n+ "@components": {\n+ "actions": {\n+ "@id": "http://localhost:55001/plone/@actions"\n+ },\n+ "breadcrumbs": {\n+ "@id": "http://localhost:55001/plone/@breadcrumbs"\n+ },\n+ "contextnavigation": {\n+ "@id": "http://localhost:55001/plone/@contextnavigation"\n+ },\n+ "navigation": {\n+ "@id": "http://localhost:55001/plone/@navigation"\n+ },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/@navroot"\n+ }\n+ },\n+ "@id": "http://localhost:55001/plone/@navroot",\n+ "@type": "Plone Site",\n+ "blocks": {},\n+ "blocks_layout": {},\n+ "description": "",\n+ "id": "plone",\n+ "is_folderish": true,\n+ "items": [\n+ {\n+ "@id": "http://localhost:55001/plone/en",\n+ "@type": "LRF",\n+ "description": "",\n+ "review_state": "published",\n+ "title": "English"\n+ },\n+ {\n+ "@id": "http://localhost:55001/plone/de",\n+ "@type": "LRF",\n+ "description": "",\n+ "review_state": "published",\n+ "title": "Deutsch"\n+ },\n+ {\n+ "@id": "http://localhost:55001/plone/es",\n+ "@type": "LRF",\n+ "description": "",\n+ "review_state": "published",\n+ "title": "Espa\\u00f1ol"\n+ },\n+ {\n+ "@id": "http://localhost:55001/plone/fr",\n+ "@type": "LRF",\n+ "description": "",\n+ "review_state": null,\n+ "title": "Fran\\u00e7ais"\n+ }\n+ ],\n+ "items_total": 4,\n+ "parent": {},\n+ "title": "Plone site"\n+ }\n+}\ndiff --git a/src/plone/restapi/tests/http-examples/navroot_standard_site_content_get.req b/src/plone/restapi/tests/http-examples/navroot_standard_site_content_get.req\nnew file mode 100644\nindex 0000000000..4611396f72\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/navroot_standard_site_content_get.req\n@@ -0,0 +1,3 @@\n+GET /plone/front-page/@navroot HTTP/1.1\n+Accept: application/json\n+Authorization: Basic YWRtaW46c2VjcmV0\ndiff --git a/src/plone/restapi/tests/http-examples/navroot_standard_site_content_get.resp b/src/plone/restapi/tests/http-examples/navroot_standard_site_content_get.resp\nnew file mode 100644\nindex 0000000000..0c374d5389\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/navroot_standard_site_content_get.resp\n@@ -0,0 +1,76 @@\n+HTTP/1.1 200 OK\n+Content-Type: application/json\n+\n+{\n+ "@id": "http://localhost:55001/plone/front-page/@navroot",\n+ "navroot": {\n+ "@components": {\n+ "actions": {\n+ "@id": "http://localhost:55001/plone/@actions"\n+ },\n+ "aliases": {\n+ "@id": "http://localhost:55001/plone/@aliases"\n+ },\n+ "breadcrumbs": {\n+ "@id": "http://localhost:55001/plone/@breadcrumbs"\n+ },\n+ "contextnavigation": {\n+ "@id": "http://localhost:55001/plone/@contextnavigation"\n+ },\n+ "navigation": {\n+ "@id": "http://localhost:55001/plone/@navigation"\n+ },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/@navroot"\n+ },\n+ "types": {\n+ "@id": "http://localhost:55001/plone/@types"\n+ },\n+ "workflow": {\n+ "@id": "http://localhost:55001/plone/@workflow"\n+ }\n+ },\n+ "@id": "http://localhost:55001/plone",\n+ "@type": "Plone Site",\n+ "UID": "55c25ebc220d400393574f37d648727c",\n+ "allow_discussion": null,\n+ "contributors": [],\n+ "creators": [\n+ "admin"\n+ ],\n+ "description": "",\n+ "effective": null,\n+ "exclude_from_nav": false,\n+ "expires": null,\n+ "id": "plone",\n+ "is_folderish": true,\n+ "items": [\n+ {\n+ "@id": "http://localhost:55001/plone/front-page",\n+ "@type": "Document",\n+ "description": "Congratulations! You have successfully installed Plone.",\n+ "review_state": "private",\n+ "title": "Welcome to Plone",\n+ "type_title": "Page"\n+ }\n+ ],\n+ "items_total": 1,\n+ "language": {\n+ "title": "English",\n+ "token": "en"\n+ },\n+ "lock": {\n+ "locked": false,\n+ "stealable": true\n+ },\n+ "parent": {},\n+ "relatedItems": [],\n+ "review_state": null,\n+ "rights": "",\n+ "subjects": [],\n+ "table_of_contents": null,\n+ "text": null,\n+ "title": "Plone site",\n+ "type_title": "Plone Site"\n+ }\n+}\ndiff --git a/src/plone/restapi/tests/http-examples/navroot_standard_site_content_get_expansion.req b/src/plone/restapi/tests/http-examples/navroot_standard_site_content_get_expansion.req\nnew file mode 100644\nindex 0000000000..20266313b2\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/navroot_standard_site_content_get_expansion.req\n@@ -0,0 +1,3 @@\n+GET /plone/front-page?expand=navroot HTTP/1.1\n+Accept: application/json\n+Authorization: Basic YWRtaW46c2VjcmV0\ndiff --git a/src/plone/restapi/tests/http-examples/navroot_standard_site_content_get_expansion.resp b/src/plone/restapi/tests/http-examples/navroot_standard_site_content_get_expansion.resp\nnew file mode 100644\nindex 0000000000..abfbe7e93f\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/navroot_standard_site_content_get_expansion.resp\n@@ -0,0 +1,149 @@\n+HTTP/1.1 200 OK\n+Content-Type: application/json\n+\n+{\n+ "@components": {\n+ "actions": {\n+ "@id": "http://localhost:55001/plone/front-page/@actions"\n+ },\n+ "aliases": {\n+ "@id": "http://localhost:55001/plone/front-page/@aliases"\n+ },\n+ "breadcrumbs": {\n+ "@id": "http://localhost:55001/plone/front-page/@breadcrumbs"\n+ },\n+ "contextnavigation": {\n+ "@id": "http://localhost:55001/plone/front-page/@contextnavigation"\n+ },\n+ "navigation": {\n+ "@id": "http://localhost:55001/plone/front-page/@navigation"\n+ },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/front-page/@navroot",\n+ "navroot": {\n+ "@components": {\n+ "actions": {\n+ "@id": "http://localhost:55001/plone/@actions"\n+ },\n+ "aliases": {\n+ "@id": "http://localhost:55001/plone/@aliases"\n+ },\n+ "breadcrumbs": {\n+ "@id": "http://localhost:55001/plone/@breadcrumbs"\n+ },\n+ "contextnavigation": {\n+ "@id": "http://localhost:55001/plone/@contextnavigation"\n+ },\n+ "navigation": {\n+ "@id": "http://localhost:55001/plone/@navigation"\n+ },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/@navroot"\n+ },\n+ "types": {\n+ "@id": "http://localhost:55001/plone/@types"\n+ },\n+ "workflow": {\n+ "@id": "http://localhost:55001/plone/@workflow"\n+ }\n+ },\n+ "@id": "http://localhost:55001/plone",\n+ "@type": "Plone Site",\n+ "UID": "55c25ebc220d400393574f37d648727c",\n+ "allow_discussion": null,\n+ "contributors": [],\n+ "creators": [\n+ "admin"\n+ ],\n+ "description": "",\n+ "effective": null,\n+ "exclude_from_nav": false,\n+ "expires": null,\n+ "id": "plone",\n+ "is_folderish": true,\n+ "items": [\n+ {\n+ "@id": "http://localhost:55001/plone/front-page",\n+ "@type": "Document",\n+ "description": "Congratulations! You have successfully installed Plone.",\n+ "review_state": "private",\n+ "title": "Welcome to Plone",\n+ "type_title": "Page"\n+ }\n+ ],\n+ "items_total": 1,\n+ "language": {\n+ "title": "English",\n+ "token": "en"\n+ },\n+ "lock": {\n+ "locked": false,\n+ "stealable": true\n+ },\n+ "parent": {},\n+ "relatedItems": [],\n+ "review_state": null,\n+ "rights": "",\n+ "subjects": [],\n+ "table_of_contents": null,\n+ "text": null,\n+ "title": "Plone site",\n+ "type_title": "Plone Site"\n+ }\n+ },\n+ "types": {\n+ "@id": "http://localhost:55001/plone/front-page/@types"\n+ },\n+ "workflow": {\n+ "@id": "http://localhost:55001/plone/front-page/@workflow"\n+ }\n+ },\n+ "@id": "http://localhost:55001/plone/front-page",\n+ "@type": "Document",\n+ "UID": "SomeUUID000000000000000000000001",\n+ "allow_discussion": false,\n+ "changeNote": "",\n+ "contributors": [],\n+ "created": "1995-07-31T13:45:00",\n+ "creators": [\n+ "test_user_1_"\n+ ],\n+ "description": "Congratulations! You have successfully installed Plone.",\n+ "effective": null,\n+ "exclude_from_nav": false,\n+ "expires": null,\n+ "id": "front-page",\n+ "is_folderish": false,\n+ "language": "",\n+ "layout": "document_view",\n+ "lock": {\n+ "locked": false,\n+ "stealable": true\n+ },\n+ "modified": "1995-07-31T17:30:00",\n+ "next_item": {},\n+ "parent": {\n+ "@id": "http://localhost:55001/plone",\n+ "@type": "Plone Site",\n+ "description": "",\n+ "title": "Plone site",\n+ "type_title": "Plone Site"\n+ },\n+ "previous_item": {},\n+ "relatedItems": [],\n+ "review_state": "private",\n+ "rights": "",\n+ "subjects": [],\n+ "table_of_contents": null,\n+ "text": {\n+ "content-type": "text/plain",\n+ "data": "

If you're seeing this instead of the web site you were expecting, the owner of this web site has just installed Plone. Do not contact the Plone Team or the Plone mailing lists about this.

",\n+ "encoding": "utf-8"\n+ },\n+ "title": "Welcome to Plone",\n+ "type_title": "Page",\n+ "version": "current",\n+ "versioning_enabled": true,\n+ "working_copy": null,\n+ "working_copy_of": null\n+}\ndiff --git a/src/plone/restapi/tests/http-examples/navroot_standard_site_get.req b/src/plone/restapi/tests/http-examples/navroot_standard_site_get.req\nnew file mode 100644\nindex 0000000000..cc9c5ca1e8\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/navroot_standard_site_get.req\n@@ -0,0 +1,3 @@\n+GET /plone/@navroot HTTP/1.1\n+Accept: application/json\n+Authorization: Basic YWRtaW46c2VjcmV0\ndiff --git a/src/plone/restapi/tests/http-examples/navroot_standard_site_get.resp b/src/plone/restapi/tests/http-examples/navroot_standard_site_get.resp\nnew file mode 100644\nindex 0000000000..532ec7d6ca\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/navroot_standard_site_get.resp\n@@ -0,0 +1,76 @@\n+HTTP/1.1 200 OK\n+Content-Type: application/json\n+\n+{\n+ "@id": "http://localhost:55001/plone/@navroot",\n+ "navroot": {\n+ "@components": {\n+ "actions": {\n+ "@id": "http://localhost:55001/plone/@actions"\n+ },\n+ "aliases": {\n+ "@id": "http://localhost:55001/plone/@aliases"\n+ },\n+ "breadcrumbs": {\n+ "@id": "http://localhost:55001/plone/@breadcrumbs"\n+ },\n+ "contextnavigation": {\n+ "@id": "http://localhost:55001/plone/@contextnavigation"\n+ },\n+ "navigation": {\n+ "@id": "http://localhost:55001/plone/@navigation"\n+ },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/@navroot"\n+ },\n+ "types": {\n+ "@id": "http://localhost:55001/plone/@types"\n+ },\n+ "workflow": {\n+ "@id": "http://localhost:55001/plone/@workflow"\n+ }\n+ },\n+ "@id": "http://localhost:55001/plone",\n+ "@type": "Plone Site",\n+ "UID": "55c25ebc220d400393574f37d648727c",\n+ "allow_discussion": null,\n+ "contributors": [],\n+ "creators": [\n+ "admin"\n+ ],\n+ "description": "",\n+ "effective": null,\n+ "exclude_from_nav": false,\n+ "expires": null,\n+ "id": "plone",\n+ "is_folderish": true,\n+ "items": [\n+ {\n+ "@id": "http://localhost:55001/plone/front-page",\n+ "@type": "Document",\n+ "description": "Congratulations! You have successfully installed Plone.",\n+ "review_state": "private",\n+ "title": "Welcome to Plone",\n+ "type_title": "Page"\n+ }\n+ ],\n+ "items_total": 1,\n+ "language": {\n+ "title": "English",\n+ "token": "en"\n+ },\n+ "lock": {\n+ "locked": false,\n+ "stealable": true\n+ },\n+ "parent": {},\n+ "relatedItems": [],\n+ "review_state": null,\n+ "rights": "",\n+ "subjects": [],\n+ "table_of_contents": null,\n+ "text": null,\n+ "title": "Plone site",\n+ "type_title": "Plone Site"\n+ }\n+}\ndiff --git a/src/plone/restapi/tests/http-examples/navroot_standard_site_get_expansion.req b/src/plone/restapi/tests/http-examples/navroot_standard_site_get_expansion.req\nnew file mode 100644\nindex 0000000000..36c802e247\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/navroot_standard_site_get_expansion.req\n@@ -0,0 +1,3 @@\n+GET /plone/?expand=navroot HTTP/1.1\n+Accept: application/json\n+Authorization: Basic YWRtaW46c2VjcmV0\ndiff --git a/src/plone/restapi/tests/http-examples/navroot_standard_site_get_expansion.resp b/src/plone/restapi/tests/http-examples/navroot_standard_site_get_expansion.resp\nnew file mode 100644\nindex 0000000000..0a33c7475c\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/navroot_standard_site_get_expansion.resp\n@@ -0,0 +1,143 @@\n+HTTP/1.1 200 OK\n+Content-Type: application/json\n+\n+{\n+ "@components": {\n+ "actions": {\n+ "@id": "http://localhost:55001/plone/@actions"\n+ },\n+ "aliases": {\n+ "@id": "http://localhost:55001/plone/@aliases"\n+ },\n+ "breadcrumbs": {\n+ "@id": "http://localhost:55001/plone/@breadcrumbs"\n+ },\n+ "contextnavigation": {\n+ "@id": "http://localhost:55001/plone/@contextnavigation"\n+ },\n+ "navigation": {\n+ "@id": "http://localhost:55001/plone/@navigation"\n+ },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/@navroot",\n+ "navroot": {\n+ "@components": {\n+ "actions": {\n+ "@id": "http://localhost:55001/plone/@actions"\n+ },\n+ "aliases": {\n+ "@id": "http://localhost:55001/plone/@aliases"\n+ },\n+ "breadcrumbs": {\n+ "@id": "http://localhost:55001/plone/@breadcrumbs"\n+ },\n+ "contextnavigation": {\n+ "@id": "http://localhost:55001/plone/@contextnavigation"\n+ },\n+ "navigation": {\n+ "@id": "http://localhost:55001/plone/@navigation"\n+ },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/@navroot"\n+ },\n+ "types": {\n+ "@id": "http://localhost:55001/plone/@types"\n+ },\n+ "workflow": {\n+ "@id": "http://localhost:55001/plone/@workflow"\n+ }\n+ },\n+ "@id": "http://localhost:55001/plone",\n+ "@type": "Plone Site",\n+ "UID": "55c25ebc220d400393574f37d648727c",\n+ "allow_discussion": null,\n+ "contributors": [],\n+ "creators": [\n+ "admin"\n+ ],\n+ "description": "",\n+ "effective": null,\n+ "exclude_from_nav": false,\n+ "expires": null,\n+ "id": "plone",\n+ "is_folderish": true,\n+ "items": [\n+ {\n+ "@id": "http://localhost:55001/plone/front-page",\n+ "@type": "Document",\n+ "description": "Congratulations! You have successfully installed Plone.",\n+ "review_state": "private",\n+ "title": "Welcome to Plone",\n+ "type_title": "Page"\n+ }\n+ ],\n+ "items_total": 1,\n+ "language": {\n+ "title": "English",\n+ "token": "en"\n+ },\n+ "lock": {\n+ "locked": false,\n+ "stealable": true\n+ },\n+ "parent": {},\n+ "relatedItems": [],\n+ "review_state": null,\n+ "rights": "",\n+ "subjects": [],\n+ "table_of_contents": null,\n+ "text": null,\n+ "title": "Plone site",\n+ "type_title": "Plone Site"\n+ }\n+ },\n+ "types": {\n+ "@id": "http://localhost:55001/plone/@types"\n+ },\n+ "workflow": {\n+ "@id": "http://localhost:55001/plone/@workflow"\n+ }\n+ },\n+ "@id": "http://localhost:55001/plone",\n+ "@type": "Plone Site",\n+ "UID": "55c25ebc220d400393574f37d648727c",\n+ "allow_discussion": null,\n+ "contributors": [],\n+ "creators": [\n+ "admin"\n+ ],\n+ "description": "",\n+ "effective": null,\n+ "exclude_from_nav": false,\n+ "expires": null,\n+ "id": "plone",\n+ "is_folderish": true,\n+ "items": [\n+ {\n+ "@id": "http://localhost:55001/plone/front-page",\n+ "@type": "Document",\n+ "description": "Congratulations! You have successfully installed Plone.",\n+ "review_state": "private",\n+ "title": "Welcome to Plone",\n+ "type_title": "Page"\n+ }\n+ ],\n+ "items_total": 1,\n+ "language": {\n+ "title": "English",\n+ "token": "en"\n+ },\n+ "lock": {\n+ "locked": false,\n+ "stealable": true\n+ },\n+ "parent": {},\n+ "relatedItems": [],\n+ "review_state": null,\n+ "rights": "",\n+ "subjects": [],\n+ "table_of_contents": null,\n+ "text": null,\n+ "title": "Plone site",\n+ "type_title": "Plone Site"\n+}\ndiff --git a/src/plone/restapi/tests/http-examples/newsitem.resp b/src/plone/restapi/tests/http-examples/newsitem.resp\nindex 7b13607f99..ddf4779998 100644\n--- a/src/plone/restapi/tests/http-examples/newsitem.resp\n+++ b/src/plone/restapi/tests/http-examples/newsitem.resp\n@@ -15,6 +15,9 @@ Content-Type: application/json\n "navigation": {\n "@id": "http://localhost:55001/plone/newsitem/@navigation"\n },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/newsitem/@navroot"\n+ },\n "types": {\n "@id": "http://localhost:55001/plone/newsitem/@types"\n },\ndiff --git a/src/plone/restapi/tests/http-examples/search_fullobjects.resp b/src/plone/restapi/tests/http-examples/search_fullobjects.resp\nindex 58edeb566a..a32e3555a4 100644\n--- a/src/plone/restapi/tests/http-examples/search_fullobjects.resp\n+++ b/src/plone/restapi/tests/http-examples/search_fullobjects.resp\n@@ -18,6 +18,9 @@ Content-Type: application/json\n "navigation": {\n "@id": "http://localhost:55001/plone/doc1/@navigation"\n },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/doc1/@navroot"\n+ },\n "types": {\n "@id": "http://localhost:55001/plone/doc1/@types"\n },\ndiff --git a/src/plone/restapi/tests/http-examples/site_get.req b/src/plone/restapi/tests/http-examples/site_get.req\nnew file mode 100644\nindex 0000000000..2c0e40f0e2\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/site_get.req\n@@ -0,0 +1,3 @@\n+GET /plone/@site HTTP/1.1\n+Accept: application/json\n+Authorization: Basic YWRtaW46c2VjcmV0\ndiff --git a/src/plone/restapi/tests/http-examples/site_get.resp b/src/plone/restapi/tests/http-examples/site_get.resp\nnew file mode 100644\nindex 0000000000..e5f274d3de\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/site_get.resp\n@@ -0,0 +1,22 @@\n+HTTP/1.1 200 OK\n+Content-Type: application/json\n+\n+{\n+ "@id": "http://localhost:55001/plone/@site",\n+ "plone.allowed_sizes": [\n+ "huge 1600:65536",\n+ "great 1200:65536",\n+ "larger 1000:65536",\n+ "large 800:65536",\n+ "teaser 600:65536",\n+ "preview 400:65536",\n+ "mini 200:65536",\n+ "thumb 128:128",\n+ "tile 64:64",\n+ "icon 32:32",\n+ "listing 16:16"\n+ ],\n+ "plone.robots_txt": "Sitemap: {portal_url}/sitemap.xml.gz\\n\\n# Define access-restrictions for robots/spiders\\n# http://www.robotstxt.org/wc/norobots.html\\n\\n\\n\\n# By default we allow robots to access all areas of our site\\n# already accessible to anonymous users\\n\\nUser-agent: *\\nDisallow:\\n\\n\\n\\n# Add Googlebot-specific syntax extension to exclude forms\\n# that are repeated for each piece of content in the site\\n# the wildcard is only supported by Googlebot\\n# http://www.google.com/support/webmasters/bin/answer.py?answer=40367&ctx=sibling\\n\\nUser-Agent: Googlebot\\nDisallow: /*?\\nDisallow: /*atct_album_view$\\nDisallow: /*folder_factories$\\nDisallow: /*folder_summary_view$\\nDisallow: /*login_form$\\nDisallow: /*mail_password_form$\\nDisallow: /@@search\\nDisallow: /*search_rss$\\nDisallow: /*sendto_form$\\nDisallow: /*summary_view$\\nDisallow: /*thumbnail_view$\\nDisallow: /*view$\\n",\n+ "plone.site_logo": null,\n+ "plone.site_title": "Plone site"\n+}\ndiff --git a/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder.req b/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder.req\nnew file mode 100644\nindex 0000000000..5eae3bdfb1\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder.req\n@@ -0,0 +1,3 @@\n+GET /plone/en?expand=navroot HTTP/1.1\n+Accept: application/json\n+Authorization: Basic YWRtaW46c2VjcmV0\ndiff --git a/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder.resp b/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder.resp\nnew file mode 100644\nindex 0000000000..d5c1a0f186\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder.resp\n@@ -0,0 +1,173 @@\n+HTTP/1.1 200 OK\n+Content-Type: application/json\n+\n+{\n+ "@components": {\n+ "actions": {\n+ "@id": "http://localhost:55001/plone/en/@actions"\n+ },\n+ "breadcrumbs": {\n+ "@id": "http://localhost:55001/plone/en/@breadcrumbs"\n+ },\n+ "contextnavigation": {\n+ "@id": "http://localhost:55001/plone/en/@contextnavigation"\n+ },\n+ "navigation": {\n+ "@id": "http://localhost:55001/plone/en/@navigation"\n+ },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/en/@navroot",\n+ "navroot": {\n+ "@components": {\n+ "actions": {\n+ "@id": "http://localhost:55001/plone/en/@actions"\n+ },\n+ "breadcrumbs": {\n+ "@id": "http://localhost:55001/plone/en/@breadcrumbs"\n+ },\n+ "contextnavigation": {\n+ "@id": "http://localhost:55001/plone/en/@contextnavigation"\n+ },\n+ "navigation": {\n+ "@id": "http://localhost:55001/plone/en/@navigation"\n+ },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/en/@navroot"\n+ },\n+ "translations": {\n+ "@id": "http://localhost:55001/plone/en/@translations"\n+ },\n+ "types": {\n+ "@id": "http://localhost:55001/plone/en/@types"\n+ },\n+ "workflow": {\n+ "@id": "http://localhost:55001/plone/en/@workflow"\n+ }\n+ },\n+ "@id": "http://localhost:55001/plone/en",\n+ "@type": "LRF",\n+ "UID": "00000000000000000000000000000001",\n+ "allow_discussion": false,\n+ "contributors": [],\n+ "created": "1995-07-31T13:45:00",\n+ "creators": [\n+ "admin"\n+ ],\n+ "description": "",\n+ "effective": null,\n+ "exclude_from_nav": true,\n+ "expires": null,\n+ "id": "en",\n+ "is_folderish": true,\n+ "items": [\n+ {\n+ "@id": "http://localhost:55001/plone/en/assets",\n+ "@type": "LIF",\n+ "description": "",\n+ "review_state": "published",\n+ "title": "Assets"\n+ },\n+ {\n+ "@id": "http://localhost:55001/plone/en/test-document",\n+ "@type": "Document",\n+ "description": "",\n+ "review_state": "private",\n+ "title": "Test document"\n+ }\n+ ],\n+ "items_total": 2,\n+ "language": {\n+ "title": "English",\n+ "token": "en"\n+ },\n+ "layout": "folder_listing",\n+ "lock": {},\n+ "modified": "1995-07-31T17:30:00",\n+ "next_item": {\n+ "@id": "http://localhost:55001/plone/de",\n+ "@type": "LRF",\n+ "description": "",\n+ "title": "Deutsch"\n+ },\n+ "parent": {\n+ "@id": "http://localhost:55001/plone",\n+ "@type": "Plone Site",\n+ "description": "",\n+ "title": "Plone site"\n+ },\n+ "previous_item": {},\n+ "review_state": "published",\n+ "rights": "",\n+ "subjects": [],\n+ "title": "English",\n+ "version": "current"\n+ }\n+ },\n+ "translations": {\n+ "@id": "http://localhost:55001/plone/en/@translations"\n+ },\n+ "types": {\n+ "@id": "http://localhost:55001/plone/en/@types"\n+ },\n+ "workflow": {\n+ "@id": "http://localhost:55001/plone/en/@workflow"\n+ }\n+ },\n+ "@id": "http://localhost:55001/plone/en",\n+ "@type": "LRF",\n+ "UID": "00000000000000000000000000000001",\n+ "allow_discussion": false,\n+ "contributors": [],\n+ "created": "1995-07-31T13:45:00",\n+ "creators": [\n+ "admin"\n+ ],\n+ "description": "",\n+ "effective": null,\n+ "exclude_from_nav": true,\n+ "expires": null,\n+ "id": "en",\n+ "is_folderish": true,\n+ "items": [\n+ {\n+ "@id": "http://localhost:55001/plone/en/assets",\n+ "@type": "LIF",\n+ "description": "",\n+ "review_state": "published",\n+ "title": "Assets"\n+ },\n+ {\n+ "@id": "http://localhost:55001/plone/en/test-document",\n+ "@type": "Document",\n+ "description": "",\n+ "review_state": "private",\n+ "title": "Test document"\n+ }\n+ ],\n+ "items_total": 2,\n+ "language": {\n+ "title": "English",\n+ "token": "en"\n+ },\n+ "layout": "folder_listing",\n+ "lock": {},\n+ "modified": "1995-07-31T17:30:00",\n+ "next_item": {\n+ "@id": "http://localhost:55001/plone/de",\n+ "@type": "LRF",\n+ "description": "",\n+ "title": "Deutsch"\n+ },\n+ "parent": {\n+ "@id": "http://localhost:55001/plone",\n+ "@type": "Plone Site",\n+ "description": "",\n+ "title": "Plone site"\n+ },\n+ "previous_item": {},\n+ "review_state": "published",\n+ "rights": "",\n+ "subjects": [],\n+ "title": "English",\n+ "version": "current"\n+}\ndiff --git a/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder_content.req b/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder_content.req\nnew file mode 100644\nindex 0000000000..3574d72d7e\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder_content.req\n@@ -0,0 +1,3 @@\n+GET /plone/en/test-document?expand=navroot HTTP/1.1\n+Accept: application/json\n+Authorization: Basic YWRtaW46c2VjcmV0\ndiff --git a/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder_content.resp b/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder_content.resp\nnew file mode 100644\nindex 0000000000..32745d5534\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder_content.resp\n@@ -0,0 +1,165 @@\n+HTTP/1.1 200 OK\n+Content-Type: application/json\n+\n+{\n+ "@components": {\n+ "actions": {\n+ "@id": "http://localhost:55001/plone/en/test-document/@actions"\n+ },\n+ "breadcrumbs": {\n+ "@id": "http://localhost:55001/plone/en/test-document/@breadcrumbs"\n+ },\n+ "contextnavigation": {\n+ "@id": "http://localhost:55001/plone/en/test-document/@contextnavigation"\n+ },\n+ "navigation": {\n+ "@id": "http://localhost:55001/plone/en/test-document/@navigation"\n+ },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/en/test-document/@navroot",\n+ "navroot": {\n+ "@components": {\n+ "actions": {\n+ "@id": "http://localhost:55001/plone/en/@actions"\n+ },\n+ "breadcrumbs": {\n+ "@id": "http://localhost:55001/plone/en/@breadcrumbs"\n+ },\n+ "contextnavigation": {\n+ "@id": "http://localhost:55001/plone/en/@contextnavigation"\n+ },\n+ "navigation": {\n+ "@id": "http://localhost:55001/plone/en/@navigation"\n+ },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/en/@navroot"\n+ },\n+ "translations": {\n+ "@id": "http://localhost:55001/plone/en/@translations"\n+ },\n+ "types": {\n+ "@id": "http://localhost:55001/plone/en/@types"\n+ },\n+ "workflow": {\n+ "@id": "http://localhost:55001/plone/en/@workflow"\n+ }\n+ },\n+ "@id": "http://localhost:55001/plone/en",\n+ "@type": "LRF",\n+ "UID": "00000000000000000000000000000001",\n+ "allow_discussion": false,\n+ "contributors": [],\n+ "created": "1995-07-31T13:45:00",\n+ "creators": [\n+ "admin"\n+ ],\n+ "description": "",\n+ "effective": null,\n+ "exclude_from_nav": true,\n+ "expires": null,\n+ "id": "en",\n+ "is_folderish": true,\n+ "items": [\n+ {\n+ "@id": "http://localhost:55001/plone/en/assets",\n+ "@type": "LIF",\n+ "description": "",\n+ "review_state": "published",\n+ "title": "Assets"\n+ },\n+ {\n+ "@id": "http://localhost:55001/plone/en/test-document",\n+ "@type": "Document",\n+ "description": "",\n+ "review_state": "private",\n+ "title": "Test document"\n+ }\n+ ],\n+ "items_total": 2,\n+ "language": {\n+ "title": "English",\n+ "token": "en"\n+ },\n+ "layout": "folder_listing",\n+ "lock": {},\n+ "modified": "1995-07-31T17:30:00",\n+ "next_item": {\n+ "@id": "http://localhost:55001/plone/de",\n+ "@type": "LRF",\n+ "description": "",\n+ "title": "Deutsch"\n+ },\n+ "parent": {\n+ "@id": "http://localhost:55001/plone",\n+ "@type": "Plone Site",\n+ "description": "",\n+ "title": "Plone site"\n+ },\n+ "previous_item": {},\n+ "review_state": "published",\n+ "rights": "",\n+ "subjects": [],\n+ "title": "English",\n+ "version": "current"\n+ }\n+ },\n+ "translations": {\n+ "@id": "http://localhost:55001/plone/en/test-document/@translations"\n+ },\n+ "types": {\n+ "@id": "http://localhost:55001/plone/en/test-document/@types"\n+ },\n+ "workflow": {\n+ "@id": "http://localhost:55001/plone/en/test-document/@workflow"\n+ }\n+ },\n+ "@id": "http://localhost:55001/plone/en/test-document",\n+ "@type": "Document",\n+ "UID": "SomeUUID000000000000000000000001",\n+ "allow_discussion": false,\n+ "changeNote": "",\n+ "contributors": [],\n+ "created": "1995-07-31T13:45:00",\n+ "creators": [\n+ "test_user_1_"\n+ ],\n+ "description": "",\n+ "effective": null,\n+ "exclude_from_nav": false,\n+ "expires": null,\n+ "id": "test-document",\n+ "is_folderish": false,\n+ "language": {\n+ "title": "English",\n+ "token": "en"\n+ },\n+ "layout": "document_view",\n+ "lock": {\n+ "locked": false,\n+ "stealable": true\n+ },\n+ "modified": "1995-07-31T17:30:00",\n+ "next_item": {},\n+ "parent": {\n+ "@id": "http://localhost:55001/plone/en",\n+ "@type": "LRF",\n+ "description": "",\n+ "review_state": "published",\n+ "title": "English"\n+ },\n+ "previous_item": {\n+ "@id": "http://localhost:55001/plone/en/assets",\n+ "@type": "LIF",\n+ "description": "",\n+ "title": "Assets"\n+ },\n+ "relatedItems": [],\n+ "review_state": "private",\n+ "rights": "",\n+ "subjects": [],\n+ "table_of_contents": null,\n+ "text": null,\n+ "title": "Test document",\n+ "version": "current",\n+ "versioning_enabled": true\n+}\ndiff --git a/src/plone/restapi/tests/http-examples/site_get_expand_navroot.req b/src/plone/restapi/tests/http-examples/site_get_expand_navroot.req\nnew file mode 100644\nindex 0000000000..36c802e247\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/site_get_expand_navroot.req\n@@ -0,0 +1,3 @@\n+GET /plone/?expand=navroot HTTP/1.1\n+Accept: application/json\n+Authorization: Basic YWRtaW46c2VjcmV0\ndiff --git a/src/plone/restapi/tests/http-examples/site_get_expand_navroot.resp b/src/plone/restapi/tests/http-examples/site_get_expand_navroot.resp\nnew file mode 100644\nindex 0000000000..b28d008a7a\n--- /dev/null\n+++ b/src/plone/restapi/tests/http-examples/site_get_expand_navroot.resp\n@@ -0,0 +1,121 @@\n+HTTP/1.1 200 OK\n+Content-Type: application/json\n+\n+{\n+ "@components": {\n+ "actions": {\n+ "@id": "http://localhost:55001/plone/@actions"\n+ },\n+ "breadcrumbs": {\n+ "@id": "http://localhost:55001/plone/@breadcrumbs"\n+ },\n+ "contextnavigation": {\n+ "@id": "http://localhost:55001/plone/@contextnavigation"\n+ },\n+ "navigation": {\n+ "@id": "http://localhost:55001/plone/@navigation"\n+ },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/@navroot",\n+ "navroot": {\n+ "@components": {\n+ "actions": {\n+ "@id": "http://localhost:55001/plone/@actions"\n+ },\n+ "breadcrumbs": {\n+ "@id": "http://localhost:55001/plone/@breadcrumbs"\n+ },\n+ "contextnavigation": {\n+ "@id": "http://localhost:55001/plone/@contextnavigation"\n+ },\n+ "navigation": {\n+ "@id": "http://localhost:55001/plone/@navigation"\n+ },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/@navroot"\n+ }\n+ },\n+ "@id": "http://localhost:55001/plone/?expand=navroot",\n+ "@type": "Plone Site",\n+ "blocks": {},\n+ "blocks_layout": {},\n+ "description": "",\n+ "id": "plone",\n+ "is_folderish": true,\n+ "items": [\n+ {\n+ "@id": "http://localhost:55001/plone/en",\n+ "@type": "LRF",\n+ "description": "",\n+ "review_state": "published",\n+ "title": "English"\n+ },\n+ {\n+ "@id": "http://localhost:55001/plone/de",\n+ "@type": "LRF",\n+ "description": "",\n+ "review_state": "published",\n+ "title": "Deutsch"\n+ },\n+ {\n+ "@id": "http://localhost:55001/plone/es",\n+ "@type": "LRF",\n+ "description": "",\n+ "review_state": "published",\n+ "title": "Espa\\u00f1ol"\n+ },\n+ {\n+ "@id": "http://localhost:55001/plone/fr",\n+ "@type": "LRF",\n+ "description": "",\n+ "review_state": null,\n+ "title": "Fran\\u00e7ais"\n+ }\n+ ],\n+ "items_total": 4,\n+ "parent": {},\n+ "title": "Plone site"\n+ }\n+ }\n+ },\n+ "@id": "http://localhost:55001/plone/?expand=navroot",\n+ "@type": "Plone Site",\n+ "blocks": {},\n+ "blocks_layout": {},\n+ "description": "",\n+ "id": "plone",\n+ "is_folderish": true,\n+ "items": [\n+ {\n+ "@id": "http://localhost:55001/plone/en",\n+ "@type": "LRF",\n+ "description": "",\n+ "review_state": "published",\n+ "title": "English"\n+ },\n+ {\n+ "@id": "http://localhost:55001/plone/de",\n+ "@type": "LRF",\n+ "description": "",\n+ "review_state": "published",\n+ "title": "Deutsch"\n+ },\n+ {\n+ "@id": "http://localhost:55001/plone/es",\n+ "@type": "LRF",\n+ "description": "",\n+ "review_state": "published",\n+ "title": "Espa\\u00f1ol"\n+ },\n+ {\n+ "@id": "http://localhost:55001/plone/fr",\n+ "@type": "LRF",\n+ "description": "",\n+ "review_state": null,\n+ "title": "Fran\\u00e7ais"\n+ }\n+ ],\n+ "items_total": 4,\n+ "parent": {},\n+ "title": "Plone site"\n+}\ndiff --git a/src/plone/restapi/tests/http-examples/siteroot.resp b/src/plone/restapi/tests/http-examples/siteroot.resp\nindex 4c00477092..a528d73376 100644\n--- a/src/plone/restapi/tests/http-examples/siteroot.resp\n+++ b/src/plone/restapi/tests/http-examples/siteroot.resp\n@@ -14,6 +14,9 @@ Content-Type: application/json\n },\n "navigation": {\n "@id": "http://localhost:55001/plone/@navigation"\n+ },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/@navroot"\n }\n },\n "@id": "http://localhost:55001/plone",\ndiff --git a/src/plone/restapi/tests/http-examples/translations_link_on_post.resp b/src/plone/restapi/tests/http-examples/translations_link_on_post.resp\nindex f82e131dfe..836ce9429d 100644\n--- a/src/plone/restapi/tests/http-examples/translations_link_on_post.resp\n+++ b/src/plone/restapi/tests/http-examples/translations_link_on_post.resp\n@@ -16,6 +16,9 @@ Location: http://localhost:55001/plone/de/mydocument\n "navigation": {\n "@id": "http://localhost:55001/plone/de/mydocument/@navigation"\n },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/de/mydocument/@navroot"\n+ },\n "translations": {\n "@id": "http://localhost:55001/plone/de/mydocument/@translations"\n },\ndiff --git a/src/plone/restapi/tests/http-examples/workingcopy_baseline_get.resp b/src/plone/restapi/tests/http-examples/workingcopy_baseline_get.resp\nindex 92a8ab1818..bb6eb48071 100644\n--- a/src/plone/restapi/tests/http-examples/workingcopy_baseline_get.resp\n+++ b/src/plone/restapi/tests/http-examples/workingcopy_baseline_get.resp\n@@ -5,80 +5,80 @@ Content-Type: application/json\n "@components": {\n "actions": {\n "@id": "http://localhost:55001/plone/document/@actions"\n- }, \n+ },\n "breadcrumbs": {\n "@id": "http://localhost:55001/plone/document/@breadcrumbs"\n- }, \n+ },\n "contextnavigation": {\n "@id": "http://localhost:55001/plone/document/@contextnavigation"\n- }, \n+ },\n "navigation": {\n "@id": "http://localhost:55001/plone/document/@navigation"\n- }, \n+ },\n "types": {\n "@id": "http://localhost:55001/plone/document/@types"\n- }, \n+ },\n "workflow": {\n "@id": "http://localhost:55001/plone/document/@workflow"\n }\n- }, \n- "@id": "http://localhost:55001/plone/document", \n- "@type": "Document", \n- "UID": "SomeUUID000000000000000000000001", \n- "allow_discussion": false, \n- "contributors": [], \n- "created": "1995-07-31T13:45:00", \n+ },\n+ "@id": "http://localhost:55001/plone/document",\n+ "@type": "Document",\n+ "UID": "SomeUUID000000000000000000000001",\n+ "allow_discussion": false,\n+ "contributors": [],\n+ "created": "1995-07-31T13:45:00",\n "creators": [\n "test_user_1_"\n- ], \n- "description": "", \n- "effective": null, \n- "exclude_from_nav": false, \n- "expires": null, \n- "id": "document", \n- "is_folderish": false, \n- "language": "", \n- "layout": "document_view", \n+ ],\n+ "description": "",\n+ "effective": null,\n+ "exclude_from_nav": false,\n+ "expires": null,\n+ "id": "document",\n+ "is_folderish": false,\n+ "language": "",\n+ "layout": "document_view",\n "lock": {\n- "created": "1995-07-31T17:30:00", \n- "creator": "admin", \n- "creator_name": "admin", \n- "creator_url": "http://localhost:55001/plone/author/admin", \n- "locked": true, \n- "name": "iterate.lock", \n- "stealable": false, \n- "time": 807201000.0, \n- "timeout": 4294967280, \n+ "created": "1995-07-31T17:30:00",\n+ "creator": "admin",\n+ "creator_name": "admin",\n+ "creator_url": "http://localhost:55001/plone/author/admin",\n+ "locked": true,\n+ "name": "iterate.lock",\n+ "stealable": false,\n+ "time": 807201000.0,\n+ "timeout": 4294967280,\n "token": "0.3217605825198149-0.016714612599692202-00105A989226:1629895144.744"\n- }, \n- "modified": "1995-07-31T17:30:00", \n+ },\n+ "modified": "1995-07-31T17:30:00",\n "next_item": {\n- "@id": "http://localhost:55001/plone/copy_of_document", \n- "@type": "Document", \n- "description": "", \n+ "@id": "http://localhost:55001/plone/copy_of_document",\n+ "@type": "Document",\n+ "description": "",\n "title": "Test document"\n- }, \n+ },\n "parent": {\n- "@id": "http://localhost:55001/plone", \n- "@type": "Plone Site", \n- "description": "", \n+ "@id": "http://localhost:55001/plone",\n+ "@type": "Plone Site",\n+ "description": "",\n "title": "Plone site"\n- }, \n- "previous_item": {}, \n- "relatedItems": [], \n- "review_state": "private", \n- "rights": "", \n- "subjects": [], \n- "table_of_contents": null, \n- "text": null, \n- "title": "Test document", \n- "version": "current", \n+ },\n+ "previous_item": {},\n+ "relatedItems": [],\n+ "review_state": "private",\n+ "rights": "",\n+ "subjects": [],\n+ "table_of_contents": null,\n+ "text": null,\n+ "title": "Test document",\n+ "version": "current",\n "working_copy": {\n- "@id": "http://localhost:55001/plone/copy_of_document", \n- "created": "1995-07-31T13:45:00", \n- "creator_name": "admin", \n- "creator_url": "http://localhost:55001/plone/author/admin", \n+ "@id": "http://localhost:55001/plone/copy_of_document",\n+ "created": "1995-07-31T13:45:00",\n+ "creator_name": "admin",\n+ "creator_url": "http://localhost:55001/plone/author/admin",\n "title": "Test document"\n- }, \n+ },\n "working_copy_of": null\n-}\n\\ No newline at end of file\n+}\ndiff --git a/src/plone/restapi/tests/http-examples/workingcopy_wc_get.resp b/src/plone/restapi/tests/http-examples/workingcopy_wc_get.resp\nindex 9c4216f49b..cb7e871387 100644\n--- a/src/plone/restapi/tests/http-examples/workingcopy_wc_get.resp\n+++ b/src/plone/restapi/tests/http-examples/workingcopy_wc_get.resp\n@@ -2,78 +2,142 @@ HTTP/1.1 200 OK\n Content-Type: application/json\n \n {\n- "@components": {\n- "actions": {\n- "@id": "http://localhost:55001/plone/copy_of_document/@actions"\n- }, \n- "breadcrumbs": {\n- "@id": "http://localhost:55001/plone/copy_of_document/@breadcrumbs"\n- }, \n- "contextnavigation": {\n- "@id": "http://localhost:55001/plone/copy_of_document/@contextnavigation"\n- }, \n- "navigation": {\n- "@id": "http://localhost:55001/plone/copy_of_document/@navigation"\n- }, \n- "types": {\n- "@id": "http://localhost:55001/plone/copy_of_document/@types"\n- }, \n- "workflow": {\n- "@id": "http://localhost:55001/plone/copy_of_document/@workflow"\n+ "@components": {\n+ "actions": {\n+ "@id": "http://localhost:55001/plone/copy_of_document/@actions"\n+ },\n+ "aliases": {\n+ "@id": "http://localhost:55001/plone/copy_of_document/@aliases"\n+ },\n+ "breadcrumbs": {\n+ "@id": "http://localhost:55001/plone/copy_of_document/@breadcrumbs"\n+ },\n+ "contextnavigation": {\n+ "@id": "http://localhost:55001/plone/copy_of_document/@contextnavigation"\n+ },\n+ "navigation": {\n+ "@id": "http://localhost:55001/plone/copy_of_document/@navigation"\n+ },\n+ "navroot": {\n+ "@id": "http://localhost:55001/plone/copy_of_document/@navroot"\n+ },\n+ "types": {\n+ "@id": "http://localhost:55001/plone/copy_of_document/@types"\n+ },\n+ "workflow": {\n+ "@id": "http://localhost:55001/plone/copy_of_document/@workflow"\n+ }\n+ },\n+ "@id": "http://localhost:55001/plone/copy_of_document",\n+ "@type": "Document",\n+ "UID": "SomeUUID000000000000000000000002",\n+ "allow_discussion": false,\n+ "contributors": [],\n+ "created": "1995-07-31T13:45:00",\n+ "creators": [\n+ "test_user_1_"\n+ ],\n+ "description": "",\n+ "effective": null,\n+ "exclude_from_nav": false,\n+ "expires": null,\n+ "id": "copy_of_document",\n+ "is_folderish": false,\n+ "language": "",\n+ "layout": "document_view",\n+ "lock": {\n+ "locked": false,\n+ "stealable": true\n+ },\n+ "modified": "1995-07-31T17:30:00",\n+ "next_item": {},\n+ "parent": {\n+ "@id": "http://localhost:55001/plone",\n+ "@type": "Plone Site",\n+ "description": "",\n+ "title": "Plone site",\n+ "type_title": "Plone Site"\n+ },\n+ "previous_item": {\n+ "@id": "http://localhost:55001/plone/document",\n+ "@type": "Document",\n+ "description": "",\n+ "title": "Test document",\n+ "type_title": "Page"\n+ },\n+ "relatedItems": [],\n+ "review_state": "private",\n+ "rights": "",\n+ "subjects": [],\n+ "table_of_contents": null,\n+ "text": null,\n+ "title": "Test document",\n+ "type_title": "Page",\n+ "version": "current",\n+ "working_copy": {\n+ "@id": "http://localhost:55001/plone/copy_of_document",\n+ "created": "1995-07-31T13:45:00",\n+ "creator_name": "admin",\n+ "creator_url": "http://localhost:55001/plone/author/admin",\n+ "title": "Test document"\n+ },\n+ "working_copy_of": {\n+ "@id": "http://localhost:55001/plone/document",\n+ "title": "Test document"\n }\n- }, \n- "@id": "http://localhost:55001/plone/copy_of_document", \n- "@type": "Document", \n- "UID": "SomeUUID000000000000000000000002", \n- "allow_discussion": false, \n- "contributors": [], \n- "created": "1995-07-31T13:45:00", \n+ },\n+ "@id": "http://localhost:55001/plone/copy_of_document",\n+ "@type": "Document",\n+ "UID": "SomeUUID000000000000000000000002",\n+ "allow_discussion": false,\n+ "contributors": [],\n+ "created": "1995-07-31T13:45:00",\n "creators": [\n "test_user_1_"\n- ], \n- "description": "", \n- "effective": null, \n- "exclude_from_nav": false, \n- "expires": null, \n- "id": "copy_of_document", \n- "is_folderish": false, \n- "language": "", \n- "layout": "document_view", \n+ ],\n+ "description": "",\n+ "effective": null,\n+ "exclude_from_nav": false,\n+ "expires": null,\n+ "id": "copy_of_document",\n+ "is_folderish": false,\n+ "language": "",\n+ "layout": "document_view",\n "lock": {\n- "locked": false, \n+ "locked": false,\n "stealable": true\n- }, \n- "modified": "1995-07-31T17:30:00", \n- "next_item": {}, \n+ },\n+ "modified": "1995-07-31T17:30:00",\n+ "next_item": {},\n "parent": {\n- "@id": "http://localhost:55001/plone", \n- "@type": "Plone Site", \n- "description": "", \n+ "@id": "http://localhost:55001/plone",\n+ "@type": "Plone Site",\n+ "description": "",\n "title": "Plone site"\n- }, \n+ },\n "previous_item": {\n- "@id": "http://localhost:55001/plone/document", \n- "@type": "Document", \n- "description": "", \n+ "@id": "http://localhost:55001/plone/document",\n+ "@type": "Document",\n+ "description": "",\n "title": "Test document"\n- }, \n- "relatedItems": [], \n- "review_state": "private", \n- "rights": "", \n- "subjects": [], \n- "table_of_contents": null, \n- "text": null, \n- "title": "Test document", \n- "version": "current", \n+ },\n+ "relatedItems": [],\n+ "review_state": "private",\n+ "rights": "",\n+ "subjects": [],\n+ "table_of_contents": null,\n+ "text": null,\n+ "title": "Test document",\n+ "version": "current",\n "working_copy": {\n- "@id": "http://localhost:55001/plone/copy_of_document", \n- "created": "1995-07-31T13:45:00", \n- "creator_name": "admin", \n- "creator_url": "http://localhost:55001/plone/author/admin", \n+ "@id": "http://localhost:55001/plone/copy_of_document",\n+ "created": "1995-07-31T13:45:00",\n+ "creator_name": "admin",\n+ "creator_url": "http://localhost:55001/plone/author/admin",\n "title": "Test document"\n- }, \n+ },\n "working_copy_of": {\n- "@id": "http://localhost:55001/plone/document", \n+ "@id": "http://localhost:55001/plone/document",\n "title": "Test document"\n }\n }\n\\ No newline at end of file\ndiff --git a/src/plone/restapi/tests/test_documentation.py b/src/plone/restapi/tests/test_documentation.py\nindex f4b40c675e..5912a1f0f3 100644\n--- a/src/plone/restapi/tests/test_documentation.py\n+++ b/src/plone/restapi/tests/test_documentation.py\n@@ -8,7 +8,7 @@\n from plone.app.discussion.interfaces import IConversation\n from plone.app.discussion.interfaces import IDiscussionSettings\n from plone.app.discussion.interfaces import IReplies\n-from plone.app.testing import applyProfile\n+\n from plone.app.testing import popGlobalRegistry\n from plone.app.testing import pushGlobalRegistry\n from plone.app.testing import setRoles\n@@ -32,9 +32,8 @@\n from six.moves import range\n from zope.component import createObject\n from zope.component import getUtility\n-from zope.component.hooks import getSite\n from zope.interface import alsoProvides\n-\n+from zope.component.hooks import getSite\n import collections\n import json\n import os\n@@ -203,7 +202,8 @@ def setUp(self):\n self.browser = Browser(self.app)\n self.browser.handleErrors = False\n self.browser.addHeader(\n- "Authorization", "Basic %s:%s" % (SITE_OWNER_NAME, SITE_OWNER_PASSWORD)\n+ "Authorization",\n+ "Basic %s:%s" % (SITE_OWNER_NAME, SITE_OWNER_PASSWORD),\n )\n \n setRoles(self.portal, TEST_USER_ID, ["Manager"])\n@@ -234,7 +234,6 @@ def setUp(self):\n super(TestDocumentation, self).setUp()\n self.document = self.create_document()\n alsoProvides(self.document, ITTWLockable)\n-\n transaction.commit()\n \n def tearDown(self):\n@@ -271,7 +270,8 @@ def test_documentation_content_crud(self):\n transaction.commit()\n \n response = self.api_session.post(\n- folder.absolute_url(), json={"@type": "Document", "title": "My Document"}\n+ folder.absolute_url(),\n+ json={"@type": "Document", "title": "My Document"},\n )\n save_request_and_response_for_docs("content_post", response)\n \n@@ -432,7 +432,11 @@ def test_documentation_search_options(self):\n self.portal.invokeFactory("Folder", id="folder1", title="Folder 1")\n self.portal.folder1.invokeFactory("Folder", id="folder2", title="Folder 2")\n transaction.commit()\n- query = {"sort_on": "path", "path.query": "/plone/folder1", "path.depth": "1"}\n+ query = {\n+ "sort_on": "path",\n+ "path.query": "/plone/folder1",\n+ "path.depth": "1",\n+ }\n response = self.api_session.get("/@search", params=query)\n save_request_and_response_for_docs("search_options", response)\n \n@@ -453,7 +457,10 @@ def test_documentation_search_multiple_paths(self):\n def test_documentation_search_metadata_fields(self):\n self.portal.invokeFactory("Document", id="doc1", title="Lorem Ipsum")\n transaction.commit()\n- query = {"SearchableText": "lorem", "metadata_fields": ["modified", "created"]}\n+ query = {\n+ "SearchableText": "lorem",\n+ "metadata_fields": ["modified", "created"],\n+ }\n response = self.api_session.get("/@search", params=query)\n save_request_and_response_for_docs("search_metadata_fields", response)\n \n@@ -498,7 +505,8 @@ def test_documentation_registry_get(self):\n \n def test_documentation_registry_update(self):\n response = self.api_session.patch(\n- "/@registry/", json={"plone.app.querystring.field.path.title": "Value"}\n+ "/@registry/",\n+ json={"plone.app.querystring.field.path.title": "Value"},\n )\n save_request_and_response_for_docs("registry_update", response)\n \n@@ -782,7 +790,9 @@ def test_documentation_batching(self):\n ]\n for i in range(7):\n folder.invokeFactory(\n- "Document", id="doc-%s" % str(i + 1), title="Document %s" % str(i + 1)\n+ "Document",\n+ id="doc-%s" % str(i + 1),\n+ title="Document %s" % str(i + 1),\n )\n transaction.commit()\n \n@@ -860,7 +870,9 @@ def test_documentation_users_get(self):\n "location": "Cambridge, MA",\n }\n api.user.create(\n- email="noam.chomsky@example.com", username="noam", properties=properties\n+ email="noam.chomsky@example.com",\n+ username="noam",\n+ properties=properties,\n )\n transaction.commit()\n response = self.api_session.get("@users/noam")\n@@ -876,7 +888,9 @@ def test_documentation_users_anonymous_get(self):\n "location": "Cambridge, MA",\n }\n api.user.create(\n- email="noam.chomsky@example.com", username="noam", properties=properties\n+ email="noam.chomsky@example.com",\n+ username="noam",\n+ properties=properties,\n )\n transaction.commit()\n \n@@ -954,7 +968,9 @@ def test_documentation_users_filtered_get(self):\n "location": "Cambridge, MA",\n }\n api.user.create(\n- email="noam.chomsky@example.com", username="noam", properties=properties\n+ email="noam.chomsky@example.com",\n+ username="noam",\n+ properties=properties,\n )\n transaction.commit()\n response = self.api_session.get("@users", params={"query": "noa"})\n@@ -1003,7 +1019,9 @@ def test_documentation_users_update(self):\n "location": "Cambridge, MA",\n }\n api.user.create(\n- email="noam.chomsky@example.com", username="noam", properties=properties\n+ email="noam.chomsky@example.com",\n+ username="noam",\n+ properties=properties,\n )\n transaction.commit()\n \n@@ -1061,7 +1079,9 @@ def test_documentation_users_delete(self):\n "location": "Cambridge, MA",\n }\n api.user.create(\n- email="noam.chomsky@example.com", username="noam", properties=properties\n+ email="noam.chomsky@example.com",\n+ username="noam",\n+ properties=properties,\n )\n transaction.commit()\n \n@@ -1161,7 +1181,10 @@ def test_documentation_groups_update(self):\n \n response = self.api_session.patch(\n "/@groups/ploneteam",\n- json={"email": "ploneteam2@plone.org", "users": {TEST_USER_ID: False}},\n+ json={\n+ "email": "ploneteam2@plone.org",\n+ "users": {TEST_USER_ID: False},\n+ },\n )\n save_request_and_response_for_docs("groups_update", response)\n \n@@ -1211,7 +1234,10 @@ def test_documentation_navigation_tree(self):\n folder, u"Folder", id=u"subfolder2", title=u"SubFolder 2"\n )\n thirdlevelfolder = createContentInContainer(\n- subfolder1, u"Folder", id=u"thirdlevelfolder", title=u"Third Level Folder"\n+ subfolder1,\n+ u"Folder",\n+ id=u"thirdlevelfolder",\n+ title=u"Third Level Folder",\n )\n createContentInContainer(\n thirdlevelfolder,\n@@ -1242,7 +1268,10 @@ def test_documentation_contextnavigation(self):\n folder, u"Folder", id=u"subfolder2", title=u"SubFolder 2"\n )\n thirdlevelfolder = createContentInContainer(\n- subfolder1, u"Folder", id=u"thirdlevelfolder", title=u"Third Level Folder"\n+ subfolder1,\n+ u"Folder",\n+ id=u"thirdlevelfolder",\n+ title=u"Third Level Folder",\n )\n createContentInContainer(\n thirdlevelfolder,\n@@ -1289,7 +1318,12 @@ def test_documentation_copy_multiple(self):\n \n response = self.api_session.post(\n "/@copy",\n- json={"source": [self.document.absolute_url(), newsitem.absolute_url()]},\n+ json={\n+ "source": [\n+ self.document.absolute_url(),\n+ newsitem.absolute_url(),\n+ ]\n+ },\n )\n save_request_and_response_for_docs("copy_multiple", response)\n \n@@ -1334,7 +1368,10 @@ def test_documentation_vocabularies_get_filtered_by_token(self):\n \n def test_documentation_sources_get(self):\n api.content.create(\n- container=self.portal, id="doc", type="DXTestDocument", title=u"DX Document"\n+ container=self.portal,\n+ id="doc",\n+ type="DXTestDocument",\n+ title=u"DX Document",\n )\n transaction.commit()\n response = self.api_session.get("/doc/@sources/test_choice_with_source")\n@@ -1526,7 +1563,8 @@ def test_locking_lock(self):\n # Replace dynamic lock token with a static one\n response._content = re.sub(\n b\'"token": "[^"]+"\',\n- b\'"token": "0.684672730996-0.25195226375-00105A989226:1477076400.000"\', # noqa\n+ b\'"token":\'\n+ b\' "0.684672730996-0.25195226375-00105A989226:1477076400.000"\', # noqa\n response.content,\n )\n save_request_and_response_for_docs("lock", response)\n@@ -1539,7 +1577,8 @@ def test_locking_lock_nonstealable_and_timeout(self):\n # Replace dynamic lock token with a static one\n response._content = re.sub(\n b\'"token": "[^"]+"\',\n- b\'"token": "0.684672730996-0.25195226375-00105A989226:1477076400.000"\', # noqa\n+ b\'"token":\'\n+ b\' "0.684672730996-0.25195226375-00105A989226:1477076400.000"\', # noqa\n response.content,\n )\n save_request_and_response_for_docs("lock_nonstealable_timeout", response)\n@@ -1565,7 +1604,8 @@ def test_locking_refresh_lock(self):\n # Replace dynamic lock token with a static one\n response._content = re.sub(\n b\'"token": "[^"]+"\',\n- b\'"token": "0.684672730996-0.25195226375-00105A989226:1477076400.000"\', # noqa\n+ b\'"token":\'\n+ b\' "0.684672730996-0.25195226375-00105A989226:1477076400.000"\', # noqa\n response.content,\n )\n save_request_and_response_for_docs("refresh_lock", response)\n@@ -1877,7 +1917,8 @@ def test_controlpanels_crud_dexterity(self):\n \n \n @unittest.skipUnless(\n- PAM_INSTALLED, "plone.app.multilingual is installed by default only in Plone 5"\n+ PAM_INSTALLED,\n+ "plone.app.multilingual is installed by default only in Plone 5",\n ) # NOQA\n class TestPAMDocumentation(TestDocumentationBase):\n \n@@ -1886,11 +1927,16 @@ class TestPAMDocumentation(TestDocumentationBase):\n def setUp(self):\n super(TestPAMDocumentation, self).setUp()\n \n- language_tool = api.portal.get_tool("portal_languages")\n- language_tool.addSupportedLanguage("en")\n- language_tool.addSupportedLanguage("es")\n- language_tool.addSupportedLanguage("de")\n- applyProfile(self.portal, "plone.app.multilingual:default")\n+ #\n+ # We manually set the UIDs for LRFs here because the static uuid\n+ # generator is not applied for LRFs.\n+ # When we have tried to apply it for LRFs we have had several\n+ # utility registration problems.\n+ #\n+ setattr(self.portal.en, "_plone.uuid", "00000000000000000000000000000001")\n+ setattr(self.portal.es, "_plone.uuid", "00000000000000000000000000000002")\n+ setattr(self.portal.fr, "_plone.uuid", "00000000000000000000000000000003")\n+ setattr(self.portal.de, "_plone.uuid", "00000000000000000000000000000004")\n \n en_id = self.portal["en"].invokeFactory(\n "Document", id="test-document", title="Test document"\n@@ -1902,9 +1948,6 @@ def setUp(self):\n self.es_content = self.portal["es"].get(es_id)\n transaction.commit()\n \n- def tearDown(self):\n- super(TestPAMDocumentation, self).tearDown()\n-\n def test_documentation_translations_post(self):\n response = self.api_session.post(\n "{}/@translations".format(self.en_content.absolute_url()),\n@@ -1929,6 +1972,7 @@ def test_documentation_translations_post_by_uid(self):\n def test_documentation_translations_get(self):\n ITranslationManager(self.en_content).register_translation("es", self.es_content)\n transaction.commit()\n+\n response = self.api_session.get(\n "{}/@translations".format(self.en_content.absolute_url())\n )\n@@ -1966,3 +2010,29 @@ def test_documentation_translation_locator(self):\n auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),\n )\n save_request_and_response_for_docs("translation_locator", response)\n+\n+ def test_site_navroot_get(self):\n+ response = self.api_session.get("/@navroot")\n+ save_request_and_response_for_docs("navroot_site_get", response)\n+\n+ def test_site_navroot_language_folder_get(self):\n+ response = self.api_session.get("/en/@navroot")\n+ save_request_and_response_for_docs("navroot_lang_folder_get", response)\n+\n+ def test_site_navroot_language_content_get(self):\n+ response = self.api_session.get("/en/test-document/@navroot")\n+ save_request_and_response_for_docs("navroot_lang_content_get", response)\n+\n+ def test_site_expansion_navroot(self):\n+ response = self.api_session.get("?expand=navroot")\n+ save_request_and_response_for_docs("site_get_expand_navroot", response)\n+\n+ def test_site_expansion_navroot_language_folder(self):\n+ response = self.api_session.get("/en?expand=navroot")\n+ save_request_and_response_for_docs("site_get_expand_lang_folder", response)\n+\n+ def test_site_expansion_navroot_language_folder_content(self):\n+ response = self.api_session.get("/en/test-document?expand=navroot")\n+ save_request_and_response_for_docs(\n+ "site_get_expand_lang_folder_content", response\n+ )\ndiff --git a/src/plone/restapi/tests/test_services_navroot.py b/src/plone/restapi/tests/test_services_navroot.py\nnew file mode 100644\nindex 0000000000..0e2e7b844b\n--- /dev/null\n+++ b/src/plone/restapi/tests/test_services_navroot.py\n@@ -0,0 +1,268 @@\n+# -*- coding: utf-8 -*-\n+from plone.app.testing import setRoles\n+from plone.app.testing import TEST_USER_ID\n+from plone.restapi.testing import (\n+ PLONE_RESTAPI_DX_FUNCTIONAL_TESTING,\n+ PLONE_RESTAPI_DX_PAM_FUNCTIONAL_TESTING,\n+)\n+from plone.restapi.testing import PAM_INSTALLED\n+from plone.restapi.testing import RelativeSession\n+from zope.component import getMultiAdapter\n+from zope.interface import alsoProvides\n+from plone.app.layout.navigation.interfaces import INavigationRoot\n+\n+import unittest\n+from plone import api\n+import transaction\n+\n+\n+class TestServicesNavroot(unittest.TestCase):\n+\n+ layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING\n+\n+ def setUp(self):\n+ self.app = self.layer["app"]\n+ self.portal = self.layer["portal"]\n+ self.portal_url = self.portal.absolute_url()\n+ setRoles(self.portal, TEST_USER_ID, ["Manager"])\n+\n+ api.content.create(\n+ container=self.portal,\n+ id="news",\n+ title="News",\n+ type="Folder",\n+ )\n+ api.content.create(\n+ container=self.portal.news,\n+ id="document",\n+ title="Document",\n+ type="Document",\n+ )\n+ api.content.transition(obj=self.portal.news, transition="publish")\n+ api.content.transition(obj=self.portal.news.document, transition="publish")\n+ transaction.commit()\n+\n+ self.api_session = RelativeSession(self.portal_url)\n+ self.api_session.headers.update({"Accept": "application/json"})\n+\n+ def test_get_navroot(self):\n+ response = self.api_session.get(\n+ "/@navroot",\n+ )\n+\n+ self.assertEqual(response.status_code, 200)\n+ portal_state = getMultiAdapter(\n+ (self.portal, self.layer["request"]), name="plone_portal_state"\n+ )\n+\n+ self.assertIn("navroot", response.json())\n+ self.assertEqual(\n+ response.json()["navroot"]["title"],\n+ portal_state.navigation_root_title(),\n+ )\n+ self.assertEqual(response.json()["@id"], self.portal_url + "/@navroot")\n+\n+ def test_get_navroot_non_multilingual_navigation_root(self):\n+ """test that the navroot is computed correctly when a section\n+ implements INavigationRoot\n+ """\n+ alsoProvides(self.portal.news, INavigationRoot)\n+ transaction.commit()\n+\n+ response = self.api_session.get(\n+ "/news/@navroot",\n+ )\n+\n+ self.assertEqual(response.status_code, 200)\n+ portal_state = getMultiAdapter(\n+ (self.portal.news, self.layer["request"]),\n+ name="plone_portal_state",\n+ )\n+\n+ self.assertIn("navroot", response.json())\n+\n+ self.assertEqual(\n+ response.json()["navroot"]["title"],\n+ portal_state.navigation_root_title(),\n+ )\n+ self.assertEqual(\n+ response.json()["navroot"]["@id"],\n+ portal_state.navigation_root_url(),\n+ )\n+\n+ def test_get_navroot_non_multilingual_navigation_root_content(self):\n+ """test that the navroot is computed correctly in a content inside a section\n+ that implements INavigationRoot\n+ """\n+ alsoProvides(self.portal.news, INavigationRoot)\n+ transaction.commit()\n+\n+ response = self.api_session.get(\n+ "/news/document/@navroot",\n+ )\n+\n+ self.assertEqual(response.status_code, 200)\n+ portal_state = getMultiAdapter(\n+ (self.portal.news, self.layer["request"]),\n+ name="plone_portal_state",\n+ )\n+\n+ self.assertIn("navroot", response.json())\n+\n+ self.assertEqual(\n+ response.json()["navroot"]["title"],\n+ portal_state.navigation_root_title(),\n+ )\n+ self.assertEqual(\n+ response.json()["navroot"]["@id"],\n+ portal_state.navigation_root_url(),\n+ )\n+\n+\n+@unittest.skipUnless(\n+ PAM_INSTALLED, "plone.app.multilingual is installed by default only in Plone 5"\n+) # NOQA\n+class TestServicesNavrootMultilingual(unittest.TestCase):\n+\n+ layer = PLONE_RESTAPI_DX_PAM_FUNCTIONAL_TESTING\n+\n+ def setUp(self):\n+ self.app = self.layer["app"]\n+ self.portal = self.layer["portal"]\n+ self.portal_url = self.portal.absolute_url()\n+ setRoles(self.portal, TEST_USER_ID, ["Manager"])\n+\n+ api.content.create(\n+ container=self.portal.en,\n+ id="news",\n+ title="News",\n+ type="Folder",\n+ )\n+ api.content.create(\n+ container=self.portal.en.news,\n+ id="document",\n+ title="Document",\n+ type="Document",\n+ )\n+ api.content.transition(obj=self.portal.en.news, transition="publish")\n+ api.content.transition(obj=self.portal.en.news.document, transition="publish")\n+\n+ self.api_session = RelativeSession(self.portal_url)\n+ self.api_session.headers.update({"Accept": "application/json"})\n+\n+ transaction.commit()\n+\n+ def test_get_navroot_site(self):\n+ response = self.api_session.get(\n+ "/@navroot",\n+ )\n+\n+ self.assertEqual(response.status_code, 200)\n+ portal_state = getMultiAdapter(\n+ (self.portal, self.layer["request"]), name="plone_portal_state"\n+ )\n+ self.assertIn("navroot", response.json())\n+\n+ self.assertEqual(\n+ response.json()["navroot"]["title"],\n+ portal_state.navigation_root_title(),\n+ )\n+ self.assertEqual(\n+ response.json()["@id"],\n+ portal_state.navigation_root_url() + "/@navroot",\n+ )\n+\n+ def test_get_navroot_language_folder(self):\n+ response = self.api_session.get(\n+ "/en/@navroot",\n+ )\n+\n+ self.assertEqual(response.status_code, 200)\n+ portal_state = getMultiAdapter(\n+ (self.portal.en, self.layer["request"]), name="plone_portal_state"\n+ )\n+ self.assertIn("navroot", response.json())\n+\n+ self.assertEqual(\n+ response.json()["navroot"]["title"],\n+ portal_state.navigation_root_title(),\n+ )\n+ self.assertEqual(\n+ response.json()["@id"],\n+ portal_state.navigation_root_url() + "/@navroot",\n+ )\n+\n+ def test_get_navroot_language_content(self):\n+ response = self.api_session.get(\n+ "/en/news/@navroot",\n+ )\n+\n+ self.assertEqual(response.status_code, 200)\n+ portal_state = getMultiAdapter(\n+ (self.portal.en.news, self.layer["request"]),\n+ name="plone_portal_state",\n+ )\n+ self.assertIn("navroot", response.json())\n+\n+ self.assertEqual(\n+ response.json()["navroot"]["title"],\n+ portal_state.navigation_root_title(),\n+ )\n+ self.assertEqual(\n+ response.json()["navroot"]["@id"],\n+ portal_state.navigation_root_url(),\n+ )\n+\n+ def test_get_navroot_non_multilingual_navigation_root(self):\n+ """test that the navroot is computed correctly when a section\n+ implements INavigationRoot\n+ """\n+ alsoProvides(self.portal.en.news, INavigationRoot)\n+ transaction.commit()\n+\n+ response = self.api_session.get(\n+ "/en/news/@navroot",\n+ )\n+\n+ self.assertEqual(response.status_code, 200)\n+ portal_state = getMultiAdapter(\n+ (self.portal.en.news, self.layer["request"]),\n+ name="plone_portal_state",\n+ )\n+ self.assertIn("navroot", response.json())\n+\n+ self.assertEqual(\n+ response.json()["navroot"]["title"],\n+ portal_state.navigation_root_title(),\n+ )\n+ self.assertEqual(\n+ response.json()["navroot"]["@id"],\n+ portal_state.navigation_root_url(),\n+ )\n+\n+ def test_get_navroot_non_multilingual_navigation_root_content(self):\n+ """test that the navroot is computed correctly in a content inside a section\n+ that implements INavigationRoot\n+ """\n+ alsoProvides(self.portal.en.news, INavigationRoot)\n+ transaction.commit()\n+\n+ response = self.api_session.get(\n+ "/en/news/document/@navroot",\n+ )\n+\n+ self.assertEqual(response.status_code, 200)\n+ portal_state = getMultiAdapter(\n+ (self.portal.en.news, self.layer["request"]),\n+ name="plone_portal_state",\n+ )\n+ self.assertIn("navroot", response.json())\n+\n+ self.assertEqual(\n+ response.json()["navroot"]["title"],\n+ portal_state.navigation_root_title(),\n+ )\n+ self.assertEqual(\n+ response.json()["navroot"]["@id"],\n+ portal_state.navigation_root_url(),\n+ )\ndiff --git a/src/plone/restapi/tests/test_services_site.py b/src/plone/restapi/tests/test_services_site.py\nnew file mode 100644\nindex 0000000000..145f81c0ac\n--- /dev/null\n+++ b/src/plone/restapi/tests/test_services_site.py\n@@ -0,0 +1,57 @@\n+# -*- coding: utf-8 -*-\n+from plone.app.testing import setRoles\n+from plone.app.testing import TEST_USER_ID\n+from plone.restapi.testing import PLONE_RESTAPI_DX_FUNCTIONAL_TESTING\n+from plone.restapi.testing import RelativeSession\n+from zope.component import getMultiAdapter\n+\n+import unittest\n+\n+IS_PLONE4 = False\n+\n+try:\n+ from Products.CMFPlone.interfaces import IImagingSchema # noqa\n+ from Products.CMFPlone.interfaces import ISiteSchema # noqa\n+except ImportError:\n+ IS_PLONE4 = True\n+\n+\n+class TestServicesSite(unittest.TestCase):\n+\n+ layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING\n+\n+ def setUp(self):\n+ self.app = self.layer["app"]\n+ self.portal = self.layer["portal"]\n+ self.portal_url = self.portal.absolute_url()\n+ setRoles(self.portal, TEST_USER_ID, ["Manager"])\n+\n+ self.api_session = RelativeSession(self.portal_url)\n+ self.api_session.headers.update({"Accept": "application/json"})\n+\n+ def test_get_site_title(self):\n+ response = self.api_session.get(\n+ "/@site",\n+ )\n+\n+ self.assertEqual(response.status_code, 200)\n+ portal_state = getMultiAdapter(\n+ (self.portal, self.layer["request"]), name="plone_portal_state"\n+ )\n+ self.assertEqual(\n+ response.json()["plone.site_title"], portal_state.portal_title()\n+ )\n+\n+ @unittest.skipIf(\n+ IS_PLONE4,\n+ "The information can only be extracted from the ISiteSchema and IImagingSchema values in registry, which are only available in Plone 5",\n+ ) # NOQA\n+ def test_get_site_other(self):\n+ response = self.api_session.get(\n+ "/@site",\n+ )\n+\n+ self.assertEqual(response.status_code, 200)\n+ self.assertIn("plone.site_logo", response.json())\n+ self.assertIn("plone.robots_txt", response.json())\n+ self.assertIn("plone.allowed_sizes", response.json())\n' +A news/3847.bugfix +M Products/CMFPlone/utils.py + +b'diff --git a/Products/CMFPlone/utils.py b/Products/CMFPlone/utils.py\nindex 930aa3df67..fe9ee6a602 100644\n--- a/Products/CMFPlone/utils.py\n+++ b/Products/CMFPlone/utils.py\n@@ -1030,6 +1030,11 @@ def _check_for_collision(contained_by, id, **kwargs):\n u\'There is already an item named ${name} in this folder.\',\n mapping={u\'name\': id})\n \n+ # containers may have a field / attribute of the same name\n+ # see original fix in https://github.com/plone/plone.base/issues/35\n+ if base_hasattr(contained_by, id):\n+ return _("${name} is reserved.", mapping={"name": id})\n+ \n if base_hasattr(contained_by, \'checkIdAvailable\'):\n # This used to be called from the check_id skin script,\n # which would check the permission automatically,\ndiff --git a/news/3847.bugfix b/news/3847.bugfix\nnew file mode 100644\nindex 0000000000..b59ef54f88\n--- /dev/null\n+++ b/news/3847.bugfix\n@@ -0,0 +1,2 @@\n+Check for container field / attribute when trying to create content with same id\n+[laulaz]\n'