Temporary Solution for Reveal Password Feature [Visible Eye]

Hello Community,

I’ve written a code snippet that allows you to add an eye icon to the password field of the HivePress login. This lets you see your password and vice versa.

I implemented it using “Code Snippets”.

  • Create a new snippet

  • Give it a name

  • Display in all areas

  • PHP

  • add_action('wp_footer', function () {
    
        // Nur auf Seiten mit dem HivePress Login-Formular
        if (!apply_filters('hivepress_is_login_page', is_page())) {
            return;
        }
        ?>
    
        <style>
        /* Input-Wrap korrekt positionieren */
        .hp-password-wrapper {
            position: relative;
        }
    
        .hp-password-toggle {
            position: absolute;
            right: 12px;
            top: 50%;
            transform: translateY(-50%);
            background: none;
            border: none;
            padding: 0;
            cursor: pointer;
            opacity: 0.7;
            width: 24px;
            height: 24px;
        }
    
        .hp-password-toggle:hover {
            opacity: 1;
        }
    
        .hp-password-toggle svg {
            width: 22px;
            height: 22px;
            pointer-events: none;
        }
        </style>
    
        <script>
        document.addEventListener("DOMContentLoaded", function () {
    
            function initPasswordToggle() {
                // Passwortfeld sicher abfragen (HivePress-Login)
                const pwField = document.querySelector('form input[type="password"][name="password"]');
                if (!pwField) return;
    
                // Wrapper nur erstellen, wenn nicht vorhanden
                if (!pwField.parentElement.classList.contains("hp-password-wrapper")) {
                    const wrapper = document.createElement("div");
                    wrapper.classList.add("hp-password-wrapper");
    
                    // Input in Wrapper einbetten
                    pwField.parentNode.insertBefore(wrapper, pwField);
                    wrapper.appendChild(pwField);
                }
    
                const wrapper = pwField.parentElement;
    
                // Button nur einmal einfügen
                if (wrapper.querySelector(".hp-password-toggle")) return;
    
                const btn = document.createElement("button");
                btn.type = "button";
                btn.className = "hp-password-toggle";
    
                btn.innerHTML = `
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                    <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>
                `;
    
                wrapper.appendChild(btn);
    
                let visible = false;
    
                btn.addEventListener("click", function () {
                    visible = !visible;
                    pwField.type = visible ? "text" : "password";
    
                    btn.innerHTML = visible
                        ? `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <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>`
                        : `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <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>`;
                });
            }
    
            // 1. sofort versuchen
            initPasswordToggle();
    
            // 2. wiederholen, falls HivePress dynamisch nachlädt
            setTimeout(initPasswordToggle, 300);
            setTimeout(initPasswordToggle, 800);
        });
        </script>
    
        <?php
    });
    
    
    
  • Save and activate

I hope everyone who’s been looking for this solution enjoys it.

Best regards, Marcel

3 Likes

Thanks for sharing this, @Marcel1987! :raising_hands:

1 Like

Thank you. @ChrisB

I’m also currently working on code that requires a double password entry for registration, and that allows users to view the password.

Most importantly, registration shouldn’t be possible unless the password is entered twice.

1 Like

I’ve now added a second field to the registration process. This makes the code visible, and registration is blocked if the password is different twice. Additionally, a score of at least 8 characters (Easy, Medium, Strong) is displayed. Since I’m a German user, you only need to adjust the code sections to your language.

The same procedure applies when going to the “Code Snippet” section.

-Create a new snippet
-Give it a name
-PHP
-Show on all pages

<?php
/**
 * 1) PHP: Registrierungsformular-Feld 'password_confirm' hinzufügen
 * Sowie: Ein Hook nach dem 'password'-Feld, um den Score-Container einzufügen.
 */
add_filter('hivepress/v1/forms/user_register', function ($form) {

    if (isset($form['fields']['password'])) {

        $new_fields = [];

        foreach ($form['fields'] as $key => $field) {
            $new_fields[$key] = $field;

            if ($key === 'password') {
                // Fügen Sie nach dem 'password'-Feld einen unsichtbaren Platzhalter ein,
                // in den wir den Score-Container über JS/HTML einfügen können,
                // oder wir nutzen wp_footer, um es zu platzieren (was wir in 3 machen).

                // password_confirm direkt nach password
                $new_fields['password_confirm'] = [
                    'type'        => 'password',
                    'label'       => __('Passwort bestätigen', 'hivepress'),
                    'required'    => true,
                    // Wenn du das Placeholder wieder entfernen möchtest, lass es weg:
                    // 'placeholder' => __('Passwort wiederholen', 'hivepress'),
                    'display_type'=> 'password',
                ];
            }
        }

        $form['fields'] = $new_fields;
    }

    return $form;
});

/**
 * 2) Server-seitige Validierung: Passwörter gleich? (Unverändert)
 */
add_filter('hivepress/v1/models/user/validate', function ($errors, $model) {

    $request = hivepress()->request;

    if (!$request) return $errors;

    $password = $request->get_param('password');
    $confirm  = $request->get_param('password_confirm');

    if (empty($password) && empty($confirm)) {
        return $errors;
    } elseif ($password !== $confirm) {
        $errors['password_confirm'] = __('Die beiden Passwörter stimmen nicht überein.', 'hivepress');
    }

    // Optional: Server-seitige Mindestlängenprüfung (zusätzlich zu HivePress' required)
    if (!empty($password) && strlen($password) < 8) {
        $errors['password'] = __('Das Passwort muss mindestens 8 Zeichen lang sein.', 'hivepress');
    }


    return $errors;
}, 10, 2);

/**
 * 3) JS + CSS: Positionierung, Icons, Live-Check UND PASSWORD STRENGTH
 */
add_action('wp_footer', function () {

    // nur auf HivePress-Registrierungsseite
    if (!apply_filters('hivepress_is_register_page', is_page())) {
        return;
    }
    ?>

    <style>
    .hp-form__field--password_confirm .hp-form__field__label {
        display: block !important;
        visibility: visible !important;
        position: static !important;
        height: auto !important;
        width: auto !important;
        margin-bottom: 5px !important;
    }
    label[for="password_confirm"] {
        display: block !important;
        visibility: visible !important;
        position: static !important;
        height: auto !important;
        width: auto !important;
        z-index: 10 !important; 
        margin-bottom: 5px !important;
        color: #333333 !important;
        font-weight: bold !important;
    }


    /* ----------------------------------------------------------------
    * NEUES CSS FÜR DEN PASSWORD-SCORE
    * ---------------------------------------------------------------- */
    #password-strength {
        margin-top: 6px;
        font-size: 13px;
        line-height: 1.4;
        min-height: 20px; /* Hält den Platz, damit das Layout nicht springt */
    }

    /* Style für die verschiedenen Stärken */
    .strength-indicator {
        display: inline-block;
        font-weight: bold;
        padding: 2px 6px;
        border-radius: 4px;
        color: #ffffff; /* Weißer Text auf farbigem Hintergrund */
        margin-left: 5px;
    }

    /* Farben für die Stärken */
    .strength-weak {
        background-color: #ff4136; /* Rot */
    }
    .strength-medium {
        background-color: #ff851b; /* Orange */
    }
    .strength-strong {
        background-color: #2ecc40; /* Grün */
    }


    /* Wrapper für Input, damit das Icon absolut positioniert werden kann */
    .hp-password-wrapper {
        position: relative;
        display: block;
        width: 100%;
    }
    /* Input bleibt 100% Breite */
    .hp-password-wrapper input {
        width: 100%;
        box-sizing: border-box;
        padding-right: 44px; /* Platz für das Icon */
    }
    .hp-password-toggle {
        position: absolute;
        right: 12px;
        top: 50%;
        transform: translateY(-50%);
        background: none;
        border: none;
        cursor: pointer;
        width: 32px;
        height: 32px;
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 0;
        opacity: .8;
    }
    .hp-password-toggle:hover { opacity: 1; }
    /* Schönes, dünnes SVG wie im Beispiel zuvor */
    .hp-password-toggle svg {
        width: 20px;
        height: 20px;
        stroke: currentColor;
        fill: none;
        stroke-width: 1.6;
        pointer-events: none;
    }
    /* Fehlertext unter dem Feld */
    .hp-password-error {
        color: #d63638;
        font-size: 13px;
        margin-top: 6px;
    }
    /* Optional: roter Rahmen, wenn ungültig */
    .hp-password-mismatch input {
        border-color: #d63638 !important;
        box-shadow: 0 0 0 1px rgba(214,54,56,0.08);
    }
    </style>

    <script>
    (function () {
        // Utility: SVG Icons (hidden / visible)
        const SVG_EYE = `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
            <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" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
            <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>`;

        
        /**
         * NEUE FUNKTION: Passwortstärke prüfen
         * Prüft Länge (8 Zeichen) und Komplexität (leicht/mittel/stark).
         */
        function checkPasswordStrength(password) {
            let score = 0;
            const lengthOk = password.length >= 8;

            if (password.length > 0) {
                // Kriterium 1: Länge
                if (password.length >= 8) {
                    score += 1;
                }

                // Kriterium 2: Klein- oder Großbuchstaben
                if (password.match(/[a-z]/) || password.match(/[A-Z]/)) {
                    score += 1;
                }

                // Kriterium 3: Zahlen
                if (password.match(/\d/)) {
                    score += 1;
                }

                // Kriterium 4: Sonderzeichen
                if (password.match(/[^a-zA-Z\d\s]/)) {
                    score += 1;
                }
            }

            // Definiere die Stufen (Score 0-4)
            let strengthText = '';
            let strengthClass = '';

            if (password.length === 0) {
                strengthText = '';
                strengthClass = '';
            } else if (score < 2) {
                strengthText = 'Schwach';
                strengthClass = 'strength-weak';
            } else if (score <= 3) {
                strengthText = 'Mittel';
                strengthClass = 'strength-medium';
            } else {
                strengthText = 'Stark';
                strengthClass = 'strength-strong';
            }

            return { score, strengthText, strengthClass, lengthOk };
        }

        // Hilfsfunktionen (Unverändert)
        function addWrapperIfMissing(input) {
            if (!input) return null;
            if (input.parentElement && input.parentElement.classList.contains('hp-password-wrapper')) {
                return input.parentElement;
            }
            const wrapper = document.createElement('div');
            wrapper.className = 'hp-password-wrapper';
            input.parentNode.insertBefore(wrapper, input);
            wrapper.appendChild(input);
            return wrapper;
        }

        function addToggle(input) {
            if (!input) return;
            const wrapper = addWrapperIfMissing(input);
            if (!wrapper) return;
            if (wrapper.querySelector('.hp-password-toggle')) return;
            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;
                input.type = visible ? 'text' : 'password';
                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);
        }

        function ensureConfirmPosition(passwordInput, confirmInput) {
            if (!passwordInput || !confirmInput) return;
            const findFieldWrapper = (input) => {
                let current = input.parentNode;
                while (current && current.tagName !== 'FORM' && current.tagName !== 'BODY') {
                    if (current.classList.contains('hp-form__field')) { 
                        return current;
                    }
                    current = current.parentNode;
                }
                return null;
            };

            const pwdFieldWrap = findFieldWrapper(passwordInput);
            const confFieldWrap = findFieldWrapper(confirmInput);

            if (!pwdFieldWrap || !confFieldWrap) return;
            if (pwdFieldWrap.nextSibling === confFieldWrap) return;

            if (pwdFieldWrap.parentNode) {
                pwdFieldWrap.parentNode.insertBefore(confFieldWrap, pwdFieldWrap.nextSibling);
            }
        }
        

        function addClientValidation(form, pwInput, confirmInput) {
            if (!form || !pwInput || !confirmInput) return;

            // Fehler-Element (Passwort-Mismatch)
            let err = form.querySelector('.hp-password-error');
            if (!err) {
                err = document.createElement('div');
                err.className = 'hp-password-error';
                const confWrap = confirmInput.parentElement;
                const fieldWrapper = confWrap.closest('.hp-form__field') || confWrap.parentNode;
                if(fieldWrapper) {
                     fieldWrapper.parentNode.insertBefore(err, fieldWrapper.nextSibling);
                } else {
                     confWrap.parentNode.insertBefore(err, confWrap.nextSibling);
                }
            }
            
            // NEUES Element: Passwortstärke-Anzeige
            let strengthDiv = form.querySelector('#password-strength');
            if (!strengthDiv) {
                strengthDiv = document.createElement('div');
                strengthDiv.id = 'password-strength';
                
                // Wir finden den Wrapper des Haupt-Passwortfeldes
                const pwWrap = pwInput.parentElement;
                const pwFieldWrapper = pwWrap.closest('.hp-form__field') || pwWrap.parentNode;

                // Position: direkt nach dem Haupt-Passwortfeld-Wrapper
                if(pwFieldWrapper) {
                     pwFieldWrapper.parentNode.insertBefore(strengthDiv, pwFieldWrapper.nextSibling);
                } else {
                     pwWrap.parentNode.insertBefore(strengthDiv, pwWrap.nextSibling);
                }
            }


            function updateVisualState() {
                const pw = pwInput.value || '';
                const cf = confirmInput.value || '';
                
                // 1. Passwort-Stärke prüfen und anzeigen
                const strength = checkPasswordStrength(pw);
                
                // Anzeige der 8-Zeichen-Prüfung + Stärke
                let strengthHtml = '';
                if (pw.length > 0) {
                    const lengthStatus = strength.lengthOk 
                        ? '✅ Mindestens 8 Zeichen' 
                        : '❌ Mindestens 8 Zeichen';
                    
                    strengthHtml = `${lengthStatus} 
                                    <span class="strength-indicator ${strength.strengthClass}">
                                        ${strength.strengthText}
                                    </span>`;
                }
                strengthDiv.innerHTML = strengthHtml;
                

                // 2. Passwort-Mismatch-Prüfung
                const fieldWrapper = confirmInput.closest('.hp-form__field') || confirmInput.parentElement;

                if (pw === '' && cf === '') {
                    confirmInput.setCustomValidity('');
                    err.textContent = '';
                    fieldWrapper.classList.remove('hp-password-mismatch');
                    return;
                }

                if (pw === cf) {
                    confirmInput.setCustomValidity('');
                    err.textContent = '';
                    fieldWrapper.classList.remove('hp-password-mismatch');
                } else {
                    confirmInput.setCustomValidity('Die Passwörter stimmen nicht überein.');
                    err.textContent = 'Die Passwörter stimmen nicht überein.';
                    fieldWrapper.classList.add('hp-password-mismatch');
                }
            }


            // Live-Feedback
            pwInput.addEventListener('input', updateVisualState);
            confirmInput.addEventListener('input', updateVisualState);

            // Beim Submit: blockieren, wenn unterschiedlich oder zu kurz
            form.addEventListener('submit', function (e) {
                const pw = pwInput.value || '';
                const cf = confirmInput.value || '';
                const strength = checkPasswordStrength(pw);

                let shouldPrevent = false;

                if (pw !== cf) {
                    shouldPrevent = true;
                }
                
                // Client-seitige Prüfung der Mindestlänge (muss mit Server-seitig übereinstimmen)
                if (!strength.lengthOk && pw.length > 0) {
                    shouldPrevent = true;
                    // Fügt visuelle Hinweise für die Länge hinzu, falls nicht vorhanden
                    if (!strengthDiv.textContent.includes('Mindestens 8 Zeichen')) {
                         updateVisualState();
                    }
                }
                
                if (shouldPrevent) {
                    e.preventDefault();
                    e.stopPropagation();
                    updateVisualState();

                    // Fokus auf das Feld, das den Fehler verursacht
                    if (pw !== cf) {
                        confirmInput.focus();
                    } else if (!strength.lengthOk) {
                        pwInput.focus();
                    }
                }
            });
            // Initialer Check, falls der Browser Passwörter vorausfüllt
            updateVisualState();
        }

        // Haupt-Init (Unverändert)
        function initRegistrationPasswordFeatures() {
            const forms = Array.from(document.querySelectorAll('form'));

            let registerForm = null;
            for (let f of forms) {
                if (f.querySelector('input[name="password"]') && (f.querySelector('input[name="email"]') || f.querySelector('input[name="username"]'))) {
                    registerForm = f;
                    break;
                }
            }

            if (!registerForm) {
                registerForm = forms.find(f => f.querySelector('input[name="password_confirm"]')) || document.querySelector('form');
                if (!registerForm) return;
            }

            const pwInput = registerForm.querySelector('input[name="password"]');
            const confirmInput = registerForm.querySelector('input[name="password_confirm"]');

            if (!pwInput || !confirmInput) return;

            ensureConfirmPosition(pwInput, confirmInput);
            addToggle(pwInput);
            addToggle(confirmInput);
            addClientValidation(registerForm, pwInput, confirmInput);
        }

        initRegistrationPasswordFeatures();
        setTimeout(initRegistrationPasswordFeatures, 250);
        setTimeout(initRegistrationPasswordFeatures, 700);
        setTimeout(initRegistrationPasswordFeatures, 1500);
    })();
    </script>

    <?php
});

Save

I hope this helps some users!

1 Like

I love when people like yourself join the community!

Thank you for sharing this snippet, as well, @Marcel1987.

2 Likes

Hi,

Thank you for sharing your solutions and contributing to the community! This can be very helpful for others.

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

2 Likes

Hi,

Thanks so much for sharing! It’s great to see our users contributing to the community like this. This will definitely be helpful to others with similar requests.

Hi,

We’ve released this feature, and it’s now available in the official version.

1 Like

I’m glad to read that, and I hope I’ve been able to help some of you.

Thank you for contributing to the improvement.

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.