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 %}
+