diff --git a/news/3953.feature b/news/3953.feature new file mode 100644 index 00000000..dc0ce40f --- /dev/null +++ b/news/3953.feature @@ -0,0 +1,2 @@ +Move Classic-UI template/view code to plone.app.layout +[tlotze] diff --git a/pyproject.toml b/pyproject.toml index d3781be4..15b06c32 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -115,7 +115,7 @@ Zope = [ 'zope.sendmail', 'Zope' ] 'plone.base' = [ - 'plone.batching', 'plone.registry', 'plone.schema','plone.z3cform', + 'plone.batching', 'plone.registry', 'plone.schema', 'Products.CMFCore', 'Products.CMFDynamicViewFTI', ] python-dateutil = ['dateutil'] diff --git a/setup.py b/setup.py index 7727305a..60429904 100644 --- a/setup.py +++ b/setup.py @@ -56,7 +56,6 @@ "Pillow", "Products.GenericSetup", "Products.PlonePAS >= 5.0.1", - "Products.statusmessages", "Zope", "plone.autoform >= 1.2", "plone.app.event", @@ -64,18 +63,14 @@ "plone.base", "plone.formwidget.namedfile >= 1.0.3", "plone.i18n", - "plone.memoize", "plone.namedfile", - "plone.protect", "plone.registry", "plone.schema", "plone.schemaeditor", "plone.supermodel", "plone.uuid", - "plone.z3cform", "z3c.form", "zope.annotation", - "zope.cachedescriptors", "zope.component", "zope.event", "zope.interface", diff --git a/src/plone/app/users/__init__.py b/src/plone/app/users/__init__.py index e69de29b..0403d8e9 100644 --- a/src/plone/app/users/__init__.py +++ b/src/plone/app/users/__init__.py @@ -0,0 +1,9 @@ +jbot_deprecations = { + "plone.app.users.browser.account-configlet.pt": "plone.app.layout.users.account-configlet.pt", + "plone.app.users.browser.account-panel.pt": "plone.app.layout.users.account-panel.pt", + "plone.app.users.browser.memberregistration.pt": "plone.app.layout.users.memberregistration.pt", + "plone.app.users.browser.newuser_form.pt": "plone.app.layout.users.newuser_form.pt", + "plone.app.users.browser.registered.pt": "plone.app.layout.users.registered.pt", + "plone.app.users.browser.register_form.pt": "plone.app.layout.users.register_form.pt", + "plone.app.users.browser.schema_layout.pt": "plone.app.layout.users.schema_layout.pt", +} diff --git a/src/plone/app/users/browser/account-configlet.pt b/src/plone/app/users/browser/account-configlet.pt deleted file mode 100644 index dcd0585f..00000000 --- a/src/plone/app/users/browser/account-configlet.pt +++ /dev/null @@ -1,61 +0,0 @@ - - -
-
-

User Details

-
- Up to List of Users -
-
- Portal status message -
-
-
-
-

${python:view.description}

- - - -
- -
- -
- - diff --git a/src/plone/app/users/browser/account-panel.pt b/src/plone/app/users/browser/account-panel.pt deleted file mode 100644 index ab4de866..00000000 --- a/src/plone/app/users/browser/account-panel.pt +++ /dev/null @@ -1,46 +0,0 @@ - - - -

${view/label}

-
- - -

${view/description}

-
- - -
- - - -
-
- - diff --git a/src/plone/app/users/browser/account.py b/src/plone/app/users/browser/account.py index 292333e0..522bfecd 100644 --- a/src/plone/app/users/browser/account.py +++ b/src/plone/app/users/browser/account.py @@ -4,33 +4,29 @@ from PIL import UnidentifiedImageError from plone.app.users.browser.interfaces import IAccountPanelForm from plone.app.users.browser.schemaeditor import getFromBaseSchema -from plone.app.users.utils import notifyWidgetActionExecutionError -from plone.autoform.form import AutoExtensibleForm from plone.base import PloneMessageFactory as _ from plone.base.interfaces import INavigationRoot from plone.base.interfaces import IPloneSiteRoot from plone.base.interfaces import ISecuritySchema from plone.base.utils import safe_text from plone.namedfile.file import NamedBlobImage -from plone.protect import CheckAuthenticator from plone.registry.interfaces import IRegistry from Products.CMFCore.utils import getToolByName -from Products.CMFPlone.controlpanel.events import ConfigurationChangedEvent -from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile from Products.PlonePAS.tools.membership import default_portrait -from Products.statusmessages.interfaces import IStatusMessage -from z3c.form import button -from z3c.form import form from z3c.form.interfaces import NOT_CHANGED from zope import schema -from zope.cachedescriptors.property import Lazy as lazy_property -from zope.component import getMultiAdapter from zope.component import getUtility from zope.component import provideAdapter -from zope.event import notify from zope.globalrequest import getRequest -from zope.interface import implementer -from ZTUtils import make_query + +import zope.deferredimport + +zope.deferredimport.initialize() + +zope.deferredimport.deprecated( + "Please use from plone.app.layout.users.account import AccountPanelForm instead.", + AccountPanelForm="plone.app.layout.users.account:AccountPanelForm", +) MESSAGE_EMAIL_CANNOT_CHANGE = _( "message_email_cannot_change", @@ -199,70 +195,10 @@ def timezone(self, value): return self._setProperty("timezone", value) -@implementer(IAccountPanelForm) -class AccountPanelForm(AutoExtensibleForm, form.Form): - """A simple form to be used as a basis for account panel screens.""" - - schema = IAccountPanelForm - template = ViewPageTemplateFile("account-panel.pt") - enableCSRFProtection = True - - hidden_widgets = [] - successMessage = _("Changes saved.") - noChangesMessage = _("No changes made.") - - @lazy_property - def member(self): - mtool = getToolByName(self.context, "portal_membership") - if self.request.get("userid"): - return mtool.getMemberById(self.request.get("userid")) - return mtool.getAuthenticatedMember() - - @property - def label(self): - return self.member.getProperty("fullname") or self.member.getUserName() - - def _differentEmail(self, email): - """Check if the submitted form email address differs from the existing - one. +class AccountPanelValidation: - Keeping your email the same (which happens when you change something - else on the personalize form) or changing it back to your login name, - is fine. - - So: we only return True if it is *really* a different email. - """ - membership = getToolByName(self.context, "portal_membership") - if self.request.get("userid"): - member = membership.getMemberById(self.request.get("userid")) - else: - member = membership.getAuthenticatedMember() - if email in (member.getId(), member.getUserName()): - return False - # By default, PAS transforms login names to lowercase, at least when - # email-as-login is used. So compare the transformed/normalized names. - pas = getToolByName(self.context, "acl_users") - email_normalized = pas.applyTransform(email) - # The user name should already have been normalized, but let's make sure. - login_normalized = pas.applyTransform(member.getUserName()) - return email_normalized != login_normalized - - def makeQuery(self): - userid = self.request.form.get("userid", None) - if userid is not None: - return "?{}".format(make_query({"userid": userid})) - return "" - - def action(self): - return self.request.getURL() + self.makeQuery() - - def validate_email(self, action, data): + def validate_email(self, data): context = aq_inner(self.context) - error_keys = [error.field.getName() for error in action.form.widgets.errors] - if "email" in error_keys: - # There is already a validation error for email, - # so there is no need for further validation. - return # We only need an extra check if email is used as login. registry = getUtility(IRegistry) security_settings = registry.forInterface(ISecuritySchema, prefix="plone") @@ -291,17 +227,13 @@ def validate_email(self, action, data): # Only allow if unchanged: then that member is us! if self._differentEmail(email): err_str = MESSAGE_EMAIL_IN_USE - if err_str: - notifyWidgetActionExecutionError(action, "email", err_str) + return err_str def validate_portrait(self, action, data): """Portrait validation. Checks if image is supported by Pillow. SVG files are not yet supported. """ - error_keys = [error.field.getName() for error in action.form.widgets.errors] - if "portrait" in error_keys: - return portrait_file = data["portrait"] if portrait_file is None or portrait_file is NOT_CHANGED: return @@ -309,103 +241,6 @@ def validate_portrait(self, action, data): try: Image.open(portrait) except UnidentifiedImageError: - notifyWidgetActionExecutionError( - action, "portrait", MESSAGE_IMAGE_NOT_SUPPORTED - ) + return MESSAGE_IMAGE_NOT_SUPPORTED except Exception as exc: raise exc - - @button.buttonAndHandler(_("Save")) - def handleSave(self, action): - CheckAuthenticator(self.request) - data, errors = self.extractData() - - # Extra validation for email, when it is there. email is not in the - # data when you are at the personal-preferences page. - if "email" in data: - self.validate_email(action, data) - - # Validate portrait, upload image could be not supported - # by PIL what raises an exception when scaling image. - if "portrait" in data: - self.validate_portrait(action, data) - - if action.form.widgets.errors: - self.status = self.formErrorsMessage - return - if self.applyChanges(data): - IStatusMessage(self.request).addStatusMessage( - self.successMessage, type="info" - ) - notify(ConfigurationChangedEvent(self, data)) - self._on_save(data) - else: - IStatusMessage(self.request).addStatusMessage( - self.noChangesMessage, type="info" - ) - self.request.response.redirect(self.action()) - - def updateActions(self): - super().updateActions() - if self.actions and "save" in self.actions: - self.actions["save"].addClass("btn btn-primary") - - @button.buttonAndHandler(_("Cancel")) - def cancel(self, action): - IStatusMessage(self.request).addStatusMessage( - _("Changes canceled."), type="info" - ) - self.request.response.redirect( - "{}{}".format(self.request["ACTUAL_URL"], self.makeQuery()) - ) - - def _on_save(self, data=None): - pass - - def prepareObjectTabs(self, default_tab="view", sort_first=["folderContents"]): - context = self.context - mt = getToolByName(context, "portal_membership") - tabs = [] - navigation_root_url = context.absolute_url() - - def _check_allowed(context, request, name): - """Check, if user has required permissions on view.""" - view = getMultiAdapter((context, request), name=name) - allowed = True - for perm in view.__ac_permissions__: - allowed = allowed and mt.checkPermission(perm[0], context) - return allowed - - if _check_allowed(context, self.request, "personal-information"): - tabs.append( - { - "title": _( - "title_personal_information_form", "Personal Information" - ), - "url": navigation_root_url + "/@@personal-information", - "selected": (self.__name__ == "personal-information"), - "id": "user_data-personal-information", - } - ) - - if _check_allowed(context, self.request, "personal-preferences"): - tabs.append( - { - "title": _("Personal Preferences"), - "url": navigation_root_url + "/@@personal-preferences", - "selected": (self.__name__ == "personal-preferences"), - "id": "user_data-personal-preferences", - } - ) - - member = mt.getAuthenticatedMember() - if member.canPasswordSet(): - tabs.append( - { - "title": _("label_password", "Password"), - "url": navigation_root_url + "/@@change-password", - "selected": (self.__name__ == "change-password"), - "id": "user_data-change-password", - } - ) - return tabs diff --git a/src/plone/app/users/browser/configure.zcml b/src/plone/app/users/browser/configure.zcml index 2e8677d6..ce9df7fc 100644 --- a/src/plone/app/users/browser/configure.zcml +++ b/src/plone/app/users/browser/configure.zcml @@ -5,105 +5,8 @@ i18n_domain="plone" > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/plone/app/users/browser/memberregistration.pt b/src/plone/app/users/browser/memberregistration.pt deleted file mode 100644 index a2d21dea..00000000 --- a/src/plone/app/users/browser/memberregistration.pt +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - -
-

Users and Groups

- -
- Portal status message -
- - -
-
- -
- -
- - diff --git a/src/plone/app/users/browser/membersearch.py b/src/plone/app/users/browser/membersearch.py index 5cc031bf..c226484d 100644 --- a/src/plone/app/users/browser/membersearch.py +++ b/src/plone/app/users/browser/membersearch.py @@ -1,139 +1,12 @@ -from plone.autoform.form import AutoExtensibleForm -from plone.base import PloneMessageFactory as _ -from plone.memoize.view import memoize -from plone.supermodel import model -from Products.CMFCore.permissions import ListPortalMembers -from Products.CMFCore.utils import getToolByName -from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile -from z3c.form import button -from z3c.form import form -from zope import schema - - -class IMemberSearchSchema(model.Schema): - """Provide schema for member search.""" - - model.fieldset( - "extra", - label=_("legend_member_search_criteria", default="User Search Criteria"), - fields=["login", "email", "fullname"], - ) - - login = schema.TextLine( - title=_("label_name", default="Name"), - description=_( - "help_search_name", default="Find users whose login name contain" - ), - required=False, - ) - email = schema.TextLine( - title=_("label_email", default="Email"), - description=_( - "help_search_email", default="Find users whose email address contain" - ), - required=False, - ) - fullname = schema.TextLine( - title=_("label_fullname", default="Full Name"), - description=_( - "help_search_fullname", default="Find users whose full names contain" - ), - required=False, - ) - # disabled: https://dev.plone.org/ticket/13862 - """ - directives.read_permission(roles='cmf.ManagePortal') - directives.write_permission(roles='cmf.ManagePortal') - directives.widget('roles', CheckBoxWidget) - roles = schema.List( - title=_(u'label_roles', default=u'Role(s)'), - description=_( - u'help_search_roles', - default=u'Find users with all of the selected roles.'), - required=False, - value_type=schema.Choice( - vocabulary='plone.app.vocabularies.Roles', - ), - ) - """ - - -def extractCriteriaFromRequest(criteria): - """Takes a dictionary of z3c.form data and sanitizes it to fit - for a pas member search. - """ - for key in [ - "_authenticator", - "form.buttons.search", - "form.widgets.roles-empty-marker", - ]: - if key in criteria: - del criteria[key] - for key, value in list(criteria.items()): - if not value: - del criteria[key] - else: - new_key = key.replace("form.widgets.", "") - criteria[new_key] = value - del criteria[key] - - return criteria - - -class MemberSearchForm(AutoExtensibleForm, form.Form): - """This search form enables you to find users by specifying one or more - search criteria. - """ - - schema = IMemberSearchSchema - ignoreContext = True - - label = _("heading_member_search", default="Search for users") - description = _( - "description_member_search", - default="This search form enables you to find users by " - "specifying one or more search criteria.", - ) - template = ViewPageTemplateFile("membersearch_form.pt") - enableCSRFProtection = True - formErrorsMessage = _("There were errors.") - - submitted = False - - @property - @memoize - def listing_allowed(self): - """ - Check if the user has the necessary "List Portal Members" permissions - to view the list of search results. - """ - pm = getToolByName(self.context, "portal_membership") - return pm.checkPermission(ListPortalMembers, self.context) - - @property - def results(self): - """ - Retrieve, merge, and sort the list of results based on search criteria. - - This is based on the methods previously defined in the - Products.PlonePAS.browser.search module. - """ - # First of all check if we have the proper permissions - if not self.listing_allowed: - return [] - - view = self.context.restrictedTraverse("@@pas_search") - criteria = extractCriteriaFromRequest(self.request.form.copy()) - return view.searchUsers(sort_by="fullname", **criteria) - - @button.buttonAndHandler(_("label_search", default="Search"), name="search") - def handleApply(self, action): - request = self.request - data, errors = self.extractData() - - if errors: - self.status = self.formErrorsMessage - return - - if request.get("form.buttons.search", None): - self.submitted = True +import zope.deferredimport + +zope.deferredimport.initialize() + +zope.deferredimport.deprecated( + "Please use from plone.app.layout.users.account import IMemberSearchSchema instead.", + IMemberSearchSchema="plone.app.layout.users.account:IMemberSearchSchema", +) +zope.deferredimport.deprecated( + "Please use from plone.app.layout.users.account import MemberSearchForm instead.", + MemberSearchForm="plone.app.layout.users.account:MemberSearchForm", +) diff --git a/src/plone/app/users/browser/membersearch_form.pt b/src/plone/app/users/browser/membersearch_form.pt deleted file mode 100644 index 95c24814..00000000 --- a/src/plone/app/users/browser/membersearch_form.pt +++ /dev/null @@ -1,120 +0,0 @@ - - - - - -
-

-
- -
-
- - -
-

Search results

- -
-

- You are not allowed to list portal members. -

- - -
- - - 234 - items matching your search terms. - - -
- -
- - - -
- -
- -
- -
- -
-
- -
-

No results were found.

-
-
-
-
- - -
- - diff --git a/src/plone/app/users/browser/newuser_form.pt b/src/plone/app/users/browser/newuser_form.pt deleted file mode 100644 index 41dd42b0..00000000 --- a/src/plone/app/users/browser/newuser_form.pt +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - -
-

-
- - -
-
- -
- - diff --git a/src/plone/app/users/browser/passwordpanel.py b/src/plone/app/users/browser/passwordpanel.py index 4d8d212a..549d8a70 100644 --- a/src/plone/app/users/browser/passwordpanel.py +++ b/src/plone/app/users/browser/passwordpanel.py @@ -1,140 +1,16 @@ -from Acquisition import aq_inner -from plone.app.users.browser.account import AccountPanelForm -from plone.app.users.utils import notifyWidgetActionExecutionError -from plone.base import PloneMessageFactory as _ -from Products.CMFCore.utils import getToolByName -from Products.statusmessages.interfaces import IStatusMessage -from z3c.form import button -from zope import schema -from zope.cachedescriptors.property import Lazy as lazy_property -from zope.interface import Interface - - -class IPasswordSchema(Interface): - """Provide schema for password form""" - - current_password = schema.Password( - title=_("label_current_password", default="Current password"), - description=_("help_current_password", default="Enter your current password."), - # constraint=checkCurrentPassword, - ) - - new_password = schema.Password( - title=_("label_new_password", default="New password"), - description=_("help_new_password", default="Enter your new password."), - ) - - new_password_ctl = schema.Password( - title=_("label_confirm_password", default="Confirm password"), - description=_( - "help_confirm_password", - default="Re-enter the password. Make sure the passwords are identical.", - ), - ) - - -class PasswordPanelAdapter: - def __init__(self, context): - self.context = getToolByName(context, "portal_membership") - - def get_dummy(self): - """We don't actually need to 'get' anything ...""" - return "" - - current_password = property(get_dummy) - - new_password = property(get_dummy) - - new_password_ctl = property(get_dummy) - - -class PasswordPanel(AccountPanelForm): - """Implementation of password reset form that uses z3c.form.""" - - description = _("Change Password") - form_name = _("legend_password_details", default="Password Details") - schema = IPasswordSchema - - @lazy_property - def member(self): - # Only show current username in label - mtool = getToolByName(self.context, "portal_membership") - return mtool.getAuthenticatedMember() - - def updateFields(self): - super().updateFields() - # Change the password description based on PAS Plugin The user needs a - # list of instructions on what kind of password is required. We'll - # reuse password errors as instructions e.g. "Must contain a letter and - # a number". Assume PASPlugin errors are already translated - registration = getToolByName(self.context, "portal_registration") - err_str = registration.testPasswordValidity("") - if err_str: - msg = _("Enter your new password. ${errors}", mapping=dict(errors=err_str)) - self.fields["new_password"].field.description = msg - - def validate_password(self, action, data): - context = aq_inner(self.context) - registration = getToolByName(context, "portal_registration") - membertool = getToolByName(context, "portal_membership") - - # check if password is correct - current_password = data.get("current_password") - if current_password: - if not membertool.testCurrentPassword(current_password): - # add error to current_password widget - err_str = _("Incorrect value for current password") - notifyWidgetActionExecutionError(action, "current_password", err_str) - - # check if passwords are same and valid according to plugin - new_password = data.get("new_password") - new_password_ctl = data.get("new_password_ctl") - if new_password and new_password_ctl: - err_str = registration.testPasswordValidity(new_password, new_password_ctl) - - if err_str: - # add error to new_password widget - notifyWidgetActionExecutionError(action, "new_password", err_str) - notifyWidgetActionExecutionError(action, "new_password_ctl", err_str) - - @button.buttonAndHandler( - _("label_change_password", default="Change Password"), name="reset_passwd" - ) - def action_reset_passwd(self, action): - data, errors = self.extractData() - - # extra password validation - self.validate_password(action, data) - - if action.form.widgets.errors: - self.status = self.formErrorsMessage - return - - membertool = getToolByName(self.context, "portal_membership") - - password = data["new_password"] - - try: - membertool.setPassword(password, None, REQUEST=self.request) - except AttributeError: - failMessage = _( - "While changing your password an AttributeError " - "occurred. This is usually caused by your user " - "being defined outside the portal." - ) - - IStatusMessage(self.request).addStatusMessage(_(failMessage), type="error") - return - - IStatusMessage(self.request).addStatusMessage( - _("Password changed"), type="info" - ) - - # hide inherited Save and Cancel buttons - @button.buttonAndHandler(_("Save"), condition=lambda form: False) - def handleSave(self, action): - pass - - @button.buttonAndHandler(_("Cancel"), condition=lambda form: False) - def cancel(self, action): - pass +import zope.deferredimport + +zope.deferredimport.initialize() + +zope.deferredimport.deprecated( + "Please use from plone.app.layout.users.passwordpanel import IPasswordSchema instead.", + IPasswordSchema="plone.app.layout.users.passwordpanel:IPasswordSchema", +) +zope.deferredimport.deprecated( + "Please use from plone.app.layout.users.passwordpanel import PasswordPanel instead.", + PasswordPanel="plone.app.layout.users.passwordpanel:PasswordPanel", +) +zope.deferredimport.deprecated( + "Please use from plone.app.layout.users.passwordpanel import PasswordPanelAdapter instead.", + PasswordPanelAdapter="plone.app.layout.users.passwordpanel:PasswordPanelAdapter", +) diff --git a/src/plone/app/users/browser/personalpreferences.py b/src/plone/app/users/browser/personalpreferences.py index d445b547..98a93b74 100644 --- a/src/plone/app/users/browser/personalpreferences.py +++ b/src/plone/app/users/browser/personalpreferences.py @@ -1,11 +1,22 @@ -from plone.app.users.browser.account import AccountPanelForm from plone.app.users.browser.account import AccountPanelSchemaAdapter from plone.base import PloneMessageFactory as _ -from Products.CMFCore.utils import getToolByName -from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile from zope.interface import Interface from zope.schema import Choice +import zope.deferredimport + +zope.deferredimport.initialize() + +zope.deferredimport.deprecated( + "Please use from plone.app.layout.users.passwordpanel import PersonalPreferencesPanel instead.", + PersonalPreferencesPanel="plone.app.layout.users.passwordpanel:PersonalPreferencesPanel", +) +zope.deferredimport.deprecated( + "Please use from plone.app.layout.users.passwordpanel import PersonalPreferencesConfiglet instead.", + PersonalPreferencesConfiglet="plone.app.layout.users.passwordpanel:PersonalPreferencesConfiglet", +) + + try: import plone.app.event # noqa @@ -56,47 +67,3 @@ class IPersonalPreferences(Interface): class PersonalPreferencesPanelAdapter(AccountPanelSchemaAdapter): schema = IPersonalPreferences - - -class PersonalPreferencesPanel(AccountPanelForm): - """Implementation of personalize form that uses z3c.form.""" - - form_name = _("legend_personal_details", "Personal Details") - schema = IPersonalPreferences - - @property - def description(self): - userid = self.request.form.get("userid") - mt = getToolByName(self.context, "portal_membership") - if userid and (userid != mt.getAuthenticatedMember().getId()): - # editing someone else's profile - return _( - "description_preferences_form_otheruser", - default="Personal settings for $name", - mapping={"name": userid}, - ) - else: - # editing my own profile - return _("description_my_preferences", default="Your personal settings.") - - def updateWidgets(self): - super().updateWidgets() - - self.widgets["language"].noValueMessage = _( - "vocabulary-missing-single-value-for-edit", - "Language neutral (site default)", - ) - self.widgets["wysiwyg_editor"].noValueMessage = _( - "vocabulary-available-editor-novalue", "Use site default" - ) - - def __call__(self): - self.request.set("disable_border", 1) - return super().__call__() - - -class PersonalPreferencesConfiglet(PersonalPreferencesPanel): - """Control panel version of the personal preferences panel""" - - template = ViewPageTemplateFile("account-configlet.pt") - tab = "userprefs" diff --git a/src/plone/app/users/browser/register.py b/src/plone/app/users/browser/register.py index 806c2b46..3e90774c 100644 --- a/src/plone/app/users/browser/register.py +++ b/src/plone/app/users/browser/register.py @@ -1,43 +1,26 @@ -from AccessControl import getSecurityManager from plone.app.users.browser.account import AccountPanelSchemaAdapter from plone.app.users.browser.account import getSchema -from plone.app.users.schema import IAddUserSchema from plone.app.users.schema import ICombinedRegisterSchema -from plone.app.users.schema import IRegisterSchema -from plone.app.users.utils import generate_login_name as _generate_login_name -from plone.app.users.utils import generate_user_id as _generate_user_id -from plone.app.users.utils import notifyWidgetActionExecutionError -from plone.autoform.form import AutoExtensibleForm -from plone.base import PloneMessageFactory as _ -from plone.base.interfaces import ISecuritySchema -from plone.base.interfaces import IUserGroupsSettingsSchema -from plone.protect import CheckAuthenticator -from plone.registry.interfaces import IRegistry -from Products.CMFCore.interfaces import ISiteRoot -from Products.CMFCore.permissions import ManagePortal -from Products.CMFCore.utils import getToolByName -from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile -from Products.statusmessages.interfaces import IStatusMessage -from z3c.form import button -from z3c.form import field -from z3c.form import form -from z3c.form.browser.checkbox import CheckBoxFieldWidget -from z3c.form.interfaces import DISPLAY_MODE -from zExceptions import Forbidden -from ZODB.POSException import ConflictError -from zope.component import getAdapter -from zope.component import getMultiAdapter -from zope.component import getUtility from zope.deferredimport import deprecated -from zope.schema import getFieldNames - -import logging deprecated( "Import from plone.app.users.utils instead.", RENAME_AFTER_CREATION_ATTEMPTS="plone.app.users.utils:RENAME_AFTER_CREATION_ATTEMPTS", ) +deprecated( + "Please use from plone.app.layout.users.register import BaseRegistrationForm instead.", + BaseRegistrationForm="plone.app.layout.users.register:BaseRegistrationForm", +) +deprecated( + "Please use from plone.app.layout.users.register import RegistrationForm instead.", + RegistrationForm="plone.app.layout.users.register:RegistrationForm", +) +deprecated( + "Please use from plone.app.layout.users.register import AddUserForm instead.", + AddUserForm="plone.app.layout.users.register:AddUserForm", +) + def getRegisterSchema(): schema = getSchema( @@ -46,535 +29,3 @@ def getRegisterSchema(): form_name="On Registration", ) return schema - - -class BaseRegistrationForm(AutoExtensibleForm, form.Form): - """Form to be used as base for Register and Add User forms.""" - - label = "" - description = "" - formErrorsMessage = _("There were errors.") - ignoreContext = True - enableCSRFProtection = True - - # this attribute indicates if user was successfully registered - _finishedRegister = False - - @property - def schema(self): - return getRegisterSchema() - - def _get_security_settings(self): - """Return security settings from the registry.""" - registry = getUtility(IRegistry) - return registry.forInterface(ISecuritySchema, prefix="plone") - - def render(self): - if self._finishedRegister: - return self.context.unrestrictedTraverse("registered")() - - return super().render() - - def updateFields(self): - """Fields are dynamic in this form, to be able to handle - different join styles. - """ - settings = self._get_security_settings() - use_email_as_login = settings.use_email_as_login - - # Finally, let autoform process the schema and any FormExtenders do - # their thing - super().updateFields() - - if use_email_as_login: - self.fields["email"].field.description = _( - "help_email_creation_for_login", - default="Enter an email " - "address. This will be your login name. We respect your " - "privacy and will not give the address away to any third " - "parties or expose it anywhere.", - ) - del self.fields["username"] - else: - self.fields["email"].field.description = _( - "help_email_creation", - default="Enter an email address. This is necessary in case " - "the password is lost. We respect your privacy and " - "will not give the address away to any third parties " - "or expose it anywhere.", - ) - - # Change the password description based on PAS Plugin The user needs a - # list of instructions on what kind of password is required. We'll - # reuse password errors as instructions e.g. "Must contain a letter and - # a number". Assume PASPlugin errors are already translated - if self.fields.get("password", None): - registration = getToolByName(self.context, "portal_registration") - err_str = registration.testPasswordValidity("") - if err_str: - msg = _( - "help_password_creation_with_errors", - default="Enter your new password. ${errors}", - mapping=dict(errors=err_str), - ) - self.fields["password"].field.description = msg - - def updateActions(self): - super().updateActions() - self.actions["register"].addClass("btn-primary") - - def generate_user_id(self, data): - """Generate a user id from data. - - Delegates to the standalone function in plone.app.users.utils. - See :func:`plone.app.users.utils.generate_user_id` for details. - """ - return _generate_user_id(self.context, data) - - def generate_login_name(self, data): - """Generate a login name from data. - - Delegates to the standalone function in plone.app.users.utils. - See :func:`plone.app.users.utils.generate_login_name` for details. - """ - return _generate_login_name(self.context, data) - - # Actions validators - def validate_registration(self, action, data): - """Specific business logic for this join form. Note: all this logic - was taken directly from the old validate_registration.py script in - Products/CMFPlone/skins/plone_login/join_form_validate.vpy - """ - - # CSRF protection - CheckAuthenticator(self.request) - - registration = getToolByName(self.context, "portal_registration") - - error_keys = [error.field.getName() for error in action.form.widgets.errors] - - form_field_names = [f for f in self.fields] - - portal = getUtility(ISiteRoot) - - # passwords should match - if "password" in form_field_names: - assert "password_ctl" in form_field_names - # Skip this check if password fields already have an error - if not ("password" in error_keys or "password_ctl" in error_keys): - password = data.get("password") - password_ctl = data.get("password_ctl") - if password != password_ctl: - err_str = _("Passwords do not match.") - notifyWidgetActionExecutionError(action, "password", err_str) - notifyWidgetActionExecutionError(action, "password_ctl", err_str) - - # Password field checked against RegistrationTool - if "password" in form_field_names: - # Skip this check if password fields already have an error - if "password" not in error_keys: - password = data.get("password") - if password: - # Use PAS to test validity - err_str = registration.testPasswordValidity(password) - if err_str: - notifyWidgetActionExecutionError(action, "password", err_str) - - settings = self._get_security_settings() - if settings.use_email_as_login: - username_field = "email" - else: - username_field = "username" - - # The term 'username' is not clear. It may be the user id or - # the login name. So here we try to be explicit. - - # Generate a nice user id and store that in the data. - user_id = self.generate_user_id(data) - # Generate a nice login name and store that in the data. - login_name = self.generate_login_name(data) - - # Do several checks to see if the user id and the login name - # are valid. - # - # Skip these checks if username was already in error list. - # - # Note that if we cannot generate a unique user id, it is not - # necessarily the fault of the username field, but it - # certainly is the most likely cause in a standard Plone - # setup. - - # check if username is valid - # Skip this check if username was already in error list - if username_field not in error_keys: - # user id may not be the same as the portal id. - if user_id == portal.getId(): - err_str = _( - "This username is reserved. Please choose a different name." - ) - notifyWidgetActionExecutionError(action, username_field, err_str) - - # Check if user id is allowed by the member id pattern. - if username_field not in error_keys: - if not registration.isMemberIdAllowed(user_id): - err_str = _( - "The login name you selected is already in use " - "or is not valid. Please choose another." - ) - notifyWidgetActionExecutionError(action, username_field, err_str) - - if username_field not in error_keys: - # Check the uniqueness of the login name, not only when - # use_email_as_login is true, but always. - pas = getToolByName(self, "acl_users") - results = pas.searchUsers(name=login_name, exact_match=True) - if results: - err_str = _( - "The login name you selected is already in use " - "or is not valid. Please choose another." - ) - notifyWidgetActionExecutionError(action, username_field, err_str) - - if "password" in form_field_names and "password" not in error_keys: - # Admin can either set a password or mail the user (or both). - if not (data["password"] or data["mail_me"]): - err_str = _( - "msg_no_password_no_mail_me", - default="You must set a password or choose to send an email.", - ) - - # set error on password field - notifyWidgetActionExecutionError(action, "password", err_str) - notifyWidgetActionExecutionError(action, "mail_me", err_str) - - @button.buttonAndHandler(_("label_register", default="Register"), name="register") - def action_join(self, action): - data, errors = self.extractData() - - # extra password validation - self.validate_registration(action, data) - - if action.form.widgets.errors: - self.status = self.formErrorsMessage - return - - self.handle_join_success(data) - - # XXX Return somewhere else, depending on what - # handle_join_success returns? - came_from = self.request.form.get("came_from") - if came_from: - utool = getToolByName(self.context, "portal_url") - if utool.isURLInPortal(came_from): - self.request.response.redirect(came_from) - return "" - - def handle_join_success(self, data): - # portal should be acquisition wrapped, this is needed for the schema - # adapter below - portal = getToolByName(self.context, "portal_url").getPortalObject() - registration = getToolByName(self.context, "portal_registration") - - # user_id and login_name should be in the data, but let's be safe. - user_id = data.get("user_id", data.get("username")) - login_name = data.get("login_name", data.get("username")) - - # Set the username for good measure, as some code may expect - # it to exist and contain the user id. - data["username"] = user_id - - # The login name may already be in the form, but not - # necessarily, for example when using email as login. This is - # at least needed for logging in immediately when password - # reset is bypassed. We need the login name here, not the - # user id. - self.request.form["form.username"] = login_name - - password = data.get("password") or registration.generatePassword() - - try: - registration.addMember(user_id, password, REQUEST=self.request) - except (AttributeError, ValueError) as err: - logging.exception(err) - IStatusMessage(self.request).addStatusMessage(err, type="error") - self._finishedRegister = False - return - - if user_id != login_name: - # The user id differs from the login name. Set the login - # name correctly. - pas = getToolByName(self.context, "acl_users") - pas.updateLoginName(user_id, login_name) - - # set member properties - self.applyProperties(user_id, data) - - settings = self._get_security_settings() - self._finishedRegister = True - if data.get("mail_me") or ( - not settings.enable_user_pwd_choice and not data.get("password") - ): - # We want to validate the email address (users cannot - # select their own passwords on the register form) or the - # admin has explicitly requested to send an email on the - # 'add new user' form. - try: - # When all goes well, this call actually returns the - # rendered mail_password_response template. As a side - # effect, this removes any status messages added to - # the request so far, as they are already shown in - # this template. - response = registration.registeredNotify(user_id) - return response - except ConflictError: - # Let Zope handle this exception. - raise - except Exception as err: - logging.exception(err) - ctrlOverview = getMultiAdapter( - (portal, self.request), name="overview-controlpanel" - ) - mail_settings_correct = not ctrlOverview.mailhost_warning() - if mail_settings_correct: - # The email settings are correct, so the most - # likely cause of an error is a wrong email - # address. We remove the account: - # Remove the account: - self.context.acl_users.userFolderDelUsers( - [user_id], REQUEST=self.request - ) - self._finishedRegister = False - IStatusMessage(self.request).addStatusMessage( - _( - "status_fatal_password_mail", - default="Failed to create your account: we were " - "unable to send instructions for setting a password " - "to your email address: ${address}", - mapping={"address": data.get("email", "")}, - ), - type="error", - ) - else: - # This should only happen when an admin registers - # a user. The admin should have seen a warning - # already, but we warn again for clarity. - IStatusMessage(self.request).addStatusMessage( - _( - "status_nonfatal_password_mail", - default="This account has been created, but we " - "were unable to send instructions for setting a " - "password to this email address: ${address}", - mapping={"address": data.get("email", "")}, - ), - type="warning", - ) - - def applyProperties(self, userid, data): - portal = getToolByName(self.context, "portal_url").getPortalObject() - mt = getToolByName(self.context, "portal_membership") - member = mt.getMemberById(userid) - - # cache adapters - adapters = {} - - # Set any fields that are simply properties for the new user, rather - # than fields to help create the new user - register_fields = getFieldNames(IRegisterSchema) + getFieldNames(IAddUserSchema) - for k, value in data.items(): - # skip fields not available in the schema - if k in ["login_name", "user_id"]: - continue - - # skip fields that are handled exclusively on user registration and - # are not part of personal information form - if k in register_fields: - continue - - # get schema adapter - schema = self.fields[k].field.interface - if schema in adapters: - adapter = adapters[schema] - else: - adapters[schema] = adapter = getAdapter(portal, schema) - adapter.context = member - adapter.schema = schema - - # finally set value - setattr(adapter, k, value) - - -class RegistrationForm(BaseRegistrationForm): - """Dynamically get fields from user data, through admin config settings.""" - - label = _("heading_registration_form", default="Registration form") - description = "" - template = ViewPageTemplateFile("register_form.pt") - - @property - def showForm(self): - """The form should not be displayed to the user if the system is - incapable of sending emails and email validation is switched on - (users are not allowed to select their own passwords). - """ - portal = getUtility(ISiteRoot) - ctrlOverview = getMultiAdapter( - (portal, self.request), name="overview-controlpanel" - ) - - settings = self._get_security_settings() - # hide form if mailhost_warning == True and - # enable_user_pwd_choice == False - return not ( - ctrlOverview.mailhost_warning() and not settings.enable_user_pwd_choice - ) - - def updateFields(self): - if not self.showForm: - # We do not want to spend time calculating fields that - # will never get displayed. - return - - super().updateFields() - defaultFields = field.Fields(self.fields) - - # Can the user actually set his/her own password? - settings = self._get_security_settings() - if not settings.enable_user_pwd_choice: - # No? Remove the password fields. - defaultFields = defaultFields.omit("password", "password_ctl") - else: - # The portal is not interested in validating emails, and - # the user is not interested in getting an email with a - # link to set his password if he can set this password in - # the current form already. - defaultFields = defaultFields.omit("mail_me") - - self.fields = defaultFields - - def updateWidgets(self): - if not self.showForm: - # We do not want to spend time calculating widgets that - # will never get displayed. - return - - super().updateWidgets() - settings = self._get_security_settings() - if not settings.enable_user_pwd_choice: - # Show a message indicating that a password reset link - # will be mailed to the user. - widget = self.widgets["mail_me"] - widget.mode = DISPLAY_MODE - widget.value = ["selected"] - widget.label = _( - "label_password_change_mail", - default="A URL will be generated and e-mailed to you; " - "follow the link to reach a page where you can " - "change your password and complete the registration " - "process.", - ) - widget.terms = None - widget.updateTerms() - - -class AddUserForm(BaseRegistrationForm): - label = _("heading_add_user_form", default="Add New User") - description = "" - template = ViewPageTemplateFile("newuser_form.pt") - - def updateFields(self): - super().updateFields() - defaultFields = field.Fields(self.fields) - - # The mail_me field needs special handling depending on the - # enable_user_pwd_choice setting and on the correctness of the mail - # settings. - portal = getUtility(ISiteRoot) - ctrlOverview = getMultiAdapter( - (portal, self.request), name="overview-controlpanel" - ) - mail_settings_correct = not ctrlOverview.mailhost_warning() - if mail_settings_correct: - # Make the password fields optional: either specify a - # password or mail the user (or both). The validation - # will check that at least one of the options is chosen. - defaultFields["password"].field.required = False - defaultFields["password_ctl"].field.required = False - settings = self._get_security_settings() - defaultFields["mail_me"].field.default = not settings.enable_user_pwd_choice - - # Append the manager-focused fields - registry = getUtility(IRegistry) - user_group_settings = registry.forInterface( - IUserGroupsSettingsSchema, prefix="plone" - ) - many_groups = user_group_settings.many_groups - if not many_groups: - allFields = defaultFields + field.Fields(IAddUserSchema) - allFields["groups"].widgetFactory = CheckBoxFieldWidget - else: - allFields = defaultFields - self.fields = allFields - - def updateWidgets(self): - super().updateWidgets() - - # set display mode for mail_me field if no mailhost is configured - portal = getUtility(ISiteRoot) - ctrlOverview = getMultiAdapter( - (portal, self.request), name="overview-controlpanel" - ) - mail_settings_correct = not ctrlOverview.mailhost_warning() - if not mail_settings_correct: - widget = self.widgets["mail_me"] - widget.mode = DISPLAY_MODE - widget.value = ["selected"] - widget.label = _( - "label_cant_mail_password_reset", - default="Normally we would offer to send the user an email " - "with instructions to set a password on completion " - "of this form. But this site does not have a valid " - "email setup. You can fix this in the Mail settings.", - ) - widget.terms = None - widget.updateTerms() - - @button.buttonAndHandler(_("label_register", default="Register"), name="register") - def action_join(self, action): - data, errors = self.extractData() - - # extra password validation - self.validate_registration(action, data) - - if action.form.widgets.errors: - self.status = self.formErrorsMessage - return - - self.handle_join_success(data) - - if not self._finishedRegister: - return - - portal_groups = getToolByName(self.context, "portal_groups") - user_id = data["user_id"] - is_zope_manager = getSecurityManager().checkPermission( - ManagePortal, - self.context, - ) - try: - # Add user to the selected group(s) - if data.get("groups", None) is not None: - for groupname in data["groups"]: - group = portal_groups.getGroupById(groupname) - if "Manager" in group.getRoles() and not is_zope_manager: - raise Forbidden - portal_groups.addPrincipalToGroup(user_id, groupname, self.request) - except (AttributeError, ValueError) as err: - IStatusMessage(self.request).addStatusMessage(err, type="error") - return - - IStatusMessage(self.request).addStatusMessage(_("User added."), type="info") - self.request.response.redirect( - self.context.absolute_url() - + "/@@usergroup-userprefs?searchstring=" - + user_id - ) diff --git a/src/plone/app/users/browser/register_form.pt b/src/plone/app/users/browser/register_form.pt deleted file mode 100644 index 384761c4..00000000 --- a/src/plone/app/users/browser/register_form.pt +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - diff --git a/src/plone/app/users/browser/registered.pt b/src/plone/app/users/browser/registered.pt deleted file mode 100644 index f2b63f7c..00000000 --- a/src/plone/app/users/browser/registered.pt +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - -
- Cookie authentication has been disabled. -
- -
- -

- Welcome! -

- -
- You have been registered. -
- -
-

- You will receive an e-mail shortly containing a URL that will allow you to - set your password. When you receive this e-mail, please follow the link to - complete your registration process. NOTE: The password reset URL will expire - on - . If your - password reset request expires, you may use the "Forgot your password" link - to generate another password reset request. -

- - -

- Click the button to log in immediately. -

- - - - -
- - - - - - - - - - - -
-
-
- -
- -
- - diff --git a/src/plone/app/users/browser/registered.py b/src/plone/app/users/browser/registered.py index bfc5c8b2..a53c597c 100644 --- a/src/plone/app/users/browser/registered.py +++ b/src/plone/app/users/browser/registered.py @@ -1,14 +1,8 @@ -from Products.CMFCore.utils import getToolByName -from Products.Five import BrowserView -from zope.component import getMultiAdapter +import zope.deferredimport -import datetime +zope.deferredimport.initialize() - -class RegisteredView(BrowserView): - def expire_date(self): - ppr = getToolByName(self.context, "portal_password_reset") - expire_length = datetime.timedelta(days=ppr.getExpirationTimeout()) - expiration_date = datetime.datetime.now() + expire_length - ploneview = getMultiAdapter((self.context, self.request), name="plone") - return ploneview.toLocalizedTime(expiration_date, long_format=1) +zope.deferredimport.deprecated( + "Please use from plone.app.layout.users.registered import RegisteredView instead.", + RegisteredView="plone.app.layout.users.registered:RegisteredView", +) diff --git a/src/plone/app/users/browser/schema_layout.pt b/src/plone/app/users/browser/schema_layout.pt deleted file mode 100644 index 9c233efe..00000000 --- a/src/plone/app/users/browser/schema_layout.pt +++ /dev/null @@ -1,69 +0,0 @@ - - - - -
- - - Site Setup - - -

Edit Member Form Fields

- -
- Portal status message -
- -
- - - - - -
- -
- - - diff --git a/src/plone/app/users/browser/schemaeditor.py b/src/plone/app/users/browser/schemaeditor.py index bda7fa3c..085c8e7e 100644 --- a/src/plone/app/users/browser/schemaeditor.py +++ b/src/plone/app/users/browser/schemaeditor.py @@ -4,31 +4,31 @@ from plone.app.users.schema import SCHEMATA_KEY from plone.base import PloneMessageFactory as _ from plone.base.interfaces import IPloneSiteRoot -from plone.schemaeditor.browser.schema.listing import SchemaListing -from plone.schemaeditor.browser.schema.traversal import SchemaContext from plone.supermodel import loadString from plone.supermodel.model import finalizeSchemas from plone.supermodel.model import Model from plone.supermodel.model import SchemaClass -from plone.supermodel.parser import IFieldMetadataHandler from plone.supermodel.serializer import serialize -from plone.supermodel.utils import ns -from plone.z3cform.layout import FormWrapper from Products.CMFCore.utils import getToolByName from Products.CMFPlone.utils import get_portal -from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile from zope.annotation.interfaces import IAnnotations from zope.component import getGlobalSiteManager -from zope.interface import implementer -from zope.interface import Interface import copy import logging import re +import zope.deferredimport -USERS_NAMESPACE = "http://namespaces.plone.org/supermodel/users" -USERS_PREFIX = "users" -SPLITTER = "_//_" +zope.deferredimport.initialize() + +zope.deferredimport.deprecated( + "Please use from plone.app.layout.users.schemaeditor import MemberSchemaContext instead.", + MemberSchemaContext="plone.app.layout.users.schemaeditor:MemberSchemaContext", +) +zope.deferredimport.deprecated( + "Please use from plone.app.layout.users.schemaeditor import SchemaListingPage instead.", + SchemaListingPage="plone.app.layout.users.schemaeditor:SchemaListingPage", +) ALLOWED_FIELDS = [ "zope.schema._bootstrapfields.TextLine", @@ -74,38 +74,6 @@ def log(message, level="info", id="plone.app.users.browser.schemaeditor"): getattr(logger, level)(message) -class IMemberFieldValidator(Interface): - """Base marker for field validators""" - - -class IMemberSchemaContext(Interface): - """ """ - - -class SchemaListingPage(FormWrapper): - form = SchemaListing - index = ViewPageTemplateFile("schema_layout.pt") - - -@implementer(IMemberSchemaContext) -class MemberSchemaContext(SchemaContext): - label = _("Edit Member Form Fields") - - def __init__(self, context, request): - self.fieldsWhichCannotBeDeleted = ["fullname", "email"] - self.showSaveDefaults = False - self.enableFieldsets = False - self.allowedFields = ALLOWED_FIELDS - - schema = getFromBaseSchema(IUserDataSchema) - super().__init__( - schema, - request, - name=SCHEMATA_KEY, - title=_("Member Fields"), - ) - - def updateSchema(object, event): snew_schema = serialize_ttw_schema(object.schema) applySchema(snew_schema) @@ -175,70 +143,6 @@ def get_ttw_edited_schema(): return "" -@implementer(IFieldMetadataHandler) -class UsersMetadataSchemaExporter: - """Support the security: namespace in model definitions.""" - - namespace = ns = USERS_NAMESPACE - prefix = USERS_PREFIX - if_attrs = ( - "min", - "max", - "order", - "min_length", - "max_length", - "required", - ) - - def read(self, fieldNode, schema, field): - for attr in self.if_attrs: - value = self.load(fieldNode.get(ns(attr, self.ns), None)) - if value is not None: - setattr(field, attr, value) - - def write(self, fieldNode, schema, field): - for attr in self.if_attrs: - value = getattr(field, attr, None) - if value is not None: - v = self.serialize(value) - fieldNode.set(ns(attr, self.ns), v) - - def load(self, value): - listre = re.compile("(?Plist|set|tuple):(?P.*)", re_flags) - ltypes = { - "list": list, - "set": set, - "tuple": tuple, - } - if isinstance(value, str): - listm = listre.search(value) - if value.startswith("int:"): - value = int(value.split("int:")[1]) - elif listm: - i = listm.groupdict() - try: - tp = i["type"] - value = i["list"].split(SPLITTER) - if tp not in ["list"]: - value = ltypes[tp](value) - except Exception: - value = [] - else: - value = {"bool:true": True, "bool:false": False}.get( - value.lower(), value - ) - return value - - def serialize(self, value): - if isinstance(value, bool): - value = value and "bool:true" or "bool:false" - elif isinstance(value, (list, set, tuple)): - value = f"{type(value).__name__}:{SPLITTER.join(value)}" - elif value is not None: - value = f"int:{value}" - return value - - def is_serialisable_field(field): ret = False if field.__class__.__name__ in field_type_mapping: diff --git a/src/plone/app/users/browser/userdatapanel.py b/src/plone/app/users/browser/userdatapanel.py index d5a362b8..bf74a8d9 100644 --- a/src/plone/app/users/browser/userdatapanel.py +++ b/src/plone/app/users/browser/userdatapanel.py @@ -1,22 +1,26 @@ from AccessControl.SecurityManagement import getSecurityManager -from plone.app.users.browser.account import AccountPanelForm from plone.app.users.browser.account import AccountPanelSchemaAdapter from plone.app.users.browser.account import getSchema from plone.app.users.schema import IUserDataSchema -from plone.base import PloneMessageFactory as _ from plone.base.interfaces import ISecuritySchema from plone.registry.interfaces import IRegistry from Products.CMFCore.utils import getToolByName from Products.CMFPlone.utils import get_portal from Products.CMFPlone.utils import set_own_login_name -from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile -from zExceptions import NotFound from zope.component import getUtility -try: - from html import escape -except ImportError: - from cgi import escape +import zope.deferredimport + +zope.deferredimport.initialize() + +zope.deferredimport.deprecated( + "Please use from plone.app.layout.users.account import UserDataPanel instead.", + UserDataPanel="plone.app.layout.users.account:UserDataPanel", +) +zope.deferredimport.deprecated( + "Please use from plone.app.layout.users.account import UserDataConfiglet instead.", + UserDataConfiglet="plone.app.layout.users.account:UserDataConfiglet", +) class UserDataPanelAdapter(AccountPanelSchemaAdapter): @@ -54,43 +58,6 @@ def set_email(self, value): email = property(get_email, set_email) -class UserDataPanel(AccountPanelForm): - form_name = _("User Data Form") - enableCSRFProtection = True - - @property - def schema(self): - schema = getUserDataSchema() - return schema - - @property - def description(self): - userid = self.request.form.get("userid") - mt = getToolByName(self.context, "portal_membership") - if userid and (userid != mt.getAuthenticatedMember().getId()): - # editing someone else's profile - return _( - "description_personal_information_form_otheruser", - default="Change personal information for $name", - mapping={"name": escape(userid)}, - ) - else: - # editing my own profile - return _( - "description_personal_information_form", - default="Change your personal information", - ) - - def __call__(self): - userid = self.request.form.get("userid") - if userid: - mt = getToolByName(self.context, "portal_membership") - if mt.getMemberById(userid) is None: - raise NotFound("User does not exist.") - self.request.set("disable_border", 1) - return super().__call__() - - def getUserDataSchema(): portal = get_portal() form_name = "In User Profile" @@ -98,10 +65,3 @@ def getUserDataSchema(): form_name = None schema = getSchema(IUserDataSchema, UserDataPanelAdapter, form_name=form_name) return schema - - -class UserDataConfiglet(UserDataPanel): - """Control panel version of the userdata panel""" - - template = ViewPageTemplateFile("account-configlet.pt") - tab = "userdata" diff --git a/src/plone/app/users/configure.zcml b/src/plone/app/users/configure.zcml index 720ad296..cec2bd54 100644 --- a/src/plone/app/users/configure.zcml +++ b/src/plone/app/users/configure.zcml @@ -56,7 +56,7 @@ @@ -64,7 +64,7 @@ diff --git a/src/plone/app/users/field_extender.py b/src/plone/app/users/field_extender.py index 0c7b262d..077be2cd 100644 --- a/src/plone/app/users/field_extender.py +++ b/src/plone/app/users/field_extender.py @@ -1,5 +1,3 @@ -from plone.app.users.browser.schemaeditor import USERS_NAMESPACE -from plone.app.users.browser.schemaeditor import USERS_PREFIX from plone.autoform import directives as form from plone.base import PloneMessageFactory as _ from plone.supermodel.interfaces import IFieldMetadataHandler @@ -14,6 +12,9 @@ import zope.schema +USERS_NAMESPACE = "http://namespaces.plone.org/supermodel/users" +USERS_PREFIX = "users" + form_vocab = SimpleVocabulary( [ SimpleTerm(value="On Registration", title=_("On Registration")), diff --git a/src/plone/app/users/schema.py b/src/plone/app/users/schema.py index d5d35a7f..f9dd896d 100644 --- a/src/plone/app/users/schema.py +++ b/src/plone/app/users/schema.py @@ -85,6 +85,10 @@ class IUserDataSchema(Interface): ) +class IMemberSchemaContext(Interface): + """ """ + + class IRegisterSchema(Interface): username = schema.ASCIILine( title=_("label_user_name", default="User Name"), diff --git a/src/plone/app/users/tests/test_loginname_generator.py b/src/plone/app/users/tests/test_loginname_generator.py index 563bdf1e..8e566da9 100644 --- a/src/plone/app/users/tests/test_loginname_generator.py +++ b/src/plone/app/users/tests/test_loginname_generator.py @@ -1,7 +1,7 @@ # Note: test setup somehow fails when only tests from this file are run. from plone.app.users.browser.interfaces import ILoginNameGenerator -from plone.app.users.browser.register import BaseRegistrationForm from plone.app.users.tests.base import BaseTestCase +from plone.app.users.utils import generate_login_name from zope.component import getSiteManager @@ -11,12 +11,11 @@ def test_custom_generator(self): behavior. """ sm = getSiteManager(context=self.portal) - form = BaseRegistrationForm(self.portal, {}) data = {"useme": "me me me", "username": "frank"} sm.registerUtility(lambda data: data["useme"], provided=ILoginNameGenerator) - self.assertEqual(form.generate_login_name(data), "me me me") + self.assertEqual(generate_login_name(self.portal, data), "me me me") self.assertEqual(data.get("login_name"), "me me me") def test_custom_generator_empty(self): @@ -24,32 +23,29 @@ def test_custom_generator_empty(self): returns an empty value. """ sm = getSiteManager(context=self.portal) - form = BaseRegistrationForm(self.portal, {}) data = {"useme": "", "username": "Frank"} sm.registerUtility(lambda data: data["useme"], provided=ILoginNameGenerator) - self.assertEqual(form.generate_login_name(data), "Frank") + self.assertEqual(generate_login_name(self.portal, data), "Frank") self.assertEqual(data.get("login_name"), "Frank") def test_use_email_as_login_disabled(self): """Test generating user_id with no custom login name generator and with the use_email_as_login security setting disabled. """ - form = BaseRegistrationForm(self.portal, {}) data = {"username": "Frank"} self.security_settings.use_email_as_login = False - self.assertEqual(form.generate_login_name(data), "Frank") + self.assertEqual(generate_login_name(self.portal, data), "Frank") self.assertEqual(data.get("login_name"), "Frank") def test_use_email_as_login_enabled(self): """Test generating user_id with no custom login name generator and with the use_email_as_login security setting enabled. """ - form = BaseRegistrationForm(self.portal, {}) data = {"username": "Frank", "email": "Frank@Test.com"} self.security_settings.use_email_as_login = True - self.assertEqual(form.generate_login_name(data), "frank@test.com") + self.assertEqual(generate_login_name(self.portal, data), "frank@test.com") self.assertEqual(data.get("login_name"), "frank@test.com") diff --git a/src/plone/app/users/tests/test_member_search.py b/src/plone/app/users/tests/test_member_search.py deleted file mode 100644 index 84b119dd..00000000 --- a/src/plone/app/users/tests/test_member_search.py +++ /dev/null @@ -1,17 +0,0 @@ -from plone.app.users.browser.membersearch import extractCriteriaFromRequest - -import unittest - - -class TestMemberSearch(unittest.TestCase): - def test_extract_criteria_from_request(self): - data = { - "_authenticator": "ab4731...", - "form.buttons.search": "Search", - "form.widgets.something": "any form value", - "form.widgets.roles-empty-marker": True, - } - - result = extractCriteriaFromRequest(data) - - self.assertEqual(result, {"something": "any form value"}) diff --git a/src/plone/app/users/tests/test_user_data_panel.py b/src/plone/app/users/tests/test_user_data_panel.py deleted file mode 100644 index 36b5a2c6..00000000 --- a/src/plone/app/users/tests/test_user_data_panel.py +++ /dev/null @@ -1,25 +0,0 @@ -from plone.app.users.browser.userdatapanel import UserDataPanel -from plone.app.users.tests.base import BaseTestCase -from zExceptions import NotFound -from zope.i18n import translate - - -class TestUserDataPanel(BaseTestCase): - def test_regression(self): - portal = self.layer["portal"] - request = self.layer["request"] - request.form.update({"userid": "admin"}) - form = UserDataPanel(portal, request) - description = translate(form.description, context=request) - self.assertTrue("admin" in description) - # form can be called without raising exception. - self.assertTrue(form()) - - def test_escape_html(self): - portal = self.layer["portal"] - request = self.layer["request"] - request.form.update({"userid": 'admin'}) - form = UserDataPanel(portal, request) - description = translate(form.description, context=request) - self.assertTrue("