diff --git a/backend/Makefile b/backend/Makefile index 1e62119..e0b19f4 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -84,15 +84,15 @@ clean: ## Clean environment .PHONY: start start: ## Start a Plone instance on localhost:8080 - ALLOWED_DISTRIBUTIONS=portalbrasil-intranet PYTHONWARNINGS=ignore $(BIN_FOLDER)/runwsgi instance/etc/zope.ini + ENABLE_PRINTING_MAILHOST=True PYTHONWARNINGS=ignore $(BIN_FOLDER)/runwsgi instance/etc/zope.ini .PHONY: console console: instance/etc/zope.ini ## Start a console into a Plone instance - ALLOWED_DISTRIBUTIONS=portalbrasil-intranet PYTHONWARNINGS=ignore $(BIN_FOLDER)/zconsole debug instance/etc/zope.conf + PYTHONWARNINGS=ignore $(BIN_FOLDER)/zconsole debug instance/etc/zope.conf .PHONY: create-site create-site: instance/etc/zope.ini ## Create a new site from scratch - ALLOWED_DISTRIBUTIONS=portalbrasil-intranet PYTHONWARNINGS=ignore $(BIN_FOLDER)/zconsole run instance/etc/zope.conf ./scripts/create_site.py + PYTHONWARNINGS=ignore $(BIN_FOLDER)/zconsole run instance/etc/zope.conf ./scripts/create_site.py # Example Content .PHONY: update-example-content diff --git a/backend/README.md b/backend/README.md index 89f0426..3508593 100644 --- a/backend/README.md +++ b/backend/README.md @@ -8,7 +8,7 @@ Works with volto-form-block >= v3.8.0 ## plone.restapi endpoints -### `@submit-form` +### `@schemaform-data` Endpoint that the frontend should call as a submit action. diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 0f4710a..4b08cb4 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -42,6 +42,7 @@ dependencies = [ "souper.plone", "click", "beautifulsoup4", + "jsonschema", "pyotp", ] @@ -101,6 +102,7 @@ dependencies = [ "plone.formwidget.recaptcha", "plone.restapi[test]", "Products.MailHost", + "Products.PrintingMailHost", "pytest-cov==5.0.0", "pytest-plone>=0.5.0", "pytest", diff --git a/backend/src/collective/volto/formsupport/captcha/honeypot.py b/backend/src/collective/volto/formsupport/captcha/honeypot.py index 9b786f5..8f77dcf 100644 --- a/backend/src/collective/volto/formsupport/captcha/honeypot.py +++ b/backend/src/collective/volto/formsupport/captcha/honeypot.py @@ -6,6 +6,8 @@ from zExceptions import BadRequest from zope.i18n import translate +import json + class HoneypotSupport(CaptchaSupport): name = _("Honeypot Support") @@ -31,6 +33,8 @@ def verify(self, data): # first check if volto-form-block send the compiled token # (because by default it does not insert the honeypot field into the submitted # form) + if isinstance(data, str): + data = json.loads(data) if not data: # @submit-form has been called not from volto-form-block so do the standard # validation. diff --git a/backend/src/collective/volto/formsupport/datamanager/catalog.py b/backend/src/collective/volto/formsupport/datamanager/catalog.py index 808a7e3..032bce9 100644 --- a/backend/src/collective/volto/formsupport/datamanager/catalog.py +++ b/backend/src/collective/volto/formsupport/datamanager/catalog.py @@ -1,7 +1,6 @@ from collective.volto.formsupport import logger from collective.volto.formsupport.interfaces import IFormDataStore from collective.volto.formsupport.utils import get_blocks -from copy import deepcopy from datetime import datetime from plone.dexterity.interfaces import IDexterityContent from plone.restapi.deserializer import json_body @@ -46,28 +45,24 @@ def block_id(self): def get_form_fields(self): blocks = get_blocks(self.context) - if not blocks: return {} - form_block = {} - for id_, block in blocks.items(): - if id_ != self.block_id: - continue - block_type = block.get("@type", "") - if block_type == "form": - form_block = deepcopy(block) - if not form_block: - return {} - - subblocks = form_block.get("subblocks", []) - - # Add the 'custom_field_id' field back in as this isn't stored with each - # subblock - for index, field in enumerate(subblocks): - if form_block.get(field["field_id"]): - subblocks[index]["custom_field_id"] = form_block.get(field["field_id"]) - - return subblocks + block = blocks.get(self.block_id, {}) + block_type = block.get("@type", "") + if block_type == "schemaForm": + return [ + {"field_id": name, "label": field.get("title", name)} + for name, field in block["schema"]["properties"].items() + ] + elif block_type == "form": + subblocks = block.get("subblocks", []) + # Add the 'custom_field_id' field back in as this isn't stored with each + # subblock + for index, field in enumerate(subblocks): + if block.get(field["field_id"]): + subblocks[index]["custom_field_id"] = block.get(field["field_id"]) + return subblocks + return {} def add(self, data): form_fields = self.get_form_fields() diff --git a/backend/src/collective/volto/formsupport/interfaces.py b/backend/src/collective/volto/formsupport/interfaces.py index d2218a4..1c97f81 100644 --- a/backend/src/collective/volto/formsupport/interfaces.py +++ b/backend/src/collective/volto/formsupport/interfaces.py @@ -1,5 +1,9 @@ +from plone.dexterity.content import DexterityContent +from zope.interface import Attribute from zope.interface import Interface from zope.publisher.interfaces.browser import IDefaultBrowserLayer +from ZPublisher.BaseRequest import BaseRequest +import dataclasses class ICollectiveVoltoFormsupportLayer(IDefaultBrowserLayer): @@ -44,3 +48,24 @@ def verify(data): """Verify the captcha @return: True if verified, Raise exception otherwise """ + + +@dataclasses.dataclass +class FormSubmissionContext: + context: DexterityContent + block: dict + form_data: dict + attachments: dict + request: BaseRequest + + +class IFormSubmissionProcessor(Interface): + """Subscriber which processes form data when it is submitted""" + + order: int = Attribute("Processors with the lowest order are processed first") + + def __init__(context: FormSubmissionContext): + pass + + def __call__(): + """Process the data.""" diff --git a/backend/src/collective/volto/formsupport/processors/__init__.py b/backend/src/collective/volto/formsupport/processors/__init__.py new file mode 100644 index 0000000..e9537f9 --- /dev/null +++ b/backend/src/collective/volto/formsupport/processors/__init__.py @@ -0,0 +1,9 @@ +def filter_parameters(data, block): + """ + TODO do not send attachments fields. + """ + return [{ + "field_id": k, + "value": v, + "label": block["schema"]["properties"].get(k, {}).get("title", k), + } for k, v in data.items()] diff --git a/backend/src/collective/volto/formsupport/processors/configure.zcml b/backend/src/collective/volto/formsupport/processors/configure.zcml new file mode 100644 index 0000000..f96d5a9 --- /dev/null +++ b/backend/src/collective/volto/formsupport/processors/configure.zcml @@ -0,0 +1,8 @@ + + + + + + diff --git a/backend/src/collective/volto/formsupport/processors/email.py b/backend/src/collective/volto/formsupport/processors/email.py new file mode 100644 index 0000000..1e67ae6 --- /dev/null +++ b/backend/src/collective/volto/formsupport/processors/email.py @@ -0,0 +1,268 @@ +from bs4 import BeautifulSoup +from collective.volto.formsupport import _ +from collective.volto.formsupport.processors import filter_parameters +from collective.volto.formsupport.interfaces import FormSubmissionContext +from collective.volto.formsupport.interfaces import IFormSubmissionProcessor +from email import policy +from email.message import EmailMessage +from plone import api +from plone.registry.interfaces import IRegistry +from zExceptions import BadRequest +from zope.component import adapter +from zope.component import getMultiAdapter +from zope.component import getUtility +from zope.i18n import translate +from zope.interface import implementer +import codecs +import os +import re + +try: + from plone.base.interfaces.controlpanel import IMailSchema +except ImportError: + from Products.CMFPlone.interfaces.controlpanel import IMailSchema + +CTE = os.environ.get("MAIL_CONTENT_TRANSFER_ENCODING", None) + + +@implementer(IFormSubmissionProcessor) +@adapter(FormSubmissionContext) +class EmailFormProcessor: + """Sends an email with submitted form data""" + + order = 1 + + def __init__(self, context: FormSubmissionContext): + self.context = context.context + self.request = context.request + self.block = context.block + self.form_data = context.form_data + self.attachments = context.attachments + + def __call__(self): + if not self.block.get("send"): + return + + portal = api.portal.get() + overview_controlpanel = getMultiAdapter( + (portal, self.request), name="overview-controlpanel" + ) + if overview_controlpanel.mailhost_warning(): + raise BadRequest("MailHost is not configured.") + registry = getUtility(IRegistry) + mail_settings = registry.forInterface(IMailSchema, prefix="plone") + charset = registry.get("plone.email_charset", "utf-8") + + subject = self.get_subject() + + # TODO + mfrom = self.form_data.get("from", "") or self.block.get("default_from", "") or mail_settings.email_from_address + mreply_to = self.get_reply_to() + + if not subject or not mfrom: + raise BadRequest( + translate( + _( + "send_required_field_missing", + default="Missing required field: subject or from.", + ), + context=self.request, + ) + ) + + # TODO sort out admin email vs acknowledgment + send_to = self.block.get("send", ["recipient"]) + if not isinstance(send_to, list): + send_to = ["recipient"] if send_to else [] + + portal_transforms = api.portal.get_tool(name="portal_transforms") + mto = self.block.get("recipients", mail_settings.email_from_address) + message = self.prepare_message() + text_message = ( + portal_transforms.convertTo("text/plain", message, mimetype="text/html") + .getData() + .strip() + ) + msg = EmailMessage(policy=policy.SMTP) + msg.set_content(text_message, cte=CTE) + msg.add_alternative(message, subtype="html", cte=CTE) + msg["Subject"] = subject + msg["From"] = mfrom + msg["To"] = mto + msg["Reply-To"] = mreply_to + + headers_to_forward = self.block.get("httpHeaders", []) + for header in headers_to_forward: + header_value = self.request.get(header) + if header_value: + msg[header] = header_value + + self.manage_attachments(msg=msg) + + if "recipient" in send_to: + self.send_mail(msg=msg, charset=charset) + + # send a copy also to the fields with bcc flag + for bcc in self.get_bcc(): + msg.replace_header("To", bcc) + self.send_mail(msg=msg, charset=charset) + + acknowledgement_message = self.block.get("acknowledgementMessage") + if acknowledgement_message and "acknowledgement" in send_to: + acknowledgement_address = self.get_acknowledgement_field_value() + if acknowledgement_address: + acknowledgement_mail = EmailMessage(policy=policy.SMTP) + acknowledgement_mail["Subject"] = subject + acknowledgement_mail["From"] = mfrom + acknowledgement_mail["To"] = acknowledgement_address + ack_msg = acknowledgement_message.get("data") + ack_msg_text = ( + portal_transforms.convertTo( + "text/plain", ack_msg, mimetype="text/html" + ) + .getData() + .strip() + ) + acknowledgement_mail.set_content(ack_msg_text, cte=CTE) + acknowledgement_mail.add_alternative(ack_msg, subtype="html", cte=CTE) + self.send_mail(msg=acknowledgement_mail, charset=charset) + + def get_reply_to(self): + """This method retrieves the 'reply to' email address. + + Three "levels" of logic: + 1. If there is a field marked with 'use_as_reply_to' set to True, that + field wins and we use that. + If not: + 2. We search for the "from" field. + If not present: + 3. We use the fallback field: "default_from" + """ + + subblocks = self.block.get("subblocks", "") + if subblocks: + for field in subblocks: + if field.get("use_as_reply_to", False): + field_id = field.get("field_id", "") + if field_id: + for data in data.get("data", ""): + if data.get("field_id", "") == field_id: + return data.get("value", "") + + return self.form_data.get("from", "") or self.block.get("default_from", "") + + def get_subject(self): + subject = self.block.get("subject") or "${subject}" + subject = self.substitute_variables(subject) + return subject + + def substitute_variables(self, value): + pattern = r"\$\{([^}]+)\}" + return re.sub(pattern, lambda match: self.get_value(match.group(1), ""), value) + + def get_value(self, field_id, default=None): + if self.block.get("@type") == "schemaForm": + return self.form_data.get(field_id, default) + + for field in self.form_data: + if field.get("field_id") == field_id: + return field.get("value", default) + return default + + def get_bcc(self): + # todo: handle bcc for schemaForm + subblocks = self.block.get("subblocks", []) + if not subblocks: + return [] + + bcc = [] + bcc_fields = [] + for field in self.block.get("subblocks", []): + if field.get("use_as_bcc", False): + field_id = field.get("field_id", "") + if field_id not in bcc_fields: + bcc_fields.append(field_id) + bcc = [] + for field in self.form_data: + value = field.get("value", "") + if not value: + continue + if field.get("field_id", "") in bcc_fields: + bcc.append(field["value"]) + return bcc + + def prepare_message(self): + mail_header = self.block.get("mail_header", {}).get("data", "") + mail_footer = self.block.get("mail_footer", {}).get("data", "") + + # Check if there is content + mail_header = BeautifulSoup(mail_header).get_text() if mail_header else None + mail_footer = BeautifulSoup(mail_footer).get_text() if mail_footer else None + + # TODO + email_format_page_template_mapping = { + "list": "send_mail_template", + "table": "send_mail_template_table", + } + email_format = self.block.get("email_format", "") + template_name = email_format_page_template_mapping.get( + email_format, "send_mail_template" + ) + + message_template = api.content.get_view( + name=template_name, + context=self.context, + request=self.request, + ) + parameters = { + "parameters": filter_parameters(self.form_data, self.block), + "url": self.context.absolute_url(), + "title": self.context.Title(), + "mail_header": mail_header, + "mail_footer": mail_footer, + } + return message_template(**parameters) + + def manage_attachments(self, msg): + attachments = self.attachments + + if not attachments: + return [] + for _key, value in attachments.items(): + content_type = "application/octet-stream" + filename = None + if isinstance(value, dict): + file_data = value.get("data", "") + if not file_data: + continue + content_type = value.get("content-type", content_type) + filename = value.get("filename", filename) + if isinstance(file_data, str): + file_data = file_data.encode("utf-8") + if "encoding" in value: + file_data = codecs.decode(file_data, value["encoding"]) + if isinstance(file_data, str): + file_data = file_data.encode("utf-8") + else: + file_data = value + maintype, subtype = content_type.split("/") + msg.add_attachment( + file_data, + maintype=maintype, + subtype=subtype, + filename=filename, + ) + + def send_mail(self, msg, charset): + host = api.portal.get_tool(name="MailHost") + # we set immediate=True because we need to catch exceptions. + # by default (False) exceptions are handled by MailHost and we can't catch them. + host.send(msg, charset=charset, immediate=True) + + def get_acknowledgement_field_value(self): + acknowledgementField = self.block["acknowledgementFields"] + for field in self.block.get("subblocks", []): + if field.get("field_id") == acknowledgementField: + for submitted in self.form_data: + if submitted.get("field_id", "") == field.get("field_id"): + return submitted.get("value") diff --git a/backend/src/collective/volto/formsupport/processors/store.py b/backend/src/collective/volto/formsupport/processors/store.py new file mode 100644 index 0000000..55493db --- /dev/null +++ b/backend/src/collective/volto/formsupport/processors/store.py @@ -0,0 +1,31 @@ +from collective.volto.formsupport.interfaces import IFormDataStore +from collective.volto.formsupport.interfaces import IFormSubmissionProcessor +from collective.volto.formsupport.interfaces import FormSubmissionContext +from collective.volto.formsupport.processors import filter_parameters +from zExceptions import BadRequest +from zope.component import adapter +from zope.component import getMultiAdapter +from zope.interface import implementer + + +@implementer(IFormSubmissionProcessor) +@adapter(FormSubmissionContext) +class StoreFormProcessor: + """Stores submitted form data""" + + order = 2 + + def __init__(self, context): + self.context = context.context + self.request = context.request + self.block = context.block + self.form_data = context.form_data + + def __call__(self): + if not self.block.get("store"): + return + + store = getMultiAdapter((self.context, self.request), IFormDataStore) + res = store.add(data=filter_parameters(self.form_data, self.block)) + if not res: + raise BadRequest("Unable to store data") diff --git a/backend/src/collective/volto/formsupport/restapi/serializer/blocks.py b/backend/src/collective/volto/formsupport/restapi/serializer/blocks.py index 7148ef4..31b0f49 100644 --- a/backend/src/collective/volto/formsupport/restapi/serializer/blocks.py +++ b/backend/src/collective/volto/formsupport/restapi/serializer/blocks.py @@ -1,5 +1,6 @@ from collective.volto.formsupport.interfaces import ICaptchaSupport from collective.volto.formsupport.interfaces import ICollectiveVoltoFormsupportLayer +from copy import deepcopy from plone import api @@ -57,3 +58,55 @@ class FormSerializerContents(FormSerializer): @adapter(IPloneSiteRoot, ICollectiveVoltoFormsupportLayer) class FormSerializerRoot(FormSerializer): """Deserializer for site-root""" + + +class SchemaFormBlockSerializer: + """ """ + + order = 200 # after standard ones + block_type = "schemaForm" + + def __init__(self, context, request): + self.context = context + self.request = request + + def __call__(self, value): + """ + If user can edit the context, return the full block data. + Otherwise, skip default values because we need them only in edit and + to send emails from the backend. + """ + if value.get("captcha"): + value["captcha_props"] = getMultiAdapter( + (self.context, self.request), + ICaptchaSupport, + name=value["captcha"], + ).serialize() + + new_schema = deepcopy(value["schema"]) + new_schema["properties"]["captchaWidget"] = { + "title": value["captcha"], + "widget": value["captcha"], + "captcha_props": value["captcha_props"], + } + if "captchaWidget" not in new_schema["fieldsets"][0]["fields"]: + new_schema["fieldsets"][0]["fields"].append("captchaWidget") + value["schema"] = new_schema + attachments_limit = os.environ.get("FORM_ATTACHMENTS_LIMIT", "") + if attachments_limit: + value["attachments_limit"] = attachments_limit + if api.user.has_permission("Modify portal content", obj=self.context): + return value + return {k: v for k, v in value.items() if not k.startswith("default_")} + + +@implementer(IBlockFieldSerializationTransformer) +@adapter(IBlocks, ICollectiveVoltoFormsupportLayer) +class SchemaFormBlockSerializerContents(SchemaFormBlockSerializer): + """Deserializer for content-types that implements IBlocks behavior""" + + +@implementer(IBlockFieldSerializationTransformer) +@adapter(IPloneSiteRoot, ICollectiveVoltoFormsupportLayer) +class SchemaFormBlockSerializerRoot(SchemaFormBlockSerializer): + """Deserializer for site-root""" diff --git a/backend/src/collective/volto/formsupport/restapi/serializer/configure.zcml b/backend/src/collective/volto/formsupport/restapi/serializer/configure.zcml index 860ca2a..1ee14c4 100644 --- a/backend/src/collective/volto/formsupport/restapi/serializer/configure.zcml +++ b/backend/src/collective/volto/formsupport/restapi/serializer/configure.zcml @@ -13,4 +13,13 @@ provides="plone.restapi.interfaces.IBlockFieldSerializationTransformer" /> + + + diff --git a/backend/src/collective/volto/formsupport/restapi/services/form_data/form_data.py b/backend/src/collective/volto/formsupport/restapi/services/form_data/form_data.py index b8e997b..f85e534 100644 --- a/backend/src/collective/volto/formsupport/restapi/services/form_data/form_data.py +++ b/backend/src/collective/volto/formsupport/restapi/services/form_data/form_data.py @@ -29,10 +29,10 @@ def get_items(self): items = [] if block: store = getMultiAdapter((self.context, self.request), IFormDataStore) - remove_data_after_days = int(block.get("remove_data_after_days") or 0) + data_wipe = int(block.get("data_wipe") or 0) data = store.search() - if remove_data_after_days > 0: - expire_date = datetime.now() - timedelta(days=remove_data_after_days) + if data_wipe > 0: + expire_date = datetime.now() - timedelta(days=data_wipe) else: expire_date = None for record in data: @@ -55,7 +55,7 @@ def __call__(self, expand=False): return {} if self.block_id: service_id = ( - f"{self.context.absolute_url()}/@form-data?block_id{self.block_id}" + f"{self.context.absolute_url()}/@form-data?block_id={self.block_id}" ) else: service_id = f"{self.context.absolute_url()}/@form-data" @@ -81,7 +81,8 @@ def form_block(self): if not blocks: return {} for id_, block in blocks.items(): - if block.get("@type", "") == "form" and block.get("store", False): + is_form_block = block.get("@type", "") in ("form", "schemaForm") + if is_form_block and block.get("store", False): if not self.block_id or self.block_id == id_: return block return {} diff --git a/backend/src/collective/volto/formsupport/restapi/services/submit_form/configure.zcml b/backend/src/collective/volto/formsupport/restapi/services/submit_form/configure.zcml index 8e566ba..625752f 100644 --- a/backend/src/collective/volto/formsupport/restapi/services/submit_form/configure.zcml +++ b/backend/src/collective/volto/formsupport/restapi/services/submit_form/configure.zcml @@ -10,7 +10,7 @@ for="plone.restapi.behaviors.IBlocks" permission="zope2.View" layer="collective.volto.formsupport.interfaces.ICollectiveVoltoFormsupportLayer" - name="@submit-form" + name="@schemaform-data" /> diff --git a/backend/src/collective/volto/formsupport/restapi/services/submit_form/post.py b/backend/src/collective/volto/formsupport/restapi/services/submit_form/post.py index 833adc0..877346f 100644 --- a/backend/src/collective/volto/formsupport/restapi/services/submit_form/post.py +++ b/backend/src/collective/volto/formsupport/restapi/services/submit_form/post.py @@ -1,44 +1,31 @@ -from bs4 import BeautifulSoup from collective.volto.formsupport import _ +from collective.volto.formsupport.interfaces import FormSubmissionContext from collective.volto.formsupport.interfaces import ICaptchaSupport -from collective.volto.formsupport.interfaces import IFormDataStore +from collective.volto.formsupport.interfaces import IFormSubmissionProcessor from collective.volto.formsupport.interfaces import IPostEvent from collective.volto.formsupport.utils import get_blocks from collective.volto.formsupport.utils import validate_email_token from copy import deepcopy -from datetime import datetime -from email import policy -from email.message import EmailMessage -from io import BytesIO from plone import api -try: - from plone.base.interfaces.controlpanel import IMailSchema -except ImportError: - from Products.CMFPlone.interfaces.controlpanel import IMailSchema - from plone.protect.interfaces import IDisableCSRFProtection -from plone.registry.interfaces import IRegistry from plone.restapi.deserializer import json_body from plone.restapi.services import Service from plone.schema.email import _isemail -from xml.etree.ElementTree import Element -from xml.etree.ElementTree import ElementTree -from xml.etree.ElementTree import SubElement from zExceptions import BadRequest from zope.component import getMultiAdapter -from zope.component import getUtility +from zope.component import subscribers from zope.event import notify from zope.i18n import translate from zope.interface import alsoProvides from zope.interface import implementer -import codecs +import json +import jsonschema import logging import math import os -import re logger = logging.getLogger(__name__) @@ -53,44 +40,43 @@ def __init__(self, context, data): class SubmitPost(Service): - def __init__(self, context, request): - super().__init__(context, request) - + def reply(self): self.block = {} self.form_data = self.cleanup_data() self.block_id = self.form_data.get("block_id", "") if self.block_id: self.block = self.get_block_data(block_id=self.block_id) - def reply(self): self.validate_form() - store_action = self.block.get("store", False) - send_action = self.block.get("send", []) - # Disable CSRF protection alsoProvides(self.request, IDisableCSRFProtection) notify(PostEventService(self.context, self.form_data)) - if send_action: + form_submission_context = FormSubmissionContext( + context=self.context, + request=self.request, + block=self.block, + form_data=self.form_data.get("data", {}), + attachments=self.form_data.get("attachments", {}), + ) + for handler in sorted(subscribers((form_submission_context,), IFormSubmissionProcessor), key=lambda h: h.order): try: - self.send_data() - except BadRequest as e: - raise e - except Exception as e: - logger.exception(e) + handler() + except BadRequest: + raise + except Exception as err: + logger.exception(err) message = translate( _( - "mail_send_exception", - default="Unable to send confirm email. Please retry later or contact site administrator.", # noqa: E501 + "form_action_exception", + default="Unable to process form. Please retry later or contact site administrator.", # noqa: E501 ), context=self.request, ) self.request.response.setStatus(500) return {"type": "InternalServerError", "message": message} - if store_action: - self.store_data() return {"data": self.form_data.get("data", [])} @@ -106,19 +92,25 @@ def cleanup_data(self): transforms = api.portal.get_tool(name="portal_transforms") block = self.get_block_data(block_id=form_data.get("block_id", "")) - block_fields = [x.get("field_id", "") for x in block.get("subblocks", [])] - for form_field in form_data.get("data", []): - if form_field.get("field_id", "") not in block_fields: - # unknown field, skip it - continue - new_field = deepcopy(form_field) - value = new_field.get("value", "") - if isinstance(value, str): - stream = transforms.convertTo("text/plain", value, mimetype="text/html") - new_field["value"] = stream.getData().strip() - fixed_fields.append(new_field) - form_data["data"] = fixed_fields + if block.get("@type") == "form": + block_fields = [x.get("field_id", "") for x in block.get("subblocks", [])] + # cleanup form data if it's a form block + for form_field in form_data.get("data", []): + if form_field.get("field_id", "") not in block_fields: + # unknown field, skip it + continue + new_field = deepcopy(form_field) + value = new_field.get("value", "") + if isinstance(value, str): + stream = transforms.convertTo( + "text/plain", value, mimetype="text/html" + ) + new_field["value"] = stream.getData().strip() + fixed_fields.append(new_field) + form_data["data"] = fixed_fields + + # TODO: cleanup form data if it's a schemaForm block return form_data def validate_form(self): @@ -147,17 +139,6 @@ def validate_form(self): ), ) - if not self.block.get("store", False) and not self.block.get("send", []): - raise BadRequest( - translate( - _( - "missing_action", - default='You need to set at least one form action between "send" and "store".', # noqa: E501 - ), - context=self.request, - ) - ) - if not self.form_data.get("data", []): raise BadRequest( translate( @@ -169,6 +150,7 @@ def validate_form(self): ) ) + self.validate_schema() self.validate_attachments() if self.block.get("captcha", False): getMultiAdapter( @@ -180,7 +162,23 @@ def validate_form(self): self.validate_email_fields() self.validate_bcc() + def validate_schema(self): + if self.block.get("@type") != "schemaForm": + return + validator = jsonschema.Draft202012Validator(self.block["schema"]) + errors = [] + for err in validator.iter_errors(self.form_data["data"]): + error = {"message": err.message} + if err.path: + error["field"] = ".".join(err.path) + errors.append(error) + if errors: + raise BadRequest(json.dumps(errors)) + def validate_email_fields(self): + # TODO: validate email fields for schemaForm block + if self.block["@type"] == "schemaForm": + return email_fields = [ x.get("field_id", "") for x in self.block.get("subblocks", []) @@ -234,6 +232,10 @@ def validate_attachments(self): ) def validate_bcc(self): + # TODO: validate email fields for schemaForm block + if self.block["@type"] == "schemaForm": + return + bcc_fields = [] for field in self.block.get("subblocks", []): if field.get("use_as_bcc", False): @@ -262,281 +264,7 @@ def get_block_data(self, block_id): if id_ != block_id: continue block_type = block.get("@type", "") - if block_type != "form": + if not (block_type == "form" or block_type == "schemaForm"): continue return block return {} - - def get_reply_to(self): - """This method retrieves the correct field to be used as 'reply to'. - - Three "levels" of logic: - 1. If there is a field marked with 'use_as_reply_to' set to True, that - field wins and we use that. - If not: - 2. We search for the "from" field. - If not present: - 3. We use the fallback field: "default_from" - """ - - subblocks = self.block.get("subblocks", "") - if subblocks: - for field in subblocks: - if field.get("use_as_reply_to", False): - field_id = field.get("field_id", "") - if field_id: - for data in self.form_data.get("data", ""): - if data.get("field_id", "") == field_id: - return data.get("value", "") - - return self.form_data.get("from", "") or self.block.get("default_from", "") - - def get_bcc(self): - bcc = [] - bcc_fields = [] - for field in self.block.get("subblocks", []): - if field.get("use_as_bcc", False): - field_id = field.get("field_id", "") - if field_id not in bcc_fields: - bcc_fields.append(field_id) - bcc = [] - for data in self.form_data.get("data", []): - value = data.get("value", "") - if not value: - continue - if data.get("field_id", "") in bcc_fields: - bcc.append(data["value"]) - return bcc - - def get_acknowledgement_field_value(self): - acknowledgementField = self.block["acknowledgementFields"] - for field in self.block.get("subblocks", []): - if field.get("field_id") == acknowledgementField: - for data in self.form_data.get("data", []): - if data.get("field_id", "") == field.get("field_id"): - return data.get("value") - - def get_subject(self): - subject = self.form_data.get("subject", "") or self.block.get( - "default_subject", "" - ) - - for i in self.form_data.get("data", []): - field_id = i.get("field_id") - - if not field_id: - continue - - # Handle this kind of id format: `field_name_123321, - # which is used by frontend package logics - pattern = r"\$\{[^}]+\}" - matches = re.findall(pattern, subject) - - for match in matches: - if field_id in match: - subject = subject.replace(match, i.get("value")) - - return subject - - def send_data(self): # noQA: C901 - subject = self.get_subject() - - mfrom = self.form_data.get("from", "") or self.block.get("default_from", "") - mreply_to = self.get_reply_to() - - if not subject or not mfrom: - raise BadRequest( - translate( - _( - "send_required_field_missing", - default="Missing required field: subject or from.", - ), - context=self.request, - ) - ) - - portal = api.portal.get() - overview_controlpanel = getMultiAdapter( - (portal, self.request), name="overview-controlpanel" - ) - if overview_controlpanel.mailhost_warning(): - raise BadRequest("MailHost is not configured.") - - registry = getUtility(IRegistry) - mail_settings = registry.forInterface(IMailSchema, prefix="plone") - charset = registry.get("plone.email_charset", "utf-8") - - should_send = self.block.get("send", []) - if should_send: - portal_transforms = api.portal.get_tool(name="portal_transforms") - mto = self.block.get("default_to", mail_settings.email_from_address) - message = self.prepare_message() - text_message = ( - portal_transforms.convertTo("text/plain", message, mimetype="text/html") - .getData() - .strip() - ) - msg = EmailMessage(policy=policy.SMTP) - msg.set_content(text_message, cte=CTE) - msg.add_alternative(message, subtype="html", cte=CTE) - msg["Subject"] = subject - msg["From"] = mfrom - msg["To"] = mto - msg["Reply-To"] = mreply_to - - headers_to_forward = self.block.get("httpHeaders", []) - for header in headers_to_forward: - header_value = self.request.get(header) - if header_value: - msg[header] = header_value - - self.manage_attachments(msg=msg) - - if isinstance(should_send, list): - if "recipient" in self.block.get("send", []): - self.send_mail(msg=msg, charset=charset) - # Backwards compatibility for forms before 'acknowledgement' sending - else: - self.send_mail(msg=msg, charset=charset) - - # send a copy also to the fields with bcc flag - for bcc in self.get_bcc(): - msg.replace_header("To", bcc) - self.send_mail(msg=msg, charset=charset) - - acknowledgement_message = self.block.get("acknowledgementMessage") - if acknowledgement_message and "acknowledgement" in self.block.get("send", []): - acknowledgement_address = self.get_acknowledgement_field_value() - if acknowledgement_address: - acknowledgement_mail = EmailMessage(policy=policy.SMTP) - acknowledgement_mail["Subject"] = subject - acknowledgement_mail["From"] = mfrom - acknowledgement_mail["To"] = acknowledgement_address - ack_msg = acknowledgement_message.get("data") - ack_msg_text = ( - portal_transforms.convertTo( - "text/plain", ack_msg, mimetype="text/html" - ) - .getData() - .strip() - ) - acknowledgement_mail.set_content(ack_msg_text, cte=CTE) - acknowledgement_mail.add_alternative(ack_msg, subtype="html", cte=CTE) - self.send_mail(msg=acknowledgement_mail, charset=charset) - - def prepare_message(self): - mail_header = self.block.get("mail_header", {}).get("data", "") - mail_footer = self.block.get("mail_footer", {}).get("data", "") - - # Check if there is content - mail_header = BeautifulSoup(mail_header).get_text() if mail_header else None - mail_footer = BeautifulSoup(mail_footer).get_text() if mail_footer else None - - email_format_page_template_mapping = { - "list": "send_mail_template", - "table": "send_mail_template_table", - } - email_format = self.block.get("email_format", "") - template_name = email_format_page_template_mapping.get( - email_format, "send_mail_template" - ) - - message_template = api.content.get_view( - name=template_name, - context=self.context, - request=self.request, - ) - parameters = { - "parameters": self.filter_parameters(), - "url": self.context.absolute_url(), - "title": self.context.Title(), - "mail_header": mail_header, - "mail_footer": mail_footer, - } - return message_template(**parameters) - - def filter_parameters(self): - """ - do not send attachments fields. - """ - skip_fields = [ - x.get("field_id", "") - for x in self.block.get("subblocks", []) - if x.get("field_type", "") == "attachment" - ] - return [ - x - for x in self.form_data.get("data", []) - if x.get("field_id", "") not in skip_fields - ] - - def send_mail(self, msg, charset): - host = api.portal.get_tool(name="MailHost") - # we set immediate=True because we need to catch exceptions. - # by default (False) exceptions are handled by MailHost and we can't catch them. - host.send(msg, charset=charset, immediate=True) - - def manage_attachments(self, msg): - attachments = self.form_data.get("attachments", {}) - - if self.block.get("attachXml", False): - self.attach_xml(msg=msg) - - if not attachments: - return [] - for _key, value in attachments.items(): - content_type = "application/octet-stream" - filename = None - if isinstance(value, dict): - file_data = value.get("data", "") - if not file_data: - continue - content_type = value.get("content-type", content_type) - filename = value.get("filename", filename) - if isinstance(file_data, str): - file_data = file_data.encode("utf-8") - if "encoding" in value: - file_data = codecs.decode(file_data, value["encoding"]) - if isinstance(file_data, str): - file_data = file_data.encode("utf-8") - else: - file_data = value - maintype, subtype = content_type.split("/") - msg.add_attachment( - file_data, - maintype=maintype, - subtype=subtype, - filename=filename, - ) - - def attach_xml(self, msg): - now = ( - datetime.now() - .isoformat(timespec="seconds") - .replace(" ", "-") - .replace(":", "") - ) - filename = f"formdata_{now}.xml" - output = BytesIO() - xmlRoot = Element("form") - - for field in self.filter_parameters(): - SubElement( - xmlRoot, "field", name=field.get("custom_field_id", field["label"]) - ).text = str(field.get("value", "")) - - doc = ElementTree(xmlRoot) - doc.write(output, encoding="utf-8", xml_declaration=True) - xmlstr = output.getvalue() - msg.add_attachment( - xmlstr, - maintype="application", - subtype="xml", - filename=filename, - ) - - def store_data(self): - store = getMultiAdapter((self.context, self.request), IFormDataStore) - res = store.add(data=self.filter_parameters()) - if not res: - raise BadRequest("Unable to store data") diff --git a/backend/src/collective/volto/formsupport/scripts/cleansing.py b/backend/src/collective/volto/formsupport/scripts/cleansing.py index 43648e0..f981537 100644 --- a/backend/src/collective/volto/formsupport/scripts/cleansing.py +++ b/backend/src/collective/volto/formsupport/scripts/cleansing.py @@ -43,10 +43,10 @@ def main(dryrun): # noqa: C901 continue if not block.get("store", False): continue - remove_data_after_days = int(block.get("remove_data_after_days") or 0) + data_wipe = int(block.get("data_wipe") or 0) # 0/None -> default value # -1 -> don't remove - if remove_data_after_days <= 0: + if data_wipe <= 0: print( f"SKIP record cleanup from {brain.getPath()} block: {block_id}" ) diff --git a/backend/src/collective/volto/formsupport/testing/__init__.py b/backend/src/collective/volto/formsupport/testing/__init__.py index 89dde79..47fb8f9 100644 --- a/backend/src/collective/volto/formsupport/testing/__init__.py +++ b/backend/src/collective/volto/formsupport/testing/__init__.py @@ -29,7 +29,7 @@ def setUpPloneSite(self, portal): applyProfile(portal, "plone.restapi:blocks") applyProfile(portal, "collective.volto.formsupport:default") - # Mock the validate email tocken function + # Mock the validate email token function def validate_email_token_mock(*args, **kwargs): return True diff --git a/docs/form-block-chooser.png b/docs/form-block-chooser.png new file mode 100644 index 0000000..9a16a93 Binary files /dev/null and b/docs/form-block-chooser.png differ diff --git a/docs/form-block-view.png b/docs/form-block-view.png new file mode 100644 index 0000000..9deab08 Binary files /dev/null and b/docs/form-block-view.png differ diff --git a/docs/form-static-fields.png b/docs/form-static-fields.png new file mode 100644 index 0000000..d6898e7 Binary files /dev/null and b/docs/form-static-fields.png differ diff --git a/docs/store-export-data.png b/docs/store-export-data.png new file mode 100644 index 0000000..cd3d9b3 Binary files /dev/null and b/docs/store-export-data.png differ diff --git a/frontend/mrs.developer.json b/frontend/mrs.developer.json index a84c586..d826a12 100644 --- a/frontend/mrs.developer.json +++ b/frontend/mrs.developer.json @@ -4,6 +4,6 @@ "package": "@plone/volto", "url": "git@github.com:plone/volto.git", "https": "https://github.com/plone/volto.git", - "tag": "18.0.0-alpha.40" + "branch": "volto-form-block-fixes" } } diff --git a/frontend/packages/volto-form-block/locales/de/LC_MESSAGES/volto.po b/frontend/packages/volto-form-block/locales/de/LC_MESSAGES/volto.po index 76a77d5..73796ae 100644 --- a/frontend/packages/volto-form-block/locales/de/LC_MESSAGES/volto.po +++ b/frontend/packages/volto-form-block/locales/de/LC_MESSAGES/volto.po @@ -16,11 +16,23 @@ msgstr "" msgid "Add field" msgstr "Feld Hinzufügen" +#. Default: "Blind carbon copy" +#: schemaFormBlock/schema +msgid "Blind carbon copy" +msgstr "" + #. Default: "Cancel" #: components/Sidebar +#: schemaFormBlock/EditSchemaForm +#: schemaFormBlock/ViewSchemaForm msgid "Cancel" msgstr "Abbrechen" +#. Default: "Cancel button label" +#: schemaFormBlock/schema +msgid "Cancel button label" +msgstr "" + #. Default: "Choices" #: components/Widget/SelectWidget msgid "Choices" @@ -36,6 +48,11 @@ msgstr "Datei auswählen" msgid "Close" msgstr "Schließen" +#. Default: "Data wipe" +#: schemaFormBlock/schema +msgid "Data wipe" +msgstr "" + #. Default: "Date" #: components/Widget/DatetimeWidget msgid "Date" @@ -48,6 +65,7 @@ msgstr "Standard" #. Default: "Description" #: components/Widget/SelectWidget +#: schemaFormBlock/schema msgid "Description" msgstr "Beschreibung" @@ -66,13 +84,25 @@ msgstr "Datei hier ablegen um eine neue Datei hochzuladen" msgid "Drop files here ..." msgstr "Datei hier ablegen um die bestehende Datei zu ersetzen" +#. Default: "Email footer" +#: schemaFormBlock/schema +msgid "Email footer" +msgstr "" + +#. Default: "Email header" +#: schemaFormBlock/schema +msgid "Email header" +msgstr "" + #. Default: "Error" #: components/FormView +#: schemaFormBlock/ViewSchemaForm msgid "Error" msgstr "Fehler" #. Default: "Form" #: components/Sidebar +#: schemaFormBlock/schema msgid "Form" msgstr "Formular" @@ -86,6 +116,16 @@ msgstr "Diese Website ist durch hCaptcha geschützt und es gelten die Pr msgid "No value" msgstr "Ez du baliorik" +#. Default: "Number of days after which, the data should be deleted. Enter -1 to store indefinitely." +#: schemaFormBlock/schema +msgid "Number of days after which, the data should be deleted. Enter -1 to store indefinitely." +msgstr "" + +#. Default: "Recipients" +#: schemaFormBlock/schema +msgid "Recipients" +msgstr "" + #. Default: "Replace existing file" #: components/Widget/FileWidget msgid "Replace existing file" @@ -103,6 +143,87 @@ msgstr "Beharrezkoa" msgid "Select…" msgstr "Aukeratu..." +#. Default: "Send email" +#: schemaFormBlock/schema +msgid "Send email" +msgstr "" + +#. Default: "Send email to recipients" +#: schemaFormBlock/schema +msgid "Send email to recipients" +msgstr "" + +#. Default: "Sender" +#: schemaFormBlock/schema +msgid "Sender" +msgstr "" + +#. Default: "Sender name" +#: schemaFormBlock/schema +msgid "Sender name" +msgstr "" + +#. Default: "Show cancel button" +#: schemaFormBlock/schema +msgid "Show cancel button" +msgstr "" + +#. Default: "Store data" +#: schemaFormBlock/schema +msgid "Store data" +msgstr "" + +#. Default: "Subject" +#: schemaFormBlock/schema +msgid "Subject" +msgstr "" + +#. Default: "Submit" +#: schemaFormBlock/EditSchemaForm +#: schemaFormBlock/ViewSchemaForm +msgid "Submit" +msgstr "" + +#. Default: "Submit button label" +#: schemaFormBlock/schema +msgid "Submit button label" +msgstr "" + +#. Default: "Text at the beginning of the email. Use the ${field_id} syntax to add a form value." +#: schemaFormBlock/schema +msgid "Text at the beginning of the email. Use the ${field_id} syntax to add a form value." +msgstr "" + +#. Default: "Text at the end of the email. Use the ${field_id} syntax to add a form value." +#: schemaFormBlock/schema +msgid "Text at the end of the email. Use the ${field_id} syntax to add a form value." +msgstr "" + +#. Default: "The email address of the sender" +#: schemaFormBlock/schema +msgid "The email address of the sender" +msgstr "" + +#. Default: "The email addresses the submitted form data will be sent to as blind carbon copy. Multiple email addresses can be entered separated by a semicolon." +#: schemaFormBlock/schema +msgid "The email addresses the submitted form data will be sent to as blind carbon copy. Multiple email addresses can be entered separated by a semicolon." +msgstr "" + +#. Default: "The email addresses the submitted form data will be sent to. Multiple email addresses can be entered separated by a semicolon." +#: schemaFormBlock/schema +msgid "The email addresses the submitted form data will be sent to. Multiple email addresses can be entered separated by a semicolon." +msgstr "" + +#. Default: "The name of the sender" +#: schemaFormBlock/schema +msgid "The name of the sender" +msgstr "" + +#. Default: "The subject used in the sent email. Use the ${field_id} syntax to add a form value to the email subject." +#: schemaFormBlock/schema +msgid "The subject used in the sent email. Use the ${field_id} syntax to add a form value to the email subject." +msgstr "" + #. Default: "Time" #: components/Widget/DatetimeWidget msgid "Time" @@ -110,6 +231,7 @@ msgstr "Ordua" #. Default: "Title" #: components/Widget/SelectWidget +#: schemaFormBlock/schema msgid "Title" msgstr "Titulua" @@ -118,6 +240,16 @@ msgstr "Titulua" msgid "Used for programmatic access to the fieldset." msgstr "Eremu-multzoa programaziotik atzitzeko izena" +#. Default: "When activated, an email will be sent to the given recipients when a form is submitted" +#: schemaFormBlock/schema +msgid "When activated, an email will be sent to the given recipients when a form is submitted" +msgstr "" + +#. Default: "When activated, the data will be stored for later use when the form is submitted" +#: schemaFormBlock/schema +msgid "When activated, the data will be stored for later use when the form is submitted" +msgstr "" + #. Default: "Use Up and Down to choose options" #: helpers/react-select msgid "ay11_Use Up and Down to choose options" @@ -225,6 +357,7 @@ msgstr "sakatu ezkerrera aukeratutako balioetan fokua jartzeko" #. Default: "Captcha provider" #: formSchema +#: schemaFormBlock/schema msgid "captcha" msgstr "" diff --git a/frontend/packages/volto-form-block/locales/fr/LC_MESSAGES/volto.po b/frontend/packages/volto-form-block/locales/fr/LC_MESSAGES/volto.po index 7b2d56e..e5577b3 100644 --- a/frontend/packages/volto-form-block/locales/fr/LC_MESSAGES/volto.po +++ b/frontend/packages/volto-form-block/locales/fr/LC_MESSAGES/volto.po @@ -16,11 +16,23 @@ msgstr "" msgid "Add field" msgstr "Ajouter un champ" +#. Default: "Blind carbon copy" +#: schemaFormBlock/schema +msgid "Blind carbon copy" +msgstr "" + #. Default: "Cancel" #: components/Sidebar +#: schemaFormBlock/EditSchemaForm +#: schemaFormBlock/ViewSchemaForm msgid "Cancel" msgstr "Annuler" +#. Default: "Cancel button label" +#: schemaFormBlock/schema +msgid "Cancel button label" +msgstr "" + #. Default: "Choices" #: components/Widget/SelectWidget msgid "Choices" @@ -36,6 +48,11 @@ msgstr "" msgid "Close" msgstr "" +#. Default: "Data wipe" +#: schemaFormBlock/schema +msgid "Data wipe" +msgstr "" + #. Default: "Date" #: components/Widget/DatetimeWidget msgid "Date" @@ -48,6 +65,7 @@ msgstr "" #. Default: "Description" #: components/Widget/SelectWidget +#: schemaFormBlock/schema msgid "Description" msgstr "" @@ -66,13 +84,25 @@ msgstr "" msgid "Drop files here ..." msgstr "" +#. Default: "Email footer" +#: schemaFormBlock/schema +msgid "Email footer" +msgstr "" + +#. Default: "Email header" +#: schemaFormBlock/schema +msgid "Email header" +msgstr "" + #. Default: "Error" #: components/FormView +#: schemaFormBlock/ViewSchemaForm msgid "Error" msgstr "Erreur" #. Default: "Form" #: components/Sidebar +#: schemaFormBlock/schema msgid "Form" msgstr "Formulaire" @@ -86,6 +116,16 @@ msgstr "" msgid "No value" msgstr "" +#. Default: "Number of days after which, the data should be deleted. Enter -1 to store indefinitely." +#: schemaFormBlock/schema +msgid "Number of days after which, the data should be deleted. Enter -1 to store indefinitely." +msgstr "" + +#. Default: "Recipients" +#: schemaFormBlock/schema +msgid "Recipients" +msgstr "" + #. Default: "Replace existing file" #: components/Widget/FileWidget msgid "Replace existing file" @@ -101,6 +141,87 @@ msgstr "" msgid "Select…" msgstr "" +#. Default: "Send email" +#: schemaFormBlock/schema +msgid "Send email" +msgstr "" + +#. Default: "Send email to recipients" +#: schemaFormBlock/schema +msgid "Send email to recipients" +msgstr "" + +#. Default: "Sender" +#: schemaFormBlock/schema +msgid "Sender" +msgstr "" + +#. Default: "Sender name" +#: schemaFormBlock/schema +msgid "Sender name" +msgstr "" + +#. Default: "Show cancel button" +#: schemaFormBlock/schema +msgid "Show cancel button" +msgstr "" + +#. Default: "Store data" +#: schemaFormBlock/schema +msgid "Store data" +msgstr "" + +#. Default: "Subject" +#: schemaFormBlock/schema +msgid "Subject" +msgstr "" + +#. Default: "Submit" +#: schemaFormBlock/EditSchemaForm +#: schemaFormBlock/ViewSchemaForm +msgid "Submit" +msgstr "" + +#. Default: "Submit button label" +#: schemaFormBlock/schema +msgid "Submit button label" +msgstr "" + +#. Default: "Text at the beginning of the email. Use the ${field_id} syntax to add a form value." +#: schemaFormBlock/schema +msgid "Text at the beginning of the email. Use the ${field_id} syntax to add a form value." +msgstr "" + +#. Default: "Text at the end of the email. Use the ${field_id} syntax to add a form value." +#: schemaFormBlock/schema +msgid "Text at the end of the email. Use the ${field_id} syntax to add a form value." +msgstr "" + +#. Default: "The email address of the sender" +#: schemaFormBlock/schema +msgid "The email address of the sender" +msgstr "" + +#. Default: "The email addresses the submitted form data will be sent to as blind carbon copy. Multiple email addresses can be entered separated by a semicolon." +#: schemaFormBlock/schema +msgid "The email addresses the submitted form data will be sent to as blind carbon copy. Multiple email addresses can be entered separated by a semicolon." +msgstr "" + +#. Default: "The email addresses the submitted form data will be sent to. Multiple email addresses can be entered separated by a semicolon." +#: schemaFormBlock/schema +msgid "The email addresses the submitted form data will be sent to. Multiple email addresses can be entered separated by a semicolon." +msgstr "" + +#. Default: "The name of the sender" +#: schemaFormBlock/schema +msgid "The name of the sender" +msgstr "" + +#. Default: "The subject used in the sent email. Use the ${field_id} syntax to add a form value to the email subject." +#: schemaFormBlock/schema +msgid "The subject used in the sent email. Use the ${field_id} syntax to add a form value to the email subject." +msgstr "" + #. Default: "Time" #: components/Widget/DatetimeWidget msgid "Time" @@ -108,6 +229,7 @@ msgstr "" #. Default: "Title" #: components/Widget/SelectWidget +#: schemaFormBlock/schema msgid "Title" msgstr "" @@ -116,6 +238,16 @@ msgstr "" msgid "Used for programmatic access to the fieldset." msgstr "" +#. Default: "When activated, an email will be sent to the given recipients when a form is submitted" +#: schemaFormBlock/schema +msgid "When activated, an email will be sent to the given recipients when a form is submitted" +msgstr "" + +#. Default: "When activated, the data will be stored for later use when the form is submitted" +#: schemaFormBlock/schema +msgid "When activated, the data will be stored for later use when the form is submitted" +msgstr "" + #. Default: "Use Up and Down to choose options" #: helpers/react-select msgid "ay11_Use Up and Down to choose options" @@ -223,6 +355,7 @@ msgstr "" #. Default: "Captcha provider" #: formSchema +#: schemaFormBlock/schema msgid "captcha" msgstr "" diff --git a/frontend/packages/volto-form-block/locales/it/LC_MESSAGES/volto.po b/frontend/packages/volto-form-block/locales/it/LC_MESSAGES/volto.po index 3d8d661..5ef7c17 100644 --- a/frontend/packages/volto-form-block/locales/it/LC_MESSAGES/volto.po +++ b/frontend/packages/volto-form-block/locales/it/LC_MESSAGES/volto.po @@ -16,11 +16,23 @@ msgstr "" msgid "Add field" msgstr "Aggiungi campo" +#. Default: "Blind carbon copy" +#: schemaFormBlock/schema +msgid "Blind carbon copy" +msgstr "" + #. Default: "Cancel" #: components/Sidebar +#: schemaFormBlock/EditSchemaForm +#: schemaFormBlock/ViewSchemaForm msgid "Cancel" msgstr "Annulla" +#. Default: "Cancel button label" +#: schemaFormBlock/schema +msgid "Cancel button label" +msgstr "" + #. Default: "Choices" #: components/Widget/SelectWidget msgid "Choices" @@ -36,6 +48,11 @@ msgstr "Scegli un file" msgid "Close" msgstr "Chiudi" +#. Default: "Data wipe" +#: schemaFormBlock/schema +msgid "Data wipe" +msgstr "" + #. Default: "Date" #: components/Widget/DatetimeWidget msgid "Date" @@ -48,6 +65,7 @@ msgstr "" #. Default: "Description" #: components/Widget/SelectWidget +#: schemaFormBlock/schema msgid "Description" msgstr "Descrizione" @@ -66,13 +84,25 @@ msgstr "Trascina il file qui per caricare un nuovo file" msgid "Drop files here ..." msgstr "Trascina i file qui" +#. Default: "Email footer" +#: schemaFormBlock/schema +msgid "Email footer" +msgstr "" + +#. Default: "Email header" +#: schemaFormBlock/schema +msgid "Email header" +msgstr "" + #. Default: "Error" #: components/FormView +#: schemaFormBlock/ViewSchemaForm msgid "Error" msgstr "Errore" #. Default: "Form" #: components/Sidebar +#: schemaFormBlock/schema msgid "Form" msgstr "Form" @@ -86,6 +116,16 @@ msgstr "" msgid "No value" msgstr "Nessun valore" +#. Default: "Number of days after which, the data should be deleted. Enter -1 to store indefinitely." +#: schemaFormBlock/schema +msgid "Number of days after which, the data should be deleted. Enter -1 to store indefinitely." +msgstr "" + +#. Default: "Recipients" +#: schemaFormBlock/schema +msgid "Recipients" +msgstr "" + #. Default: "Replace existing file" #: components/Widget/FileWidget msgid "Replace existing file" @@ -101,6 +141,87 @@ msgstr "Obbligatorio" msgid "Select…" msgstr "Selezionare…" +#. Default: "Send email" +#: schemaFormBlock/schema +msgid "Send email" +msgstr "" + +#. Default: "Send email to recipients" +#: schemaFormBlock/schema +msgid "Send email to recipients" +msgstr "" + +#. Default: "Sender" +#: schemaFormBlock/schema +msgid "Sender" +msgstr "" + +#. Default: "Sender name" +#: schemaFormBlock/schema +msgid "Sender name" +msgstr "" + +#. Default: "Show cancel button" +#: schemaFormBlock/schema +msgid "Show cancel button" +msgstr "" + +#. Default: "Store data" +#: schemaFormBlock/schema +msgid "Store data" +msgstr "" + +#. Default: "Subject" +#: schemaFormBlock/schema +msgid "Subject" +msgstr "" + +#. Default: "Submit" +#: schemaFormBlock/EditSchemaForm +#: schemaFormBlock/ViewSchemaForm +msgid "Submit" +msgstr "" + +#. Default: "Submit button label" +#: schemaFormBlock/schema +msgid "Submit button label" +msgstr "" + +#. Default: "Text at the beginning of the email. Use the ${field_id} syntax to add a form value." +#: schemaFormBlock/schema +msgid "Text at the beginning of the email. Use the ${field_id} syntax to add a form value." +msgstr "" + +#. Default: "Text at the end of the email. Use the ${field_id} syntax to add a form value." +#: schemaFormBlock/schema +msgid "Text at the end of the email. Use the ${field_id} syntax to add a form value." +msgstr "" + +#. Default: "The email address of the sender" +#: schemaFormBlock/schema +msgid "The email address of the sender" +msgstr "" + +#. Default: "The email addresses the submitted form data will be sent to as blind carbon copy. Multiple email addresses can be entered separated by a semicolon." +#: schemaFormBlock/schema +msgid "The email addresses the submitted form data will be sent to as blind carbon copy. Multiple email addresses can be entered separated by a semicolon." +msgstr "" + +#. Default: "The email addresses the submitted form data will be sent to. Multiple email addresses can be entered separated by a semicolon." +#: schemaFormBlock/schema +msgid "The email addresses the submitted form data will be sent to. Multiple email addresses can be entered separated by a semicolon." +msgstr "" + +#. Default: "The name of the sender" +#: schemaFormBlock/schema +msgid "The name of the sender" +msgstr "" + +#. Default: "The subject used in the sent email. Use the ${field_id} syntax to add a form value to the email subject." +#: schemaFormBlock/schema +msgid "The subject used in the sent email. Use the ${field_id} syntax to add a form value to the email subject." +msgstr "" + #. Default: "Time" #: components/Widget/DatetimeWidget msgid "Time" @@ -108,6 +229,7 @@ msgstr "Ora" #. Default: "Title" #: components/Widget/SelectWidget +#: schemaFormBlock/schema msgid "Title" msgstr "Titolo" @@ -116,6 +238,16 @@ msgstr "Titolo" msgid "Used for programmatic access to the fieldset." msgstr "Utilizzato per l'accesso programmatico al fieldset." +#. Default: "When activated, an email will be sent to the given recipients when a form is submitted" +#: schemaFormBlock/schema +msgid "When activated, an email will be sent to the given recipients when a form is submitted" +msgstr "" + +#. Default: "When activated, the data will be stored for later use when the form is submitted" +#: schemaFormBlock/schema +msgid "When activated, the data will be stored for later use when the form is submitted" +msgstr "" + #. Default: "Use Up and Down to choose options" #: helpers/react-select msgid "ay11_Use Up and Down to choose options" @@ -223,6 +355,7 @@ msgstr "premi la freccia a sinistra per evidenziare i valori selezionati" #. Default: "Captcha provider" #: formSchema +#: schemaFormBlock/schema msgid "captcha" msgstr "" diff --git a/frontend/packages/volto-form-block/locales/ja/LC_MESSAGES/volto.po b/frontend/packages/volto-form-block/locales/ja/LC_MESSAGES/volto.po index 2c8479a..d04f57f 100644 --- a/frontend/packages/volto-form-block/locales/ja/LC_MESSAGES/volto.po +++ b/frontend/packages/volto-form-block/locales/ja/LC_MESSAGES/volto.po @@ -16,11 +16,23 @@ msgstr "" msgid "Add field" msgstr "" +#. Default: "Blind carbon copy" +#: schemaFormBlock/schema +msgid "Blind carbon copy" +msgstr "" + #. Default: "Cancel" #: components/Sidebar +#: schemaFormBlock/EditSchemaForm +#: schemaFormBlock/ViewSchemaForm msgid "Cancel" msgstr "" +#. Default: "Cancel button label" +#: schemaFormBlock/schema +msgid "Cancel button label" +msgstr "" + #. Default: "Choices" #: components/Widget/SelectWidget msgid "Choices" @@ -36,6 +48,11 @@ msgstr "" msgid "Close" msgstr "" +#. Default: "Data wipe" +#: schemaFormBlock/schema +msgid "Data wipe" +msgstr "" + #. Default: "Date" #: components/Widget/DatetimeWidget msgid "Date" @@ -48,6 +65,7 @@ msgstr "" #. Default: "Description" #: components/Widget/SelectWidget +#: schemaFormBlock/schema msgid "Description" msgstr "" @@ -66,13 +84,25 @@ msgstr "" msgid "Drop files here ..." msgstr "" +#. Default: "Email footer" +#: schemaFormBlock/schema +msgid "Email footer" +msgstr "" + +#. Default: "Email header" +#: schemaFormBlock/schema +msgid "Email header" +msgstr "" + #. Default: "Error" #: components/FormView +#: schemaFormBlock/ViewSchemaForm msgid "Error" msgstr "" #. Default: "Form" #: components/Sidebar +#: schemaFormBlock/schema msgid "Form" msgstr "" @@ -86,6 +116,16 @@ msgstr "" msgid "No value" msgstr "" +#. Default: "Number of days after which, the data should be deleted. Enter -1 to store indefinitely." +#: schemaFormBlock/schema +msgid "Number of days after which, the data should be deleted. Enter -1 to store indefinitely." +msgstr "" + +#. Default: "Recipients" +#: schemaFormBlock/schema +msgid "Recipients" +msgstr "" + #. Default: "Replace existing file" #: components/Widget/FileWidget msgid "Replace existing file" @@ -101,6 +141,87 @@ msgstr "" msgid "Select…" msgstr "" +#. Default: "Send email" +#: schemaFormBlock/schema +msgid "Send email" +msgstr "" + +#. Default: "Send email to recipients" +#: schemaFormBlock/schema +msgid "Send email to recipients" +msgstr "" + +#. Default: "Sender" +#: schemaFormBlock/schema +msgid "Sender" +msgstr "" + +#. Default: "Sender name" +#: schemaFormBlock/schema +msgid "Sender name" +msgstr "" + +#. Default: "Show cancel button" +#: schemaFormBlock/schema +msgid "Show cancel button" +msgstr "" + +#. Default: "Store data" +#: schemaFormBlock/schema +msgid "Store data" +msgstr "" + +#. Default: "Subject" +#: schemaFormBlock/schema +msgid "Subject" +msgstr "" + +#. Default: "Submit" +#: schemaFormBlock/EditSchemaForm +#: schemaFormBlock/ViewSchemaForm +msgid "Submit" +msgstr "" + +#. Default: "Submit button label" +#: schemaFormBlock/schema +msgid "Submit button label" +msgstr "" + +#. Default: "Text at the beginning of the email. Use the ${field_id} syntax to add a form value." +#: schemaFormBlock/schema +msgid "Text at the beginning of the email. Use the ${field_id} syntax to add a form value." +msgstr "" + +#. Default: "Text at the end of the email. Use the ${field_id} syntax to add a form value." +#: schemaFormBlock/schema +msgid "Text at the end of the email. Use the ${field_id} syntax to add a form value." +msgstr "" + +#. Default: "The email address of the sender" +#: schemaFormBlock/schema +msgid "The email address of the sender" +msgstr "" + +#. Default: "The email addresses the submitted form data will be sent to as blind carbon copy. Multiple email addresses can be entered separated by a semicolon." +#: schemaFormBlock/schema +msgid "The email addresses the submitted form data will be sent to as blind carbon copy. Multiple email addresses can be entered separated by a semicolon." +msgstr "" + +#. Default: "The email addresses the submitted form data will be sent to. Multiple email addresses can be entered separated by a semicolon." +#: schemaFormBlock/schema +msgid "The email addresses the submitted form data will be sent to. Multiple email addresses can be entered separated by a semicolon." +msgstr "" + +#. Default: "The name of the sender" +#: schemaFormBlock/schema +msgid "The name of the sender" +msgstr "" + +#. Default: "The subject used in the sent email. Use the ${field_id} syntax to add a form value to the email subject." +#: schemaFormBlock/schema +msgid "The subject used in the sent email. Use the ${field_id} syntax to add a form value to the email subject." +msgstr "" + #. Default: "Time" #: components/Widget/DatetimeWidget msgid "Time" @@ -108,6 +229,7 @@ msgstr "" #. Default: "Title" #: components/Widget/SelectWidget +#: schemaFormBlock/schema msgid "Title" msgstr "" @@ -116,6 +238,16 @@ msgstr "" msgid "Used for programmatic access to the fieldset." msgstr "" +#. Default: "When activated, an email will be sent to the given recipients when a form is submitted" +#: schemaFormBlock/schema +msgid "When activated, an email will be sent to the given recipients when a form is submitted" +msgstr "" + +#. Default: "When activated, the data will be stored for later use when the form is submitted" +#: schemaFormBlock/schema +msgid "When activated, the data will be stored for later use when the form is submitted" +msgstr "" + #. Default: "Use Up and Down to choose options" #: helpers/react-select msgid "ay11_Use Up and Down to choose options" @@ -223,6 +355,7 @@ msgstr "" #. Default: "Captcha provider" #: formSchema +#: schemaFormBlock/schema msgid "captcha" msgstr "" diff --git a/frontend/packages/volto-form-block/locales/nl/LC_MESSAGES/volto.po b/frontend/packages/volto-form-block/locales/nl/LC_MESSAGES/volto.po index fc5ddb4..4322e73 100644 --- a/frontend/packages/volto-form-block/locales/nl/LC_MESSAGES/volto.po +++ b/frontend/packages/volto-form-block/locales/nl/LC_MESSAGES/volto.po @@ -16,11 +16,23 @@ msgstr "" msgid "Add field" msgstr "Voeg veld toe" +#. Default: "Blind carbon copy" +#: schemaFormBlock/schema +msgid "Blind carbon copy" +msgstr "" + #. Default: "Cancel" #: components/Sidebar +#: schemaFormBlock/EditSchemaForm +#: schemaFormBlock/ViewSchemaForm msgid "Cancel" msgstr "Annuleren" +#. Default: "Cancel button label" +#: schemaFormBlock/schema +msgid "Cancel button label" +msgstr "" + #. Default: "Choices" #: components/Widget/SelectWidget msgid "Choices" @@ -36,6 +48,11 @@ msgstr "Kies een bestand" msgid "Close" msgstr "Sluiten" +#. Default: "Data wipe" +#: schemaFormBlock/schema +msgid "Data wipe" +msgstr "" + #. Default: "Date" #: components/Widget/DatetimeWidget msgid "Date" @@ -48,6 +65,7 @@ msgstr "Standaard" #. Default: "Description" #: components/Widget/SelectWidget +#: schemaFormBlock/schema msgid "Description" msgstr "Omschrijving" @@ -66,13 +84,25 @@ msgstr "" msgid "Drop files here ..." msgstr "" +#. Default: "Email footer" +#: schemaFormBlock/schema +msgid "Email footer" +msgstr "" + +#. Default: "Email header" +#: schemaFormBlock/schema +msgid "Email header" +msgstr "" + #. Default: "Error" #: components/FormView +#: schemaFormBlock/ViewSchemaForm msgid "Error" msgstr "" #. Default: "Form" #: components/Sidebar +#: schemaFormBlock/schema msgid "Form" msgstr "" @@ -86,6 +116,16 @@ msgstr "" msgid "No value" msgstr "" +#. Default: "Number of days after which, the data should be deleted. Enter -1 to store indefinitely." +#: schemaFormBlock/schema +msgid "Number of days after which, the data should be deleted. Enter -1 to store indefinitely." +msgstr "" + +#. Default: "Recipients" +#: schemaFormBlock/schema +msgid "Recipients" +msgstr "" + #. Default: "Replace existing file" #: components/Widget/FileWidget msgid "Replace existing file" @@ -101,6 +141,87 @@ msgstr "" msgid "Select…" msgstr "" +#. Default: "Send email" +#: schemaFormBlock/schema +msgid "Send email" +msgstr "" + +#. Default: "Send email to recipients" +#: schemaFormBlock/schema +msgid "Send email to recipients" +msgstr "" + +#. Default: "Sender" +#: schemaFormBlock/schema +msgid "Sender" +msgstr "" + +#. Default: "Sender name" +#: schemaFormBlock/schema +msgid "Sender name" +msgstr "" + +#. Default: "Show cancel button" +#: schemaFormBlock/schema +msgid "Show cancel button" +msgstr "" + +#. Default: "Store data" +#: schemaFormBlock/schema +msgid "Store data" +msgstr "" + +#. Default: "Subject" +#: schemaFormBlock/schema +msgid "Subject" +msgstr "" + +#. Default: "Submit" +#: schemaFormBlock/EditSchemaForm +#: schemaFormBlock/ViewSchemaForm +msgid "Submit" +msgstr "" + +#. Default: "Submit button label" +#: schemaFormBlock/schema +msgid "Submit button label" +msgstr "" + +#. Default: "Text at the beginning of the email. Use the ${field_id} syntax to add a form value." +#: schemaFormBlock/schema +msgid "Text at the beginning of the email. Use the ${field_id} syntax to add a form value." +msgstr "" + +#. Default: "Text at the end of the email. Use the ${field_id} syntax to add a form value." +#: schemaFormBlock/schema +msgid "Text at the end of the email. Use the ${field_id} syntax to add a form value." +msgstr "" + +#. Default: "The email address of the sender" +#: schemaFormBlock/schema +msgid "The email address of the sender" +msgstr "" + +#. Default: "The email addresses the submitted form data will be sent to as blind carbon copy. Multiple email addresses can be entered separated by a semicolon." +#: schemaFormBlock/schema +msgid "The email addresses the submitted form data will be sent to as blind carbon copy. Multiple email addresses can be entered separated by a semicolon." +msgstr "" + +#. Default: "The email addresses the submitted form data will be sent to. Multiple email addresses can be entered separated by a semicolon." +#: schemaFormBlock/schema +msgid "The email addresses the submitted form data will be sent to. Multiple email addresses can be entered separated by a semicolon." +msgstr "" + +#. Default: "The name of the sender" +#: schemaFormBlock/schema +msgid "The name of the sender" +msgstr "" + +#. Default: "The subject used in the sent email. Use the ${field_id} syntax to add a form value to the email subject." +#: schemaFormBlock/schema +msgid "The subject used in the sent email. Use the ${field_id} syntax to add a form value to the email subject." +msgstr "" + #. Default: "Time" #: components/Widget/DatetimeWidget msgid "Time" @@ -108,6 +229,7 @@ msgstr "" #. Default: "Title" #: components/Widget/SelectWidget +#: schemaFormBlock/schema msgid "Title" msgstr "" @@ -116,6 +238,16 @@ msgstr "" msgid "Used for programmatic access to the fieldset." msgstr "" +#. Default: "When activated, an email will be sent to the given recipients when a form is submitted" +#: schemaFormBlock/schema +msgid "When activated, an email will be sent to the given recipients when a form is submitted" +msgstr "" + +#. Default: "When activated, the data will be stored for later use when the form is submitted" +#: schemaFormBlock/schema +msgid "When activated, the data will be stored for later use when the form is submitted" +msgstr "" + #. Default: "Use Up and Down to choose options" #: helpers/react-select msgid "ay11_Use Up and Down to choose options" @@ -223,6 +355,7 @@ msgstr "" #. Default: "Captcha provider" #: formSchema +#: schemaFormBlock/schema msgid "captcha" msgstr "" diff --git a/frontend/packages/volto-form-block/locales/pt/LC_MESSAGES/volto.po b/frontend/packages/volto-form-block/locales/pt/LC_MESSAGES/volto.po index 2c8479a..d04f57f 100644 --- a/frontend/packages/volto-form-block/locales/pt/LC_MESSAGES/volto.po +++ b/frontend/packages/volto-form-block/locales/pt/LC_MESSAGES/volto.po @@ -16,11 +16,23 @@ msgstr "" msgid "Add field" msgstr "" +#. Default: "Blind carbon copy" +#: schemaFormBlock/schema +msgid "Blind carbon copy" +msgstr "" + #. Default: "Cancel" #: components/Sidebar +#: schemaFormBlock/EditSchemaForm +#: schemaFormBlock/ViewSchemaForm msgid "Cancel" msgstr "" +#. Default: "Cancel button label" +#: schemaFormBlock/schema +msgid "Cancel button label" +msgstr "" + #. Default: "Choices" #: components/Widget/SelectWidget msgid "Choices" @@ -36,6 +48,11 @@ msgstr "" msgid "Close" msgstr "" +#. Default: "Data wipe" +#: schemaFormBlock/schema +msgid "Data wipe" +msgstr "" + #. Default: "Date" #: components/Widget/DatetimeWidget msgid "Date" @@ -48,6 +65,7 @@ msgstr "" #. Default: "Description" #: components/Widget/SelectWidget +#: schemaFormBlock/schema msgid "Description" msgstr "" @@ -66,13 +84,25 @@ msgstr "" msgid "Drop files here ..." msgstr "" +#. Default: "Email footer" +#: schemaFormBlock/schema +msgid "Email footer" +msgstr "" + +#. Default: "Email header" +#: schemaFormBlock/schema +msgid "Email header" +msgstr "" + #. Default: "Error" #: components/FormView +#: schemaFormBlock/ViewSchemaForm msgid "Error" msgstr "" #. Default: "Form" #: components/Sidebar +#: schemaFormBlock/schema msgid "Form" msgstr "" @@ -86,6 +116,16 @@ msgstr "" msgid "No value" msgstr "" +#. Default: "Number of days after which, the data should be deleted. Enter -1 to store indefinitely." +#: schemaFormBlock/schema +msgid "Number of days after which, the data should be deleted. Enter -1 to store indefinitely." +msgstr "" + +#. Default: "Recipients" +#: schemaFormBlock/schema +msgid "Recipients" +msgstr "" + #. Default: "Replace existing file" #: components/Widget/FileWidget msgid "Replace existing file" @@ -101,6 +141,87 @@ msgstr "" msgid "Select…" msgstr "" +#. Default: "Send email" +#: schemaFormBlock/schema +msgid "Send email" +msgstr "" + +#. Default: "Send email to recipients" +#: schemaFormBlock/schema +msgid "Send email to recipients" +msgstr "" + +#. Default: "Sender" +#: schemaFormBlock/schema +msgid "Sender" +msgstr "" + +#. Default: "Sender name" +#: schemaFormBlock/schema +msgid "Sender name" +msgstr "" + +#. Default: "Show cancel button" +#: schemaFormBlock/schema +msgid "Show cancel button" +msgstr "" + +#. Default: "Store data" +#: schemaFormBlock/schema +msgid "Store data" +msgstr "" + +#. Default: "Subject" +#: schemaFormBlock/schema +msgid "Subject" +msgstr "" + +#. Default: "Submit" +#: schemaFormBlock/EditSchemaForm +#: schemaFormBlock/ViewSchemaForm +msgid "Submit" +msgstr "" + +#. Default: "Submit button label" +#: schemaFormBlock/schema +msgid "Submit button label" +msgstr "" + +#. Default: "Text at the beginning of the email. Use the ${field_id} syntax to add a form value." +#: schemaFormBlock/schema +msgid "Text at the beginning of the email. Use the ${field_id} syntax to add a form value." +msgstr "" + +#. Default: "Text at the end of the email. Use the ${field_id} syntax to add a form value." +#: schemaFormBlock/schema +msgid "Text at the end of the email. Use the ${field_id} syntax to add a form value." +msgstr "" + +#. Default: "The email address of the sender" +#: schemaFormBlock/schema +msgid "The email address of the sender" +msgstr "" + +#. Default: "The email addresses the submitted form data will be sent to as blind carbon copy. Multiple email addresses can be entered separated by a semicolon." +#: schemaFormBlock/schema +msgid "The email addresses the submitted form data will be sent to as blind carbon copy. Multiple email addresses can be entered separated by a semicolon." +msgstr "" + +#. Default: "The email addresses the submitted form data will be sent to. Multiple email addresses can be entered separated by a semicolon." +#: schemaFormBlock/schema +msgid "The email addresses the submitted form data will be sent to. Multiple email addresses can be entered separated by a semicolon." +msgstr "" + +#. Default: "The name of the sender" +#: schemaFormBlock/schema +msgid "The name of the sender" +msgstr "" + +#. Default: "The subject used in the sent email. Use the ${field_id} syntax to add a form value to the email subject." +#: schemaFormBlock/schema +msgid "The subject used in the sent email. Use the ${field_id} syntax to add a form value to the email subject." +msgstr "" + #. Default: "Time" #: components/Widget/DatetimeWidget msgid "Time" @@ -108,6 +229,7 @@ msgstr "" #. Default: "Title" #: components/Widget/SelectWidget +#: schemaFormBlock/schema msgid "Title" msgstr "" @@ -116,6 +238,16 @@ msgstr "" msgid "Used for programmatic access to the fieldset." msgstr "" +#. Default: "When activated, an email will be sent to the given recipients when a form is submitted" +#: schemaFormBlock/schema +msgid "When activated, an email will be sent to the given recipients when a form is submitted" +msgstr "" + +#. Default: "When activated, the data will be stored for later use when the form is submitted" +#: schemaFormBlock/schema +msgid "When activated, the data will be stored for later use when the form is submitted" +msgstr "" + #. Default: "Use Up and Down to choose options" #: helpers/react-select msgid "ay11_Use Up and Down to choose options" @@ -223,6 +355,7 @@ msgstr "" #. Default: "Captcha provider" #: formSchema +#: schemaFormBlock/schema msgid "captcha" msgstr "" diff --git a/frontend/packages/volto-form-block/locales/pt_BR/LC_MESSAGES/volto.po b/frontend/packages/volto-form-block/locales/pt_BR/LC_MESSAGES/volto.po index 9b1ea41..520f7f1 100644 --- a/frontend/packages/volto-form-block/locales/pt_BR/LC_MESSAGES/volto.po +++ b/frontend/packages/volto-form-block/locales/pt_BR/LC_MESSAGES/volto.po @@ -22,11 +22,23 @@ msgstr "" msgid "Add field" msgstr "Adicionar campo" +#. Default: "Blind carbon copy" +#: schemaFormBlock/schema +msgid "Blind carbon copy" +msgstr "" + #. Default: "Cancel" #: components/Sidebar +#: schemaFormBlock/EditSchemaForm +#: schemaFormBlock/ViewSchemaForm msgid "Cancel" msgstr "Cancelar" +#. Default: "Cancel button label" +#: schemaFormBlock/schema +msgid "Cancel button label" +msgstr "" + #. Default: "Choices" #: components/Widget/SelectWidget msgid "Choices" @@ -42,6 +54,11 @@ msgstr "" msgid "Close" msgstr "" +#. Default: "Data wipe" +#: schemaFormBlock/schema +msgid "Data wipe" +msgstr "" + #. Default: "Date" #: components/Widget/DatetimeWidget msgid "Date" @@ -54,6 +71,7 @@ msgstr "" #. Default: "Description" #: components/Widget/SelectWidget +#: schemaFormBlock/schema msgid "Description" msgstr "" @@ -72,13 +90,25 @@ msgstr "" msgid "Drop files here ..." msgstr "" +#. Default: "Email footer" +#: schemaFormBlock/schema +msgid "Email footer" +msgstr "" + +#. Default: "Email header" +#: schemaFormBlock/schema +msgid "Email header" +msgstr "" + #. Default: "Error" #: components/FormView +#: schemaFormBlock/ViewSchemaForm msgid "Error" msgstr "Erro" #. Default: "Form" #: components/Sidebar +#: schemaFormBlock/schema msgid "Form" msgstr "Formulário" @@ -92,6 +122,16 @@ msgstr "" msgid "No value" msgstr "" +#. Default: "Number of days after which, the data should be deleted. Enter -1 to store indefinitely." +#: schemaFormBlock/schema +msgid "Number of days after which, the data should be deleted. Enter -1 to store indefinitely." +msgstr "" + +#. Default: "Recipients" +#: schemaFormBlock/schema +msgid "Recipients" +msgstr "" + #. Default: "Replace existing file" #: components/Widget/FileWidget msgid "Replace existing file" @@ -107,6 +147,87 @@ msgstr "" msgid "Select…" msgstr "" +#. Default: "Send email" +#: schemaFormBlock/schema +msgid "Send email" +msgstr "" + +#. Default: "Send email to recipients" +#: schemaFormBlock/schema +msgid "Send email to recipients" +msgstr "" + +#. Default: "Sender" +#: schemaFormBlock/schema +msgid "Sender" +msgstr "" + +#. Default: "Sender name" +#: schemaFormBlock/schema +msgid "Sender name" +msgstr "" + +#. Default: "Show cancel button" +#: schemaFormBlock/schema +msgid "Show cancel button" +msgstr "" + +#. Default: "Store data" +#: schemaFormBlock/schema +msgid "Store data" +msgstr "" + +#. Default: "Subject" +#: schemaFormBlock/schema +msgid "Subject" +msgstr "" + +#. Default: "Submit" +#: schemaFormBlock/EditSchemaForm +#: schemaFormBlock/ViewSchemaForm +msgid "Submit" +msgstr "" + +#. Default: "Submit button label" +#: schemaFormBlock/schema +msgid "Submit button label" +msgstr "" + +#. Default: "Text at the beginning of the email. Use the ${field_id} syntax to add a form value." +#: schemaFormBlock/schema +msgid "Text at the beginning of the email. Use the ${field_id} syntax to add a form value." +msgstr "" + +#. Default: "Text at the end of the email. Use the ${field_id} syntax to add a form value." +#: schemaFormBlock/schema +msgid "Text at the end of the email. Use the ${field_id} syntax to add a form value." +msgstr "" + +#. Default: "The email address of the sender" +#: schemaFormBlock/schema +msgid "The email address of the sender" +msgstr "" + +#. Default: "The email addresses the submitted form data will be sent to as blind carbon copy. Multiple email addresses can be entered separated by a semicolon." +#: schemaFormBlock/schema +msgid "The email addresses the submitted form data will be sent to as blind carbon copy. Multiple email addresses can be entered separated by a semicolon." +msgstr "" + +#. Default: "The email addresses the submitted form data will be sent to. Multiple email addresses can be entered separated by a semicolon." +#: schemaFormBlock/schema +msgid "The email addresses the submitted form data will be sent to. Multiple email addresses can be entered separated by a semicolon." +msgstr "" + +#. Default: "The name of the sender" +#: schemaFormBlock/schema +msgid "The name of the sender" +msgstr "" + +#. Default: "The subject used in the sent email. Use the ${field_id} syntax to add a form value to the email subject." +#: schemaFormBlock/schema +msgid "The subject used in the sent email. Use the ${field_id} syntax to add a form value to the email subject." +msgstr "" + #. Default: "Time" #: components/Widget/DatetimeWidget msgid "Time" @@ -114,6 +235,7 @@ msgstr "" #. Default: "Title" #: components/Widget/SelectWidget +#: schemaFormBlock/schema msgid "Title" msgstr "" @@ -122,6 +244,16 @@ msgstr "" msgid "Used for programmatic access to the fieldset." msgstr "" +#. Default: "When activated, an email will be sent to the given recipients when a form is submitted" +#: schemaFormBlock/schema +msgid "When activated, an email will be sent to the given recipients when a form is submitted" +msgstr "" + +#. Default: "When activated, the data will be stored for later use when the form is submitted" +#: schemaFormBlock/schema +msgid "When activated, the data will be stored for later use when the form is submitted" +msgstr "" + #. Default: "Use Up and Down to choose options" #: helpers/react-select msgid "ay11_Use Up and Down to choose options" @@ -229,6 +361,7 @@ msgstr "" #. Default: "Captcha provider" #: formSchema +#: schemaFormBlock/schema msgid "captcha" msgstr "" diff --git a/frontend/packages/volto-form-block/locales/ro/LC_MESSAGES/volto.po b/frontend/packages/volto-form-block/locales/ro/LC_MESSAGES/volto.po index 2c8479a..d04f57f 100644 --- a/frontend/packages/volto-form-block/locales/ro/LC_MESSAGES/volto.po +++ b/frontend/packages/volto-form-block/locales/ro/LC_MESSAGES/volto.po @@ -16,11 +16,23 @@ msgstr "" msgid "Add field" msgstr "" +#. Default: "Blind carbon copy" +#: schemaFormBlock/schema +msgid "Blind carbon copy" +msgstr "" + #. Default: "Cancel" #: components/Sidebar +#: schemaFormBlock/EditSchemaForm +#: schemaFormBlock/ViewSchemaForm msgid "Cancel" msgstr "" +#. Default: "Cancel button label" +#: schemaFormBlock/schema +msgid "Cancel button label" +msgstr "" + #. Default: "Choices" #: components/Widget/SelectWidget msgid "Choices" @@ -36,6 +48,11 @@ msgstr "" msgid "Close" msgstr "" +#. Default: "Data wipe" +#: schemaFormBlock/schema +msgid "Data wipe" +msgstr "" + #. Default: "Date" #: components/Widget/DatetimeWidget msgid "Date" @@ -48,6 +65,7 @@ msgstr "" #. Default: "Description" #: components/Widget/SelectWidget +#: schemaFormBlock/schema msgid "Description" msgstr "" @@ -66,13 +84,25 @@ msgstr "" msgid "Drop files here ..." msgstr "" +#. Default: "Email footer" +#: schemaFormBlock/schema +msgid "Email footer" +msgstr "" + +#. Default: "Email header" +#: schemaFormBlock/schema +msgid "Email header" +msgstr "" + #. Default: "Error" #: components/FormView +#: schemaFormBlock/ViewSchemaForm msgid "Error" msgstr "" #. Default: "Form" #: components/Sidebar +#: schemaFormBlock/schema msgid "Form" msgstr "" @@ -86,6 +116,16 @@ msgstr "" msgid "No value" msgstr "" +#. Default: "Number of days after which, the data should be deleted. Enter -1 to store indefinitely." +#: schemaFormBlock/schema +msgid "Number of days after which, the data should be deleted. Enter -1 to store indefinitely." +msgstr "" + +#. Default: "Recipients" +#: schemaFormBlock/schema +msgid "Recipients" +msgstr "" + #. Default: "Replace existing file" #: components/Widget/FileWidget msgid "Replace existing file" @@ -101,6 +141,87 @@ msgstr "" msgid "Select…" msgstr "" +#. Default: "Send email" +#: schemaFormBlock/schema +msgid "Send email" +msgstr "" + +#. Default: "Send email to recipients" +#: schemaFormBlock/schema +msgid "Send email to recipients" +msgstr "" + +#. Default: "Sender" +#: schemaFormBlock/schema +msgid "Sender" +msgstr "" + +#. Default: "Sender name" +#: schemaFormBlock/schema +msgid "Sender name" +msgstr "" + +#. Default: "Show cancel button" +#: schemaFormBlock/schema +msgid "Show cancel button" +msgstr "" + +#. Default: "Store data" +#: schemaFormBlock/schema +msgid "Store data" +msgstr "" + +#. Default: "Subject" +#: schemaFormBlock/schema +msgid "Subject" +msgstr "" + +#. Default: "Submit" +#: schemaFormBlock/EditSchemaForm +#: schemaFormBlock/ViewSchemaForm +msgid "Submit" +msgstr "" + +#. Default: "Submit button label" +#: schemaFormBlock/schema +msgid "Submit button label" +msgstr "" + +#. Default: "Text at the beginning of the email. Use the ${field_id} syntax to add a form value." +#: schemaFormBlock/schema +msgid "Text at the beginning of the email. Use the ${field_id} syntax to add a form value." +msgstr "" + +#. Default: "Text at the end of the email. Use the ${field_id} syntax to add a form value." +#: schemaFormBlock/schema +msgid "Text at the end of the email. Use the ${field_id} syntax to add a form value." +msgstr "" + +#. Default: "The email address of the sender" +#: schemaFormBlock/schema +msgid "The email address of the sender" +msgstr "" + +#. Default: "The email addresses the submitted form data will be sent to as blind carbon copy. Multiple email addresses can be entered separated by a semicolon." +#: schemaFormBlock/schema +msgid "The email addresses the submitted form data will be sent to as blind carbon copy. Multiple email addresses can be entered separated by a semicolon." +msgstr "" + +#. Default: "The email addresses the submitted form data will be sent to. Multiple email addresses can be entered separated by a semicolon." +#: schemaFormBlock/schema +msgid "The email addresses the submitted form data will be sent to. Multiple email addresses can be entered separated by a semicolon." +msgstr "" + +#. Default: "The name of the sender" +#: schemaFormBlock/schema +msgid "The name of the sender" +msgstr "" + +#. Default: "The subject used in the sent email. Use the ${field_id} syntax to add a form value to the email subject." +#: schemaFormBlock/schema +msgid "The subject used in the sent email. Use the ${field_id} syntax to add a form value to the email subject." +msgstr "" + #. Default: "Time" #: components/Widget/DatetimeWidget msgid "Time" @@ -108,6 +229,7 @@ msgstr "" #. Default: "Title" #: components/Widget/SelectWidget +#: schemaFormBlock/schema msgid "Title" msgstr "" @@ -116,6 +238,16 @@ msgstr "" msgid "Used for programmatic access to the fieldset." msgstr "" +#. Default: "When activated, an email will be sent to the given recipients when a form is submitted" +#: schemaFormBlock/schema +msgid "When activated, an email will be sent to the given recipients when a form is submitted" +msgstr "" + +#. Default: "When activated, the data will be stored for later use when the form is submitted" +#: schemaFormBlock/schema +msgid "When activated, the data will be stored for later use when the form is submitted" +msgstr "" + #. Default: "Use Up and Down to choose options" #: helpers/react-select msgid "ay11_Use Up and Down to choose options" @@ -223,6 +355,7 @@ msgstr "" #. Default: "Captcha provider" #: formSchema +#: schemaFormBlock/schema msgid "captcha" msgstr "" diff --git a/frontend/packages/volto-form-block/locales/volto.pot b/frontend/packages/volto-form-block/locales/volto.pot index 4d94268..2684e8b 100644 --- a/frontend/packages/volto-form-block/locales/volto.pot +++ b/frontend/packages/volto-form-block/locales/volto.pot @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Plone\n" -"POT-Creation-Date: 2024-07-04T09:29:55.227Z\n" +"POT-Creation-Date: 2024-09-11T09:06:00.713Z\n" "Last-Translator: Plone i18n \n" "Language-Team: Plone i18n \n" "Content-Type: text/plain; charset=utf-8\n" @@ -18,11 +18,23 @@ msgstr "" msgid "Add field" msgstr "" +#. Default: "Blind carbon copy" +#: schemaFormBlock/schema +msgid "Blind carbon copy" +msgstr "" + #. Default: "Cancel" #: components/Sidebar +#: schemaFormBlock/EditSchemaForm +#: schemaFormBlock/ViewSchemaForm msgid "Cancel" msgstr "" +#. Default: "Cancel button label" +#: schemaFormBlock/schema +msgid "Cancel button label" +msgstr "" + #. Default: "Choices" #: components/Widget/SelectWidget msgid "Choices" @@ -38,6 +50,11 @@ msgstr "" msgid "Close" msgstr "" +#. Default: "Data wipe" +#: schemaFormBlock/schema +msgid "Data wipe" +msgstr "" + #. Default: "Date" #: components/Widget/DatetimeWidget msgid "Date" @@ -50,6 +67,7 @@ msgstr "" #. Default: "Description" #: components/Widget/SelectWidget +#: schemaFormBlock/schema msgid "Description" msgstr "" @@ -68,13 +86,25 @@ msgstr "" msgid "Drop files here ..." msgstr "" +#. Default: "Email footer" +#: schemaFormBlock/schema +msgid "Email footer" +msgstr "" + +#. Default: "Email header" +#: schemaFormBlock/schema +msgid "Email header" +msgstr "" + #. Default: "Error" #: components/FormView +#: schemaFormBlock/ViewSchemaForm msgid "Error" msgstr "" #. Default: "Form" #: components/Sidebar +#: schemaFormBlock/schema msgid "Form" msgstr "" @@ -88,6 +118,16 @@ msgstr "" msgid "No value" msgstr "" +#. Default: "Number of days after which, the data should be deleted. Enter -1 to store indefinitely." +#: schemaFormBlock/schema +msgid "Number of days after which, the data should be deleted. Enter -1 to store indefinitely." +msgstr "" + +#. Default: "Recipients" +#: schemaFormBlock/schema +msgid "Recipients" +msgstr "" + #. Default: "Replace existing file" #: components/Widget/FileWidget msgid "Replace existing file" @@ -103,6 +143,87 @@ msgstr "" msgid "Select…" msgstr "" +#. Default: "Send email" +#: schemaFormBlock/schema +msgid "Send email" +msgstr "" + +#. Default: "Send email to recipients" +#: schemaFormBlock/schema +msgid "Send email to recipients" +msgstr "" + +#. Default: "Sender" +#: schemaFormBlock/schema +msgid "Sender" +msgstr "" + +#. Default: "Sender name" +#: schemaFormBlock/schema +msgid "Sender name" +msgstr "" + +#. Default: "Show cancel button" +#: schemaFormBlock/schema +msgid "Show cancel button" +msgstr "" + +#. Default: "Store data" +#: schemaFormBlock/schema +msgid "Store data" +msgstr "" + +#. Default: "Subject" +#: schemaFormBlock/schema +msgid "Subject" +msgstr "" + +#. Default: "Submit" +#: schemaFormBlock/EditSchemaForm +#: schemaFormBlock/ViewSchemaForm +msgid "Submit" +msgstr "" + +#. Default: "Submit button label" +#: schemaFormBlock/schema +msgid "Submit button label" +msgstr "" + +#. Default: "Text at the beginning of the email. Use the ${field_id} syntax to add a form value." +#: schemaFormBlock/schema +msgid "Text at the beginning of the email. Use the ${field_id} syntax to add a form value." +msgstr "" + +#. Default: "Text at the end of the email. Use the ${field_id} syntax to add a form value." +#: schemaFormBlock/schema +msgid "Text at the end of the email. Use the ${field_id} syntax to add a form value." +msgstr "" + +#. Default: "The email address of the sender" +#: schemaFormBlock/schema +msgid "The email address of the sender" +msgstr "" + +#. Default: "The email addresses the submitted form data will be sent to as blind carbon copy. Multiple email addresses can be entered separated by a semicolon." +#: schemaFormBlock/schema +msgid "The email addresses the submitted form data will be sent to as blind carbon copy. Multiple email addresses can be entered separated by a semicolon." +msgstr "" + +#. Default: "The email addresses the submitted form data will be sent to. Multiple email addresses can be entered separated by a semicolon." +#: schemaFormBlock/schema +msgid "The email addresses the submitted form data will be sent to. Multiple email addresses can be entered separated by a semicolon." +msgstr "" + +#. Default: "The name of the sender" +#: schemaFormBlock/schema +msgid "The name of the sender" +msgstr "" + +#. Default: "The subject used in the sent email. Use the ${field_id} syntax to add a form value to the email subject." +#: schemaFormBlock/schema +msgid "The subject used in the sent email. Use the ${field_id} syntax to add a form value to the email subject." +msgstr "" + #. Default: "Time" #: components/Widget/DatetimeWidget msgid "Time" @@ -110,6 +231,7 @@ msgstr "" #. Default: "Title" #: components/Widget/SelectWidget +#: schemaFormBlock/schema msgid "Title" msgstr "" @@ -118,6 +240,16 @@ msgstr "" msgid "Used for programmatic access to the fieldset." msgstr "" +#. Default: "When activated, an email will be sent to the given recipients when a form is submitted" +#: schemaFormBlock/schema +msgid "When activated, an email will be sent to the given recipients when a form is submitted" +msgstr "" + +#. Default: "When activated, the data will be stored for later use when the form is submitted" +#: schemaFormBlock/schema +msgid "When activated, the data will be stored for later use when the form is submitted" +msgstr "" + #. Default: "Use Up and Down to choose options" #: helpers/react-select msgid "ay11_Use Up and Down to choose options" @@ -225,6 +357,7 @@ msgstr "" #. Default: "Captcha provider" #: formSchema +#: schemaFormBlock/schema msgid "captcha" msgstr "" diff --git a/frontend/packages/volto-form-block/src/actions/index.js b/frontend/packages/volto-form-block/src/actions/index.js index 08d5921..29dab5c 100644 --- a/frontend/packages/volto-form-block/src/actions/index.js +++ b/frontend/packages/volto-form-block/src/actions/index.js @@ -19,7 +19,7 @@ export function submitForm(path = '', block_id, data, attachments, captcha) { subrequest: block_id, request: { op: 'post', - path: path + '/@submit-form', + path: path + '/@schemaform-data', data: { block_id, data, diff --git a/frontend/packages/volto-form-block/src/components/Widget/HoneypotCaptchaWidget.jsx b/frontend/packages/volto-form-block/src/components/Widget/HoneypotCaptchaWidget.jsx index d6781c4..a8e65ec 100644 --- a/frontend/packages/volto-form-block/src/components/Widget/HoneypotCaptchaWidget.jsx +++ b/frontend/packages/volto-form-block/src/components/Widget/HoneypotCaptchaWidget.jsx @@ -40,6 +40,7 @@ const HoneypotCaptchaWidget = ({ }, []); const [value, setValue] = useState(); + return (
{ + config.widgets.widget.honeypot = HoneypotCaptchaWidget; + config.blocks.blocksConfig = { ...config.blocks.blocksConfig, + schemaForm: { + id: 'schemaForm', + title: 'schemaForm', + icon: formSVG, + group: 'text', + view: schemaFormBlockView, + edit: schemaFormBlockEdit, + formSchema: FormSchema, + blockSchema: schemaFormBlockSchema, + fieldSchema: FieldSchema, + filterFactory: [ + 'label_textline_field', + 'label_text_field', + 'label_choice_field', + 'label_multi_choice_field', + 'label_boolean_field', + 'label_date_field', + 'label_datetime_field', + 'File Upload', + 'label_email', + 'hidden', + 'static_text', + ], + additionalFactory: [ + { value: 'hidden', label: 'Hidden' }, + { value: 'static_text', label: 'Static Text' }, + ], + defaultSender: 'noreply@plone.org', + defaultSenderName: 'Plone', + additionalFields: [], + fieldTypeSchemaExtenders: { + select: SelectionSchemaExtender, + single_choice: SelectionSchemaExtender, + multiple_choice: SelectionSchemaExtender, + from: FromSchemaExtender, + hidden: HiddenSchemaExtender, + }, + schemaValidators: { + /*fieldname: validationFN(data)*/ + default_from: (data, intl) => { + return validateDefaultFrom(data.default_from, intl); + }, + default_to: (data, intl) => { + return validateDefaultTo(data.default_to, intl); + }, + }, + attachment_fields: ['attachment'], + restricted: false, + mostUsed: true, + security: { + addPermission: [], + view: [], + }, + sidebarTab: 1, + }, form: { id: 'form', title: 'Form', diff --git a/frontend/packages/volto-form-block/src/schemaFormBlock/EditSchemaForm.jsx b/frontend/packages/volto-form-block/src/schemaFormBlock/EditSchemaForm.jsx new file mode 100644 index 0000000..1a3f53c --- /dev/null +++ b/frontend/packages/volto-form-block/src/schemaFormBlock/EditSchemaForm.jsx @@ -0,0 +1,115 @@ +import React, { Component } from 'react'; +import { isEmpty } from 'lodash'; +import { compose } from 'redux'; +import { defineMessages, injectIntl } from 'react-intl'; +import { SidebarPortal } from '@plone/volto/components'; +import { Form, BlockDataForm } from '@plone/volto/components/manage/Form'; +import { withBlockExtensions } from '@plone/volto/helpers'; +import config from '@plone/volto/registry'; + +const messages = defineMessages({ + submit: { + id: 'Submit', + defaultMessage: 'Submit', + }, + cancel: { + id: 'Cancel', + defaultMessage: 'Cancel', + }, +}); + +class Edit extends Component { + render() { + const FormSchema = config.blocks.blocksConfig.schemaForm.blockSchema; + const filterFactory = config.blocks.blocksConfig.schemaForm.filterFactory; + const additionalFactory = + config.blocks.blocksConfig.schemaForm.additionalFactory; + const schema = FormSchema(this.props); + const { data } = this.props; + + const defaultEmptyData = { + fieldsets: [ + { + id: 'default', + title: 'Default', + fields: [], + }, + ], + properties: {}, + required: [], + }; + + const dummyHandler = () => {}; + + return ( + <> +
{ + this.props.onChangeBlock(this.props.block, { + ...data, + schema: formData.schema, + }); + }} + onSubmit={dummyHandler} + onCancel={data.show_cancel ? dummyHandler : null} + submitLabel={ + data.submit_label || this.props.intl.formatMessage(messages.submit) + } + cancelLabel={ + data.cancel_label || this.props.intl.formatMessage(messages.cancel) + } + /> + + + { + this.props.onChangeBlock(this.props.block, { + ...this.props.data, + [id]: value, + }); + }} + onChangeBlock={this.props.onChangeBlock} + formData={this.props.data} + block={this.props.block} + navRoot={this.props.navRoot} + contentType={this.props.contentType} + /> + + + ); + } +} + +export default compose(withBlockExtensions, injectIntl)(Edit); diff --git a/frontend/packages/volto-form-block/src/schemaFormBlock/HoneypotCaptchaWidget.jsx b/frontend/packages/volto-form-block/src/schemaFormBlock/HoneypotCaptchaWidget.jsx new file mode 100644 index 0000000..0891853 --- /dev/null +++ b/frontend/packages/volto-form-block/src/schemaFormBlock/HoneypotCaptchaWidget.jsx @@ -0,0 +1,36 @@ +import React, { useEffect } from 'react'; +import TextWidget from '@plone/volto/components/manage/Widgets/TextWidget'; + +import 'volto-form-block/components/Widget/HoneypotCaptchaWidget.css'; + +/* By default, captcha token is setted, and becames empty if user/bot fills the field. */ +const HoneypotCaptchaWidget = ({ id, value, onChange, captcha_props }) => { + const title = ''; + const createToken = (id, value) => { + const token = { + id: id, + value: value, + }; + return JSON.stringify(token); + }; + + useEffect(() => { + onChange('captchaToken', createToken(id, new Date().toString())); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( +
+ +
+ ); +}; + +export default HoneypotCaptchaWidget; diff --git a/frontend/packages/volto-form-block/src/schemaFormBlock/ViewSchemaForm.jsx b/frontend/packages/volto-form-block/src/schemaFormBlock/ViewSchemaForm.jsx new file mode 100644 index 0000000..b5cf18a --- /dev/null +++ b/frontend/packages/volto-form-block/src/schemaFormBlock/ViewSchemaForm.jsx @@ -0,0 +1,89 @@ +import React from 'react'; +import { useDispatch } from 'react-redux'; +import { defineMessages, useIntl } from 'react-intl'; +import { Form } from '@plone/volto/components/manage/Form'; +import { submitForm } from 'volto-form-block/actions'; +import { tryParseJSON, extractInvariantErrors } from '@plone/volto/helpers'; +import { toast } from 'react-toastify'; +import { Toast } from '@plone/volto/components'; +import { useLocation } from 'react-router-dom'; +import qs from 'query-string'; +import { pickBy, keys } from 'lodash'; + +const messages = defineMessages({ + error: { + id: 'Error', + defaultMessage: 'Error', + }, + submit: { + id: 'Submit', + defaultMessage: 'Submit', + }, + cancel: { + id: 'Cancel', + defaultMessage: 'Cancel', + }, +}); + +const FormBlockView = ({ data, id, properties, metadata, path }) => { + const dispatch = useDispatch(); + const intl = useIntl(); + let attachments = {}; + const location = useLocation(); + + const propertyNames = keys(data.schema.properties); + const initialData = pickBy(qs.parse(location.search), (value, key) => + propertyNames.includes(key), + ); + + const onCancel = () => {}; + + const onSubmit = (formData) => { + let captcha = { + provider: data.captcha, + token: formData.captchaToken, + }; + if (data.captcha === 'honeypot') { + captcha.value = formData['captchaWidget']?.value ?? ''; + delete formData.captchaToken; + } + + dispatch(submitForm(path, id, formData, attachments, captcha)).catch( + (err) => { + let message = + err?.response?.body?.error?.message || + err?.response?.body?.message || + err?.response?.text || + ''; + const errorsList = tryParseJSON(message); + let invariantErrors = []; + if (Array.isArray(errorsList)) { + invariantErrors = extractInvariantErrors(errorsList); + } + if (invariantErrors.length > 0) { + toast.error( + , + ); + } + }, + ); + }; + + return ( + + ); +}; + +export default FormBlockView; diff --git a/frontend/packages/volto-form-block/src/schemaFormBlock/schema.js b/frontend/packages/volto-form-block/src/schemaFormBlock/schema.js new file mode 100644 index 0000000..2e759af --- /dev/null +++ b/frontend/packages/volto-form-block/src/schemaFormBlock/schema.js @@ -0,0 +1,284 @@ +import { defineMessages } from 'react-intl'; +import config from '@plone/volto/registry'; + +const messages = defineMessages({ + form: { + id: 'Form', + defaultMessage: 'Form', + }, + title: { + id: 'Title', + defaultMessage: 'Title', + }, + description: { + id: 'Description', + defaultMessage: 'Description', + }, + submit_label: { + id: 'Submit button label', + defaultMessage: 'Submit button label', + }, + show_cancel: { + id: 'Show cancel button', + defaultMessage: 'Show cancel button', + }, + cancel_label: { + id: 'Cancel button label', + defaultMessage: 'Cancel button label', + }, + captcha: { + id: 'captcha', + defaultMessage: 'Captcha provider', + }, + + fieldset_email: { + id: 'Send email', + defaultMessage: 'Send email', + }, + send: { + id: 'Send email to recipients', + defaultMessage: 'Send email to recipients', + }, + send_description: { + id: 'When activated, an email will be sent to the given recipients when a form is submitted', + defaultMessage: + 'When activated, an email will be sent to the given recipients when a form is submitted', + }, + recipients: { + id: 'Recipients', + defaultMessage: 'Recipients', + }, + recipients_description: { + id: 'The email addresses the submitted form data will be sent to. Multiple email addresses can be entered separated by a semicolon.', + defaultMessage: + 'The email addresses the submitted form data will be sent to. Multiple email addresses can be entered separated by a semicolon.', + }, + bcc: { + id: 'Blind carbon copy', + defaultMessage: 'Blind carbon copy', + }, + bcc_description: { + id: 'The email addresses the submitted form data will be sent to as blind carbon copy. Multiple email addresses can be entered separated by a semicolon.', + defaultMessage: + 'The email addresses the submitted form data will be sent to as blind carbon copy. Multiple email addresses can be entered separated by a semicolon.', + }, + sender: { + id: 'Sender', + defaultMessage: 'Sender', + }, + sender_description: { + id: 'The email address of the sender', + defaultMessage: 'The email address of the sender', + }, + sender_name: { + id: 'Sender name', + defaultMessage: 'Sender name', + }, + sender_name_description: { + id: 'The name of the sender', + defaultMessage: 'The name of the sender', + }, + subject: { + id: 'Subject', + defaultMessage: 'Subject', + }, + subject_description: { + id: + // eslint-disable-next-line no-template-curly-in-string + 'The subject used in the sent email. Use the ${field_id} syntax to add a form value to the email subject.', + defaultMessage: + // eslint-disable-next-line no-template-curly-in-string + 'The subject used in the sent email. Use the ${field_id} syntax to add a form value to the email subject.', + }, + mail_header: { + id: 'Email header', + defaultMessage: 'Email header', + }, + mail_header_description: { + // eslint-disable-next-line no-template-curly-in-string + id: 'Text at the beginning of the email. Use the ${field_id} syntax to add a form value.', + // eslint-disable-next-line no-template-curly-in-string + defaultMessage: + // eslint-disable-next-line no-template-curly-in-string + 'Text at the beginning of the email. Use the ${field_id} syntax to add a form value.', + }, + mail_footer: { + id: 'Email footer', + defaultMessage: 'Email footer', + }, + mail_footer_description: { + // eslint-disable-next-line no-template-curly-in-string + id: 'Text at the end of the email. Use the ${field_id} syntax to add a form value.', + defaultMessage: + // eslint-disable-next-line no-template-curly-in-string + 'Text at the end of the email. Use the ${field_id} syntax to add a form value.', + }, + + fieldset_store: { + id: 'Store data', + defaultMessage: 'Store data', + }, + store: { + id: 'Store data', + defaultMessage: 'Store data', + }, + store_description: { + id: 'When activated, the data will be stored for later use when the form is submitted', + defaultMessage: + 'When activated, the data will be stored for later use when the form is submitted', + }, + data_wipe: { + id: 'Data wipe', + defaultMessage: 'Data wipe', + }, + data_wipe_description: { + id: 'Number of days after which, the data should be deleted. Enter -1 to store indefinitely.', + defaultMessage: + 'Number of days after which, the data should be deleted. Enter -1 to store indefinitely.', + }, +}); + +const defaultEmptyData = { + fieldsets: [ + { + id: 'default', + title: 'Default', + fields: [], + }, + ], + properties: {}, + required: [], +}; + +export const schemaFormBlockSchema = ({ data, intl }) => { + let conditional_required = []; + if (!data?.store && !data?.send) { + conditional_required.push('store'); + conditional_required.push('send'); + } + + return { + title: intl.formatMessage(messages.form), + fieldsets: [ + { + id: 'default', + title: 'Default', + fields: [ + 'title', + 'description', + 'submit_label', + 'show_cancel', + ...(data?.show_cancel ? ['cancel_label'] : []), + 'captcha', + ], + }, + { + id: 'email', + title: intl.formatMessage(messages.fieldset_email), + fields: [ + 'send', + ...(data?.send + ? [ + 'recipients', + 'bcc', + 'sender', + 'sender_name', + 'subject', + 'mail_header', + 'mail_footer', + ] + : []), + ], + }, + { + id: 'savedata', + title: intl.formatMessage(messages.fieldset_store), + fields: ['store', ...(data?.store ? ['data_wipe'] : [])], + }, + ], + properties: { + schema: { + title: 'Schema', + default: defaultEmptyData, + }, + title: { + title: intl.formatMessage(messages.title), + }, + description: { + title: intl.formatMessage(messages.description), + type: 'textarea', + }, + submit_label: { + title: intl.formatMessage(messages.submit_label), + }, + show_cancel: { + type: 'boolean', + title: intl.formatMessage(messages.show_cancel), + default: false, + }, + cancel_label: { + title: intl.formatMessage(messages.cancel_label), + }, + captcha: { + title: intl.formatMessage(messages.captcha), + type: 'string', + vocabulary: { + '@id': 'collective.volto.formsupport.captcha.providers', + }, + }, + + send: { + type: 'boolean', + title: intl.formatMessage(messages.send), + description: intl.formatMessage(messages.send_description), + }, + recipients: { + title: intl.formatMessage(messages.recipients), + description: intl.formatMessage(messages.recipients_description), + }, + bcc: { + title: intl.formatMessage(messages.bcc), + description: intl.formatMessage(messages.bcc_description), + }, + sender: { + title: intl.formatMessage(messages.sender), + description: intl.formatMessage(messages.sender_description), + default: config.blocks?.blocksConfig?.schemaForm?.defaultSender, + }, + sender_name: { + title: intl.formatMessage(messages.sender_name), + description: intl.formatMessage(messages.sender_name_description), + default: config.blocks?.blocksConfig?.schemaForm?.defaultSenderName, + }, + subject: { + title: intl.formatMessage(messages.subject), + description: intl.formatMessage(messages.subject_description), + }, + mail_header: { + title: intl.formatMessage(messages.mail_header), + widget: 'richtext', + type: 'string', + description: intl.formatMessage(messages.mail_header_description), + }, + mail_footer: { + title: intl.formatMessage(messages.mail_footer), + widget: 'richtext', + type: 'string', + description: intl.formatMessage(messages.mail_footer_description), + }, + + store: { + type: 'boolean', + title: intl.formatMessage(messages.store), + description: intl.formatMessage(messages.store_description), + }, + data_wipe: { + type: 'integer', + title: intl.formatMessage(messages.data_wipe), + description: intl.formatMessage(messages.data_wipe_description), + default: -1, + }, + }, + required: ['default_from', ...conditional_required], + }; +}; diff --git a/frontend/packages/volto-form-block/tsconfig.json b/frontend/packages/volto-form-block/tsconfig.json index ddb0f54..1874161 100644 --- a/frontend/packages/volto-form-block/tsconfig.json +++ b/frontend/packages/volto-form-block/tsconfig.json @@ -16,7 +16,7 @@ "@plone/volto/*": ["../../core/packages/volto/src/*"] } }, - "include": ["**/*.ts", "**/*.tsx"], + "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"], "exclude": [ "node_modules", "build", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index fcaa21f..589ed22 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -882,9 +882,6 @@ importers: react-select-async-paginate: specifier: 0.5.3 version: 0.5.3(react-dom@18.2.0(react@18.2.0))(react-select@4.3.1(@types/react@18.2.27)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0) - react-share: - specifier: 2.3.1 - version: 2.3.1(react@18.2.0) react-side-effect: specifier: 2.1.2 version: 2.1.2(react@18.2.0) @@ -1059,7 +1056,7 @@ importers: version: 8.1.11(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@testing-library/cypress': specifier: 10.0.1 - version: 10.0.1(cypress@13.6.6) + version: 10.0.1(cypress@13.13.2) '@testing-library/jest-dom': specifier: 6.4.2 version: 6.4.2(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@26.6.3)(vitest@1.6.0(jsdom@16.7.0)(less@3.11.1)(sass@1.77.6)) @@ -1136,14 +1133,14 @@ importers: specifier: 5.2.7 version: 5.2.7(webpack@5.90.1) cypress: - specifier: 13.6.6 - version: 13.6.6 + specifier: 13.13.2 + version: 13.13.2 cypress-axe: specifier: 1.5.0 - version: 1.5.0(axe-core@4.4.2)(cypress@13.6.6) + version: 1.5.0(axe-core@4.4.2)(cypress@13.13.2) cypress-file-upload: specifier: 5.0.8 - version: 5.0.8(cypress@13.6.6) + version: 5.0.8(cypress@13.13.2) deep-freeze: specifier: 0.0.1 version: 0.0.1 @@ -1417,7 +1414,7 @@ importers: dependencies: '@testing-library/cypress': specifier: 10.0.1 - version: 10.0.1(cypress@13.6.6) + version: 10.0.1(cypress@13.13.2) '@testing-library/jest-dom': specifier: 6.4.2 version: 6.4.2(@jest/globals@29.7.0)(@types/jest@29.5.12)(vitest@1.6.0) @@ -1428,14 +1425,14 @@ importers: specifier: 4.8.4 version: 4.8.4 cypress: - specifier: 13.6.6 - version: 13.6.6 + specifier: 13.13.2 + version: 13.13.2 cypress-axe: specifier: 1.5.0 - version: 1.5.0(axe-core@4.8.4)(cypress@13.6.6) + version: 1.5.0(axe-core@4.8.4)(cypress@13.13.2) cypress-file-upload: specifier: 5.0.8 - version: 5.0.8(cypress@13.6.6) + version: 5.0.8(cypress@13.13.2) devDependencies: release-it: specifier: ^17.1.1 @@ -1460,7 +1457,7 @@ importers: version: 1.10.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) volto-subblocks: specifier: ^2.1.0 - version: 2.1.0(@plone/volto@18.0.0-alpha.40(@babel/runtime@7.24.7)(@popperjs/core@2.11.8)(@types/react-dom@18.2.12)(@types/react@18.2.27)(react-with-direction@1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(seamless-immutable@7.1.4))(react@18.2.0) + version: 2.1.0(@plone/volto@18.0.0-alpha.42(@babel/runtime@7.24.7)(@popperjs/core@2.11.8)(@types/react-dom@18.2.12)(@types/react@18.2.27)(react-with-direction@1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(seamless-immutable@7.1.4))(react@18.2.0) devDependencies: '@plone/scripts': specifier: ^3.6.1 @@ -3652,8 +3649,8 @@ packages: resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@plone/registry@1.7.0': - resolution: {integrity: sha512-Ar5eXwtGitYbj9yHQTX1bs7FZfdmSW3YSrG6HOP/FNwdM47m9ES4vfkliS2rvuC8TVpWn27U0DK5v7I+mlSZeQ==} + '@plone/registry@1.8.0': + resolution: {integrity: sha512-xQH7vhnzQpxPxZLZtObpN/jv4JOIBhYEKsdWUCYmL1e8wlbxb+Xk2C6qNK9+i+kyjmLdAUzDH0v5Xl9Y7igxKw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -3665,11 +3662,11 @@ packages: resolution: {integrity: sha512-52S2Nb8sUDnHobzoYBstn6UkXxri+UGEh5DWZfy0xJ9h/ia6n9XI+mjtSEG3vKkr+hsbLH9sO8RlX4tpG3Kyhg==} hasBin: true - '@plone/volto-slate@18.0.0-alpha.16': - resolution: {integrity: sha512-c0I2/iZ94Ozoxsc+K2ODPeRKDyg4yUJL0shuCHXro7VIM0Q48j6v8dGoT+p+XKVwGKEDkPEhMNrCciR42jhhWw==} + '@plone/volto-slate@18.0.0-alpha.17': + resolution: {integrity: sha512-ztO3BwN1vZ0q+ZVOPcU7Yz0PVaIpMj4xzsGr1+6/cAdCIxBD9JXaoI7fu3QJQFDHXDKNTg4cLWKQzjiyslczHw==} - '@plone/volto@18.0.0-alpha.40': - resolution: {integrity: sha512-ZthD2KjA8qrdiXFx+RGCsJQyGy5E9Ig0YSAuBzO2cJw1NRNSrirHEaxcB1PlR+OXcwfG6oF3jkfoC7QeazG1Ig==} + '@plone/volto@18.0.0-alpha.42': + resolution: {integrity: sha512-PXuHWSgI7ggGYbpN8NwnqM5mL0QX4SjD/yx+i0U7UWSexfeAs6RgRyZJSDW6A8QkzRN+qvctjaqZv6vimg90vw==} engines: {node: ^16 || ^18 || ^20} '@pmmmwh/react-refresh-webpack-plugin@0.4.3': @@ -7177,8 +7174,8 @@ packages: peerDependencies: cypress: '>3.0.0' - cypress@13.6.6: - resolution: {integrity: sha512-S+2S9S94611hXimH9a3EAYt81QM913ZVA03pUmGDfLTFa5gyp85NJ8dJGSlEAEmyRsYkioS1TtnWtbv/Fzt11A==} + cypress@13.13.2: + resolution: {integrity: sha512-PvJQU33933NvS1StfzEb8/mu2kMy4dABwCF+yd5Bi7Qly1HOVf+Bufrygee/tlmty/6j5lX+KIi8j9Q3JUMbhA==} engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} hasBin: true @@ -10112,9 +10109,6 @@ packages: jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - jsonp@0.2.1: - resolution: {integrity: sha512-pfog5gdDxPdV4eP7Kg87M8/bHgshlZ5pybl+yKxAnCZ5O7lCIn7Ixydj03wOlnDQesky2BPyA91SQ+5Y/mNwzw==} - jsonpack@1.1.5: resolution: {integrity: sha512-d2vwomK605ks7Q+uCpbwGyoIF5j+UZuJjlYcugISBt3CxM+eBo/W6y63yVPIyIvbYON+pvJYsYZjCYbzqJj/xQ==} @@ -12653,12 +12647,6 @@ packages: peerDependencies: react: ^16.0.0 || ^17.0.0 || ^18.0.0 - react-share@2.3.1: - resolution: {integrity: sha512-iLUQwCaeVgOkXJxa+8B0iyIIlUajY6BxO9TRb2j8erM+q1ORe0akQZ1gtjEfWYMG4UnGTMU1fOstqes+X0AStw==} - engines: {node: '>=6.9.0', npm: '>=5.0.0'} - peerDependencies: - react: ^0.13.0 || ^0.14.0 || ^15.0.0 || ^16.0.0-0 - react-side-effect@2.1.2: resolution: {integrity: sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==} peerDependencies: @@ -14127,6 +14115,10 @@ packages: resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} engines: {node: '>=8.17.0'} + tmp@0.2.3: + resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} + engines: {node: '>=14.14'} + tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -18659,7 +18651,7 @@ snapshots: '@pkgr/core@0.1.1': {} - '@plone/registry@1.7.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@plone/registry@1.8.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: crypto-random-string: 3.2.0 debug: 4.3.2 @@ -18713,7 +18705,7 @@ snapshots: - debug - supports-color - '@plone/volto-slate@18.0.0-alpha.16(@types/react-dom@18.2.12)(@types/react@18.2.27)(redux@4.2.1)': + '@plone/volto-slate@18.0.0-alpha.17(@types/react-dom@18.2.12)(@types/react@18.2.27)(redux@4.2.1)': dependencies: classnames: 2.2.6 github-slugger: 1.4.0 @@ -18747,13 +18739,13 @@ snapshots: - supports-color - utf-8-validate - '@plone/volto@18.0.0-alpha.40(@babel/runtime@7.24.7)(@popperjs/core@2.11.8)(@types/react-dom@18.2.12)(@types/react@18.2.27)(react-with-direction@1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(seamless-immutable@7.1.4)': + '@plone/volto@18.0.0-alpha.42(@babel/runtime@7.24.7)(@popperjs/core@2.11.8)(@types/react-dom@18.2.12)(@types/react@18.2.27)(react-with-direction@1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(seamless-immutable@7.1.4)': dependencies: '@loadable/component': 5.14.1(react@18.2.0) '@loadable/server': 5.14.0(@loadable/component@5.14.1(react@18.2.0))(react@18.2.0) - '@plone/registry': 1.7.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@plone/registry': 1.8.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@plone/scripts': 3.6.2(debug@4.3.2) - '@plone/volto-slate': 18.0.0-alpha.16(@types/react-dom@18.2.12)(@types/react@18.2.27)(redux@4.2.1) + '@plone/volto-slate': 18.0.0-alpha.17(@types/react-dom@18.2.12)(@types/react@18.2.27)(redux@4.2.1) '@redux-devtools/extension': 3.3.0(redux@4.2.1) classnames: 2.2.6 connected-react-router: 6.8.0(history@4.10.1)(immutable@3.8.2)(react-redux@8.1.2(@types/react-dom@18.2.12)(@types/react@18.2.27)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(redux@4.2.1))(react-router@5.2.0(react@18.2.0))(react@18.2.0)(redux@4.2.1)(seamless-immutable@7.1.4) @@ -18813,7 +18805,6 @@ snapshots: react-router-hash-link: 2.4.3(react-router-dom@5.2.0(react@18.2.0))(react@18.2.0) react-select: 4.3.1(@types/react@18.2.27)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react-select-async-paginate: 0.5.3(react-dom@18.2.0(react@18.2.0))(react-select@4.3.1(@types/react@18.2.27)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0) - react-share: 2.3.1(react@18.2.0) react-side-effect: 2.1.2(react@18.2.0) react-simple-code-editor: 0.7.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react-sortable-hoc: 2.0.0(prop-types@15.7.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -20958,11 +20949,11 @@ snapshots: '@tanstack/query-core': 5.49.1 react: 18.2.0 - '@testing-library/cypress@10.0.1(cypress@13.6.6)': + '@testing-library/cypress@10.0.1(cypress@13.13.2)': dependencies: '@babel/runtime': 7.20.6 '@testing-library/dom': 9.3.4 - cypress: 13.6.6 + cypress: 13.13.2 '@testing-library/dom@10.1.0': dependencies: @@ -23819,21 +23810,21 @@ snapshots: csstype@3.1.3: {} - cypress-axe@1.5.0(axe-core@4.4.2)(cypress@13.6.6): + cypress-axe@1.5.0(axe-core@4.4.2)(cypress@13.13.2): dependencies: axe-core: 4.4.2 - cypress: 13.6.6 + cypress: 13.13.2 - cypress-axe@1.5.0(axe-core@4.8.4)(cypress@13.6.6): + cypress-axe@1.5.0(axe-core@4.8.4)(cypress@13.13.2): dependencies: axe-core: 4.8.4 - cypress: 13.6.6 + cypress: 13.13.2 - cypress-file-upload@5.0.8(cypress@13.6.6): + cypress-file-upload@5.0.8(cypress@13.13.2): dependencies: - cypress: 13.6.6 + cypress: 13.13.2 - cypress@13.6.6: + cypress@13.13.2: dependencies: '@cypress/request': 3.0.1 '@cypress/xvfb': 1.2.4(supports-color@8.1.1) @@ -23874,7 +23865,7 @@ snapshots: request-progress: 3.0.0 semver: 7.6.2 supports-color: 8.1.1 - tmp: 0.2.1 + tmp: 0.2.3 untildify: 4.0.0 yauzl: 2.10.0 @@ -27859,12 +27850,6 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - jsonp@0.2.1: - dependencies: - debug: 2.6.9 - transitivePeerDependencies: - - supports-color - jsonpack@1.1.5: {} jsonparse@1.3.1: {} @@ -31260,16 +31245,6 @@ snapshots: react: 18.2.0 react-is: 18.3.1 - react-share@2.3.1(react@18.2.0): - dependencies: - babel-runtime: 6.26.0 - classnames: 2.2.6 - jsonp: 0.2.1 - prop-types: 15.7.2 - react: 18.2.0 - transitivePeerDependencies: - - supports-color - react-side-effect@2.1.2(react@18.2.0): dependencies: react: 18.2.0 @@ -33240,6 +33215,8 @@ snapshots: dependencies: rimraf: 3.0.2 + tmp@0.2.3: {} + tmpl@1.0.5: {} to-fast-properties@1.0.3: {} @@ -34082,9 +34059,9 @@ snapshots: - terser optional: true - volto-subblocks@2.1.0(@plone/volto@18.0.0-alpha.40(@babel/runtime@7.24.7)(@popperjs/core@2.11.8)(@types/react-dom@18.2.12)(@types/react@18.2.27)(react-with-direction@1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(seamless-immutable@7.1.4))(react@18.2.0): + volto-subblocks@2.1.0(@plone/volto@18.0.0-alpha.42(@babel/runtime@7.24.7)(@popperjs/core@2.11.8)(@types/react-dom@18.2.12)(@types/react@18.2.27)(react-with-direction@1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(seamless-immutable@7.1.4))(react@18.2.0): dependencies: - '@plone/volto': 18.0.0-alpha.40(@babel/runtime@7.24.7)(@popperjs/core@2.11.8)(@types/react-dom@18.2.12)(@types/react@18.2.27)(react-with-direction@1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(seamless-immutable@7.1.4) + '@plone/volto': 18.0.0-alpha.42(@babel/runtime@7.24.7)(@popperjs/core@2.11.8)(@types/react-dom@18.2.12)(@types/react@18.2.27)(react-with-direction@1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(seamless-immutable@7.1.4) react-dnd: 5.0.0(react@18.2.0) react-dnd-html5-backend: 5.0.1 transitivePeerDependencies: