Compare commits

...

22 Commits

Author SHA1 Message Date
7ae47ee590 Merge pull request '#105_kontaktformular' (#121) from #105_kontaktformular into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #121
2022-08-23 21:58:46 +02:00
2edfdcc179 Merge pull request '#122_umlaute_kontaktformular' (#123) from #122_umlaute_kontaktformular into #105_kontaktformular
All checks were successful
continuous-integration/drone/pr Build is passing
Reviewed-on: #123
2022-08-21 14:27:24 +02:00
54482d90f6 fix: Ensure \r\n for line breaks and properly encode subject for umlauts. 2022-08-21 14:18:23 +02:00
4e7b22fde6 fix: Fix sanitation to not break Umlauts and use specific email filter. 2022-08-21 14:18:10 +02:00
b65755de8d feat: Switched from hardcoded domain to relative path to improve portability.
Some checks reported errors
continuous-integration/drone/pr Build encountered an error
2022-08-20 15:55:16 +02:00
92d2c37000 fix: Added missing error message for missing subject. 2022-08-18 20:53:16 +02:00
e2fa3f75cc fix: Changed user facing text to formal speech. 2022-08-18 20:53:00 +02:00
360f90fc9d fix: Not just pass it along but really use the subject ;) 2022-08-18 20:52:50 +02:00
f58ba3bbc1 feat: Now really send emails. 2022-08-18 20:52:40 +02:00
fa9cf1bf37 fix: Remove opposite highlight classes to just display the most recent one. 2022-08-18 20:52:29 +02:00
e53449352f feat: Added subject to contact form. 2022-08-18 20:52:19 +02:00
4f7edc53a8 fix: Duzen all the way. 2022-08-18 20:52:12 +02:00
9b9f2b95c5 fix: Removed empty class definition. 2022-08-18 20:45:07 +02:00
553bba8917 feat: Improved layout of contact form submit button. 2022-08-18 20:44:58 +02:00
88276c2e2e feat: Switched time based bot protection from JS to PHP sessions. 2022-08-03 18:04:03 +02:00
487f2268b6 feat: Fehlermeldungen anzeigen und Botdetection anhand von Bearbeitunszeit. (#105) 2022-08-03 17:56:54 +02:00
2af9144fcb feat: Arrow functions aufgeräumt und Fehlerbehandlung ergänzt. (#105) 2022-08-03 17:56:54 +02:00
5a089b6a2a feat: Bot-Detection erweitert. (#105) 2022-08-03 17:56:54 +02:00
83d4694190 fix: Fixed Syntax in manifest.json. 2022-08-03 17:56:54 +02:00
bbc1bc2b61 chore: Updated .gitignore. 2022-08-03 17:56:54 +02:00
aeb1ae24fa feat: WIP Kontaktformular hinzugefügt. (#105)
Die URL ist noch hardcoded. Rückmeldung für den User fehlt noch. Die Nachricht geht noch nirgends hin. Spamprotection ohne Captcha ist nur in Ansätzen zu erkennen.
2022-08-03 17:55:25 +02:00
436be5830b feat: Added dev server to project file. 2022-08-03 17:55:25 +02:00
11 changed files with 345 additions and 5 deletions

11
.gitignore vendored
View File

@ -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

View File

@ -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 */

78
assets/js/contact_form.js Normal file
View File

@ -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 + '<br>';
}
} 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);

View File

@ -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');
});

View File

@ -6,6 +6,6 @@
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
},
}
]
}

132
assets/php/contact_form.php Normal file
View File

@ -0,0 +1,132 @@
<?php
session_start();
function sanitize_text(string $name, string $type) {
$filters = array(
'text' => 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("&#13;", "\r", $message);
$message = str_replace("&#10;", "\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);
}

View File

@ -1,4 +1,4 @@
_model: page
_model: contact_page
---
title: Kontakt
---

4
models/contact_page.ini Normal file
View File

@ -0,0 +1,4 @@
[model]
name = Contact Page
label = {{ this.title }}
inherits = page

View File

@ -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 %}
<div class="content__box">
<div class="content__inner_box">
<h1>{{ this.title }}</h1>
</div>
</div>
<section class="content -odd">
<div class="content__box">
<div class="content__inner_box -width_constraint">
{{ this.body }}
</div>
<div class="content__inner_box -width_constraint content__contact_form_wrapper" style="display:none">
<h2>Kontaktformular</h2>
<form id="contact_form" class="content__contact_form">
<p class="contact_form__note">
Deine E-Mail-Adresse wird nicht veröffentlicht.<br>
<span aria-hidden="true">Erforderliche Felder sind gekennzeichnet <span class="contact_form--required" aria-hidden="true">*</span></span>
</p>
<p class="contact_form__text_input">
<label for="name">Betreff <span class="contact_form--required" aria-hidden="true">*</span></label>
<input id="name" class="contact_form__subject" name="subject" type="text" value="" size="30" maxlength="245" required />
</p>
<p class="contact_form__textarea">
<label for="message">Nachricht <span class="contact_form--required" aria-hidden="true">*</span></label>
<textarea id="message" class="contact_form__message" aria-label="message" aria-hidden="true" cols="65" rows="7" name="message" required></textarea>
</p>
<p class="contact_form__text_input">
<label for="name">Name <span class="contact_form--required" aria-hidden="true">*</span></label>
<input id="name" class="contact_form__name" name="name" type="text" value="" size="30" maxlength="245" required />
</p>
<p class="contact_form__text_input">
<label for="email">E-Mail-Adresse <span class="contact_form--required" aria-hidden="true">*</span></label>
<input id="email" class="contact_form__email" name="email" type="email" value="" size="30" maxlength="100" aria-describedby="email-address" required />
</p>
<p class="contact_form__captcha">
<label for="captcha">Captcha <span class="contact_form--required" aria-hidden="true">*</span></label>
<input id="captcha" class="contact_form__captcha" name="captcha" type="captcha" value="…" size="30" maxlength="100" required placeholder="Wie viele Ecken hat ein Pentagramm?"/>
</p>
<p class="contact_form__submit">
<input name="submit" type="submit" id="submit" class="contact_form__submit_button" value="Kommentar abschicken" />
<p class="contact_form__feedback"></p>
</p>
</form>
</div>
</div>
</section>
{% endblock %}

View File

@ -63,6 +63,9 @@ __ ____________________
{% if 'manifest.json'|asseturl is defined -%}
<link rel="manifest" href="{{ 'manifest.json'|asseturl }}">
{%- endif %}
{% if '/js/contact_form_toggle.js'|asseturl is defined -%}
<script type="text/javascript" src="{{ '/js/contact_form_toggle.js'|asseturl }}"></script>
{%- endif %}
</head>
<body>
<header>
@ -128,4 +131,7 @@ __ ____________________
{%- if '/js/nav_toggle.js'|asseturl is defined -%}
<script type="text/javascript" src="{{ '/js/nav_toggle.js'|asseturl }}"></script>
{%- endif %}
{% if '/js/contact_form_toggle.js'|asseturl is defined -%}
<script type="text/javascript" src="{{ '/js/contact_form.js'|asseturl }}"></script>
{%- endif %}
</body>

View File

@ -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