Compare commits

..

No commits in common. "2f42d92273c38c647c3a4cb1b588fddf1737395a" and "4f1a638b64e00b4b5e0e76b64539fdc3d27e1dba" have entirely different histories.

3 changed files with 163 additions and 237 deletions

View file

@ -1,229 +1,152 @@
// Configuration and Messages window.onload = function () {
const debugEnabled = true const FORMDEBUG = false
const mouseDebugEnabled = false const btn = document.getElementById('kontaktformular-btn')
const zsrCheckEnabled = false const kontaktformular = document.getElementById('kontaktformular')
const interactionThreshold = 15 // Time in seconds
const interactionCountThreshold = 5 // Number of interactions
let botDetected = false
let currentMessages = []
const messages = { // custom Validation messages
de: { const messagesGerman = {
required: 'Bitte füllen Sie dieses Feld aus', required: 'Bitte füllen Sie dieses Feld aus',
email: 'Bitte geben Sie eine gültige E-Mail-Adresse ein', email: 'Bitte geben Sie eine gültige E-Mail-Adresse ein',
success: 'Die Bestellung wurde erfolgreich übermittelt!', minlength: 'Bitte geben Sie mindestens {0} Zeichen ein',
thankyou: 'Vielen Dank für Ihre Anfrage.', maxlength: 'Bitte geben Sie maximal {0} Zeichen ein',
zsrTooltip: 'Bitte geben Sie eine gültige ZSR-Nummer, oder "beantragt" ein.', min: 'Bitte geben Sie mindestens {0} ein',
captcha: 'Geben Sie den angezeigten Captcha-Code ein', max: 'Bitte geben Sie maximal {0} ein',
captchaButton: 'Überprüfen', range: 'Bitte geben Sie zwischen {0} und {1} ein',
}, }
fr: { const messagesFrench = {
required: 'Veuillez remplir ce champ', required: 'Veuillez remplir ce champ',
email: 'Veuillez saisir une adresse email valide', email: 'Veuillez saisir une adresse email valide',
success: 'La commande a bien été envoyée!', minlength: 'Veuillez saisir au moins {0} caractères',
thankyou: 'Merci de votre demande.', maxlength: 'Veuillez saisir au plus {0} caractères',
zsrTooltip: 'Veuillez saisir une ZSR-Nummer valide, ou indiquer "demandé".', min: 'Veuillez saisir au moins {0} caractères',
captcha: 'Entrez le code Captcha affiché', max: 'Veuillez saisir au plus {0} caractères',
captchaButton: 'Vérifier', range: 'Veuillez saisir au moins {0} et au plus {1} caractères',
},
}
// DOM Selectors
const debugLabel = document.createElement('div')
const submitButton = document.getElementById('kontaktformular-btn')
const form = document.querySelector('form#kontaktformular')
const notification = document.getElementById('notification')
const zsrTooltip = document.getElementById('zsr-tooltip')
const honeypotInput1 = document.getElementById('age')
const honeypotInput2 = document.getElementById('hobbies')
const verifyEmailInput = document.getElementById('verify_email')
const emailInput = document.getElementById('mail')
const textInputs = document.querySelectorAll('input[type="text"]')
const captcha = document.querySelectorAll('.captcha')
const captchaInput = document.querySelectorAll('.captcha-input')
const captchaVerifyButton = document.querySelectorAll('.captcha-verify')
const botBadge = document.createElement('div')
if (debugEnabled) {
botBadge.className = 'bot-badge'
document.body.appendChild(botBadge)
botBadge.setAttribute(
'style',
'position: fixed; top: 0; right: 0; z-index: 9999; background-color: red; color: white; font-weight: bold; height:20px; width:20px'
)
}
// Utility variables
let startTime = Date.now()
let interactionCount = 0
let userInteracted = false
let lastInteractionTime = null
const mousePositions = []
const interactionTimes = []
let isStraightLine = true
// Utility functions
function log(thing) {
console.log(thing)
}
function getCurrentLangMessages() {
log(messages[document.documentElement.lang.split('-')[0]])
return messages[document.documentElement.lang.split('-')[0]]
}
function setUserInteracted() {
userInteracted = true
interactionCount++
checkForBotBehavior()
}
function handleMouseMove(event) {
mousePositions.push({ x: event.clientX, y: event.clientY })
if (debugEnabled && mouseDebugEnabled) log('Mouse Position:', { x: event.clientX, y: event.clientY })
if (mousePositions.length > 2) {
const len = mousePositions.length
const { x: x1, y: y1 } = mousePositions[len - 3]
const { x: x2, y: y2 } = mousePositions[len - 2]
const { x: x3, y: y3 } = mousePositions[len - 1]
// Calculate the area of the triangle formed by three consecutive points
const area = 0.5 * Math.abs(x1 * y2 + x2 * y3 + x3 * y1 - y1 * x2 - y2 * x3 - y3 * x1)
if (debugEnabled && mouseDebugEnabled) log('Triangle Area:', area)
if (area > 0.5) {
// Threshold for detecting non-straight line, adjust as needed
isStraightLine = false
if (debugEnabled && mouseDebugEnabled) log('Detected non-straight line movement.')
}
}
}
function checkForBotBehavior() {
let timeSpent = (Date.now() - startTime) / 1000
botDetected =
!userInteracted ||
interactionCount === 0 ||
honeypotInput1.value !== '' ||
honeypotInput2.value !== '' ||
verifyEmailInput.value !== ''
if (debugEnabled)
if (!botDetected) {
botBadge.style.backgroundColor = 'green'
} else {
botBadge.style.backgroundColor = 'red'
}
console.log(
'Bot Detected: ' +
botDetected +
' userInteracted:' +
userInteracted +
' interactionCount:' +
interactionCount +
' timeSpent:' +
timeSpent +
' isStraightLine:' +
isStraightLine +
' mousePositions:' +
mousePositions.length +
' honeypotInput1:' +
honeypotInput1.value +
' honeypotInput2:' +
honeypotInput2.value +
' verifyEmailInput:' +
verifyEmailInput.value
)
}
function handleSubmit(e) {
e.preventDefault()
currentMessages = getCurrentLangMessages()
checkForBotBehavior()
if (botDetected) {
fakeOut()
return false
} }
const formData = new FormData(form) // custom Validation rules
const formDataEncoded = new URLSearchParams(formData).toString() // determine which language depending on html lang attribute
const formURL = form.action + '.json' const lang = document.documentElement.lang
console.log('lang', lang)
const messages = lang === 'de-DE' ? messagesGerman : messagesFrench
// set custom validation messages for each validator
console.log('messages', messages)
fetch(formURL, { let textInputs = document.querySelectorAll('input[type="text"]')
method: 'POST', const emailInput = document.getElementById('email')
headers: {
'Content-Type': 'application/x-www-form-urlencoded', // Wichtig für die Vermeidung von CORS-Problemen Array.from(textInputs).forEach(function (input) {
}, input.addEventListener('invalid', function () {
body: formDataEncoded, this.setCustomValidity(messages['required'])
})
}) })
.then((response) => {
if (!response.ok) { emailInput.addEventListener('invalid', function () {
throw new Error('Network response was not ok') this.setCustomValidity(messages['email'])
} })
return response.json()
})
.then((data) => { // initieiere Zeitmessung zur Botprevention
submitButton.disabled = true var startTime = Date.now()
submitButton.innerHTML = `
<svg class="svg-spin" fill="#ffffff" width="30px" height="30px" viewBox="0 0 9.6 9.6" id="Flat" xmlns="http://www.w3.org/2000/svg"><path d="M5.1 1.2v1.2a0.3 0.3 0 0 1 -0.6 0V1.2a0.3 0.3 0 0 1 0.6 0m3.3 3.3h-1.2a0.3 0.3 0 0 0 0 0.6h1.2a0.3 0.3 0 0 0 0 -0.6m-1.691 1.785a0.3 0.3 0 0 0 -0.424 0.424l0.849 0.849a0.3 0.3 0 1 0 0.424 -0.424ZM4.8 6.9a0.3 0.3 0 0 0 -0.3 0.3v1.2a0.3 0.3 0 0 0 0.6 0v-1.2a0.3 0.3 0 0 0 -0.3 -0.3m-1.909 -0.615L2.042 7.133a0.3 0.3 0 0 0 0.424 0.424l0.849 -0.849a0.3 0.3 0 0 0 -0.424 -0.424M2.7 4.8a0.3 0.3 0 0 0 -0.3 -0.3H1.2a0.3 0.3 0 0 0 0 0.6h1.2a0.3 0.3 0 0 0 0.3 -0.3m-0.233 -2.758a0.3 0.3 0 0 0 -0.424 0.424l0.849 0.849a0.3 0.3 0 0 0 0.424 -0.424Z"/></svg> // Messe ob mit der Seite agiert wird
` var userInteracted = false
setTimeout(() => {
// if data.success is true, show a success message function setUserInteracted() {
if (data) { var timeSpent = (Date.now() - startTime) / 1000 // Zeit in Sekunden
submitButton.style.display = 'none' if (timeSpent > 5) {
notification.innerHTML = `<span>${currentMessages.thankyou}</span>` btn.disabled = false
notification.className = 'bg-green-500 text-white px-4 py-2 rounded block' }
} else { userInteracted = true
submitButton.style.display = 'none' }
notification.textContent = 'Es gab ein Problem mit Ihrer Anfrage' setTimeout(function () {
notification.className = 'bg-blue-500 text-white px-4 py-2 rounded block' if (userInteracted) {
} btn.disabled = false
}, 3000) }
}) }, 5000)
.catch((error) => { // Eventlistener für Interaktionen - setzt userInteracted auf true bei Interaktion
submitButton.style.display = 'none'
notification.textContent = 'Fehler beim Senden der Nachricht.'
notification.className = 'bg-blue-500 text-white px-4 py-2 rounded block'
console.error(error)
})
}
function fakeOut() {
submitButton.disabled = true
submitButton.innerHTML = `
<svg class="svg-spin" fill="#ffffff" width="30px" height="30px" viewBox="0 0 9.6 9.6" id="Flat" xmlns="http://www.w3.org/2000/svg"><path d="M5.1 1.2v1.2a0.3 0.3 0 0 1 -0.6 0V1.2a0.3 0.3 0 0 1 0.6 0m3.3 3.3h-1.2a0.3 0.3 0 0 0 0 0.6h1.2a0.3 0.3 0 0 0 0 -0.6m-1.691 1.785a0.3 0.3 0 0 0 -0.424 0.424l0.849 0.849a0.3 0.3 0 1 0 0.424 -0.424ZM4.8 6.9a0.3 0.3 0 0 0 -0.3 0.3v1.2a0.3 0.3 0 0 0 0.6 0v-1.2a0.3 0.3 0 0 0 -0.3 -0.3m-1.909 -0.615L2.042 7.133a0.3 0.3 0 0 0 0.424 0.424l0.849 -0.849a0.3 0.3 0 0 0 -0.424 -0.424M2.7 4.8a0.3 0.3 0 0 0 -0.3 -0.3H1.2a0.3 0.3 0 0 0 0 0.6h1.2a0.3 0.3 0 0 0 0.3 -0.3m-0.233 -2.758a0.3 0.3 0 0 0 -0.424 0.424l0.849 0.849a0.3 0.3 0 0 0 0.424 -0.424Z"/></svg>
`
setTimeout(() => {
submitButton.style.display = 'none'
notification.innerHTML = `<span>${currentMessages.thankyou}</span>`
notification.className = 'bg-green-500 text-white px-4 py-2 rounded block'
}, 3000)
}
function init() {
// Event Listeners
document.addEventListener('mousedown', setUserInteracted) document.addEventListener('mousedown', setUserInteracted)
document.addEventListener('touchstart', setUserInteracted) document.addEventListener('touchstart', setUserInteracted)
document.addEventListener('keydown', setUserInteracted) document.addEventListener('keydown', setUserInteracted)
document.addEventListener('mousemove', handleMouseMove)
form.addEventListener('submit', handleSubmit) kontaktformular.addEventListener('submit', function (e) {
emailInput.addEventListener('invalid', () => { e.preventDefault()
const currentMessages = getCurrentLangMessages()
emailInput.setCustomValidity(currentMessages.email) const form = e.target
}) const notification = document.getElementById('notification')
textInputs.forEach((input) => { const zsrTooltip = document.getElementById('zsr-tooltip')
input.addEventListener('invalid', () => {
const currentMessages = getCurrentLangMessages() // Spinner und button disabled anzeigen
input.setCustomValidity(currentMessages.required)
var endTime = Date.now()
var timeSpent = (endTime - startTime) / 1000 // Zeit in Sekunden
// Setze die Werte für die Botvalidierung zum Auswerten in PHP
document.getElementById('age').value = timeSpent
document.getElementById('hobbies').value = userInteracted ? 'true' : 'false'
btn.innerHTML = `
<svg class="text-gray-300 animate-spin mx-auto" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg"
width="24" height="24">
<path
d="M32 3C35.8083 3 39.5794 3.75011 43.0978 5.20749C46.6163 6.66488 49.8132 8.80101 52.5061 11.4939C55.199 14.1868 57.3351 17.3837 58.7925 20.9022C60.2499 24.4206 61 28.1917 61 32C61 35.8083 60.2499 39.5794 58.7925 43.0978C57.3351 46.6163 55.199 49.8132 52.5061 52.5061C49.8132 55.199 46.6163 57.3351 43.0978 58.7925C39.5794 60.2499 35.8083 61 32 61C28.1917 61 24.4206 60.2499 20.9022 58.7925C17.3837 57.3351 14.1868 55.199 11.4939 52.5061C8.801 49.8132 6.66487 46.6163 5.20749 43.0978C3.7501 39.5794 3 35.8083 3 32C3 28.1917 3.75011 24.4206 5.2075 20.9022C6.66489 17.3837 8.80101 14.1868 11.4939 11.4939C14.1868 8.80099 17.3838 6.66487 20.9022 5.20749C24.4206 3.7501 28.1917 3 32 3L32 3Z"
stroke="currentColor" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"></path>
<path
d="M32 3C36.5778 3 41.0906 4.08374 45.1692 6.16256C49.2477 8.24138 52.7762 11.2562 55.466 14.9605C58.1558 18.6647 59.9304 22.9531 60.6448 27.4748C61.3591 31.9965 60.9928 36.6232 59.5759 40.9762"
stroke="currentColor" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" class="text-gray-900">
</path>
</svg>
`
btn.disabled = true
if (FORMDEBUG) {
console.log('userInteracted: ' + userInteracted)
console.log('timeSpent: ' + timeSpent)
console.log('hobbies: ' + document.getElementById('hobbies').value)
console.log('age: ' + document.getElementById('age').value)
console.log('verify_email(honeypot): ' + document.getElementById('verify_email').value)
}
// Konvertiere das JSON-Objekt in einen String, um es zu senden
const formData = new FormData(form)
const formDataEncoded = new URLSearchParams(formData).toString()
const formURL = form.action + '.json'
fetch(formURL, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded', // Wichtig für die Vermeidung von CORS-Problemen
},
body: formDataEncoded,
}) })
}) .then((response) => {
log('captchaInput', captchaInput) if (!response.ok) {
captchaInput?.forEach((input) => { throw new Error('Network response was not ok')
input.setAttribute('placeholder', getCurrentLangMessages().captcha) }
}) return response.json()
captchaVerifyButton?.forEach((button) => { })
button.textContent = getCurrentLangMessages().captchaButton .then((data) => {
// Erfolgsnachricht anzeigen
// TODO Nachricht anpassen wenn französische Version
notification.textContent = 'Anfrage erfolgreich versendet.'
btn.className = 'submitbutton text-white mx-auto submit-after-valid-captchaaaa fadeOut'
setTimeout(() => {
btn.style.visibility = 'hidden'
btn.style.display = 'none'
notification.style.visibility = 'visible'
notification.style.display = 'block'
notification.classList.remove('fadeIn') // Remove fadeIn class
void notification.offsetWidth
notification.className = 'bg-green-500 text-white px-4 py-2 rounded block fadeIn'
}, 1000)
// setTimeout(() => notification.className = 'bg-green-500 text-white px-4 py-2 rounded hidden', 5000); // Benachrichtigung nach 5 Sekunden ausblenden
})
.catch((error) => {
// Fehlermeldung anzeigen
notification.textContent = 'Fehler beim Senden der Nachricht.'
console.log(error)
notification.className = 'bg-red-500 text-white px-4 py-2 rounded block'
// setTimeout(() => notification.className = 'bg-red-500 text-white px-4 py-2 rounded hidden', 5000); // Benachrichtigung nach 5 Sekunden ausblenden
})
}) })
} }
document.addEventListener('DOMContentLoaded', function () {
init()
})

View file

@ -32,6 +32,10 @@ Nous vous contacterons dans les plus brefs délais.
<div id="formPartOne"> <div id="formPartOne">
<input type="hidden" name="formularart" value="Kontaktformular Französisch"> <input type="hidden" name="formularart" value="Kontaktformular Französisch">
<input type="hidden" name="project" value="fr-support"> <input type="hidden" name="project" value="fr-support">
<div>
<label for="organisation">Nom de l'organisation</label>
<input type="text" id="organisation" name="organisation">
</div>
<div> <div>
<select id="gender" name="gender" required > <select id="gender" name="gender" required >
<option value="männlich">Monsieur</option> <option value="männlich">Monsieur</option>
@ -40,7 +44,7 @@ Nous vous contacterons dans les plus brefs délais.
</select> </select>
</div> </div>
<div> <div>
<label for="name">Nom, Nom de famille</label> <label for="name">Nom de famille</label>
<input type="text" id="name" name="name" required > <input type="text" id="name" name="name" required >
</div> </div>
<div> <div>
@ -54,8 +58,8 @@ Nous vous contacterons dans les plus brefs délais.
<input type="text" id="telefon" name="telefon" required > <input type="text" id="telefon" name="telefon" required >
</div> </div>
<div> <div>
<label for="mail">Email</label> <label for="email">Email</label>
<input type="mail" id="mail" name="mail" required > <input type="email" id="email" name="email" required >
</div> </div>
<div> <div>
<label for="subject">Objet</label> <label for="subject">Objet</label>
@ -63,15 +67,14 @@ Nous vous contacterons dans les plus brefs délais.
</div> </div>
<div> <div>
<label for="description">Remarque</label> <label for="description">Remarque</label>
<textarea id="description" name="description" rows="4" required></textarea> <textarea id="description" name="description" rows="4"></textarea>
</div> </div>
</div> </div>
<center class="w-full"> <!-- <div class ="captcha-container"></div> wir verzichten erstmal auf das Captcha -->
{{< captcha >}} <div class="w-full flex justify-center">
<button id="kontaktformular-btn" type="submit" class="submitbutton text-white mx-auto submit-after-valid-captcha" >Envoyer la demande</button> <button id="kontaktformular-btn" type="submit" disabled class="submitbutton text-white mx-auto submit-after-valid-captcha-bak">Envoyer la demande</button>
<div id="notification" class="bg-blue-500 text-white rounded invisible opacity-0 hidden">Notification</div>
<p><div id="notification" class="bg-blue-500 text-white rounded invisible opacity-0 hidden">Notification</div></p> </div>
</center>
</form> </form>
{{% /section %}} {{% /section %}}

View file

@ -32,6 +32,10 @@ Wir nehmen zeitnah Kontakt zu Ihnen auf.
<div id="formPartOne"> <div id="formPartOne">
<input type="hidden" name="formularart" value="Kontaktformular Deutsch"> <input type="hidden" name="formularart" value="Kontaktformular Deutsch">
<input type="hidden" name="project" value="de-support"> <input type="hidden" name="project" value="de-support">
<div>
<label for="organisation">Name der Organisation</label>
<input type="text" id="organisation" name="organisation">
</div>
<div> <div>
<select id="gender" name="gender" required > <select id="gender" name="gender" required >
<option value="männlich">Herr</option> <option value="männlich">Herr</option>
@ -40,7 +44,7 @@ Wir nehmen zeitnah Kontakt zu Ihnen auf.
</select> </select>
</div> </div>
<div> <div>
<label for="name">Nachname, Vorname</label> <label for="name">Nachname</label>
<input type="text" id="name" name="name" required > <input type="text" id="name" name="name" required >
</div> </div>
<div> <div>
@ -54,11 +58,8 @@ Wir nehmen zeitnah Kontakt zu Ihnen auf.
<input type="text" id="telefon" name="telefon" required > <input type="text" id="telefon" name="telefon" required >
</div> </div>
<div> <div>
<label for="mail">Email</label> <label for="email">Email</label>
<input type="mail" id="mail" name="mail" required > <input type="email" id="email" name="email" required >
</div>
<div>
<input type="verify_email" id="verify_email" name="verify_email" >
</div> </div>
<div> <div>
<label for="subject">Betreff</label> <label for="subject">Betreff</label>
@ -66,15 +67,14 @@ Wir nehmen zeitnah Kontakt zu Ihnen auf.
</div> </div>
<div> <div>
<label for="description">Anmerkung</label> <label for="description">Anmerkung</label>
<textarea id="description" name="description" rows="4" required></textarea> <textarea id="description" name="description" rows="4"></textarea>
</div> </div>
</div> </div>
<center class="w-full"> <!-- <div class ="captcha-container"></div> wir verzichten erstmal auf das Captcha -->
{{< captcha >}} <div class="w-full flex justify-center">
<button id="kontaktformular-btn" type="submit" class="submitbutton text-white mx-auto submit-after-valid-captcha" >Anfrage absenden</button> <button id="kontaktformular-btn" type="submit" disabled class="submitbutton text-white mx-auto submit-after-valid-captcha-bak">Anfrage absenden</button>
<div id="notification" class="bg-blue-500 text-white rounded invisible opacity-0 hidden">Notification</div>
<p><div id="notification" class="bg-blue-500 text-white rounded invisible opacity-0 hidden">Notification</div></p> </div>
</center>
</form> </form>
{{% /section %}} {{% /section %}}