diff --git a/.doit.db.db b/.doit.db.db index 9523f64..ff040e2 100644 Binary files a/.doit.db.db and b/.doit.db.db differ diff --git a/pages/inscription.md b/pages/inscription.md new file mode 100644 index 0000000..cb05590 --- /dev/null +++ b/pages/inscription.md @@ -0,0 +1,14 @@ + + +{{% nextcloud_forms link="https://nube.txs.es/apps/forms/s/BPYqzqF44bFCXb3H7eFe8888" %}} +¡Muchas gracias! En breve nos pondremos en contacto contigo +{{% /nextcloud_forms %}} diff --git a/plugins/nextcloud_forms/README.md b/plugins/nextcloud_forms/README.md new file mode 100644 index 0000000..1f02df3 --- /dev/null +++ b/plugins/nextcloud_forms/README.md @@ -0,0 +1,40 @@ +This plugin embeds a Nextcloud Forms formular in a static site. +It provides a mechanism for the static HTML site to embed a form and to collect +and store the user data. + +The form specification is loaded at build time from the Nextcloud using the +public form link. The HTML form is then build according to the extracted JSON +form specification. + +When the submit button is pressed a simple JS function collects the form data +and sends it to the Nextcloud server using the public API. + +## Requirements and setup + +For this plugin to work a Nextcloud with the Forms App is needed. + +Create a form and make it public. Copy the public link and pass it as the +`link` parameter to the nextcloud_forms shortcut: + +``` +{{% nextcloud_forms link="https://nextcloud_url/index.php/apps/forms/..." %}} +Success Text +{{% /nextcloud_forms %}} +``` + +The `Success Text` is shown after the form is successfully sent to the server. + +## Heads up + +Nextcloud Forms public API is not handling CORS requests correctly, especially +the preflight checks. This leads to the XmlHTTPRequest failing with a CORS +error and the browser not submitting the data. + +At the moment there are two open feature requests at Nextcloud Server and +Nextcloud Forms that should fix these issues: + +- https://github.com/nextcloud/server/pull/31698 +- https://github.com/nextcloud/forms/pull/1139 + +Until these are merged into upstream, at least the changes to Nextcloud Forms +have to be applied manually to the Nextcloud installation. diff --git a/plugins/nextcloud_forms/nextcloud_forms.plugin b/plugins/nextcloud_forms/nextcloud_forms.plugin new file mode 100644 index 0000000..aa51c36 --- /dev/null +++ b/plugins/nextcloud_forms/nextcloud_forms.plugin @@ -0,0 +1,12 @@ +[Core] +name = nextcloud_forms +module = nextcloud_forms + +[Nikola] +PluginCategory = Shortcode + +[Documentation] +author = Andreas Brinner +version = 0.1 +website = https://getnikola.com/ +description = Embed Nextcloud Forms diff --git a/plugins/nextcloud_forms/nextcloud_forms.py b/plugins/nextcloud_forms/nextcloud_forms.py new file mode 100644 index 0000000..fea63b6 --- /dev/null +++ b/plugins/nextcloud_forms/nextcloud_forms.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- + +# Copyright © 2022, Andreas Brinner. + +# Permission is hereby granted, free of charge, to any +# person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the +# Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the +# Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice +# shall be included in all copies or substantial portions of +# the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +import os +import json +import base64 +import requests + +from html.parser import HTMLParser +from urllib.parse import urlparse + +from nikola.plugin_categories import ShortcodePlugin + + +SUBMIT_FORM_JS_PATH = os.path.join(os.path.dirname(__file__), 'submit_form.js') + + +class FormDataParser(HTMLParser): + def __init__(self, *args, **argv): + self.data = {} + HTMLParser.__init__(self, *args, **argv) + + def handle_starttag(self, tag, attrs): + if tag == "input": + data = dict(attrs) + if "id" in data and "value" in data: + self.data[data["id"]] = json.loads(base64.b64decode(data["value"])) + + +class Plugin(ShortcodePlugin): + """Plugin for nextcloud_forms directive.""" + + name = "nextcloud_forms" + + def get_public_form_data(self, link): + try: + data = requests.get(link).text + except requests.exceptions.RequestException: + self.logger.error('Cannot get form data from url={0}', link) + + parser = FormDataParser() + parser.feed(data) + + return parser.data + + def generate_ocs_url(self, link): + """Generate the OCS API base url from the given link. + + see also: + https://github.com/nextcloud/nextcloud-router/blob/master/lib/index.ts#L29" + """ + url = urlparse(link) + return "{}://{}/ocs/v2.php/apps/forms".format(url.scheme, url.netloc) + + def set_site(self, site): + super(type(self), self).set_site(site) + with open(SUBMIT_FORM_JS_PATH, "r") as fd: + submit_form_js = "".format(fd.read()) + site.template_hooks['body_end'].append(submit_form_js) + + def handler(self, link, template="nextcloud_forms.tmpl", + site=None, data=None, lang=None, post=None, **argv): + """Create HTML for Nextcloud Forms formular.""" + + form_data = self.get_public_form_data(link) + + endpoint = self.generate_ocs_url(link) + endpoint += "/api/v1.1/submission/insert" + + form = form_data.get("initial-state-forms-form") + if not form: + self.logger.error('No form defintion fond in url={}'.format(link)) + + template_deps = site.template_system.template_deps(template, site.GLOBAL_CONTEXT) + template_data = site.GLOBAL_CONTEXT.copy() + template_data.update({ + 'lang': lang, + 'form': form, + 'endpoint': endpoint, + 'initial_state': form_data, + 'success_data': data, + }) + output = site.template_system.render_template( + template, None, template_data) + return output, template_deps + [__file__, SUBMIT_FORM_JS_PATH] diff --git a/plugins/nextcloud_forms/requirements-nonpy.txt b/plugins/nextcloud_forms/requirements-nonpy.txt new file mode 100644 index 0000000..dbd65e6 --- /dev/null +++ b/plugins/nextcloud_forms/requirements-nonpy.txt @@ -0,0 +1,2 @@ +Nextcloud::https://nextcloud.com/install +Nextcloud Forms App::https://apps.nextcloud.com/apps/forms diff --git a/plugins/nextcloud_forms/submit_form.js b/plugins/nextcloud_forms/submit_form.js new file mode 100644 index 0000000..488de9f --- /dev/null +++ b/plugins/nextcloud_forms/submit_form.js @@ -0,0 +1,69 @@ +(function () { + function submit(ev) { + const form = ev.target; + + ev.preventDefault(); + + const answers = {}; + const [,formId] = form.id.split("-"); + + for (let i = 0; i < form.length; i++) { + var input = form[i]; + + // extract question id (qid) and answer id (aid) + var [,,qid,,aid] = input.id.split("-"); + + if ( qid && !(qid in answers) ) + answers[qid] = []; + + switch (input.type) { + case "checkbox": + case "radio": + if (input.checked) + answers[qid].push(aid); + console.log(input.type, input.id, qid, aid, input.checked); + break; + + case "select-one": + case "text": + case "textarea": + case "date": + case "datetime-local": + if (input.value) + answers[qid].push(input.value); + console.log(input.type, input.id, qid, input.value); + break; + + default: + console.log("unknown form element type:", input.type); + } + } + + const request = new XMLHttpRequest(); + request.open("POST", form.action, true); + request.setRequestHeader("OCS-APIRequest", "true"); + request.setRequestHeader("Accept", "application/json"); + request.setRequestHeader("Content-Type", "application/json"); + request.onload = function () { + const message = document.getElementById("form-" + formId + "-messages"); + const form = document.getElementById("form-" + formId); + const success = document.getElementById("form-" + formId + "-success"); + const response = JSON.parse(this.response); + + if (this.status == 200) { + // success + form.style.display = "none"; + success.style.display = "block"; + } else { + message.innerHTML = '