diff --git a/HISTORY.rst b/HISTORY.rst
index bd9fc5822..fd2c9bca3 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -4,7 +4,8 @@ Changelog
3.0.0b1 (unreleased)
--------------------
-- Nothing changed yet.
+- Implement functionality to hide related resource from being displayed for
+ unpublished content
3.0.0b0 (2021-04-30)
diff --git a/castle/cms/browser/viewlets/configure.zcml b/castle/cms/browser/viewlets/configure.zcml
index 1c6bc84c4..ad5779463 100644
--- a/castle/cms/browser/viewlets/configure.zcml
+++ b/castle/cms/browser/viewlets/configure.zcml
@@ -23,6 +23,16 @@
layer="castle.cms.interfaces.ICastleLayer"
template="social_tags_body.pt"
/>
+
+
+
diff --git a/castle/cms/browser/viewlets/relateditems.py b/castle/cms/browser/viewlets/relateditems.py
new file mode 100644
index 000000000..90361d792
--- /dev/null
+++ b/castle/cms/browser/viewlets/relateditems.py
@@ -0,0 +1,31 @@
+from plone.app.layout.viewlets.content import ContentRelatedItems as BaseContentRelatedItems
+from Products.CMFCore.utils import getToolByName
+from plone import api
+
+
+class ContentRelatedItems(BaseContentRelatedItems):
+
+ # override this method to respect castle.display_unpublished_related_items registry setting
+ def related2brains(self, related):
+ catalog = getToolByName(self.context, "portal_catalog")
+ brains = []
+ for r in related:
+ path = r.to_path
+ if path is None:
+ # Item was deleted. The related item should have been cleaned
+ # up, but apparently this does not happen.
+ continue
+ # the query will return an empty list if the user
+ # has no permission to see the target object
+ catalog_args = {
+ 'path': dict(query=path, depth=0),
+ }
+ if not api.portal.get_registry_record(
+ 'plone.display_unpublished_related_items',
+ default=False,
+ ):
+ catalog_args['review_state'] = 'published'
+ brains.extend(
+ catalog(**catalog_args)
+ )
+ return brains
diff --git a/castle/cms/interfaces/controlpanel.py b/castle/cms/interfaces/controlpanel.py
index 577468e8f..42caefda7 100644
--- a/castle/cms/interfaces/controlpanel.py
+++ b/castle/cms/interfaces/controlpanel.py
@@ -213,6 +213,14 @@ class ISecuritySchema(controlpanel.ISecuritySchema):
default=False,
required=False)
+ display_unpublished_related_items = schema.Bool(
+ title=u'Display unpublished related items',
+ description=u'Check this box to allow Related Items that are not currently published '
+ u'to be displayed when viewing content containing related items.',
+ default=False,
+ required=False,
+ )
+
class IAnnouncementData(Interface):
show_announcement = schema.Bool(
diff --git a/castle/cms/profiles/3_0_00/metadata.xml b/castle/cms/profiles/3_0_00/metadata.xml
new file mode 100644
index 000000000..b6bd42a67
--- /dev/null
+++ b/castle/cms/profiles/3_0_00/metadata.xml
@@ -0,0 +1,4 @@
+
+
+ 3000
+
diff --git a/castle/cms/profiles/3_0_00/registry.xml b/castle/cms/profiles/3_0_00/registry.xml
new file mode 100644
index 000000000..a1a4d3190
--- /dev/null
+++ b/castle/cms/profiles/3_0_00/registry.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ False
+
+
diff --git a/castle/cms/profiles/default/metadata.xml b/castle/cms/profiles/default/metadata.xml
index 7ed429eb5..326afc723 100644
--- a/castle/cms/profiles/default/metadata.xml
+++ b/castle/cms/profiles/default/metadata.xml
@@ -1,6 +1,6 @@
- 2631
+ 3000
profile-plone.app.querystring:default
profile-plone.app.mosaic:default
diff --git a/castle/cms/tests/test_related_resources.py b/castle/cms/tests/test_related_resources.py
new file mode 100644
index 000000000..6727ad4ce
--- /dev/null
+++ b/castle/cms/tests/test_related_resources.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+import unittest
+
+from castle.cms.testing import CASTLE_PLONE_INTEGRATION_TESTING
+from plone import api
+from plone.app.testing import TEST_USER_ID
+from plone.app.testing import TEST_USER_NAME
+from plone.app.testing import login
+from plone.app.testing import setRoles
+from zope.component import getUtility
+from zope.intid.interfaces import IIntIds
+from z3c.relationfield import RelationValue
+from castle.cms.browser.viewlets.relateditems import ContentRelatedItems
+
+
+class TestRelatedResources(unittest.TestCase):
+
+ layer = CASTLE_PLONE_INTEGRATION_TESTING
+
+ def setUp(self):
+ self.portal = self.layer['portal']
+ self.request = self.layer['request']
+ self.intid_utility = getUtility(IIntIds)
+
+ login(self.portal, TEST_USER_NAME)
+ setRoles(self.portal, TEST_USER_ID, ('Member', 'Manager'))
+ self.source_document = self.create_document('source')
+
+ def create_document(self, id):
+ return api.content.create(
+ type='Document',
+ id=id,
+ container=self.portal,
+ )
+
+ def publish(self, item):
+ api.content.transition(
+ obj=item,
+ to_state='published',
+ )
+
+ @property
+ def related_items(self):
+ content_related_items = ContentRelatedItems(
+ context=self.source_document,
+ request=self.source_document.REQUEST,
+ view=self.source_document.view,
+ )
+ return [
+ brain.getObject()
+ for brain in content_related_items.related_items()
+ ]
+
+ def set_up_target_documents(self):
+ target_documents = [
+ self.create_document('target_1'),
+ self.create_document('target_2'),
+ ]
+ self.source_document.relatedItems = [
+ RelationValue(self.intid_utility.getId(target))
+ for target in target_documents
+ ]
+ self.assertEqual(
+ len(self.source_document.relatedItems),
+ 2,
+ )
+ return target_documents
+
+ def assertContentRelatedItemsLength(self, asserted_length):
+ self.assertEqual(
+ len(self.related_items),
+ asserted_length,
+ )
+
+ def test_display_unpublished_related_items_false_by_default(self):
+ display_unpublished_related_items = api.portal.get_registry_record(
+ 'plone.display_unpublished_related_items',
+ default=True,
+ )
+ self.assertFalse(display_unpublished_related_items)
+
+ def test_content_related_items_override_when_display_unpublished_false(self):
+ target_documents = self.set_up_target_documents()
+ api.portal.set_registry_record(
+ 'plone.display_unpublished_related_items',
+ False,
+ )
+ self.assertEqual(len(self.related_items), 0)
+ for expected_related_items_count, target_document in enumerate(target_documents, start=1):
+ self.assertFalse(target_document in self.related_items)
+ self.publish(target_document)
+ self.assertEqual(
+ len(self.related_items),
+ expected_related_items_count,
+ )
+ self.assertTrue(target_document in self.related_items)
+
+ def test_content_related_items_override_when_display_unpublished_true(self):
+ target_documents = self.set_up_target_documents()
+ api.portal.set_registry_record(
+ 'plone.display_unpublished_related_items',
+ True,
+ )
+ self.assertEqual(len(self.related_items), 2)
+ for target_document in target_documents:
+ self.assertTrue(target_document in self.related_items)
diff --git a/castle/cms/upgrades.zcml b/castle/cms/upgrades.zcml
index 55f0d135d..e28f1731c 100644
--- a/castle/cms/upgrades.zcml
+++ b/castle/cms/upgrades.zcml
@@ -441,7 +441,7 @@
+
+
+
+
+
diff --git a/castle/cms/upgrades/__init__.py b/castle/cms/upgrades/__init__.py
index 2abd75e2b..9a41edf97 100644
--- a/castle/cms/upgrades/__init__.py
+++ b/castle/cms/upgrades/__init__.py
@@ -68,3 +68,5 @@ def upgrade(context, logger=None):
upgrade_2_6_27 = default_upgrade_factory('2_6_27')
upgrade_2_6_30 = default_upgrade_factory('2_6_30')
upgrade_2_6_31 = default_upgrade_factory('2_6_31')
+
+upgrade_3_0_00 = default_upgrade_factory('3_0_00')
diff --git a/docs/controlpanel.rst b/docs/controlpanel.rst
index 8383bb2c5..37ceee54b 100644
--- a/docs/controlpanel.rst
+++ b/docs/controlpanel.rst
@@ -135,4 +135,13 @@ The can be configured on the environment with these environment settings:
- `GOOGLE_CLIENT_SECRET`
(you can also provide twitter auth key and secret through control panel; however,
-in the future, these will all be environment variables)
\ No newline at end of file
+in the future, these will all be environment variables)
+
+
+Security Settings
+-----------------
+
+in /@@security-controlpanel, you can change settings for the way some content shows
+up. For example, `Allow access to published objects inside private containers` and
+`Display unpublished related items` settings can be changed there. (Both of these
+values default to False)