Hello community,
I’ve revised my code again today, as it only worked with the pop-up.
Now it also works with the regular login field and registration.
Insert the code as before in the plugin or in the functions.php file.
Code Login:
add_action('wp_footer', function () {
// Nur dort aktivieren, wo ein HivePress Login-Feld existiert
if ( !apply_filters('hivepress_is_login_page', is_page()) ) {
// Trotzdem nicht abbrechen – auch Popups müssen funktionieren!
// => wir prüfen im Script selbst die Felder
}
?>
<style>
.hp-password-wrapper {
position: relative;
width: 100%;
}
.hp-password-wrapper input[type="password"] {
width: 100%;
padding-right: 44px;
}
.hp-password-toggle {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
width: 28px;
height: 28px;
padding: 0;
opacity: .75;
}
.hp-password-toggle:hover {
opacity: 1;
}
.hp-password-toggle svg {
width: 22px;
height: 22px;
stroke: currentColor;
fill: none;
stroke-width: 2;
pointer-events: none;
}
</style>
<script>
document.addEventListener("DOMContentLoaded", function () {
const EYE = `
<svg viewBox="0 0 24 24">
<path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7z"></path>
<circle cx="12" cy="12" r="3"></circle>
</svg>
`;
const EYE_OFF = `
<svg viewBox="0 0 24 24">
<path d="M17.94 17.94A10.94 10.94 0 0 1 12 19c-7 0-11-7-11-7a21.77 21.77 0 0 1 5.06-5.94"/>
<path d="M1 1l22 22"/>
<path d="M14.12 14.12A3 3 0 0 1 9.88 9.88"/>
</svg>
`;
function enhancePasswordFields() {
// Suche ALLE Passwortfelder – sowohl Popup, als auch normales Formular
const pwFields = document.querySelectorAll('input[type="password"][name="password"]');
if (!pwFields.length) return;
pwFields.forEach(function (pw) {
// Bereits erweitert?
if (pw.parentElement.classList.contains("hp-password-wrapper")) return;
// Wrapper erzeugen
const wrapper = document.createElement("div");
wrapper.classList.add("hp-password-wrapper");
pw.parentNode.insertBefore(wrapper, pw);
wrapper.appendChild(pw);
// Button erstellen
const btn = document.createElement("button");
btn.type = "button";
btn.className = "hp-password-toggle";
btn.innerHTML = EYE;
wrapper.appendChild(btn);
// Toggle-Logik
let visible = false;
btn.addEventListener("click", function () {
visible = !visible;
pw.type = visible ? "text" : "password";
btn.innerHTML = visible ? EYE_OFF : EYE;
});
});
}
// Sofort ausführen
enhancePasswordFields();
// Wiederholen, da HivePress Popups dynamisch geladen werden
setTimeout(enhancePasswordFields, 250);
setTimeout(enhancePasswordFields, 700);
setTimeout(enhancePasswordFields, 1500);
setInterval(enhancePasswordFields, 2000); // Sicherheit für spätes Rendering
});
</script>
<?php
});
Code registration:
<?php
/**
* Final: Universaler HivePress Registration Enhancer (Verbessert)
* - Vermeidet doppelte Elemente / mehrfaches Initialisieren
* - Sorgt für korrekte Reihenfolge und nur eine Fehlermeldung / Score-Anzeige
* - Fügt password_confirm (server-side) hinzu, validiert server- & client-side
*/
/**
* 1) Server-seitig: password_confirm direkt nach password einfügen (wenn noch nicht vorhanden)
*/
add_filter('hivepress/v1/forms/user_register', function ($form) {
if (!isset($form['fields']) || !is_array($form['fields'])) {
return $form;
}
if (isset($form['fields']['password']) && !isset($form['fields']['password_confirm'])) {
$new_fields = [];
foreach ($form['fields'] as $key => $field) {
$new_fields[$key] = $field;
if ($key === 'password') {
$new_fields['password_confirm'] = [
'type' => 'password',
'label' => __('Passwort bestätigen', 'hivepress'),
'required' => true,
'display_type' => 'password',
];
}
}
$form['fields'] = $new_fields;
}
return $form;
});
/**
* 2) Server-seitige Validierung (sichert Registrierung zuverlässig ab)
*/
$validate_callback = function ($errors, $model) {
$request = hivepress()->request ?? null;
if (!$request) return $errors;
$pw = $request->get_param('password');
$pw2 = $request->get_param('password_confirm');
if (($pw !== null || $pw2 !== null) && $pw !== $pw2) {
$errors['password_confirm'] = __('Die eingegebenen Passwörter stimmen nicht überein.', 'hivepress');
}
if (!empty($pw) && is_string($pw) && strlen($pw) < 8) {
$errors['password'] = __('Das Passwort muss mindestens 8 Zeichen lang sein.', 'hivepress');
}
return $errors;
};
add_filter('hivepress/v1/models/user_register/validate', $validate_callback, 10, 2);
add_filter('hivepress/v1/models/user/validate', $validate_callback, 10, 2);
/**
* 3) Frontend: JS + CSS (Icons, Position, Strength, Live-Validation)
* Verbesserungen: idempotent, keine Duplikate, korrekte Reihenfolge
*/
add_action('wp_footer', function () {
?>
<style>
.hp-password-wrapper { position: relative; width:100%; }
.hp-password-wrapper input[type="password"], .hp-password-wrapper input[type="text"] { padding-right:46px; box-sizing:border-box; width:100%; }
.hp-password-toggle { position:absolute; right:10px; top:50%; transform:translateY(-50%); background:none; border:none; width:36px; height:36px; display:flex; align-items:center; justify-content:center; cursor:pointer; opacity:.85; }
.hp-password-toggle svg { width:20px; height:20px; stroke:currentColor; fill:none; stroke-width:1.6; pointer-events:none; }
.hp-password-error { color:#d63638; font-size:13px; margin-top:6px; }
.hp-password-mismatch input { border-color:#d63638 !important; }
#hp-password-strength { margin-top:6px; font-size:13px; min-height:20px; }
.hp-strength-indicator { font-weight:700; padding:2px 6px; border-radius:4px; color:#fff; margin-left:8px; }
.hp-strength-weak{ background:#ff4136; } .hp-strength-medium{ background:#ff851b; } .hp-strength-strong{ background:#2ecc40; }
</style>
<script>
(function () {
const SVG_EYE = '<svg viewBox="0 0 24 24"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7z"/><circle cx="12" cy="12" r="3"/></svg>';
const SVG_EYE_OFF = '<svg viewBox="0 0 24 24"><path d="M17.94 17.94A10.94 10.94 0 0 1 12 19c-7 0-11-7-11-7a21.77 21.77 0 0 1 5.06-5.94"/><path d="M1 1l22 22"/><path d="M14.12 14.12A3 3 0 0 1 9.88 9.88"/></svg>';
/** Erkennung: ist Formular eine Registrierung? (Login explizit ausschließen) */
function isLikelyRegisterForm(form) {
if (!form || form.nodeName !== 'FORM') return false;
const action = (form.getAttribute('action') || form.getAttribute('data-action') || '').toLowerCase();
const classes = (form.className || '').toLowerCase();
// Eindeutige Kennzeichen für Login -> ausschließen
if ( action.includes('/login') ||
classes.includes('login') ||
classes.includes('hp-form--login') ||
form.querySelector('input[name="rememberme"]') ||
form.querySelector('input[name="log"]') ) {
return false;
}
// Kennzeichen für Registrierung
if ( action.includes('/register') ||
classes.includes('register') ||
classes.includes('hp-form--register') ||
(form.querySelector('input[name="email"]') && form.querySelector('input[name="password"]')) ) {
return true;
}
return false;
}
/** Einmalige Kennzeichnung einer Form, damit sie nicht mehrfach bearbeitet wird */
function markFormEnhanced(form) {
form.classList.add('hp-password-enhanced');
}
function isFormEnhanced(form) {
return form.classList && form.classList.contains('hp-password-enhanced');
}
/** Passwortstärke (einfach) */
function checkPasswordStrength(pw) {
let score = 0;
if (!pw || pw.length === 0) return {score:0, text:'', cls:'', lengthOk:false};
if (pw.length >= 8) score++;
if (/[a-z]/.test(pw) && /[A-Z]/.test(pw)) score++;
if (/\d/.test(pw)) score++;
if (/[^a-zA-Z0-9]/.test(pw)) score++;
if (score < 2) return {score, text:'Schwach', cls:'hp-strength-weak', lengthOk: pw.length >= 8};
if (score <= 3) return {score, text:'Mittel', cls:'hp-strength-medium', lengthOk: pw.length >= 8};
return {score, text:'Stark', cls:'hp-strength-strong', lengthOk: pw.length >= 8};
}
/** Wrapper erzeugen oder bestehenden wiederverwenden */
function addWrapperIfMissing(input) {
if (!input) return null;
// Wenn bereits in hp-password-wrapper
if (input.parentElement && input.parentElement.classList.contains('hp-password-wrapper')) {
return input.parentElement;
}
// Falls .hp-form__field vorhanden, nutzen wir den als Referenz und packen Input hinein
const fieldWrap = input.closest('.hp-form__field');
if (fieldWrap) {
// Wenn fieldWrap bereits einen hp-password-wrapper enthält, nutzen wir diesen
const existingWrapper = fieldWrap.querySelector('.hp-password-wrapper');
if (existingWrapper) {
// move input inside existingWrapper if not already inside
if (input.parentElement !== existingWrapper) {
existingWrapper.appendChild(input);
}
return existingWrapper;
}
// Erzeuge inneren wrapper
const wrapper = document.createElement('div');
wrapper.className = 'hp-password-wrapper';
// Füge wrapper an die Stelle des inputs und verschiebe input rein
input.parentNode.insertBefore(wrapper, input);
wrapper.appendChild(input);
return wrapper;
}
// Default-Fall: neuen wrapper direkt um das Input legen
const wrapper = document.createElement('div');
wrapper.className = 'hp-password-wrapper';
input.parentNode.insertBefore(wrapper, input);
wrapper.appendChild(input);
return wrapper;
}
/** Toggle-Button hinzufügen (idempotent) */
function addToggleToInput(input) {
if (!input) return;
const wrapper = addWrapperIfMissing(input);
if (!wrapper) return;
if (wrapper.querySelector('.hp-password-toggle')) return; // bereits vorhanden
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'hp-password-toggle';
btn.setAttribute('aria-label', 'Passwort anzeigen');
btn.innerHTML = SVG_EYE;
let visible = false;
btn.addEventListener('click', function (e) {
e.preventDefault();
visible = !visible;
try { input.type = visible ? 'text' : 'password'; } catch (err) {}
btn.setAttribute('aria-label', visible ? 'Passwort verbergen' : 'Passwort anzeigen');
btn.innerHTML = visible ? SVG_EYE_OFF : SVG_EYE;
});
btn.addEventListener('keydown', function (e) {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
btn.click();
}
});
wrapper.appendChild(btn);
}
/** Sicherstellen, dass confirm direkt nach password steht (DOM-Reordering) */
function ensureConfirmAfterPassword(form, pwInput, confirmInput) {
if (!pwInput || !confirmInput) return;
const pwFieldWrap = pwInput.closest('.hp-form__field') || pwInput.parentElement;
const confFieldWrap = confirmInput.closest('.hp-form__field') || confirmInput.parentElement;
if (!pwFieldWrap || !confFieldWrap) return;
// Wenn confirm bereits direkt danach steht: fertig
if (pwFieldWrap.nextSibling === confFieldWrap) return;
// Verschiebe confirm direkt nach pw
if (pwFieldWrap.parentNode) {
pwFieldWrap.parentNode.insertBefore(confFieldWrap, pwFieldWrap.nextSibling);
}
}
/** Falls serverseitig kein confirm vorhanden: clientseitig erzeugen (idempotent) */
function ensureConfirmField(form, pwInput) {
if (!form || !pwInput) return null;
let confirmInput = form.querySelector('input[name="password_confirm"]');
if (confirmInput) return confirmInput;
// Erzeuge nur, wenn noch nicht vorhanden
// prepare markup similar to HivePress
const fieldWrap = document.createElement('div');
fieldWrap.className = 'hp-form__field hp-form__field--password hp-form__field--password_confirm';
const label = document.createElement('label');
label.className = 'hp-field__label hp-form__label';
label.innerHTML = '<span>Passwort bestätigen</span>';
fieldWrap.appendChild(label);
confirmInput = document.createElement('input');
confirmInput.type = 'password';
confirmInput.name = 'password_confirm';
confirmInput.required = true;
confirmInput.maxLength = 64;
confirmInput.className = 'hp-field hp-field--password';
fieldWrap.appendChild(confirmInput);
// Platziere direkt nach pw field wrapper wenn möglich
const pwFieldWrap = pwInput.closest('.hp-form__field') || pwInput.parentElement;
if (pwFieldWrap && pwFieldWrap.parentNode) {
pwFieldWrap.parentNode.insertBefore(fieldWrap, pwFieldWrap.nextSibling);
} else {
// Fallback: in die .hp-form__fields oder ans Ende des Formulars
const container = form.querySelector('.hp-form__fields') || form;
container.appendChild(fieldWrap);
}
return confirmInput;
}
/** Clientseitige Validation: nur ein Fehler-Element & nur eine Strength-Anzeige erzeugen */
function attachClientValidation(form, pwInput, confirmInput) {
if (!form || !pwInput || !confirmInput) return;
// Kennzeichnung, damit wir nicht mehrere Validation-Attachments durchführen
if (form.dataset.hpValidationAttached === '1') return;
form.dataset.hpValidationAttached = '1';
// Error-Element: falls nicht vorhanden erzeugen
let err = form.querySelector('.hp-password-error');
if (!err) {
err = document.createElement('div');
err.className = 'hp-password-error';
// Position: nach confirm field wrapper (wenn vorhanden)
const confWrap = confirmInput.closest('.hp-form__field') || confirmInput.parentElement;
if (confWrap && confWrap.parentNode) {
confWrap.parentNode.insertBefore(err, confWrap.nextSibling);
} else {
confirmInput.parentNode.insertBefore(err, confirmInput.nextSibling);
}
}
// Strength-Element: falls nicht vorhanden erzeugen (ID pro form vermeiden Duplikate)
let strengthDiv = form.querySelector('.hp-password-strength');
if (!strengthDiv) {
strengthDiv = document.createElement('div');
strengthDiv.className = 'hp-password-strength';
// Position: nach password field wrapper
const pwWrap = pwInput.closest('.hp-form__field') || pwInput.parentElement;
if (pwWrap && pwWrap.parentNode) {
pwWrap.parentNode.insertBefore(strengthDiv, pwWrap.nextSibling);
} else {
pwInput.parentNode.insertBefore(strengthDiv, pwInput.nextSibling);
}
}
function updateVisuals() {
const p = pwInput.value || '';
const c = confirmInput.value || '';
// Strength
const s = checkPasswordStrength(p);
if (s.text) {
strengthDiv.innerHTML = (p.length >= 8 ? '✅ Mindestens 8 Zeichen' : '❌ Mindestens 8 Zeichen')
+ ' <span class="hp-strength-indicator ' + s.cls + '">' + s.text + '</span>';
} else {
strengthDiv.innerHTML = '';
}
// Match
const confWrap = confirmInput.closest('.hp-form__field') || confirmInput.parentElement;
if (p === '' && c === '') {
err.textContent = '';
confirmInput.setCustomValidity('');
confWrap.classList.remove('hp-password-mismatch');
return true;
}
if (p === c) {
err.textContent = '';
confirmInput.setCustomValidity('');
confWrap.classList.remove('hp-password-mismatch');
return s.lengthOk;
} else {
err.textContent = 'Die Passwörter stimmen nicht überein.';
confirmInput.setCustomValidity('Die Passwörter stimmen nicht überein.');
confWrap.classList.add('hp-password-mismatch');
return false;
}
}
// Live events (idempotent)
pwInput.removeEventListener && pwInput.removeEventListener('input', updateVisuals);
confirmInput.removeEventListener && confirmInput.removeEventListener('input', updateVisuals);
pwInput.addEventListener('input', updateVisuals);
confirmInput.addEventListener('input', updateVisuals);
// Submit interception: capture + mousedown + click on submit buttons
function preventIfInvalid(e) {
const ok = updateVisuals();
const p = pwInput.value || '';
const s = checkPasswordStrength(p);
if (!ok || !s.lengthOk) {
if (e && e.preventDefault) e.preventDefault();
if (e && e.stopImmediatePropagation) e.stopImmediatePropagation();
if (e && e.stopPropagation) e.stopPropagation();
// Fokus aufs fehlerhafte Feld
if (!ok) confirmInput.focus(); else pwInput.focus();
return false;
}
return true;
}
// Capture submit early
form.addEventListener('submit', function (e) { preventIfInvalid(e); }, true);
// Buttons
const submits = Array.from(form.querySelectorAll('button[type="submit"], input[type="submit"]'));
submits.forEach(btn => {
// avoid duplicate attachments
if (btn.dataset.hpSubmitHook === '1') return;
btn.dataset.hpSubmitHook = '1';
btn.addEventListener('click', function (e) { preventIfInvalid(e); }, true);
btn.addEventListener('mousedown', function (e) { preventIfInvalid(e); }, true);
});
// Initial run for autofill
setTimeout(updateVisuals, 120);
}
/** Enhance a single form once */
function enhanceRegisterForm(form) {
if (!isLikelyRegisterForm(form)) return;
if (isFormEnhanced(form)) return; // schon bearbeitet
// Markieren (vermeidet Double-Processing)
markFormEnhanced(form);
// Finde password (Hauptfeld)
const pwInput = form.querySelector('input[name="password"]') || form.querySelector('input[type="password"]');
if (!pwInput) return;
// Confirm (falls nicht vorhanden clientseitig erzeugen)
const confirmInput = ensureConfirmField(form, pwInput);
if (!confirmInput) return;
// Stelle Reihenfolge sicher
ensureConfirmAfterPassword(form, pwInput, confirmInput);
// Icons setzen
addToggleToInput(pwInput);
addToggleToInput(confirmInput);
// Validation & Strength (einmalig)
attachClientValidation(form, pwInput, confirmInput);
}
/** Initial scan + MutationObserver zum Erfassen dynamischer Inserts */
function initEnhancer() {
// einmaliger Scan
Array.from(document.querySelectorAll('form')).forEach(form => enhanceRegisterForm(form));
// observer für später geladene forms/popups
const observer = new MutationObserver(function (mutations) {
mutations.forEach(m => {
if (!m.addedNodes) return;
m.addedNodes.forEach(node => {
if (!(node instanceof HTMLElement)) return;
// Falls direkt ein form eingefügt wurde
if (node.tagName === 'FORM') {
enhanceRegisterForm(node);
} else {
// Suche forms im subtree
const childForms = node.querySelectorAll ? node.querySelectorAll('form') : [];
Array.from(childForms).forEach(f => enhanceRegisterForm(f));
}
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
// zusätzliche Zeit-Fallbacks (für langsame Builder)
setTimeout(() => Array.from(document.querySelectorAll('form')).forEach(f => enhanceRegisterForm(f)), 300);
setTimeout(() => Array.from(document.querySelectorAll('form')).forEach(f => enhanceRegisterForm(f)), 900);
setTimeout(() => Array.from(document.querySelectorAll('form')).forEach(f => enhanceRegisterForm(f)), 2500);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initEnhancer);
} else {
initEnhancer();
}
})();
</script>
<?php
});
Best regards, Marcel