Refactor captcha plugin layout and styles, and improve form bot detection and validation with multi-language support
This commit is contained in:
parent
4f1a638b64
commit
5d831bee81
1 changed files with 214 additions and 137 deletions
|
|
@ -1,113 +1,150 @@
|
||||||
window.onload = function () {
|
// Configuration and Messages
|
||||||
const FORMDEBUG = false
|
const debugEnabled = true
|
||||||
const btn = document.getElementById('kontaktformular-btn')
|
const mouseDebugEnabled = false
|
||||||
const kontaktformular = document.getElementById('kontaktformular')
|
const zsrCheckEnabled = false
|
||||||
|
const interactionThreshold = 15 // Time in seconds
|
||||||
|
const interactionCountThreshold = 5 // Number of interactions
|
||||||
|
let botDetected = false
|
||||||
|
let currentMessages = []
|
||||||
|
|
||||||
// custom Validation messages
|
const messages = {
|
||||||
const messagesGerman = {
|
de: {
|
||||||
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',
|
||||||
minlength: 'Bitte geben Sie mindestens {0} Zeichen ein',
|
success: 'Die Bestellung wurde erfolgreich übermittelt!',
|
||||||
maxlength: 'Bitte geben Sie maximal {0} Zeichen ein',
|
thankyou: 'Vielen Dank für Ihre Anfrage.',
|
||||||
min: 'Bitte geben Sie mindestens {0} ein',
|
zsrTooltip: 'Bitte geben Sie eine gültige ZSR-Nummer, oder "beantragt" ein.',
|
||||||
max: 'Bitte geben Sie maximal {0} ein',
|
captcha: 'Geben Sie den angezeigten Captcha-Code ein',
|
||||||
range: 'Bitte geben Sie zwischen {0} und {1} ein',
|
captchaButton: 'Überprüfen',
|
||||||
}
|
},
|
||||||
const messagesFrench = {
|
fr: {
|
||||||
required: 'Veuillez remplir ce champ',
|
required: 'Veuillez remplir ce champ',
|
||||||
email: 'Veuillez saisir une adresse email valide',
|
email: 'Veuillez saisir une adresse email valide',
|
||||||
minlength: 'Veuillez saisir au moins {0} caractères',
|
success: 'La commande a bien été envoyée!',
|
||||||
maxlength: 'Veuillez saisir au plus {0} caractères',
|
thankyou: 'Merci de votre demande.',
|
||||||
min: 'Veuillez saisir au moins {0} caractères',
|
zsrTooltip: 'Veuillez saisir une ZSR-Nummer valide, ou indiquer "demandé".',
|
||||||
max: 'Veuillez saisir au plus {0} caractères',
|
captcha: 'Entrez le code Captcha affiché',
|
||||||
range: 'Veuillez saisir au moins {0} et au plus {1} caractères',
|
captchaButton: 'Vérifier',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// custom Validation rules
|
// DOM Selectors
|
||||||
// determine which language depending on html lang attribute
|
const debugLabel = document.createElement('div')
|
||||||
const lang = document.documentElement.lang
|
const submitButton = document.getElementById('kontaktformular-btn')
|
||||||
console.log('lang', lang)
|
const form = document.querySelector('form#kontaktformular')
|
||||||
const messages = lang === 'de-DE' ? messagesGerman : messagesFrench
|
|
||||||
// set custom validation messages for each validator
|
|
||||||
console.log('messages', messages)
|
|
||||||
|
|
||||||
let textInputs = document.querySelectorAll('input[type="text"]')
|
|
||||||
const emailInput = document.getElementById('email')
|
|
||||||
|
|
||||||
Array.from(textInputs).forEach(function (input) {
|
|
||||||
input.addEventListener('invalid', function () {
|
|
||||||
this.setCustomValidity(messages['required'])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
emailInput.addEventListener('invalid', function () {
|
|
||||||
this.setCustomValidity(messages['email'])
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
// initieiere Zeitmessung zur Botprevention
|
|
||||||
var startTime = Date.now()
|
|
||||||
|
|
||||||
// Messe ob mit der Seite agiert wird
|
|
||||||
var userInteracted = false
|
|
||||||
|
|
||||||
function setUserInteracted() {
|
|
||||||
var timeSpent = (Date.now() - startTime) / 1000 // Zeit in Sekunden
|
|
||||||
if (timeSpent > 5) {
|
|
||||||
btn.disabled = false
|
|
||||||
}
|
|
||||||
userInteracted = true
|
|
||||||
}
|
|
||||||
setTimeout(function () {
|
|
||||||
if (userInteracted) {
|
|
||||||
btn.disabled = false
|
|
||||||
}
|
|
||||||
}, 5000)
|
|
||||||
// Eventlistener für Interaktionen - setzt userInteracted auf true bei Interaktion
|
|
||||||
document.addEventListener('mousedown', setUserInteracted)
|
|
||||||
document.addEventListener('touchstart', setUserInteracted)
|
|
||||||
document.addEventListener('keydown', setUserInteracted)
|
|
||||||
|
|
||||||
kontaktformular.addEventListener('submit', function (e) {
|
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
const form = e.target
|
|
||||||
const notification = document.getElementById('notification')
|
const notification = document.getElementById('notification')
|
||||||
const zsrTooltip = document.getElementById('zsr-tooltip')
|
const zsrTooltip = document.getElementById('zsr-tooltip')
|
||||||
|
const honeypotInput1 = document.getElementById('age')
|
||||||
// Spinner und button disabled anzeigen
|
const honeypotInput2 = document.getElementById('hobbies')
|
||||||
|
const verifyEmailInput = document.getElementById('verify_email')
|
||||||
var endTime = Date.now()
|
const emailInput = document.getElementById('mail')
|
||||||
var timeSpent = (endTime - startTime) / 1000 // Zeit in Sekunden
|
const textInputs = document.querySelectorAll('input[type="text"]')
|
||||||
|
const captcha = document.querySelectorAll('.captcha')
|
||||||
// Setze die Werte für die Botvalidierung zum Auswerten in PHP
|
const captchaInput = document.querySelectorAll('.captcha-input')
|
||||||
document.getElementById('age').value = timeSpent
|
const captchaVerifyButton = document.querySelectorAll('.captcha-verify')
|
||||||
document.getElementById('hobbies').value = userInteracted ? 'true' : 'false'
|
const botBadge = document.createElement('div')
|
||||||
|
if (debugEnabled) {
|
||||||
btn.innerHTML = `
|
botBadge.className = 'bot-badge'
|
||||||
<svg class="text-gray-300 animate-spin mx-auto" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg"
|
document.body.appendChild(botBadge)
|
||||||
width="24" height="24">
|
botBadge.setAttribute(
|
||||||
<path
|
'style',
|
||||||
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"
|
'position: fixed; top: 0; right: 0; z-index: 9999; background-color: red; color: white; font-weight: bold; height:20px; width:20px'
|
||||||
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">
|
// Utility variables
|
||||||
</path>
|
let startTime = Date.now()
|
||||||
</svg>
|
let interactionCount = 0
|
||||||
`
|
let userInteracted = false
|
||||||
|
let lastInteractionTime = null
|
||||||
btn.disabled = true
|
const mousePositions = []
|
||||||
|
const interactionTimes = []
|
||||||
if (FORMDEBUG) {
|
let isStraightLine = true
|
||||||
console.log('userInteracted: ' + userInteracted)
|
|
||||||
console.log('timeSpent: ' + timeSpent)
|
// Utility functions
|
||||||
console.log('hobbies: ' + document.getElementById('hobbies').value)
|
function log(thing) {
|
||||||
console.log('age: ' + document.getElementById('age').value)
|
console.log(thing)
|
||||||
console.log('verify_email(honeypot): ' + document.getElementById('verify_email').value)
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Konvertiere das JSON-Objekt in einen String, um es zu senden
|
|
||||||
const formData = new FormData(form)
|
const formData = new FormData(form)
|
||||||
const formDataEncoded = new URLSearchParams(formData).toString()
|
const formDataEncoded = new URLSearchParams(formData).toString()
|
||||||
const formURL = form.action + '.json'
|
const formURL = form.action + '.json'
|
||||||
|
|
@ -126,27 +163,67 @@ window.onload = function () {
|
||||||
return response.json()
|
return response.json()
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
// Erfolgsnachricht anzeigen
|
submitButton.disabled = true
|
||||||
// TODO Nachricht anpassen wenn französische Version
|
submitButton.innerHTML = `
|
||||||
notification.textContent = 'Anfrage erfolgreich versendet.'
|
<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>
|
||||||
btn.className = 'submitbutton text-white mx-auto submit-after-valid-captchaaaa fadeOut'
|
`
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
btn.style.visibility = 'hidden'
|
// if data.success is true, show a success message
|
||||||
btn.style.display = 'none'
|
if (data) {
|
||||||
notification.style.visibility = 'visible'
|
submitButton.style.display = 'none'
|
||||||
notification.style.display = 'block'
|
notification.innerHTML = `<span>${currentMessages.thankyou}</span>`
|
||||||
notification.classList.remove('fadeIn') // Remove fadeIn class
|
notification.className = 'bg-green-500 text-white px-4 py-2 rounded block'
|
||||||
void notification.offsetWidth
|
} else {
|
||||||
notification.className = 'bg-green-500 text-white px-4 py-2 rounded block fadeIn'
|
submitButton.style.display = 'none'
|
||||||
}, 1000)
|
notification.textContent = 'Es gab ein Problem mit Ihrer Anfrage'
|
||||||
// setTimeout(() => notification.className = 'bg-green-500 text-white px-4 py-2 rounded hidden', 5000); // Benachrichtigung nach 5 Sekunden ausblenden
|
notification.className = 'bg-blue-500 text-white px-4 py-2 rounded block'
|
||||||
|
}
|
||||||
|
}, 3000)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
// Fehlermeldung anzeigen
|
submitButton.style.display = 'none'
|
||||||
notification.textContent = 'Fehler beim Senden der Nachricht.'
|
notification.textContent = 'Fehler beim Senden der Nachricht.'
|
||||||
console.log(error)
|
notification.className = 'bg-blue-500 text-white px-4 py-2 rounded block'
|
||||||
notification.className = 'bg-red-500 text-white px-4 py-2 rounded block'
|
console.error(error)
|
||||||
// setTimeout(() => notification.className = 'bg-red-500 text-white px-4 py-2 rounded hidden', 5000); // Benachrichtigung nach 5 Sekunden ausblenden
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
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('touchstart', setUserInteracted)
|
||||||
|
document.addEventListener('keydown', setUserInteracted)
|
||||||
|
document.addEventListener('mousemove', handleMouseMove)
|
||||||
|
form.addEventListener('submit', handleSubmit)
|
||||||
|
emailInput.addEventListener('invalid', () => {
|
||||||
|
const currentMessages = getCurrentLangMessages()
|
||||||
|
emailInput.setCustomValidity(currentMessages.email)
|
||||||
|
})
|
||||||
|
textInputs.forEach((input) => {
|
||||||
|
input.addEventListener('invalid', () => {
|
||||||
|
const currentMessages = getCurrentLangMessages()
|
||||||
|
input.setCustomValidity(currentMessages.required)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
log('captchaInput', captchaInput)
|
||||||
|
captchaInput?.forEach((input) => {
|
||||||
|
input.setAttribute('placeholder', getCurrentLangMessages().captcha)
|
||||||
|
})
|
||||||
|
captchaVerifyButton?.forEach((button) => {
|
||||||
|
button.textContent = getCurrentLangMessages().captchaButton
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
init()
|
||||||
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue