diff --git a/plone/app/discussion/browser/comments.py b/plone/app/discussion/browser/comments.py index 0ab018f..387cead 100644 --- a/plone/app/discussion/browser/comments.py +++ b/plone/app/discussion/browser/comments.py @@ -11,12 +11,12 @@ from plone.app.discussion.interfaces import IConversation from plone.app.discussion.interfaces import IDiscussionSettings from plone.app.discussion.interfaces import IReplies from plone.app.layout.viewlets.common import ViewletBase +from plone.base.utils import safe_text from plone.registry.interfaces import IRegistry from plone.z3cform import z2 from plone.z3cform.fieldsets import extensible from plone.z3cform.interfaces import IWrappedForm from Products.CMFCore.utils import getToolByName -from Products.CMFPlone.utils import safe_unicode from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile from Products.statusmessages.interfaces import IStatusMessage from urllib.parse import quote @@ -151,9 +151,9 @@ class CommentForm(extensible.ExtensibleForm, form.Form): # Make sure author_name/ author_email is properly encoded if "author_name" in data: - author_name = safe_unicode(data["author_name"]) + author_name = safe_text(data["author_name"]) if "author_email" in data: - author_email = safe_unicode(data["author_email"]) + author_email = safe_text(data["author_email"]) # Set comment author properties for anonymous users or members portal_membership = getToolByName(context, "portal_membership") @@ -161,13 +161,13 @@ class CommentForm(extensible.ExtensibleForm, form.Form): if not anon and getSecurityManager().checkPermission("Reply to item", context): # Member member = portal_membership.getAuthenticatedMember() - email = safe_unicode(member.getProperty("email")) + email = safe_text(member.getProperty("email")) fullname = member.getProperty("fullname") if not fullname or fullname == "": fullname = member.getUserName() - fullname = safe_unicode(fullname) + fullname = safe_text(fullname) author_name = fullname - email = safe_unicode(email) + email = safe_text(email) # XXX: according to IComment interface author_email must not be # noqa T000 # set for logged in users, cite: # 'for anonymous comments only, set to None for logged in comments' diff --git a/plone/app/discussion/browser/configure.zcml b/plone/app/discussion/browser/configure.zcml index 63f80a4..0892259 100644 --- a/plone/app/discussion/browser/configure.zcml +++ b/plone/app/discussion/browser/configure.zcml @@ -48,7 +48,7 @@ diff --git a/plone/app/discussion/browser/controlpanel.py b/plone/app/discussion/browser/controlpanel.py index f359c48..b9a3e23 100644 --- a/plone/app/discussion/browser/controlpanel.py +++ b/plone/app/discussion/browser/controlpanel.py @@ -1,11 +1,12 @@ -from plone.app.discussion.interfaces import _ -from plone.app.discussion.interfaces import IDiscussionSettings -from plone.app.discussion.upgrades import update_registry +from ..interfaces import _ +from ..interfaces import IDiscussionSettings +from ..upgrades import update_registry from plone.app.registry.browser import controlpanel +from plone.base.interfaces.controlpanel import IConfigurationChangedEvent +from plone.base.interfaces.controlpanel import IMailSchema from plone.registry.interfaces import IRecordModifiedEvent from plone.registry.interfaces import IRegistry from Products.CMFCore.utils import getToolByName -from Products.CMFPlone.interfaces.controlpanel import IMailSchema from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile from Products.statusmessages.interfaces import IStatusMessage from z3c.form import button @@ -16,16 +17,6 @@ from zope.component import queryUtility from zope.component.hooks import getSite -# try/except was added because Configuration Changed Event was moved inside the -# controlpanel file in the PR #2495 on Products.CMFPlone -try: - from Products.CMFPlone.interfaces.controlpanel import ( # noqa: E501 - IConfigurationChangedEvent, - ) -except ImportError: - from Products.CMFPlone.interfaces import IConfigurationChangedEvent - - class DiscussionSettingsEditForm(controlpanel.RegistryEditForm): """Discussion settings form.""" diff --git a/plone/app/discussion/browser/conversation.py b/plone/app/discussion/browser/conversation.py index f2356c8..ac0c932 100644 --- a/plone/app/discussion/browser/conversation.py +++ b/plone/app/discussion/browser/conversation.py @@ -1,13 +1,13 @@ +from ..interfaces import IDiscussionSettings from Acquisition import aq_base from Acquisition import aq_chain from Acquisition import aq_inner -from plone.app.discussion.interfaces import IDiscussionSettings +from plone.base.interfaces import INonStructuralFolder +from plone.base.interfaces import IPloneSiteRoot +from plone.base.utils import safe_hasattr from plone.registry.interfaces import IRegistry from Products.CMFCore.interfaces import IFolderish from Products.CMFCore.utils import getToolByName -from Products.CMFPlone.interfaces import INonStructuralFolder -from Products.CMFPlone.interfaces import IPloneSiteRoot -from Products.CMFPlone.utils import safe_hasattr from zope.component import queryUtility diff --git a/plone/app/discussion/catalog.py b/plone/app/discussion/catalog.py index 91b683d..6413d11 100644 --- a/plone/app/discussion/catalog.py +++ b/plone/app/discussion/catalog.py @@ -6,10 +6,10 @@ Also provide event handlers to actually catalog the comments. from DateTime import DateTime from plone.app.discussion.interfaces import IComment from plone.app.discussion.interfaces import IConversation +from plone.base.utils import safe_text from plone.indexer import indexer from plone.uuid.interfaces import IUUID from Products.CMFCore.interfaces import IContentish -from Products.CMFPlone.utils import safe_unicode from Products.ZCatalog.interfaces import IZCatalog @@ -72,7 +72,7 @@ def title(object): def creator(object): if not object.creator: return - value = safe_unicode(object.creator) + value = safe_text(object.creator) return value diff --git a/plone/app/discussion/comment.py b/plone/app/discussion/comment.py index a2bef65..438e726 100644 --- a/plone/app/discussion/comment.py +++ b/plone/app/discussion/comment.py @@ -20,14 +20,14 @@ from plone.app.discussion.events import ReplyRemovedEvent from plone.app.discussion.interfaces import IComment from plone.app.discussion.interfaces import IConversation from plone.app.discussion.interfaces import IDiscussionSettings +from plone.base.interfaces.controlpanel import IMailSchema +from plone.base.utils import safe_text from plone.registry.interfaces import IRegistry from Products.CMFCore import permissions from Products.CMFCore.CMFCatalogAware import CatalogAware from Products.CMFCore.CMFCatalogAware import WorkflowAware from Products.CMFCore.DynamicType import DynamicType from Products.CMFCore.utils import getToolByName -from Products.CMFPlone.interfaces.controlpanel import IMailSchema -from Products.CMFPlone.utils import safe_unicode from smtplib import SMTPException from zope.annotation.interfaces import IAnnotatable from zope.component import getUtility @@ -205,8 +205,8 @@ class Comment( Message( COMMENT_TITLE, mapping={ - "author_name": safe_unicode(author_name), - "content": safe_unicode(content.Title()), + "author_name": safe_text(author_name), + "content": safe_text(content.Title()), }, ) ) @@ -374,7 +374,7 @@ def notify_user(obj, event): Message( MAIL_NOTIFICATION_MESSAGE, mapping={ - "title": safe_unicode(content_object.title), + "title": safe_text(content_object.title), "link": content_object.absolute_url() + "/view#" + obj.id, "text": obj.text, }, @@ -442,7 +442,7 @@ def notify_moderator(obj, event): Message( MAIL_NOTIFICATION_MESSAGE_MODERATOR, mapping={ - "title": safe_unicode(content_object.title), + "title": safe_text(content_object.title), "link": content_object.absolute_url() + "/view#" + obj.id, "text": obj.text, "commentator": obj.author_email diff --git a/plone/app/discussion/configure.zcml b/plone/app/discussion/configure.zcml index aabd851..fb72cee 100644 --- a/plone/app/discussion/configure.zcml +++ b/plone/app/discussion/configure.zcml @@ -39,7 +39,7 @@ description="Commenting infrastructure for Plone" directory="profiles/default" provides="Products.GenericSetup.interfaces.EXTENSION" - for="Products.CMFPlone.interfaces.IPloneSiteRoot" + for="plone.base.interfaces.IPloneSiteRoot" /> diff --git a/plone/app/discussion/conversation.py b/plone/app/discussion/conversation.py index 8af1b71..1cdb266 100644 --- a/plone/app/discussion/conversation.py +++ b/plone/app/discussion/conversation.py @@ -9,6 +9,10 @@ manipulate the same data structures, but provide an API for finding and manipulating the comments directly in reply to a particular comment or at the top level of the conversation. """ +from .comment import Comment +from .interfaces import DISCUSSION_ANNOTATION_KEY as ANNOTATION_KEY +from .interfaces import IConversation +from .interfaces import IReplies from AccessControl.SpecialUsers import nobody as user_nobody from Acquisition import aq_base from Acquisition import aq_inner @@ -21,11 +25,7 @@ from OFS.event import ObjectWillBeAddedEvent from OFS.event import ObjectWillBeRemovedEvent from OFS.Traversable import Traversable from persistent import Persistent -from plone.app.discussion.comment import Comment -from plone.app.discussion.interfaces import IConversation -from plone.app.discussion.interfaces import IReplies -from Products.CMFPlone import DISCUSSION_ANNOTATION_KEY as ANNOTATION_KEY -from Products.CMFPlone.interfaces import IHideFromBreadcrumbs +from plone.base.interfaces import IHideFromBreadcrumbs from zope.annotation.interfaces import IAnnotatable from zope.annotation.interfaces import IAnnotations from zope.component import adapter diff --git a/plone/app/discussion/interfaces.py b/plone/app/discussion/interfaces.py index 1f35487..e53628d 100644 --- a/plone/app/discussion/interfaces.py +++ b/plone/app/discussion/interfaces.py @@ -11,6 +11,9 @@ from zope.interface.common.mapping import IIterableMapping from zope.interface.interfaces import IObjectEvent +DISCUSSION_ANNOTATION_KEY = "plone.app.discussion:conversation" + + def isEmail(value): portal = getUtility(ISiteRoot) reg_tool = getToolByName(portal, "portal_registration") diff --git a/plone/app/discussion/subscribers.zcml b/plone/app/discussion/subscribers.zcml index 85e74e2..a797741 100644 --- a/plone/app/discussion/subscribers.zcml +++ b/plone/app/discussion/subscribers.zcml @@ -72,7 +72,7 @@ diff --git a/plone/app/discussion/tests/test_comments_viewlet.py b/plone/app/discussion/tests/test_comments_viewlet.py index 020dfc0..e168378 100644 --- a/plone/app/discussion/tests/test_comments_viewlet.py +++ b/plone/app/discussion/tests/test_comments_viewlet.py @@ -1,15 +1,13 @@ +from .. import interfaces +from ..browser.comment import EditCommentForm +from ..browser.comments import CommentForm +from ..browser.comments import CommentsViewlet +from ..interfaces import IConversation +from ..interfaces import IDiscussionSettings +from ..testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING from AccessControl import Unauthorized from datetime import datetime from OFS.Image import Image -from plone.app.discussion import interfaces -from plone.app.discussion.browser.comment import EditCommentForm -from plone.app.discussion.browser.comments import CommentForm -from plone.app.discussion.browser.comments import CommentsViewlet -from plone.app.discussion.interfaces import IConversation -from plone.app.discussion.interfaces import IDiscussionSettings -from plone.app.discussion.testing import ( # noqa - PLONE_APP_DISCUSSION_INTEGRATION_TESTING, -) from plone.app.testing import login from plone.app.testing import logout from plone.app.testing import setRoles @@ -17,7 +15,6 @@ from plone.app.testing import TEST_USER_ID from plone.app.testing import TEST_USER_NAME from plone.registry.interfaces import IRegistry from Products.CMFCore.utils import getToolByName -from Products.CMFPlone.tests import dummy from z3c.form.interfaces import IFormLayer from zope import interface from zope.annotation.interfaces import IAttributeAnnotatable @@ -29,9 +26,43 @@ from zope.interface import alsoProvides from zope.interface import Interface from zope.publisher.browser import TestRequest from zope.publisher.interfaces.browser import IBrowserRequest +from ZPublisher.HTTPRequest import FileUpload import time import unittest +import io + + +TEXT = b"file data" + + +class DummyFile(FileUpload): + """Dummy upload object + Used to fake uploaded files. + """ + + __allow_access_to_unprotected_subobjects__ = 1 + filename = "dummy.txt" + data = TEXT + headers = {} + + def __init__(self, filename=None, data=None, headers=None): + if filename is not None: + self.filename = filename + if data is not None: + self.data = data + if headers is not None: + self.headers = headers + self.file = io.BytesIO(self.data) + + def seek(self, *args): + pass + + def tell(self, *args): + return 1 + + def read(self, *args): + return self.data class TestCommentForm(unittest.TestCase): @@ -631,7 +662,7 @@ class TestCommentsViewlet(unittest.TestCase): self.memberdata._setPortrait( Image( id="jim", - file=dummy.File(), + file=DummyFile(), title="", ), "jim", diff --git a/plone/app/discussion/tests/test_conversation.py b/plone/app/discussion/tests/test_conversation.py index 9cbfbc8..febce28 100644 --- a/plone/app/discussion/tests/test_conversation.py +++ b/plone/app/discussion/tests/test_conversation.py @@ -1,13 +1,13 @@ +from ..interfaces import IComment +from ..interfaces import IConversation +from ..interfaces import IDiscussionLayer +from ..interfaces import IDiscussionSettings +from ..interfaces import IReplies +from ..testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING from Acquisition import aq_base from Acquisition import aq_parent from datetime import datetime from datetime import timedelta -from plone.app.discussion import interfaces -from plone.app.discussion.interfaces import IComment -from plone.app.discussion.interfaces import IConversation -from plone.app.discussion.interfaces import IDiscussionSettings -from plone.app.discussion.interfaces import IReplies -from plone.app.discussion.testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID from plone.app.vocabularies.types import BAD_TYPES @@ -29,7 +29,7 @@ class ConversationTest(unittest.TestCase): def setUp(self): self.portal = self.layer["portal"] setRoles(self.portal, TEST_USER_ID, ["Manager"]) - interface.alsoProvides(self.portal.REQUEST, interfaces.IDiscussionLayer) + interface.alsoProvides(self.portal.REQUEST, IDiscussionLayer) self.typetool = self.portal.portal_types self.portal_discussion = getToolByName( @@ -675,7 +675,7 @@ class ConversationEnabledForDexterityTypesTest(unittest.TestCase): setRoles(self.portal, TEST_USER_ID, ["Manager"]) interface.alsoProvides( self.portal.REQUEST, - interfaces.IDiscussionLayer, + IDiscussionLayer, ) interface.alsoProvides( diff --git a/plone/app/discussion/tests/test_events.py b/plone/app/discussion/tests/test_events.py index 175af1c..a6cc1cd 100644 --- a/plone/app/discussion/tests/test_events.py +++ b/plone/app/discussion/tests/test_events.py @@ -1,8 +1,6 @@ -from plone.app.discussion.interfaces import IConversation -from plone.app.discussion.interfaces import IReplies -from plone.app.discussion.testing import ( # noqa - PLONE_APP_DISCUSSION_INTEGRATION_TESTING, -) +from ..interfaces import IConversation +from ..interfaces import IReplies +from ..testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID from Zope2.App import zcml diff --git a/plone/app/discussion/tests/test_functional.py b/plone/app/discussion/tests/test_functional.py index f5f1181..995016e 100644 --- a/plone/app/discussion/tests/test_functional.py +++ b/plone/app/discussion/tests/test_functional.py @@ -2,7 +2,7 @@ These test are only triggered when Plone 4 (and plone.testing) is installed. """ -from plone.app.discussion.testing import PLONE_APP_DISCUSSION_FUNCTIONAL_TESTING # noqa +from ..testing import PLONE_APP_DISCUSSION_FUNCTIONAL_TESTING # noqa from plone.testing import layered import doctest diff --git a/plone/app/discussion/tests/test_indexers.py b/plone/app/discussion/tests/test_indexers.py index 0581576..b1b30f1 100644 --- a/plone/app/discussion/tests/test_indexers.py +++ b/plone/app/discussion/tests/test_indexers.py @@ -1,12 +1,10 @@ """Test for the plone.app.discussion indexers """ +from .. import catalog +from ..interfaces import IConversation +from ..testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING # noqa from datetime import datetime from DateTime import DateTime -from plone.app.discussion import catalog -from plone.app.discussion.interfaces import IConversation -from plone.app.discussion.testing import ( # noqa - PLONE_APP_DISCUSSION_INTEGRATION_TESTING, -) from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID from plone.indexer.delegate import DelegatingIndexerFactory diff --git a/plone/app/discussion/tests/test_moderation_multiple_state_view.py b/plone/app/discussion/tests/test_moderation_multiple_state_view.py index 2247c6f..6a12a72 100644 --- a/plone/app/discussion/tests/test_moderation_multiple_state_view.py +++ b/plone/app/discussion/tests/test_moderation_multiple_state_view.py @@ -1,12 +1,10 @@ -from plone.app.discussion.browser.moderation import BulkActionsView -from plone.app.discussion.browser.moderation import CommentTransition -from plone.app.discussion.browser.moderation import DeleteComment -from plone.app.discussion.browser.moderation import View -from plone.app.discussion.interfaces import IConversation -from plone.app.discussion.interfaces import IDiscussionSettings -from plone.app.discussion.testing import ( # noqa - PLONE_APP_DISCUSSION_INTEGRATION_TESTING, -) +from ..browser.moderation import BulkActionsView +from ..browser.moderation import CommentTransition +from ..browser.moderation import DeleteComment +from ..browser.moderation import View +from ..interfaces import IConversation +from ..interfaces import IDiscussionSettings +from ..testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID from plone.registry.interfaces import IRegistry diff --git a/plone/app/discussion/tests/test_moderation_view.py b/plone/app/discussion/tests/test_moderation_view.py index 5458404..639eaa3 100644 --- a/plone/app/discussion/tests/test_moderation_view.py +++ b/plone/app/discussion/tests/test_moderation_view.py @@ -1,12 +1,10 @@ -from plone.app.discussion.browser.moderation import BulkActionsView -from plone.app.discussion.browser.moderation import CommentTransition -from plone.app.discussion.browser.moderation import DeleteComment -from plone.app.discussion.browser.moderation import View -from plone.app.discussion.interfaces import IConversation -from plone.app.discussion.interfaces import IDiscussionSettings -from plone.app.discussion.testing import ( # noqa - PLONE_APP_DISCUSSION_INTEGRATION_TESTING, -) +from ..browser.moderation import BulkActionsView +from ..browser.moderation import CommentTransition +from ..browser.moderation import DeleteComment +from ..browser.moderation import View +from ..interfaces import IConversation +from ..interfaces import IDiscussionSettings +from ..testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID from plone.registry.interfaces import IRegistry diff --git a/plone/app/discussion/tests/test_notifications.py b/plone/app/discussion/tests/test_notifications.py index fdf326f..17e628a 100644 --- a/plone/app/discussion/tests/test_notifications.py +++ b/plone/app/discussion/tests/test_notifications.py @@ -1,14 +1,14 @@ +from ..interfaces import IConversation +from ..testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING from Acquisition import aq_base -from plone.app.discussion.interfaces import IConversation -from plone.app.discussion.testing import ( # noqa - PLONE_APP_DISCUSSION_INTEGRATION_TESTING, -) +from persistent.list import PersistentList from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID +from plone.base.interfaces import IMailSchema from plone.registry.interfaces import IRegistry -from Products.CMFPlone.interfaces import IMailSchema -from Products.CMFPlone.tests.utils import MockMailHost from Products.MailHost.interfaces import IMailHost +from Products.MailHost.MailHost import _mungeHeaders +from Products.MailHost.MailHost import MailBase from zope.component import createObject from zope.component import getSiteManager from zope.component import getUtility @@ -17,6 +17,42 @@ from zope.component import queryUtility import unittest +class MockMailHost(MailBase): + """A MailHost that collects messages instead of sending them.""" + + def __init__(self, id): + self.reset() + + def reset(self): + self.messages = PersistentList() + + def _send(self, mfrom, mto, messageText, immediate=False): + """Send the message""" + self.messages.append(messageText) + + def send( + self, + messageText, + mto=None, + mfrom=None, + subject=None, + encode=None, + immediate=False, + charset=None, + msg_type=None, + ): + """send *messageText* modified by the other parameters. + + *messageText* can either be an ``email.message.Message`` + or a string. + Note that Products.MailHost 4.10 had changes here. + """ + msg, mto, mfrom = _mungeHeaders( + messageText, mto, mfrom, subject, charset, msg_type, encode + ) + self.messages.append(msg) + + class TestUserNotificationUnit(unittest.TestCase): layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING diff --git a/plone/app/discussion/tests/test_robot.py b/plone/app/discussion/tests/test_robot.py index e6487d5..c626689 100644 --- a/plone/app/discussion/tests/test_robot.py +++ b/plone/app/discussion/tests/test_robot.py @@ -1,4 +1,4 @@ -from plone.app.discussion.testing import PLONE_APP_DISCUSSION_ROBOT_TESTING +from ..testing import PLONE_APP_DISCUSSION_ROBOT_TESTING from plone.app.testing import ROBOT_TEST_LEVEL from plone.testing import layered diff --git a/plone/app/discussion/tests/test_workflow.py b/plone/app/discussion/tests/test_workflow.py index 5667f00..ece7d8a 100644 --- a/plone/app/discussion/tests/test_workflow.py +++ b/plone/app/discussion/tests/test_workflow.py @@ -1,11 +1,9 @@ """Test plone.app.discussion workflow and permissions. """ +from ..interfaces import IConversation +from ..interfaces import IDiscussionLayer +from ..testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING from AccessControl import Unauthorized -from plone.app.discussion.interfaces import IConversation -from plone.app.discussion.interfaces import IDiscussionLayer -from plone.app.discussion.testing import ( # noqa - PLONE_APP_DISCUSSION_INTEGRATION_TESTING, -) from plone.app.testing import login from plone.app.testing import logout from plone.app.testing import setRoles