PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 4.0.0
WP 2FA – Two-factor authentication for WordPress v4.0.0
4.0.0 1.7.1 2.0.0 2.0.1 2.1.0 2.2.0 2.2.1 2.3.0 2.4.0 2.4.1 2.4.2 2.5.0 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.8.0 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0 3.0.1 3.1.0 3.1.1 3.1.1.2 trunk 1.2.0 1.3.0 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.6.0 1.6.1 1.6.2 1.7.0
wp-2fa / includes / classes / Admin / Methods / passkeys / class-passkeys.php
wp-2fa / includes / classes / Admin / Methods / passkeys Last commit date
assets 23 hours ago format 23 hours ago helpers 23 hours ago class-ajax-passkeys.php 23 hours ago class-api-register.php 23 hours ago class-api-signin.php 23 hours ago class-attestation-object.php 23 hours ago class-authenticate-server.php 23 hours ago class-authenticator-data.php 23 hours ago class-byte-buffer.php 23 hours ago class-chor-decoder.php 23 hours ago class-passkeys-endpoints.php 23 hours ago class-passkeys-user-profile.php 23 hours ago class-passkeys-wizard-steps.php 23 hours ago class-passkeys.php 23 hours ago class-pending-2fa-helper.php 23 hours ago class-source-repository.php 23 hours ago class-web-authn-exception.php 23 hours ago class-web-authn.php 23 hours ago index.php 23 hours ago
class-passkeys.php
737 lines
1 <?php
2 /**
3 * Responsible for the Passkeys extension plugin settings
4 *
5 * @package wp2fa
6 * @subpackage passkeys
7 * @since 3.0.0
8 * @copyright 2026 Melapress
9 * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
10 * @link https://wordpress.org/plugins/wp-2fa/
11 */
12
13 declare(strict_types=1);
14
15 namespace WP2FA\Methods;
16
17 use WP2FA\WP2FA;
18 use WP2FA\Admin\User_Profile;
19 use WP2FA\Utils\Settings_Utils;
20 use WP2FA\Passkeys\Ajax_Passkeys;
21 use WP2FA\Admin\Controllers\Methods;
22 use WP2FA\Admin\Helpers\User_Helper;
23 use WP2FA\Admin\Controllers\Settings;
24 use WP2FA\Passkeys\PassKeys_Endpoints;
25 use WP2FA\Admin\Methods\Traits\Providers;
26 use WP2FA\Passkeys\Passkeys_User_Profile;
27 use WP2FA\Extensions\EmailBackup\Email_Backup;
28 use WP2FA\Methods\Wizards\PassKeys_Wizard_Steps;
29 use WP2FA\Extensions\RoleSettings\Role_Settings_Controller;
30
31 defined( 'ABSPATH' ) || exit; // Exit if accessed directly.
32
33 /**
34 * Passkeys settings class
35 */
36 if ( ! class_exists( '\WP2FA\Methods\Passkeys' ) ) {
37
38 /**
39 * Responsible for setting different 2FA Passkeys settings
40 *
41 * @since 3.0.0
42 */
43 class Passkeys {
44
45 use Providers;
46
47 public const PASSKEY_DIR = 'includes' . \DIRECTORY_SEPARATOR . 'classes' . \DIRECTORY_SEPARATOR . 'Admin' . \DIRECTORY_SEPARATOR . 'Methods' . \DIRECTORY_SEPARATOR . 'passkeys';
48
49 public const METHOD_NAME = 'passkeys';
50 public const POLICY_SETTINGS_NAME = 'enable_passkeys';
51
52 public const USER_PROFILE_JS_MODULE = 'wp_2fa_passkeys_user_profile';
53 public const USER_LOGIN_JS_MODULE = 'wp_2fa_passkeys_user_login';
54
55 /**
56 * Is the passkeys method enabled
57 *
58 * @since 3.0.0
59 *
60 * @var bool
61 */
62 private static $enabled = null;
63
64 /**
65 * Inits the main Passkeys class and sets the methods.
66 *
67 * @return void
68 *
69 * @since 3.0.0
70 */
71 public static function init() {
72
73 self::always_init();
74
75 \add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ) );
76 \add_action( WP_2FA_PREFIX . 'shortcode_scripts', array( __CLASS__, 'enqueue_scripts' ), 10, 1 );
77
78 \add_filter( 'script_loader_tag', array( __CLASS__, 'set_script_type_attribute' ), 10, 3 );
79
80 \add_filter( WP_2FA_PREFIX . 'providers_translated_names', array( __CLASS__, 'provider_name_translated' ) );
81
82 \add_filter( WP_2FA_PREFIX . 'default_settings', array( __CLASS__, 'add_default_settings' ) );
83
84 \add_filter( WP_2FA_PREFIX . 'loop_settings', array( __CLASS__, 'settings_loop' ), 10, 1 );
85
86 \add_filter( WP_2FA_PREFIX . 'no_method_enabled', array( __CLASS__, 'return_default_selection' ), 10, 1 );
87
88 \add_filter( WP_2FA_PREFIX . 'save_additional_enabled_methods', array( __CLASS__, 'method_enabled' ), 10, 2 );
89
90 \add_filter( WP_2FA_PREFIX . 'enable_2fa_user_setting', array( __CLASS__, 'enable_2fa_user_setting' ), 10, 3 );
91
92 // Login forms.
93
94 \add_action( 'login_enqueue_scripts', array( __CLASS__, 'enqueue_login_scripts' ) );
95
96 \add_action( 'woocommerce_login_form_end', array( __CLASS__, 'woocommerce_login_form' ) );
97
98 \add_action( 'login_form', array( __CLASS__, 'add_to_admin_login_page' ) );
99
100 // \add_action( WP_2FA_PREFIX . 'white_label_wizard_options', array( __CLASS__, 'white_label_option_labels' ) );
101
102 \add_filter( WP_2FA_PREFIX . 'white_label_default_settings', array( __CLASS__, 'add_whitelabel_settings' ) );
103
104 \add_filter( WP_2FA_PREFIX . 'method_settings_disabled', array( __CLASS__, 'disable_email_backup' ), 10, 3 );
105
106 \add_filter( WP_2FA_PREFIX . 'before_settings_save_roles', array( __CLASS__, 'set_this_method' ), 10, 1 );
107
108 \add_filter( WP_2FA_PREFIX . 'before_settings_save', array( __CLASS__, 'before_settings_save' ), 10, 1 );
109
110 \add_filter( WP_2FA_PREFIX . 'before_remove_role', array( __CLASS__, 'set_this_method' ), 10, 1 );
111
112 // Login forms end.
113
114 // add the Passkeys methods to the list of available methods if enabled.
115 \add_filter(
116 WP_2FA_PREFIX . 'available_2fa_methods',
117 function ( $available_methods ) {
118 if ( ! empty( Settings_Utils::get_setting_role( null, self::POLICY_SETTINGS_NAME ) ) ) {
119 array_push( $available_methods, self::METHOD_NAME );
120 }
121
122 return $available_methods;
123 }
124 );
125
126 PassKeys_Wizard_Steps::init();
127
128 PassKeys_Endpoints::init();
129
130 Passkeys_User_Profile::init();
131
132 Ajax_Passkeys::init();
133 }
134
135 /**
136 * Responsible for providing proper assets on the user profile page.
137 *
138 * @param bool $shortcodes - Whether the scripts are being loaded for shortcodes.
139 *
140 * @return void
141 *
142 * @since 3.0.0
143 */
144 public static function enqueue_scripts( $shortcodes = false ) {
145 if ( ! \is_admin() && \function_exists( 'is_user_logged_in' ) && ! \is_user_logged_in() ) {
146 return;
147 }
148 global $current_screen;
149
150 $woo = '';
151 if ( function_exists( 'wc_get_page_id' ) ) {
152 $woo = (string) \get_permalink( \wc_get_page_id( 'myaccount' ) );
153 }
154
155 if ( ! isset( $current_screen->id ) || ( isset( $current_screen->id ) && ! in_array(
156 $current_screen->id,
157 array(
158 'wp-2fa_page_wp-2fa-settings',
159 'toplevel_page_wp-2fa-policies',
160 ),
161 true
162 ) ) ) {
163 // if ( ! self::is_set() ) {
164 // return;
165 // }
166 }
167
168 if ( ( ( isset( $current_screen->id ) && in_array(
169 $current_screen->id,
170 array(
171 'wp-2fa_page_wp-2fa-settings',
172 'toplevel_page_wp-2fa-policies',
173 'profile',
174 'profile-network',
175 'user-edit',
176 'user-edit-network',
177 ),
178 true
179 ) ) || true === $shortcodes )
180 || ( isset( $_GET['is_initial_setup'] ) && 'true' === $_GET['is_initial_setup'] ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
181 || ( isset( $_SERVER['SCRIPT_NAME'] ) && false !== stripos( \wp_login_url(), \sanitize_text_field( \wp_unslash( $_SERVER['SCRIPT_NAME'] ) ) ) )
182 || ( isset( $_SERVER['REQUEST_URI'] ) && false !== stripos( \wp_login_url(), \sanitize_text_field( \wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) )
183 || ( isset( $_SERVER['REQUEST_URI'] ) && false !== stripos( $woo, \sanitize_text_field( \wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) )
184 ) {
185 if ( false === Settings_Utils::string_to_bool( WP2FA::get_wp2fa_general_setting( 'disable_rest' ) ) ) {
186 \wp_enqueue_script(
187 self::USER_PROFILE_JS_MODULE,
188 \trailingslashit( WP_2FA_URL ) . \trailingslashit( self::PASSKEY_DIR ) . 'assets/js/user-profile.js',
189 array( 'wp-api-fetch', 'wp-dom-ready', 'wp-i18n' ),
190 WP_2FA_VERSION,
191 array( 'in_footer' => true )
192 );
193 } else {
194 \wp_enqueue_script(
195 self::USER_PROFILE_JS_MODULE,
196 \trailingslashit( WP_2FA_URL ) . \trailingslashit( self::PASSKEY_DIR ) . 'assets/js/user-profile-ajax.js',
197 array( 'wp-dom-ready', 'wp-i18n' ),
198 WP_2FA_VERSION,
199 array( 'in_footer' => true )
200 );
201
202 \wp_localize_script(
203 self::USER_PROFILE_JS_MODULE,
204 'wp2faData',
205 array(
206 'ajaxURL' => \admin_url( 'admin-ajax.php' ),
207 )
208 );
209 }
210
211 // Passkeys profile CSS.
212 \wp_enqueue_style(
213 'wp-2fa-passkeys-profile',
214 \trailingslashit( WP_2FA_URL ) . 'css/passkeys/user-profile.css',
215 array(),
216 WP_2FA_VERSION
217 );
218
219 // Passkeys profile modal JS (vanilla, non-module).
220 \wp_enqueue_script(
221 'wp-2fa-passkeys-profile-modal',
222 \trailingslashit( WP_2FA_URL ) . 'js/passkeys/user-profile.js',
223 array( 'wp-dom-ready', 'wp-util' ),
224 WP_2FA_VERSION,
225 array( 'in_footer' => true )
226 );
227
228 \wp_localize_script(
229 'wp-2fa-passkeys-profile-modal',
230 'wp2faPasskeys',
231 array(
232 'nonce' => \wp_create_nonce( 'wp2fa_profile_register' ),
233 'setupTitle' => \esc_html__( 'Set up Passkey Login', 'wp-2fa' ),
234 'setupDescription' => \esc_html__( 'Passkeys replace traditional passwords with a quick and secure login method built right into your device. Depending on your setup, this could be a fingerprint, facial recognition, or another trusted device. Even if your device doesn\'t support biometrics, you can still create and use a passkey. A Passkey is stored safely on your device. So it can\'t be phished, leaked, or reused by hackers.', 'wp-2fa' ),
235 'stepsHeading' => \esc_html__( 'Steps to add a passkey:', 'wp-2fa' ),
236 'step1' => \esc_html__( 'Click the "Add Passkey" button and a pop-up will appear.', 'wp-2fa' ),
237 'step2' => \esc_html__( 'Follow the instructions on the popup', 'wp-2fa' ),
238 'step3' => \esc_html__( 'Specify a name for your passkey and that is it!', 'wp-2fa' ),
239 'cancelLabel' => \esc_html__( 'Cancel and back', 'wp-2fa' ),
240 'usbLabel' => \esc_html__( 'Add a USB Security Key', 'wp-2fa' ),
241 'passkeyLabel' => \esc_html__( 'Add a Passkey', 'wp-2fa' ),
242 'successTitle' => \esc_html__( 'Passkey Added!', 'wp-2fa' ),
243 'successDescription' => \esc_html__( 'The passkey has been added successfully. Give it a name or leave for auto-generated name.', 'wp-2fa' ),
244 'namePlaceholder' => \esc_html__( 'Passkey name', 'wp-2fa' ),
245 'submitLabel' => \esc_html__( 'Continue', 'wp-2fa' ),
246 'nameValidationError' => \esc_html__( 'Only letters, numbers, dashes, underscores, and spaces allowed.', 'wp-2fa' ),
247 )
248 );
249
250 // Print passkey templates in admin footer.
251 \add_action( 'admin_footer', array( '\WP2FA\Passkeys\Passkeys_User_Profile', 'print_passkey_templates' ) );
252 \add_action( 'wp_footer', array( '\WP2FA\Passkeys\Passkeys_User_Profile', 'print_passkey_templates' ) );
253 }
254 }
255
256 /**
257 * Adds login scripts to the login page
258 *
259 * @return void
260 *
261 * @since 3.0.0
262 */
263 public static function enqueue_login_scripts() {
264 if ( \is_user_logged_in() || ! self::is_globally_enabled() ) {
265 return;
266 }
267 if ( false === Settings_Utils::string_to_bool( WP2FA::get_wp2fa_general_setting( 'disable_rest' ) ) ) {
268 \wp_enqueue_script(
269 self::USER_LOGIN_JS_MODULE,
270 \trailingslashit( WP_2FA_URL ) . \trailingslashit( self::PASSKEY_DIR ) . 'assets/js/user-login.js',
271 array( 'wp-api-fetch', 'wp-dom-ready' ),
272 WP_2FA_VERSION,
273 array( 'in_footer' => true )
274 );
275 } else {
276 \wp_enqueue_script(
277 self::USER_LOGIN_JS_MODULE,
278 \trailingslashit( WP_2FA_URL ) . \trailingslashit( self::PASSKEY_DIR ) . 'assets/js/user-login-ajax.js',
279 array( 'jquery', 'wp-dom-ready' ),
280 WP_2FA_VERSION,
281 array( 'in_footer' => true )
282 );
283
284 $variables = array(
285 'ajaxurl' => \admin_url( 'admin-ajax.php' ),
286 );
287 \wp_localize_script( self::USER_LOGIN_JS_MODULE, 'login', $variables );
288 }
289 }
290
291 /**
292 * Add `type="module"` to a script tag
293 *
294 * @param string $tag - Original script tag.
295 * @param string $handle - Handle of the script that's currently being filtered.
296 * @param string $src - Script src attribut.
297 *
298 * @return string Script tag with attribute `type="module"` added.
299 *
300 * @since 3.0.0
301 */
302 public static function set_script_type_attribute( string $tag, string $handle, string $src ): string {
303 if ( self::USER_PROFILE_JS_MODULE !== $handle && self::USER_LOGIN_JS_MODULE !== $handle ) {
304 return $tag;
305 }
306
307 $tag = '<script type="module" src="' . esc_url( $src ) . '" id="' . self::USER_PROFILE_JS_MODULE . '"></script>' . "\n"; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
308
309 return $tag;
310 }
311
312 /**
313 * Adds passkeys provider translatable name
314 *
315 * @param array $providers - Array with all currently supported providers and their translated names.
316 *
317 * @return array
318 *
319 * @since 3.0.0
320 */
321 public static function provider_name_translated( array $providers ) {
322 $providers[ self::METHOD_NAME ] = WP2FA::get_wp2fa_white_label_setting( 'passkeys-option-label', true );
323
324 return $providers;
325 }
326
327 /**
328 * Adds the extension default settings to the main plugin settings
329 *
330 * @param array $default_settings - array with plugin default settings.
331 *
332 * @return array
333 *
334 * @since 3.0.0
335 */
336 public static function add_default_settings( array $default_settings ) {
337 $default_settings[ self::POLICY_SETTINGS_NAME ] = false; // self::POLICY_SETTINGS_NAME;
338
339 return $default_settings;
340 }
341
342 /**
343 * Add extension settings to the loop array
344 *
345 * @param array $loop_settings - Currently available settings array.
346 *
347 * @return array
348 *
349 * @since 3.0.0
350 */
351 public static function settings_loop( array $loop_settings ) {
352 array_push( $loop_settings, self::POLICY_SETTINGS_NAME );
353 array_push( $loop_settings, self::POLICY_SETTINGS_NAME . '_certain_roles' );
354
355 return $loop_settings;
356 }
357
358 /**
359 * Shows the passkeys use button to the main WP login form
360 *
361 * @return void
362 *
363 * @since 3.0.0
364 */
365 public static function add_to_admin_login_page() {
366 if ( \is_user_logged_in() || ! self::is_globally_enabled() ) {
367 return;
368 }
369
370 echo self::load_style(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
371 ?>
372
373 <div class="wp-2fa-login-wrapper" id="wp-2fa-login-wrapper">
374 <div id="errorMessage" class="notice notice-error" style="display: none;"></div>
375 <div id="successMessage" class="notice notice-success" style="display: none;"></div>
376 <button type="button" id="wp-2fa-login-via-passkey" class="button button-large wp-2fa-login-via-passkey">
377 <span id="buttonText"><?php esc_html_e( 'Log in with a passkey', 'wp-2fa' ); ?></span>
378 </button>
379 <div style="display: none;margin-top: 10px;" id="wp-2fa-standard-login-wrapper">
380 <button type="button" id="wp-2fa-login-standard" class="button button-large wp-2fa-login-standard">
381 <span id="buttonText"><?php esc_html_e( 'Log in with your username and password', 'wp-2fa' ); ?></span>
382 </button>
383 </div>
384 </div>
385 <?php
386 }
387
388 /**
389 * Loads the login form stylesheet
390 *
391 * @return string
392 *
393 * @since 3.0.0
394 */
395 public static function load_style(): string {
396 \ob_start();
397 ?>
398 <style>
399 /** Default */
400 .wp-2fa-login-wrapper {
401 text-align: center;
402 display: block;
403 border-top: 1px solid #ccc;
404 padding: 20px 0;
405 }
406
407 .wp-2fa-login-wrapper .notice-error {
408 border-left-color: #d63638;
409 border-right-color: #d63638;
410 }
411
412 .wp-2fa-login-wrapper .notice-success {
413 border-left-color: #358f49;
414 border-right-color: #358f49;
415 }
416
417 /** Short Code */
418 .wp-2fa-login-short-code-wrapper .wp-2fa-login-via-passkey {
419 display: inline-block;
420 font-weight: 400;
421 color: #1e1e1e;
422 text-align: center;
423 vertical-align: middle;
424 user-select: none;
425 background-color: #fff;
426 border: 1px solid #e9e9e9;
427 padding: 0.375rem 0.75rem;
428 font-size: 1rem;
429 line-height: 1.5;
430 transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;
431 }
432
433 .wp-2fa-login-wrapper .notice {
434 text-align: initial;
435 }
436
437 .wp-2fa-login-short-code-wrapper .notice-success {
438 color: #358f49;
439 background-color: #f3fff6;
440 border: 1px solid #d6f3dd;
441 padding: 10px;
442 border-radius: 5px;
443 margin: 15px 0;
444 font-size: 13px;
445 }
446
447 .wp-2fa-login-short-code-wrapper .notice-error {
448 color: #721c24;
449 background-color: #fdf1f2;
450 border: 1px solid #ffdbdf;
451 padding: 10px;
452 border-radius: 5px;
453 margin: 15px 0;
454 font-size: 13px;
455 }
456
457 .wp-2fa-login-short-code-wrapper .wp-2fa-login-via-passkey:hover {
458 background-color: #e2e2e2;
459 border-color: #e2e2e2;
460 }
461
462 .wp-2fa-login-short-code-wrapper .wp-2fa-login-via-passkey:disabled {
463 opacity: 0.65;
464 cursor: not-allowed;
465 }
466
467 /** WooCommerce */
468 .wp-2fa-login-woocommerce-wrapper {
469 text-align: center;
470 margin-top: 50px;
471 display: block;
472 border-top: 1px solid #ccc;
473 padding: 20px 0;
474 margin-block-start: 1em;
475 margin-block-end: 1em;
476 }
477
478 .wp-2fa-login-woocommerce-wrapper .wp-2fa-login-via-passkey {
479 float: unset !important;
480 }
481
482 .wp-2fa-login-woocommerce-wrapper .wp-2fa-login-via-passkey:disabled {
483 opacity: 0.65;
484 cursor: not-allowed;
485 }
486
487 .wp-2fa-login-woocommerce-wrapper .wc-block-components-notice-banner {
488 padding: 10px !important;
489 }
490
491 /* Ultimate Member */
492 .wp-2fa-login-ultimate-member-wrapper .is-error {
493 margin-top: 10px;
494 margin-bottom: 10px;
495 }
496
497 .wp-2fa-login-ultimate-member-wrapper .is-success {
498 margin-top: 10px !important;
499 margin-bottom: 10px !important;
500 }
501 </style>
502 <?php
503
504 return \ob_get_clean();
505 }
506
507 /**
508 * Shows passkey on the woocommerce login form
509 *
510 * @return void
511 *
512 * @since 3.0.0
513 */
514 public static function woocommerce_login_form() {
515 if ( \is_user_logged_in() || ! self::is_globally_enabled() ) {
516 return;
517 }
518
519 self::enqueue_login_scripts();
520
521 echo self::load_style(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
522
523 ?>
524
525 <div class="wp-2fa-login-woocommerce-wrapper" id="wp-2fa-login-woocommerce-wrapper">
526 <div id="errorMessage" class="notice notice-error" style="display: none;"></div>
527 <div id="successMessage" class="notice notice-success" style="display: none;"></div>
528 <button type="button" id="wp-2fa-login-via-passkey" class="woocommerce-button button woocommerce-form-login__submit wp-element-button wp-2fa-login-via-passkey">
529 <span id="buttonText"><?php esc_html_e( 'Log in with a passkey', 'wp-2fa' ); ?></span>
530 </button>
531 <div style="display: none;margin-top: 10px;" id="wp-2fa-standard-login-wrapper">
532 <button type="button" id="wp-2fa-login-standard" class="woocommerce-form-login__submit wp-element-button wp-2fa-login-standard" style="float: unset !important;">
533 <span id="buttonText"><?php esc_html_e( 'Log in with your username and password', 'wp-2fa' ); ?></span>
534 </button>
535 </div>
536 </div>
537 <?php
538 }
539
540 /**
541 * Shows dates in proper format
542 *
543 * @param string|null $datetime - The unix timestamp.
544 *
545 * @return string|null
546 *
547 * @since 3.1.0
548 */
549 public static function get_datetime_from_now( ?string $datetime ) {
550 if ( ! $datetime ) {
551 return null;
552 }
553
554 $human_time_diff = human_time_diff( $datetime );
555
556 // translators: %s represents a human-readable time difference (e.g., "5 minutes").
557 return sprintf( __( '%s ago', 'wp-2fa' ), $human_time_diff );
558 }
559
560 /**
561 * Shows / hides the "Change 2FA settings" button in user profile page.
562 *
563 * @param bool $status - The currently set value.
564 * @param int $user_id - The ID of the user to check for.
565 *
566 * @since 3.1.0
567 */
568 public static function enable_2fa_user_setting( bool $status, $user_id ): bool {
569 if ( true === $status ) {
570
571 $user_role = User_Helper::get_user_role( $user_id );
572 $available_methods = Methods::get_enabled_methods( $user_role );
573
574 if ( $available_methods && isset( $available_methods[ $user_role ] ) && count( $available_methods[ $user_role ] ) === 1 && isset( $available_methods[ $user_role ][ self::METHOD_NAME ] ) ) {
575 return false;
576 }
577 }
578
579 return $status;
580 }
581
582 /**
583 * Shows the Method option label and hint in the White Label settings.
584 *
585 * @return void
586 *
587 * @since 3.1.0
588 */
589 public static function white_label_option_labels() {
590 ?>
591 <strong class="description">
592 <?php
593 echo \esc_html(
594 \wp_sprintf(
595 // translators: Method option label.
596 __( '%s option label', 'wp-2fa' ),
597 WP2FA::get_wp2fa_white_label_setting( 'passkeys-option-label', true )
598 )
599 );
600 ?>
601 </strong>
602 <br>
603 <fieldset>
604 <input type="text" id="passkeys-option-label" name="wp_2fa_white_label[passkeys-option-label]" class="large-text" value="<?php echo \esc_attr( WP2FA::get_wp2fa_white_label_setting( 'passkeys-option-label', true ) ); ?>">
605 </fieldset>
606 <br>
607 <?php
608 }
609
610 /**
611 * Fills up the White Label settings array with the method defaults.
612 *
613 * @param array $default_settings - The array with the collected white label settings.
614 *
615 * @return array
616 *
617 * @since 3.1.0
618 */
619 public static function add_whitelabel_settings( array $default_settings ): array {
620
621 $default_settings['passkeys-option-label'] = __( 'Passkeys', 'wp-2fa' );
622
623 return $default_settings;
624 }
625
626 /**
627 * Disables email backup method if only Passkeys is enabled
628 *
629 * @param boolean $disabled - The current disabled status.
630 * @param string $method - The method to check if the logic needs to fire.
631 * @param string|null $role - The role to check for.
632 *
633 * @return boolean
634 *
635 * @since 3.1.0
636 */
637 public static function disable_email_backup( bool $disabled, string $method, $role = null ): bool {
638 if ( 'WP2FA\Extensions\EmailBackup\Email_Backup' === $method ) {
639 $available_methods = Settings::get_enabled_providers_for_role( (string) $role );
640 if ( $available_methods && count( $available_methods ) === 2 && isset( $available_methods[ self::METHOD_NAME ] ) && isset( $available_methods[ Email_Backup::METHOD_NAME ] ) ) {
641 return true;
642 } elseif ( $available_methods && count( $available_methods ) === 1 && isset( $available_methods[ self::METHOD_NAME ] ) ) {
643 return true;
644 }
645 }
646
647 return $disabled;
648 }
649
650 /**
651 * Sets the Passkeys as a method for the given user.
652 *
653 * @param \WP_User $user - The user for which the method has to be set, if null, it uses the current user.
654 *
655 * @return void
656 *
657 * @since 3.1.0
658 */
659 public static function set_user_method( $user = null ) {
660 if ( null === $user ) {
661 $user = \wp_get_current_user();
662 }
663
664 // User_Helper::set_enabled_method_for_user( self::METHOD_NAME, $user ); .
665 User_Profile::delete_expire_and_enforced_keys( $user->ID );
666 User_Helper::set_user_status( $user );
667 }
668
669 /**
670 * Sets this method in the role settings options before saving.
671 *
672 * @param array $roles_options - The currently set roles options.
673 *
674 * @return array
675 *
676 * @since 3.1.0
677 */
678 public static function set_this_method( array $roles_options ): array {
679 $roles_options_stored = Settings_Utils::get_option( Role_Settings_Controller::SETTINGS_NAME );
680 if ( ! $roles_options_stored || ! is_array( $roles_options_stored ) ) {
681 return $roles_options;
682 }
683
684 $stored_policy_settings = WP2FA::get_policy_settings();
685
686 if ( isset( $stored_policy_settings[ self::POLICY_SETTINGS_NAME ] ) && isset( $stored_policy_settings[ self::POLICY_SETTINGS_NAME . '_certain_roles' ] ) && ! empty( $stored_policy_settings[ self::POLICY_SETTINGS_NAME . '_certain_roles' ] ) ) {
687
688 foreach ( $roles_options_stored as $role => $settings ) {
689 if ( isset( $settings[ self::POLICY_SETTINGS_NAME ] ) && ! empty( $settings[ self::POLICY_SETTINGS_NAME ] ) ) {
690 $roles_options[ $role ][ self::POLICY_SETTINGS_NAME ] = self::POLICY_SETTINGS_NAME;
691 }
692 }
693 } else {
694 foreach ( $roles_options_stored as $role => $settings ) {
695 unset( $roles_options_stored[ $role ][ self::POLICY_SETTINGS_NAME . '_certain_roles' ] );
696 unset( $roles_options_stored[ $role ][ self::POLICY_SETTINGS_NAME ] );
697 }
698
699 foreach ( $roles_options as $role => $settings ) {
700 unset( $roles_options[ $role ][ self::POLICY_SETTINGS_NAME . '_certain_roles' ] );
701 unset( $roles_options[ $role ][ self::POLICY_SETTINGS_NAME ] );
702 }
703 }
704
705 return $roles_options;
706 }
707
708 /**
709 * Before saving the settings, it checks if the Passkeys policy settings are set and saves them.
710 *
711 * @param array $settings - The current settings array.
712 *
713 * @return array
714 *
715 * @since 3.1.0
716 */
717 public static function before_settings_save( array $settings ): array {
718 $stored_policy_settings = WP2FA::get_policy_settings();
719
720 if ( ! $settings[ self::POLICY_SETTINGS_NAME . '_certain_roles' ] && isset( $stored_policy_settings[ self::POLICY_SETTINGS_NAME . '_certain_roles' ] ) ) {
721 $settings[ self::POLICY_SETTINGS_NAME . '_certain_roles' ] = $stored_policy_settings[ self::POLICY_SETTINGS_NAME . '_certain_roles' ];
722 }
723 if ( ! $settings[ self::POLICY_SETTINGS_NAME ] && isset( $stored_policy_settings[ self::POLICY_SETTINGS_NAME ] ) ) {
724 $settings[ self::POLICY_SETTINGS_NAME ] = $stored_policy_settings[ self::POLICY_SETTINGS_NAME ];
725 }
726
727 if ( isset( $settings[ self::POLICY_SETTINGS_NAME ] ) && ! empty( $settings[ self::POLICY_SETTINGS_NAME ] ) ) {
728 $settings[ self::POLICY_SETTINGS_NAME ] = true;
729 } else {
730 $settings[ self::POLICY_SETTINGS_NAME ] = false;
731 }
732
733 return $settings;
734 }
735 }
736 }
737