diff --git a/.gitignore b/.gitignore index 503e0960..b9e885ac 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ temp/ .DS_Store .AppleDouble .LSOverride +.nova # Icon must end with two \r Icon @@ -86,8 +87,12 @@ $RECYCLE.BIN/ # Windows shortcuts *.lnk -# Python virtual enviroment -env +### Python ### +# Compiled python +*.pyc + +# Virtaul environments .env -venv .venv +env +venv diff --git a/assets/css/style.css b/assets/css/style.css index 3104edf0..bce22454 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -31,6 +31,10 @@ --wtf-light-grey: #edefeb; --wtf-lila: #6600ff; + /* misc colors */ + --dark-red: #dc0000; + --dark-green: #007000; + --column-count: 3; } @@ -882,6 +886,47 @@ hr.-even { flex-direction: column; justify-content: flex-start; } + +.contact_form--required, +.--error { + color: var(--dark-red) +} + +.--success { + color: var(--dark-green); +} + +.contact_form__textarea, +.contact_form__text_input, +.contact_form__captcha { + display: flex; + flex-direction: column; +} + +.contact_form__message { + height: 12em; +} + +.contact_form__subject, +.contact_form__message, +.contact_form__name, +.contact_form__email, +.contact_form__captcha { + font-family: 'Lato', sans-serif; + line-height: 1.3rem; + font-size: 1rem; +} + +.contact_form__submit_button { + font-size: 1rem; + padding: 0 0.25rem; +} + +/* Hide captcha field as part of spam protection. + We got no real captcha. */ +.contact_form__captcha { + display: none; +} /* main - Ende */ /* footer - Start */ diff --git a/assets/js/contact_form.js b/assets/js/contact_form.js new file mode 100644 index 00000000..1d8bf657 --- /dev/null +++ b/assets/js/contact_form.js @@ -0,0 +1,78 @@ +const ajaxUrl = '../php/contact_form.php'; +const contact_form = document.getElementsByClassName('content__contact_form')[0]; +const subject = document.getElementsByClassName('contact_form__subject')[0]; +const message = document.getElementsByClassName('contact_form__message')[0]; +const name = document.getElementsByClassName('contact_form__name')[0]; +const email = document.getElementsByClassName('contact_form__email')[0]; +const captcha = document.getElementsByClassName('contact_form__captcha')[0]; +const now = (new Date().getTime()/1000).toFixed(); +const feedback = document.getElementsByClassName('contact_form__feedback')[0]; + +window.addEventListener('DOMContentLoaded', function(event) { + let formData = new FormData(); + formData.append('action', 'start_session'); + fetch(ajaxUrl, { + method: 'POST', + mode: 'same-origin', + body: formData, + }) + .then(response => response.json()) + .then(json => { + console.log(json); + }) +}); + +contact_form.addEventListener('submit', function(event) { + event.preventDefault(); + let formData = new FormData(); + formData.append('action', 'handle_form'); + formData.append('subject', subject.value); + formData.append('message', message.value); + formData.append('name', name.value); + formData.append('email', email.value); + + // If some bot entered some value, return. + if (typeof captcha.value == 'undefined') { + formData.append('captcha', 'Nudelsuppe'); + } else { + console.log('bot detected'); + return; + } + + fetch(ajaxUrl, { + method: 'POST', + mode: 'same-origin', + body: formData, + }) + .then(response => response.json()) + .then(json => { + console.log(json); + if (json.errors) { + feedback.classList.remove('--success'); + feedback.classList.add('--error'); + // Über errors iterieren und diese ausgeben (evtl. nur ersten Fehler ausgeben?) + let error_message = ''; + json.errors.forEach(function(error){ + /** + * Nur Zeilenumbrüche wenn mehrer Fehlermeldungen existieren, + * aber bei der letzten nicht. + */ + if (json.errors.length > 1) { + if (error == json.errors[json.errors.length - 1]) { + error_message = error_message + error; + } else { + error_message = error_message + error + '
'; + } + } else { + error_message = error_message + error; + } + }) + feedback.innerHTML = error_message; + } else if (json.status == 'ok') { + feedback.classList.remove('--error'); + feedback.classList.add('--success'); + feedback.textContent = "Ihre Nachricht wurde erfolgreich ans Office geschickt."; + } + }) + .catch(error => console.log(error)); +}, false); diff --git a/assets/js/contact_form_toggle.js b/assets/js/contact_form_toggle.js new file mode 100644 index 00000000..b2dce0c2 --- /dev/null +++ b/assets/js/contact_form_toggle.js @@ -0,0 +1,5 @@ +/* Unhide contact form if JS is enabled */ +window.addEventListener('DOMContentLoaded', (event) => { + const contact_form_wrapper = document.getElementsByClassName('content__contact_form_wrapper')[0]; + contact_form_wrapper.style.setProperty('display', 'block'); +}); diff --git a/assets/manifest.json b/assets/manifest.json index 844121f9..7f70b548 100644 --- a/assets/manifest.json +++ b/assets/manifest.json @@ -6,6 +6,6 @@ "sizes": "96x96", "type": "image\/png", "density": "2.0" - }, + } ] } \ No newline at end of file diff --git a/assets/php/contact_form.php b/assets/php/contact_form.php new file mode 100644 index 00000000..d0c8e33c --- /dev/null +++ b/assets/php/contact_form.php @@ -0,0 +1,132 @@ + FILTER_SANITIZE_SPECIAL_CHARS, + 'email' => FILTER_SANITIZE_EMAIL, + ); + $text = trim($text); + $text = filter_var($_POST[$name], $filters[$type]); + $text = stripslashes($text); + + return $text; +} + +function prepare_message_body($message) { + // Replace HTML-Entities with actual carriage returns and line feeds + $message = str_replace(" ", "\r", $message); + $message = str_replace(" ", "\r", $message); + + // Ensure line breaks via carriage return + line feed + $message = str_replace("\r\n", "\n", $message); + $message = str_replace("\n", "\r\n", $message); + + $message = "Nachricht von: $name\r\n\r\n" . $message; + $message = base64_encode($message); + + return $message; +} + +/** + * Sending email + * + * mail(): Braucht auf dem Server einen korrekt konfigurierten Mailserver + * phpmailer: Bibliothek, der per Composer installiert wird. Tut ganz gut mit SMTP. + */ +function send_message_to_office($subject, $message, $name, $email) { + return mail( + getenv('WTF_CONTACT_TO'), + "=?UTF-8?B?" . base64_encode($subject) . "?=", + prepare_message_body($message), + $additional_headers = array( + "From" => getenv('WTF_CONTACT_FROM'), + "Reply-To" => $email, + "Return-Path" => getenv('WTF_RETURN_PATH'), + "Content-Type" => "text/plain; charset=utf-8", + "Content-Transfer-Encoding" => "base64", + ), + ); +} + +function send_response($response_data) { + $json = json_encode($response_data); + if ($json === false) { + // Avoid echo of empty string (which is invalid JSON), and + // JSONify the error message instead: + $json = json_encode(["jsonError" => json_last_error_msg()]); + if ($json === false) { + // This should not happen, but … + $json = '{"jsonError":"unknown"}'; + } + // Set HTTP response status code to: 500 - Internal Server Error + http_response_code(500); + } + header('Content-type: application/json'); + echo $json; +} + +function prepare_response() { + $response = array(); + + if (empty($_POST['message'])) { + $response['errors'][] = 'Sieh haben keine Nachricht eingegeben.'; + } + if (empty($_POST['email'])) { + $response['errors'][] = 'Sie haben keine E-Mail-Adresse eingegeben.'; + } + if (empty($_POST['name'])) { + $response['errors'][] = 'Sie haben keinen Namen eingegeben.'; + } + if (empty($_POST['subject'])) { + $response['errors'][] = 'Sie haben keinen Betreff eingegeben.'; + } + /** + * Idee zur Bot-Erkennung: + * 1. Ein Bot hat das Pseudocaptcha entweder leer abgeschickt, oder sich selbst etwas ausgedacht. + * 2. Ein Bot schickt die Daten in unter 5s ab. + * 3. Ein Mensch braucht nicht länger als 60min. + */ + if ( + $_POST['captcha'] != 'Nudelsuppe' or + time() - $_SESSION['start_time'] < 5 or + time() - $_SESSION['start_time'] > 3600 + ) { + $response['errors'][] = 'Wir glauben Sie sind ein Bot.'; + } + if (!array_key_exists('errors', $response)) { + $subject = sanitize_text('subject', 'text'); + $message = sanitize_text('message', 'text'); + $name = sanitize_text('name', 'text'); + $email = sanitize_text('email', 'email'); + + if (!send_message_to_office($subject, $message, $name, $email)) { + $response['errors'][] = 'Ihre Nachricht konnte nicht übermittelt werden.'; + } else { + $response['status'] = 'ok'; + } + } + return $response; +} + +if ($_SERVER["REQUEST_METHOD"] == "POST") { + $response = array(); + + if (empty($_POST['action'])){ + $response['errors'][] = 'Kann eigentlich nicht passieren :/'; + } else { + if ($_POST['action'] == 'start_session') { + $_SESSION['start_time'] = time(); + // $response['session_start_time'] = $_SESSION['start_time']; + // $response['session_id_before'] = session_id(); + } elseif ($_POST['action'] == 'handle_form') { + $response = prepare_response(); + session_destroy(); + } else { + $response['errors'][] = 'Kann eigentlich auch nicht passieren :/'; + } + } + send_response($response); +} else { + http_response_code(404); +} diff --git a/content/kontakt/contents.lr b/content/kontakt/contents.lr index 149dd421..9094eac6 100644 --- a/content/kontakt/contents.lr +++ b/content/kontakt/contents.lr @@ -1,4 +1,4 @@ -_model: page +_model: contact_page --- title: Kontakt --- diff --git a/models/contact_page.ini b/models/contact_page.ini new file mode 100644 index 00000000..1ad4298d --- /dev/null +++ b/models/contact_page.ini @@ -0,0 +1,4 @@ +[model] +name = Contact Page +label = {{ this.title }} +inherits = page diff --git a/templates/contact_page.html b/templates/contact_page.html new file mode 100644 index 00000000..f90a6826 --- /dev/null +++ b/templates/contact_page.html @@ -0,0 +1,56 @@ +{% extends "header_slim.html" %} +{%- block title -%}{{ this.title }}{%- endblock -%} +{%- block meta_description -%} + {%- if this.meta_description is defined and this.meta_description != "" -%} + {{ this.meta_description }} + {%- else -%} + Werkkooperative der Technikfreundinnen eG + {%- endif -%} +{%- endblock -%} +{% block body %} +
+
+

{{ this.title }}

+
+
+
+
+
+ {{ this.body }} +
+ +
+
+{% endblock %} \ No newline at end of file diff --git a/templates/layout.html b/templates/layout.html index 6b8ead4b..79dd07cd 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -63,6 +63,9 @@ __ ____________________ {% if 'manifest.json'|asseturl is defined -%} {%- endif %} + {% if '/js/contact_form_toggle.js'|asseturl is defined -%} + + {%- endif %}
@@ -128,4 +131,7 @@ __ ____________________ {%- if '/js/nav_toggle.js'|asseturl is defined -%} {%- endif %} + {% if '/js/contact_form_toggle.js'|asseturl is defined -%} + + {%- endif %} diff --git a/wtf-eg.lektorproject b/wtf-eg.lektorproject index dce195e4..2d9c6fe6 100644 --- a/wtf-eg.lektorproject +++ b/wtf-eg.lektorproject @@ -13,6 +13,15 @@ default = yes locale = de_DE target = rsync://wtf@www.wtf-eg.net:/srv/www/www.wtf-eg.de/ +[servers.dev] +name = dev +url = https://spielwiese.wtf-eg.de/ +url_style = absolute +enabled = yes +default = yes +locale = de_DE +target = rsync://wtf@www.wtf-eg.net:/srv/www/spielwiese.wtf-eg.de/ + [alternatives.de] name = Deutsch primary = true