prefix . ADVAPAFO_Passkeys::TABLE_CREDENTIALS; if ( 1 !== preg_match( '/^[A-Za-z0-9_]+$/', $table_name ) ) { return false; } $table = '`' . $table_name . '`'; $count = (int) $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- custom credentials table presence check. $wpdb->prepare( 'SELECT COUNT(*) FROM ' . $table . ' WHERE user_id = %d AND revoked_at IS NULL', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- table identifier is strict-validated via regex. $user_id ) ); return $count > 0; } /** * Ensure profile scripts and styles are available for register/profile UI. * * @return void */ private function enqueue_profile_assets(): void { if ( ! wp_script_is( 'advapafo-profile', 'enqueued' ) ) { wp_enqueue_script( 'advapafo-profile', ADVAPAFO_PLUGIN_URL . 'admin/js/advapafo-profile.js', array(), ADVAPAFO_VERSION, true ); wp_localize_script( 'advapafo-profile', 'ADVAPAFOProfile', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'advapafo_profile' ), 'messages' => array( 'labelPlaceholder' => __( 'e.g. iPhone 15, YubiKey 5', 'advanced-passkey-login' ), 'starting' => __( 'Starting passkey registration…', 'advanced-passkey-login' ), 'success' => __( 'Passkey registered successfully.', 'advanced-passkey-login' ), 'failed' => __( 'Passkey registration failed. Try again.', 'advanced-passkey-login' ), 'notSupported' => __( 'This browser does not support passkeys.', 'advanced-passkey-login' ), 'mobileHint' => __( 'Tip: open this page on your phone to save a passkey to iCloud Keychain or Google Password Manager.', 'advanced-passkey-login' ), 'confirmRevoke' => __( 'Revoke this passkey? You will need to re-register to use it again.', 'advanced-passkey-login' ), 'revokeFailed' => __( 'Failed to revoke passkey.', 'advanced-passkey-login' ), 'limitReached' => __( 'You have reached the maximum number of passkeys. Revoke an existing one to add a new one.', 'advanced-passkey-login' ), ), ) ); } if ( ! wp_style_is( 'advapafo-admin', 'enqueued' ) ) { wp_enqueue_style( 'advapafo-admin', ADVAPAFO_PLUGIN_URL . 'admin/css/advapafo-admin.css', array(), ADVAPAFO_VERSION ); } } /** * Render a full passkey profile/management section. * * @param array $atts Shortcode attributes. * @return string */ public function render_passkey_profile( $atts ): string { if ( ! class_exists( 'ADVAPAFO_Passkeys' ) || ! ADVAPAFO_Passkeys::is_enabled() ) { return ''; } if ( ! is_user_logged_in() ) { return ''; } if ( ! class_exists( 'lbuchs\\WebAuthn\\WebAuthn' ) ) { return ''; } $user = wp_get_current_user(); if ( ! ADVAPAFO_Passkeys::user_is_eligible( $user ) ) { return ''; } $this->enqueue_profile_assets(); $engine = ADVAPAFO_Passkeys::get_instance(); if ( ! $engine ) { return $this->render_register_button( $atts ); } ob_start(); $engine->render_profile_section( $user ); return ob_get_clean(); } /** * Render an onboarding prompt card for users without a passkey. * * @param array $atts Shortcode attributes. * @return string */ public function render_passkey_prompt( $atts ): string { if ( ! class_exists( 'ADVAPAFO_Passkeys' ) || ! ADVAPAFO_Passkeys::is_enabled() ) { return ''; } if ( ! is_user_logged_in() ) { return ''; } if ( ! class_exists( 'lbuchs\\WebAuthn\\WebAuthn' ) ) { return ''; } $user = wp_get_current_user(); if ( ! ADVAPAFO_Passkeys::user_is_eligible( $user ) ) { return ''; } $atts = shortcode_atts( array( 'title' => __( 'Upgrade your account security with a passkey', 'advanced-passkey-login' ), 'message' => __( 'Use Face ID, Touch ID, Windows Hello, or a hardware key for fast passwordless sign-in.', 'advanced-passkey-login' ), 'button_label' => __( 'Register a Passkey', 'advanced-passkey-login' ), 'class' => '', 'force_show' => '0', ), $atts, 'advapafo_passkey_prompt' ); $force_show = in_array( strtolower( (string) $atts['force_show'] ), array( '1', 'true', 'yes' ), true ) && current_user_can( 'manage_options' ); if ( ! $force_show && $this->user_has_active_passkey( (int) $user->ID ) ) { return ''; } $title = sanitize_text_field( $atts['title'] ); $message = sanitize_text_field( $atts['message'] ); $button_label = sanitize_text_field( $atts['button_label'] ); $extra_class = implode( ' ', array_map( 'sanitize_html_class', preg_split( '/\s+/', trim( $atts['class'] ) ) ) ); $register_markup = $this->render_register_button( array( 'label' => $button_label, 'class' => 'advapafo-passkey-prompt-register', ) ); if ( $register_markup === '' ) { return ''; } $wrapper_class = 'advapafo-passkey-prompt' . ( '' !== $extra_class ? ' ' . $extra_class : '' ); $allowed_html = array( 'div' => array( 'class' => true, 'id' => true, 'aria-live' => true, 'aria-describedby'=> true, ), 'span' => array( 'class' => true, 'aria-hidden' => true, ), 'p' => array( 'class' => true, 'id' => true, 'aria-live' => true, ), 'label' => array( 'for' => true, 'class' => true, ), 'input' => array( 'type' => true, 'id' => true, 'class' => true, 'placeholder' => true, 'maxlength' => true, ), 'button' => array( 'type' => true, 'class' => true, 'data-advapafo-passkey-register' => true, 'data-advapafo-passkey-input-id' => true, 'data-advapafo-passkey-message-id'=> true, 'aria-describedby' => true, ), 'svg' => array( 'xmlns' => true, 'width' => true, 'height' => true, 'viewBox' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true, 'aria-hidden' => true, ), 'path' => array( 'd' => true, ), 'section' => array( 'class' => true, 'aria-label' => true, ), 'h3' => array(), 'ul' => array( 'class' => true, 'role' => true, ), 'li' => array(), ); ob_start(); ?>

__( 'Sign in with Passkey', 'advanced-passkey-login' ), 'redirect_to' => '', 'class' => '', 'allow_multiple' => '0', ), $atts, 'advapafo_login_button' ); $allow_multiple = in_array( strtolower( (string) $atts['allow_multiple'] ), array( '1', 'true', 'yes' ), true ); if ( ! $allow_multiple && self::$login_button_rendered ) { return ''; } $label = sanitize_text_field( $atts['label'] ); $redirect_to = '' !== $atts['redirect_to'] ? esc_url( $atts['redirect_to'] ) : ''; $extra_class = implode( ' ', array_map( 'sanitize_html_class', preg_split( '/\s+/', trim( $atts['class'] ) ) ) ); // Enqueue assets if not already queued. if ( ! wp_script_is( 'advapafo-login', 'enqueued' ) ) { wp_enqueue_script( 'advapafo-login', ADVAPAFO_PLUGIN_URL . 'admin/js/advapafo-login.js', array(), ADVAPAFO_VERSION, true ); wp_localize_script( 'advapafo-login', 'ADVAPAFOLogin', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'advapafo_login' ), 'conditionalUi' => class_exists( 'ADVAPAFO_Passkeys' ) && method_exists( 'ADVAPAFO_Passkeys', 'is_conditional_ui_enabled' ) ? ADVAPAFO_Passkeys::is_conditional_ui_enabled() : false, 'messages' => array( 'notSupported' => __( 'Passkeys are unavailable here. Use HTTPS (or localhost) in a passkey-capable browser, or sign in with your password.', 'advanced-passkey-login' ), 'genericError' => __( 'Passkey sign-in failed. Please try again or use your password.', 'advanced-passkey-login' ), 'signingIn' => __( 'Signing in…', 'advanced-passkey-login' ), 'usernamelessUnsupported' => __( 'Username-free passkey sign-in is not available with this browser or authenticator. Enter your username or email, then try passkey sign-in again.', 'advanced-passkey-login' ), ), ) ); } if ( ! wp_style_is( 'advapafo-login', 'enqueued' ) ) { wp_enqueue_style( 'advapafo-login', ADVAPAFO_PLUGIN_URL . 'admin/css/advapafo-admin.css', array(), ADVAPAFO_VERSION ); } $wrapper_class = 'advapafo-shortcode-login-wrap' . ( $extra_class ? ' ' . $extra_class : '' ); $button_style_class = $this->get_button_style_class(); self::$login_button_rendered = true; ob_start(); ?>
__( 'Register a Passkey', 'advanced-passkey-login' ), 'class' => '', ), $atts, 'advapafo_register_button' ); $label = sanitize_text_field( $atts['label'] ); $extra_class = implode( ' ', array_map( 'sanitize_html_class', preg_split( '/\s+/', trim( $atts['class'] ) ) ) ); $this->enqueue_profile_assets(); $wrapper_class = 'advapafo-shortcode-register-wrap' . ( $extra_class ? ' ' . $extra_class : '' ); $instance_id = wp_unique_id( 'advapafo-passkey-register-' ); $input_id = $instance_id . '-label'; $message_id = $instance_id . '-message'; ob_start(); ?>