Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
sneridagh committed Jul 26, 2024
1 parent f8cc6e9 commit 535dc49
Show file tree
Hide file tree
Showing 14 changed files with 568 additions and 45 deletions.
4 changes: 4 additions & 0 deletions backend/src/collective/volto/formsupport/captcha/honeypot.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from zExceptions import BadRequest
from zope.i18n import translate

import json


class HoneypotSupport(CaptchaSupport):
name = _("Honeypot Support")
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,15 @@ def get_form_fields(self):
if id_ != self.block_id:
continue
block_type = block.get("@type", "")
if block_type == "form":
if block_type == "form" or block_type == "schemaForm":
form_block = deepcopy(block)
if not form_block:
return {}

# TODO: get fields for schemaForm block
if block["@type"] == "schemaForm":
return block.get("data", {})

subblocks = form_block.get("subblocks", [])

# Add the 'custom_field_id' field back in as this isn't stored with each
Expand Down
Original file line number Diff line number Diff line change
@@ -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


Expand Down Expand Up @@ -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"],
}
new_schema["fieldsets"][0]["fields"].append("captchaWidget")
value["schema"] = new_schema
print(value["captcha_props"])
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"""
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,13 @@
provides="plone.restapi.interfaces.IBlockFieldSerializationTransformer"
/>

<subscriber
factory=".blocks.SchemaFormBlockSerializerContents"
provides="plone.restapi.interfaces.IBlockFieldSerializationTransformer"
/>
<subscriber
factory=".blocks.SchemaFormBlockSerializerRoot"
provides="plone.restapi.interfaces.IBlockFieldSerializationTransformer"
/>

</configure>
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,23 @@ def cleanup_data(self):
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["@type"] == "form":
# 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):
Expand Down Expand Up @@ -171,16 +177,23 @@ def validate_form(self):

self.validate_attachments()
if self.block.get("captcha", False):
# breakpoint()
getMultiAdapter(
(self.context, self.request),
ICaptchaSupport,
name=self.block["captcha"],
).verify(self.form_data.get("captcha"))
).verify(
self.form_data.get("captcha")
or self.form_data["data"].get("captchaWidget")
)

self.validate_email_fields()
self.validate_bcc()

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", [])
Expand Down Expand Up @@ -234,6 +247,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):
Expand Down Expand Up @@ -262,7 +279,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 {}
Expand Down Expand Up @@ -459,6 +476,10 @@ def filter_parameters(self):
"""
do not send attachments fields.
"""
# TODO: do not send attachments for schemaForm block
if self.block["@type"] == "schemaForm":
return self.form_data.get("data", [])

skip_fields = [
x.get("field_id", "")
for x in self.block.get("subblocks", [])
Expand Down Expand Up @@ -537,6 +558,7 @@ def attach_xml(self, msg):

def store_data(self):
store = getMultiAdapter((self.context, self.request), IFormDataStore)
breakpoint()
res = store.add(data=self.filter_parameters())
if not res:
raise BadRequest("Unable to store data")
2 changes: 1 addition & 1 deletion frontend/mrs.developer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
"package": "@plone/volto",
"url": "[email protected]:plone/volto.git",
"https": "https://github.com/plone/volto.git",
"tag": "18.0.0-alpha.40"
"branch": "main"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ const HoneypotCaptchaWidget = ({
}, []);

const [value, setValue] = useState();
console.log('oldWidget', value);

Check warning on line 43 in frontend/packages/volto-form-block/src/components/Widget/HoneypotCaptchaWidget.jsx

View workflow job for this annotation

GitHub Actions / frontend / code-analysis

Unexpected console statement

return (
<div className="honey-wrapper" key={'honeypot-captcha'}>
<TextWidget
Expand Down
43 changes: 43 additions & 0 deletions frontend/packages/volto-form-block/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ import {
validateDefaultTo,
} from 'volto-form-block/helpers/validators';

import { schemaFormBlockSchema } from 'volto-form-block/schemaFormBlock/schema';
import schemaFormBlockEdit from 'volto-form-block/schemaFormBlock/EditSchemaForm';
import schemaFormBlockView from 'volto-form-block/schemaFormBlock/ViewSchemaForm';
import HoneypotCaptchaWidget from 'volto-form-block/schemaFormBlock/HoneypotCaptchaWidget';

export {
submitForm,
getFormData,
Expand All @@ -40,8 +45,46 @@ export {
export { isValidEmail };

const applyConfig = (config) => {
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,
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',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, { Component } from 'react';
import { isEmpty } from 'lodash';
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';

class Edit extends Component {
render() {
const FormSchema = config.blocks.blocksConfig.schemaForm.blockSchema;
const schema = FormSchema(this.props);
const { data } = this.props;

const defaultEmptyData = {
fieldsets: [
{
id: 'default',
title: 'Default',
fields: [],
},
],
properties: {},
required: [],
};

return (
<>
<Form
schema={{
fieldsets: [
{
behavior: 'plone',
fields: ['schema'],
id: 'default',
title: 'Default',
},
],
properties: {
schema: {
description: '',
factory: 'Text',
title: 'Schema',
type: 'string',
widget: 'schema',
default: defaultEmptyData,
},
},
required: [],
title: 'Form',
type: 'object',
}}
formData={
isEmpty(data.schema)
? { schema: defaultEmptyData }
: { schema: data.schema }
}
onChangeFormData={(formData) => {
this.props.onChangeBlock(this.props.block, {
...data,
schema: formData.schema,
});
}}
hideActions
/>

<SidebarPortal selected={this.props.selected}>
<BlockDataForm
schema={schema}
title={schema.title}
onChangeField={(id, value) => {
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}
/>
</SidebarPortal>
</>
);
}
}

export default withBlockExtensions(Edit);
Loading

0 comments on commit 535dc49

Please sign in to comment.