diff --git a/.gitignore b/.gitignore index 9f97022..8c1be6f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,11 @@ -target/ \ No newline at end of file +target/ +demo/lib +demo/logs +demo/modules +demo/hosts/demo/modules_data +demo/cms.pid +demo/*.jar +demo/LICENSE +demo/log4j2.xml +demo/README.md +demo/server.yaml \ No newline at end of file diff --git a/demo/hosts/demo/assets/favicon.ico b/demo/hosts/demo/assets/favicon.ico new file mode 100644 index 0000000..6848441 Binary files /dev/null and b/demo/hosts/demo/assets/favicon.ico differ diff --git a/demo/hosts/demo/assets/form-1.css b/demo/hosts/demo/assets/form-1.css new file mode 100644 index 0000000..df4d6dc --- /dev/null +++ b/demo/hosts/demo/assets/form-1.css @@ -0,0 +1,3 @@ +span#reloadCaptcha:hover { + cursor: pointer; +} \ No newline at end of file diff --git a/demo/hosts/demo/assets/form-1.js b/demo/hosts/demo/assets/form-1.js new file mode 100644 index 0000000..618a3db --- /dev/null +++ b/demo/hosts/demo/assets/form-1.js @@ -0,0 +1,105 @@ +// form.js +const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; +const generateString = (length) => { + let result = '' + const charactersLength = characters.length + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)) + } + + return result; +} + +const validateCaptcha = async (event) => { + event.preventDefault(); + let request = { + code: document.getElementById("inputCaptcha").value, + key: document.getElementById("captchaKey").value + } + + const response = await fetch('/module/forms-module/captcha/validate', { + method: 'POST', + body: JSON.stringify(request) + }) + + const validationResponse = await response.json() + + if (!validationResponse.valid) { + alert("captcha code is not valid") + event.preventDefault() + return false + } else { + console.log(event.target) + event.target.submit() + return true + } +} + +const ajaxValidateCaptcha = async () => { + let request = { + code: document.getElementById("inputCaptcha").value, + key: document.getElementById("captchaKey").value + } + + const response = await fetch('/module/forms-module/captcha/validate', { + method: 'POST', + body: JSON.stringify(request) + }) + + const validationResponse = await response.json() + + if (!validationResponse.valid) { + return false + } else { + return true + } +} + +document.addEventListener("DOMContentLoaded", () => { + if (document.getElementById("reloadCaptcha")) { + document.getElementById("reloadCaptcha").addEventListener("click", () => { + let href = new URL(document.getElementById("captchaImg").src) + let key = generateString(8) + href.searchParams.set('key', key) + + document.getElementById("captchaKey").value = key + document.getElementById("captchaImg").src = href.toString() + }) + } + + if (document.getElementById("ajaxForm")) { + document.getElementById("ajaxForm").addEventListener("submit", (event) => { + event.preventDefault() + console.log("send form via ajax") + if (!ajaxValidateCaptcha()) { + alert("invalid captcha provided"); + return false + } + var form = event.target; + var formData = new FormData(form); + fetch(form.action, { + method: "post", + body: formData + }).then(res => res.json()).then(console.log); + + return false + }) + document.getElementById("submit-btn-test").addEventListener("click", (event) => { + event.preventDefault() + + if (ajaxValidateCaptcha()) { + alert("invalid captcha provided"); + return false + } + + var form = document.getElementById("ajaxForm") + var formData = new FormData(form); + fetch(form.action, { + method: "post", + body: formData + }).then(res => res.json()).then(console.log); + return false; + }) + } + +}) diff --git a/demo/hosts/demo/assets/images/test.jpg b/demo/hosts/demo/assets/images/test.jpg new file mode 100644 index 0000000..e473243 Binary files /dev/null and b/demo/hosts/demo/assets/images/test.jpg differ diff --git a/demo/hosts/demo/config/forms.yaml b/demo/hosts/demo/config/forms.yaml new file mode 100644 index 0000000..e73dc41 --- /dev/null +++ b/demo/hosts/demo/config/forms.yaml @@ -0,0 +1,19 @@ +mail: + smtp: + hostname: 127.0.0.1 + port: 3025 + username: test@example.test + password: password +forms: + - name: contact + to: contact@example.com + subject: Ich suche Kontakt! + fields: [message] + redirects: + success: /forms/contact/success + - name: test-form + fields: [message] + redirects: + success: /forms/contact/success +redirects: + error: /forms/error \ No newline at end of file diff --git a/demo/hosts/demo/content/.technical/404.md b/demo/hosts/demo/content/.technical/404.md new file mode 100644 index 0000000..776ccf0 --- /dev/null +++ b/demo/hosts/demo/content/.technical/404.md @@ -0,0 +1,5 @@ +--- +title: Leider nichts gefunden +template: error.html +--- +Da haben wir leider nichts gefunden! diff --git a/demo/hosts/demo/content/ajax.md b/demo/hosts/demo/content/ajax.md new file mode 100644 index 0000000..39c4454 --- /dev/null +++ b/demo/hosts/demo/content/ajax.md @@ -0,0 +1,8 @@ +--- +title: Startseite +template: ajax.html +search: + index: false +--- + +# Demo Project diff --git a/demo/hosts/demo/content/contact.md b/demo/hosts/demo/content/contact.md new file mode 100644 index 0000000..5762410 --- /dev/null +++ b/demo/hosts/demo/content/contact.md @@ -0,0 +1,8 @@ +--- +title: Startseite +template: contact.html +search: + index: false +--- + +# Demo Project diff --git a/demo/hosts/demo/content/forms/contact/success.md b/demo/hosts/demo/content/forms/contact/success.md new file mode 100644 index 0000000..f39443c --- /dev/null +++ b/demo/hosts/demo/content/forms/contact/success.md @@ -0,0 +1,8 @@ +--- +title: Form submitted +template: form-submitted.html +menu: + visible: false +--- + +## Your request was successfully submitted diff --git a/demo/hosts/demo/content/forms/error.md b/demo/hosts/demo/content/forms/error.md new file mode 100644 index 0000000..64cfe1f --- /dev/null +++ b/demo/hosts/demo/content/forms/error.md @@ -0,0 +1,8 @@ +--- +title: Error sending form +template: form-submitted.html +menu: + visible: false +--- + +## Error submitting your request! \ No newline at end of file diff --git a/demo/hosts/demo/content/index.md b/demo/hosts/demo/content/index.md new file mode 100644 index 0000000..66c4067 --- /dev/null +++ b/demo/hosts/demo/content/index.md @@ -0,0 +1,8 @@ +--- +title: Startseite +template: page.html +search: + index: false +--- + +# Demo Project diff --git a/demo/hosts/demo/content/robots.txt b/demo/hosts/demo/content/robots.txt new file mode 100644 index 0000000..aadfde9 --- /dev/null +++ b/demo/hosts/demo/content/robots.txt @@ -0,0 +1,3 @@ +User-agent: * +Disallow: /about/impressum +Allow: / \ No newline at end of file diff --git a/demo/hosts/demo/extensions/test.extension.js b/demo/hosts/demo/extensions/test.extension.js new file mode 100644 index 0000000..bb628f6 --- /dev/null +++ b/demo/hosts/demo/extensions/test.extension.js @@ -0,0 +1,6 @@ +import { $hooks } from 'system/hooks.mjs'; + +$hooks.registerAction("forms/test-form/submit", (context) => { + console.log("test-form submitted", context); + return null; +}) \ No newline at end of file diff --git a/demo/hosts/demo/site.yaml b/demo/hosts/demo/site.yaml new file mode 100644 index 0000000..3325c8e --- /dev/null +++ b/demo/hosts/demo/site.yaml @@ -0,0 +1,28 @@ +id: demo-site +hostname: + - localhost +baseurl: "http://localhost:1010" +language: en +template: + engine: thymeleaf +modules: + active: + - thymeleaf-module + - forms-module +media: + formats: + - name: small + width: 256 + height: 256 + format: webp + compression: true + - name: big + width: 512 + height: 512 + format: webp + compression: true + - name: test2 + width: 72 + height: 72 + format: webp + compression: true \ No newline at end of file diff --git a/demo/hosts/demo/templates/ajax.html b/demo/hosts/demo/templates/ajax.html new file mode 100644 index 0000000..2760a19 --- /dev/null +++ b/demo/hosts/demo/templates/ajax.html @@ -0,0 +1,52 @@ + + + + + + + + + + + +
+
+
+ +
+

Test Formular

+
+ +
+ + +
+
+ + +
+
+ + + reload +
+
+ + +
+
+ +
+
+
+ + + + + + \ No newline at end of file diff --git a/demo/hosts/demo/templates/contact.html b/demo/hosts/demo/templates/contact.html new file mode 100644 index 0000000..2a07bfc --- /dev/null +++ b/demo/hosts/demo/templates/contact.html @@ -0,0 +1,50 @@ + + + + + + + + + + + +
+
+
+ +
+

Contact form

+
+ +
+ + +
+
+ + +
+
+ + + reload +
+
+ + +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/demo/hosts/demo/templates/form-submitted.html b/demo/hosts/demo/templates/form-submitted.html new file mode 100644 index 0000000..4936a77 --- /dev/null +++ b/demo/hosts/demo/templates/form-submitted.html @@ -0,0 +1,19 @@ + + + + + + + + + + + +
+
+
+ + + + + \ No newline at end of file diff --git a/demo/hosts/demo/templates/libs/fragments.html b/demo/hosts/demo/templates/libs/fragments.html new file mode 100644 index 0000000..ed7ee6f --- /dev/null +++ b/demo/hosts/demo/templates/libs/fragments.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demo/hosts/demo/templates/page.html b/demo/hosts/demo/templates/page.html new file mode 100644 index 0000000..4c89e20 --- /dev/null +++ b/demo/hosts/demo/templates/page.html @@ -0,0 +1,51 @@ + + + + + + + + + + + +
+
+
+ +
+

Test Formular

+
+ +
+ + +
+
+ + +
+
+ + + reload +
+
+ + +
+
+ +
+
+
+ + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index c93cd47..c949d56 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.condation.cms.modules forms-module - 2.0.0 + 2.1.0 jar UTF-8 @@ -14,7 +14,7 @@ forms module NORMAL - 6.0.0-SNAPSHOT + 6.1.0 21 21 diff --git a/src/main/java/com/condation/cms/modules/forms/FormsConfig.java b/src/main/java/com/condation/cms/modules/forms/FormsConfig.java index fc9e943..d89f450 100644 --- a/src/main/java/com/condation/cms/modules/forms/FormsConfig.java +++ b/src/main/java/com/condation/cms/modules/forms/FormsConfig.java @@ -24,6 +24,7 @@ import java.util.List; +import java.util.Map; import java.util.Optional; import lombok.Data; @@ -47,10 +48,11 @@ public Optional
findForm (final String name) { @Data public static class Form { private String name; - private String to; - private String subject; private Redirects redirects; private List fields; + private String to; + private String subject; + private Map data; } @Data diff --git a/src/main/java/com/condation/cms/modules/forms/FormsHttpHandlerExtension.java b/src/main/java/com/condation/cms/modules/forms/FormsHttpHandlerExtension.java index 406585c..b4d8c33 100644 --- a/src/main/java/com/condation/cms/modules/forms/FormsHttpHandlerExtension.java +++ b/src/main/java/com/condation/cms/modules/forms/FormsHttpHandlerExtension.java @@ -25,9 +25,10 @@ import com.condation.cms.api.extensions.HttpHandlerExtensionPoint; import com.condation.cms.api.extensions.Mapping; +import com.condation.cms.api.feature.features.HookSystemFeature; import com.condation.cms.modules.forms.handler.AjaxCaptchaValidationHandler; -import com.condation.cms.modules.forms.handler.GenerateCaptchaHandler; import com.condation.cms.modules.forms.handler.AjaxSubmitFormHandler; +import com.condation.cms.modules.forms.handler.GenerateCaptchaHandler; import com.condation.cms.modules.forms.handler.SubmitFormHandler; import com.condation.modules.api.annotation.Extension; import org.eclipse.jetty.http.pathmap.PathSpec; @@ -45,8 +46,12 @@ public Mapping getMapping() { mapping.add(PathSpec.from("/captcha/validate"), new AjaxCaptchaValidationHandler()); mapping.add(PathSpec.from("/captcha/generate"), new GenerateCaptchaHandler()); - mapping.add(PathSpec.from("/form/submit/ajax"), new AjaxSubmitFormHandler()); - mapping.add(PathSpec.from("/form/submit"), new SubmitFormHandler()); + mapping.add(PathSpec.from("/form/submit/ajax"), + new AjaxSubmitFormHandler(requestContext.get(HookSystemFeature.class).hookSystem()) + ); + mapping.add(PathSpec.from("/form/submit"), + new SubmitFormHandler(requestContext.get(HookSystemFeature.class).hookSystem()) + ); return mapping; } diff --git a/src/main/java/com/condation/cms/modules/forms/FormsLifecycleExtension.java b/src/main/java/com/condation/cms/modules/forms/FormsLifecycleExtension.java index 510cb46..0e15403 100644 --- a/src/main/java/com/condation/cms/modules/forms/FormsLifecycleExtension.java +++ b/src/main/java/com/condation/cms/modules/forms/FormsLifecycleExtension.java @@ -23,13 +23,13 @@ */ -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; import com.condation.cms.api.feature.features.DBFeature; import com.condation.cms.api.module.CMSModuleContext; import com.condation.cms.api.module.CMSRequestContext; import com.condation.modules.api.ModuleLifeCycleExtension; import com.condation.modules.api.annotation.Extension; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; diff --git a/src/main/java/com/condation/cms/modules/forms/FormsTemplateModelExtensionPoint.java b/src/main/java/com/condation/cms/modules/forms/FormsTemplateModelExtensionPoint.java index f58dc46..6da254f 100644 --- a/src/main/java/com/condation/cms/modules/forms/FormsTemplateModelExtensionPoint.java +++ b/src/main/java/com/condation/cms/modules/forms/FormsTemplateModelExtensionPoint.java @@ -25,10 +25,8 @@ import com.condation.cms.api.extensions.TemplateModelExtendingExtentionPoint; import com.condation.cms.api.template.TemplateEngine; -import com.condation.cms.api.extensions.TemplateModelExtendingExtentionPoint; import com.condation.cms.modules.forms.template.FormsTemplateModel; import com.condation.modules.api.annotation.Extension; -import com.condation.modules.api.annotation.Extension; /** * diff --git a/src/main/java/com/condation/cms/modules/forms/handler/AjaxCaptchaValidationHandler.java b/src/main/java/com/condation/cms/modules/forms/handler/AjaxCaptchaValidationHandler.java index dab482f..6759eb3 100644 --- a/src/main/java/com/condation/cms/modules/forms/handler/AjaxCaptchaValidationHandler.java +++ b/src/main/java/com/condation/cms/modules/forms/handler/AjaxCaptchaValidationHandler.java @@ -40,7 +40,7 @@ @Slf4j public class AjaxCaptchaValidationHandler implements HttpHandler { - private static Gson GSON = new Gson(); + private static final Gson GSON = new Gson(); @Override public boolean handle(Request request, Response response, Callback callback) throws Exception { diff --git a/src/main/java/com/condation/cms/modules/forms/handler/AjaxSubmitFormHandler.java b/src/main/java/com/condation/cms/modules/forms/handler/AjaxSubmitFormHandler.java index 25b86e5..70b99bd 100644 --- a/src/main/java/com/condation/cms/modules/forms/handler/AjaxSubmitFormHandler.java +++ b/src/main/java/com/condation/cms/modules/forms/handler/AjaxSubmitFormHandler.java @@ -24,65 +24,114 @@ import com.condation.cms.api.extensions.HttpHandler; +import com.condation.cms.api.hooks.HookSystem; import com.condation.cms.modules.forms.FormsLifecycleExtension; import com.google.gson.Gson; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.http.MultiPart; +import org.eclipse.jetty.http.MultiPartFormData; import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.server.FormFields; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.Callback; -import org.simplejavamail.email.EmailBuilder; +import org.eclipse.jetty.util.Fields; /** * * @author t.marx */ @Slf4j +@RequiredArgsConstructor public class AjaxSubmitFormHandler implements HttpHandler { - private static Gson GSON = new Gson(); + private final static Gson GSON = new Gson(); + + private final HookSystem hookSystem; @Override public boolean handle(Request request, Response response, Callback callback) throws Exception { - if (!"POST".equalsIgnoreCase(request.getMethod())) { - response.setStatus(405); - callback.succeeded(); - return true; - } - - String body = readBody(request); - var formData = GSON.fromJson(body, FormsData.class); + String contentType = request.getHeaders().get(HttpHeader.CONTENT_TYPE); + + FormsHandling formHandling = new FormsHandling(hookSystem); - String content = "false"; - String captchaCode = FormsLifecycleExtension.CAPTCHAS.getIfPresent(formData.key()); - if (captchaCode != null && captchaCode.equals(formData.code())) { - content = "true"; - var form = FormsLifecycleExtension.FORMSCONFIG.findForm(formData.form()).get(); - FormsLifecycleExtension.MAILER.sendMail(EmailBuilder.startingBlank() - .to(form.getTo()) - .from(formData.from()) - .appendText(formData.body()) - .withSubject(form.getSubject()) - .buildEmail()); - } + final AtomicReference formResponse = new AtomicReference<>(); + try { + if (MimeTypes.Type.FORM_ENCODED.is(contentType)) { + CompletableFuture completableFields = FormFields.from(request, StandardCharsets.UTF_8); + completableFields.whenComplete((fields, failure) -> { + try { + if (failure == null) { + final String formName = fields.get("form").getValue(); + var form = FormsLifecycleExtension.FORMSCONFIG.findForm(formName).get(); + formHandling.handleForm(form, (field) -> { + if (fields.get(field) != null) { + return fields.get(field).getValue(); + } + return field; + }); + formResponse.set(new FormResponse(false)); + callback.succeeded(); + } else { + formResponse.set(new FormResponse(true)); + } + } catch (FormHandlingException fhe) { + log.error(null, fhe); + formResponse.set(new FormResponse(true)); + } + }); + } else if (contentType.startsWith(MimeTypes.Type.MULTIPART_FORM_DATA.asString())) { + String boundary = MultiPart.extractBoundary(contentType); + MultiPartFormData.Parser parser = new MultiPartFormData.Parser(boundary); + parser.setFilesDirectory(Files.createTempDirectory("cms-upload")); + CompletableFuture completableParts = parser.parse(request); - Content.Sink.write(response, true, content, callback); + completableParts.whenComplete((parts, failure) + -> { + try { + if (failure == null) { - return true; - } + String formName = parts.getFirst("form").getContentAsString(StandardCharsets.UTF_8); + var form = FormsLifecycleExtension.FORMSCONFIG.findForm(formName).get(); + formHandling.handleForm(form, (field) -> { + if (parts.getAll(field) != null && !parts.getAll(field).isEmpty()) { + return parts.getAll(field).getFirst().getContentAsString(StandardCharsets.UTF_8); + } + return field; + }); - private String readBody(final Request request) { - try (var inputStream = Request.asInputStream(request)) { - return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - } catch (Exception ex) { - log.error("", ex); + formResponse.set(new FormResponse(false)); + } else { + formResponse.set(new FormResponse(true)); + } + } catch (FormHandlingException fhe) { + log.error(null, fhe); + formResponse.set(new FormResponse(true)); + } + }); + } + } catch (Exception e) { + log.error("error processing form", e); } - return ""; - } - - public record FormsData(String form, String body, String from, String code, String key) { + if (formResponse.get().error()) { + response.setStatus(HttpStatus.BAD_REQUEST_400); + } else { + response.setStatus(HttpStatus.OK_200); + } + Content.Sink.write(response, true, GSON.toJson(formResponse.get()), callback); + + return true; } + + private static record FormResponse (boolean error){}; } diff --git a/src/main/java/com/condation/cms/modules/forms/FormHandlingException.java b/src/main/java/com/condation/cms/modules/forms/handler/FormHandlingException.java similarity index 93% rename from src/main/java/com/condation/cms/modules/forms/FormHandlingException.java rename to src/main/java/com/condation/cms/modules/forms/handler/FormHandlingException.java index 9244ea5..dbcd7b1 100644 --- a/src/main/java/com/condation/cms/modules/forms/FormHandlingException.java +++ b/src/main/java/com/condation/cms/modules/forms/handler/FormHandlingException.java @@ -1,4 +1,4 @@ -package com.condation.cms.modules.forms; +package com.condation.cms.modules.forms.handler; /*- * #%L @@ -23,6 +23,7 @@ */ +import com.condation.cms.modules.forms.FormsConfig; import java.util.Optional; /** diff --git a/src/main/java/com/condation/cms/modules/forms/FormsHandling.java b/src/main/java/com/condation/cms/modules/forms/handler/FormsHandling.java similarity index 68% rename from src/main/java/com/condation/cms/modules/forms/FormsHandling.java rename to src/main/java/com/condation/cms/modules/forms/handler/FormsHandling.java index 7b526e6..a79c8b0 100644 --- a/src/main/java/com/condation/cms/modules/forms/FormsHandling.java +++ b/src/main/java/com/condation/cms/modules/forms/handler/FormsHandling.java @@ -1,4 +1,4 @@ -package com.condation.cms.modules.forms; +package com.condation.cms.modules.forms.handler; /*- * #%L @@ -23,7 +23,14 @@ */ +import com.condation.cms.api.hooks.HookSystem; +import com.condation.cms.modules.forms.FormsConfig; +import com.condation.cms.modules.forms.FormsLifecycleExtension; +import com.condation.cms.modules.forms.utils.StringUtil; +import java.util.HashMap; +import java.util.Map; import java.util.function.Function; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.simplejavamail.email.EmailBuilder; @@ -31,9 +38,12 @@ * * @author t.marx */ +@RequiredArgsConstructor @Slf4j public class FormsHandling { + private final HookSystem hookSystem; + private void validateCaptcha(final FormsConfig.Form form, final String key, final String code) throws FormHandlingException { String captchaCode = FormsLifecycleExtension.CAPTCHAS.getIfPresent(key); if (captchaCode == null || !captchaCode.equals(code)) { @@ -55,6 +65,23 @@ private String buildMessage(final FormsConfig.Form form, final Function hookData (final FormsConfig.Form form, final Function parameters) { + Map data = new HashMap<>(); + + if (form.getFields() != null) { + form.getFields().forEach(field -> { + var value = parameters.apply(field); + data.put(field, value); + }); + } + + if (form.getData() != null) { + data.putAll(form.getData()); + } + + return data; + } + public void handleForm(final FormsConfig.Form form, final Function parameters) throws FormHandlingException { try { final String key = parameters.apply("key"); @@ -63,6 +90,15 @@ public void handleForm(final FormsConfig.Form form, final Function. * #L% */ - import com.condation.cms.api.extensions.HttpHandler; -import com.condation.cms.modules.forms.FormHandlingException; -import com.condation.cms.modules.forms.FormsHandling; +import com.condation.cms.api.hooks.HookSystem; import com.condation.cms.modules.forms.FormsLifecycleExtension; import com.google.common.base.Strings; import com.google.gson.Gson; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.concurrent.CompletableFuture; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; @@ -48,10 +47,13 @@ * @author t.marx */ @Slf4j +@RequiredArgsConstructor public class SubmitFormHandler implements HttpHandler { private static Gson GSON = new Gson(); + private final HookSystem hookSystem; + @Override public boolean handle(Request request, Response response, Callback callback) throws Exception { @@ -62,82 +64,86 @@ public boolean handle(Request request, Response response, Callback callback) thr String contentType = request.getHeaders().get(HttpHeader.CONTENT_TYPE); - FormsHandling formHandling = new FormsHandling(); + FormsHandling formHandling = new FormsHandling(hookSystem); - if (MimeTypes.Type.FORM_ENCODED.is(contentType)) { - CompletableFuture completableFields = FormFields.from(request, StandardCharsets.UTF_8); - completableFields.whenComplete((fields, failure) -> { - try { - if (failure == null) { - final String formName = fields.get("form").getValue(); - var form = FormsLifecycleExtension.FORMSCONFIG.findForm(formName).get(); - formHandling.handleForm(form, (field) -> { - if (fields.get(field) != null) { - return fields.get(field).getValue(); - } - return field; - }); - response.getHeaders().add("Location", form.getRedirects().getSuccess()); - response.setStatus(HttpStatus.MOVED_TEMPORARILY_302); - callback.succeeded(); - } else { - response.getHeaders().add("Location", FormsLifecycleExtension.FORMSCONFIG.getRedirects().getError()); - response.setStatus(HttpStatus.MOVED_TEMPORARILY_302); - } - } catch (FormHandlingException fhe) { - log.error(null, fhe); - var formOpt = fhe.getForm(); - if (formOpt.isPresent() && !Strings.isNullOrEmpty(formOpt.get().getRedirects().getError())) { - response.getHeaders().add("Location", formOpt.get().getRedirects().getError()); - response.setStatus(HttpStatus.MOVED_TEMPORARILY_302); - } else { - response.getHeaders().add("Location", FormsLifecycleExtension.FORMSCONFIG.getRedirects().getError()); - response.setStatus(HttpStatus.MOVED_TEMPORARILY_302); + try { + if (MimeTypes.Type.FORM_ENCODED.is(contentType)) { + CompletableFuture completableFields = FormFields.from(request, StandardCharsets.UTF_8); + completableFields.whenComplete((fields, failure) -> { + try { + if (failure == null) { + final String formName = fields.get("form").getValue(); + var form = FormsLifecycleExtension.FORMSCONFIG.findForm(formName).get(); + formHandling.handleForm(form, (field) -> { + if (fields.get(field) != null) { + return fields.get(field).getValue(); + } + return field; + }); + response.getHeaders().add("Location", form.getRedirects().getSuccess()); + response.setStatus(HttpStatus.MOVED_TEMPORARILY_302); + callback.succeeded(); + } else { + response.getHeaders().add("Location", FormsLifecycleExtension.FORMSCONFIG.getRedirects().getError()); + response.setStatus(HttpStatus.MOVED_TEMPORARILY_302); + } + } catch (FormHandlingException fhe) { + log.error(null, fhe); + var formOpt = fhe.getForm(); + if (formOpt.isPresent() && !Strings.isNullOrEmpty(formOpt.get().getRedirects().getError())) { + response.getHeaders().add("Location", formOpt.get().getRedirects().getError()); + response.setStatus(HttpStatus.MOVED_TEMPORARILY_302); + } else { + response.getHeaders().add("Location", FormsLifecycleExtension.FORMSCONFIG.getRedirects().getError()); + response.setStatus(HttpStatus.MOVED_TEMPORARILY_302); + } } - } - callback.succeeded(); - }); - return true; - } else if (contentType.startsWith(MimeTypes.Type.MULTIPART_FORM_DATA.asString())) { - String boundary = MultiPart.extractBoundary(contentType); - MultiPartFormData.Parser parser = new MultiPartFormData.Parser(boundary); - parser.setFilesDirectory(Files.createTempDirectory("cms-upload")); - CompletableFuture completableParts = parser.parse(request); + callback.succeeded(); + }); + return true; + } else if (contentType.startsWith(MimeTypes.Type.MULTIPART_FORM_DATA.asString())) { + String boundary = MultiPart.extractBoundary(contentType); + MultiPartFormData.Parser parser = new MultiPartFormData.Parser(boundary); + parser.setFilesDirectory(Files.createTempDirectory("cms-upload")); + CompletableFuture completableParts = parser.parse(request); - completableParts.whenComplete((parts, failure) - -> { - try { - if (failure == null) { + completableParts.whenComplete((parts, failure) + -> { + try { + if (failure == null) { - String formName = parts.getFirst("form").getContentAsString(StandardCharsets.UTF_8); - var form = FormsLifecycleExtension.FORMSCONFIG.findForm(formName).get(); - formHandling.handleForm(form, (field) -> { - if (parts.getAll(field) != null && !parts.getAll(field).isEmpty()) { - return parts.getAll(field).getFirst().getContentAsString(StandardCharsets.UTF_8); - } - return field; - }); + String formName = parts.getFirst("form").getContentAsString(StandardCharsets.UTF_8); + var form = FormsLifecycleExtension.FORMSCONFIG.findForm(formName).get(); + formHandling.handleForm(form, (field) -> { + if (parts.getAll(field) != null && !parts.getAll(field).isEmpty()) { + return parts.getAll(field).getFirst().getContentAsString(StandardCharsets.UTF_8); + } + return field; + }); - response.getHeaders().add("Location", form.getRedirects().getSuccess()); - response.setStatus(HttpStatus.MOVED_TEMPORARILY_302); - } else { - response.getHeaders().add("Location", FormsLifecycleExtension.FORMSCONFIG.getRedirects().getError()); - response.setStatus(HttpStatus.MOVED_TEMPORARILY_302); - } - } catch (FormHandlingException fhe) { - log.error(null, fhe); - var formOpt = fhe.getForm(); - if (formOpt.isPresent() && !Strings.isNullOrEmpty(formOpt.get().getRedirects().getError())) { - response.getHeaders().add("Location", formOpt.get().getRedirects().getError()); - response.setStatus(HttpStatus.MOVED_TEMPORARILY_302); - } else { - response.getHeaders().add("Location", FormsLifecycleExtension.FORMSCONFIG.getRedirects().getError()); - response.setStatus(HttpStatus.MOVED_TEMPORARILY_302); + response.getHeaders().add("Location", form.getRedirects().getSuccess()); + response.setStatus(HttpStatus.MOVED_TEMPORARILY_302); + } else { + response.getHeaders().add("Location", FormsLifecycleExtension.FORMSCONFIG.getRedirects().getError()); + response.setStatus(HttpStatus.MOVED_TEMPORARILY_302); + } + } catch (FormHandlingException fhe) { + log.error(null, fhe); + var formOpt = fhe.getForm(); + if (formOpt.isPresent() && !Strings.isNullOrEmpty(formOpt.get().getRedirects().getError())) { + response.getHeaders().add("Location", formOpt.get().getRedirects().getError()); + response.setStatus(HttpStatus.MOVED_TEMPORARILY_302); + } else { + response.getHeaders().add("Location", FormsLifecycleExtension.FORMSCONFIG.getRedirects().getError()); + response.setStatus(HttpStatus.MOVED_TEMPORARILY_302); + } } - } - callback.succeeded(); - }); - return true; + callback.succeeded(); + }); + return true; + } + } catch (Exception e) { + log.error("error processing form", e); } response.getHeaders().add("Location", FormsLifecycleExtension.FORMSCONFIG.getRedirects().getError()); diff --git a/src/main/java/com/condation/cms/modules/forms/template/FormsTemplateModel.java b/src/main/java/com/condation/cms/modules/forms/template/FormsTemplateModel.java index 1c1addd..e585b99 100644 --- a/src/main/java/com/condation/cms/modules/forms/template/FormsTemplateModel.java +++ b/src/main/java/com/condation/cms/modules/forms/template/FormsTemplateModel.java @@ -31,7 +31,7 @@ */ public class FormsTemplateModel { - private Captcha captcha = new Captcha(); + private final Captcha captcha = new Captcha(); public Captcha getCaptcha () { return captcha; diff --git a/src/main/java/com/condation/cms/modules/forms/utils/StringUtil.java b/src/main/java/com/condation/cms/modules/forms/utils/StringUtil.java index 7fcd8e4..3f84ce2 100644 --- a/src/main/java/com/condation/cms/modules/forms/utils/StringUtil.java +++ b/src/main/java/com/condation/cms/modules/forms/utils/StringUtil.java @@ -44,4 +44,8 @@ public static String random_string() { .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) .toString(); } + + public static boolean isNullOrEmpty (String value) { + return value == null || value.isBlank(); + } } diff --git a/src/test/java/com/condation/cms/modules/forms/FormConfigTest.java b/src/test/java/com/condation/cms/modules/forms/FormConfigTest.java new file mode 100644 index 0000000..88f345e --- /dev/null +++ b/src/test/java/com/condation/cms/modules/forms/FormConfigTest.java @@ -0,0 +1,46 @@ +package com.condation.cms.modules.forms; + +/*- + * #%L + * forms-module + * %% + * Copyright (C) 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.yaml.snakeyaml.Yaml; + +/** + * + * @author t.marx + */ +public class FormConfigTest { + + @Test + void test_config () throws Exception { + var FORMSCONFIG = new Yaml().loadAs( + Files.readString(Path.of("src/test/resources/config/forms.yaml"), StandardCharsets.UTF_8), FormsConfig.class); + + Assertions.assertThat(FORMSCONFIG.findForm("contact")).isPresent(); + Assertions.assertThat(FORMSCONFIG.findForm("test-form")).isPresent(); + } +} diff --git a/src/test/resources/config/forms.yaml b/src/test/resources/config/forms.yaml new file mode 100644 index 0000000..637bf5b --- /dev/null +++ b/src/test/resources/config/forms.yaml @@ -0,0 +1,18 @@ +mail: + smtp: + hostname: 127.0.0.1 + port: 3025 + username: test@example.test + password: password +forms: + - name: contact + to: contact@example.com + subject: Ich suche Kontakt! + fields: [message] + redirects: + success: /forms/contact/success + - name: test-form + redirects: + success: /forms/contact/success +redirects: + error: /forms/error \ No newline at end of file