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)