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 / SettingsPages / class-settings-page-general.php
wp-2fa / includes / classes / Admin / SettingsPages Last commit date
class-settings-page-email.php 1 week ago class-settings-page-general.php 1 week ago class-settings-page-new.php 1 week ago class-settings-page-passkeys.php 1 week ago class-settings-page-policies-new.php 1 week ago class-settings-page-policies.php 1 week ago class-settings-page-render.php 1 week ago class-settings-page-white-label.php 1 week ago class-settings-page-white-labeling-new.php 1 week ago class-setup-wizard-new.php 1 week ago index.php 1 week ago
class-settings-page-general.php
519 lines
1 <?php
2 /**
3 * Generals settings class.
4 *
5 * @package wp2fa
6 * @subpackage settings-pages
7 *
8 * @copyright 2026 Melapress
9 * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
10 *
11 * @see https://wordpress.org/plugins/wp-2fa/
12 */
13
14 namespace WP2FA\Admin\SettingsPages;
15
16 use WP2FA\Admin\Settings_Page;
17 use WP2FA\Utils\Debugging;
18 use WP2FA\Utils\Settings_Utils;
19 use WP2FA\WP2FA;
20
21 /*
22 * General settings tab
23 */
24 if ( ! class_exists( '\WP2FA\Admin\SettingsPages\Settings_Page_General' ) ) {
25 /**
26 * Settings_Page_General - Class for handling general settings.
27 *
28 * @since 2.0.0
29 */
30 class Settings_Page_General {
31 /**
32 * Renders the settings.
33 *
34 * @return void
35 *
36 * @since 2.0.0
37 */
38 public static function render() {
39 if ( ! current_user_can( 'manage_options' ) ) {
40 return;
41 }
42 settings_fields( WP_2FA_SETTINGS_NAME );
43 self::use_new_interface_setting();
44 self::no_method_exists();
45 self::disable_brute_force_settings();
46 self::limit_settings_access();
47 self::disable_rest();
48 self::enable_rest();
49 self::remove_data_upon_uninstall();
50 submit_button( null, 'primary', WP_2FA_SETTINGS_NAME . '[submit]' );
51 }
52
53 /**
54 * Validate options before saving.
55 *
56 * @param array $input The settings array.
57 *
58 * @return array|void
59 *
60 * @since 3.0.0
61 */
62 public static function validate_and_sanitize( $input ) {
63 // Bail if user doesn't have permissions to be here.
64 if ( ! current_user_can( 'manage_options' ) || ! isset( $_POST['action'] ) && ! check_admin_referer( 'wp2fa-step-choose-method' ) ) {
65 return;
66 }
67
68 Debugging::log( 'The following settings will be processed (General): ' . "\n" . wp_json_encode( $input ) );
69
70 $simple_settings_we_can_loop = array(
71 'enable_destroy_session',
72 'limit_access',
73 'enable_rest',
74 'disable_rest',
75 'brute_force_disable',
76 'skip_2fa_for_passkeys',
77 'delete_data_upon_uninstall',
78 'method_invalid_setting',
79 'use_new_interface',
80 );
81
82 /**
83 * Gives the ability to change the default general settings.
84 *
85 * @param array $general_settings - The array with the default settings.
86 *
87 * @since 2.0.0
88 */
89 $simple_settings_we_can_loop = \apply_filters( WP_2FA_PREFIX . 'loop_general_settings', $simple_settings_we_can_loop );
90
91 $settings_to_turn_into_bools = array(
92 'enable_destroy_session',
93 'limit_access',
94 'enable_rest',
95 'disable_rest',
96 'brute_force_disable',
97 'skip_2fa_for_passkeys',
98 'delete_data_upon_uninstall',
99 'use_new_interface',
100 );
101
102 foreach ( $simple_settings_we_can_loop as $simple_setting ) {
103 if ( ! in_array( $simple_setting, $settings_to_turn_into_bools, true ) ) {
104 // Is item is not one of our possible settings we want to turn into a bool, process.
105 $output[ $simple_setting ] = ( isset( $input[ $simple_setting ] ) && ! empty( $input[ $simple_setting ] ) ) ? trim( (string) \sanitize_text_field( $input[ $simple_setting ] ) ) : false;
106 } else {
107 // This item is one we treat as a bool, so process correctly.
108 $output[ $simple_setting ] = ( isset( $input[ $simple_setting ] ) && ! empty( $input[ $simple_setting ] ) ) ? true : false;
109 }
110 }
111
112 if ( true === $output['disable_rest'] ) {
113 $output['enable_rest'] = false;
114 }
115
116 if ( isset( $input['2fa_settings_last_updated_by'] ) && ! empty( $input['2fa_settings_last_updated_by'] ) ) {
117 $policies = WP2FA::get_wp2fa_setting();
118 if ( false === $policies ) {
119 $policies = WP2FA::get_default_settings();
120 }
121 $policies['2fa_settings_last_updated_by'] = (int) get_current_user_id();
122
123 // Temporarily remove the policy sanitize callback so this
124 // targeted update does not re-run the full policy validation
125 // pipeline (which would wipe role-specific settings via
126 // the filter_output_content hook).
127 \remove_filter( 'sanitize_option_' . WP_2FA_POLICY_SETTINGS_NAME, array( Settings_Page_Policies::class, 'validate_and_sanitize' ) );
128
129 WP2FA::update_plugin_settings( $policies );
130
131 \add_filter( 'sanitize_option_' . WP_2FA_POLICY_SETTINGS_NAME, array( Settings_Page_Policies::class, 'validate_and_sanitize' ) );
132 }
133
134 // Remove duplicates from settings errors. We do this as this sanitization callback is actually fired twice, so we end up with duplicates when saving the settings for the FIRST TIME only. The issue is not present once the settings are in the DB as the sanitization wont fire again. For details on this core issue - https://core.trac.wordpress.org/ticket/21989.
135 global $wp_settings_errors;
136 if ( isset( $wp_settings_errors ) ) {
137 $errors = array_map( 'unserialize', array_unique( array_map( 'serialize', $wp_settings_errors ) ) );
138 $wp_settings_errors = $errors; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
139 }
140
141 /**
142 * Filter the values we are about to store in the plugin settings.
143 *
144 * @param array $output - The output array with all the data we will store in the settings.
145 * @param array $input - The input array with all the data we received from the user.
146 *
147 * @since 2.0.0
148 */
149 $output = \apply_filters( WP_2FA_PREFIX . 'filter_output_content_general_settings', $output, $input );
150
151 // We have overridden any defaults by now so can clear this.
152 Settings_Utils::delete_option( WP_2FA_PREFIX . 'default_settings_applied' );
153
154 Debugging::log( 'The following settings are being saved (General): ' . "\n" . wp_json_encode( $output ) );
155
156 return $output;
157 }
158
159 /**
160 * Updates global settings network options.
161 *
162 * @return void
163 *
164 * @since 2.9.0
165 */
166 public static function update_wp2fa_network_options() {
167 if ( ! \current_user_can( 'manage_options' ) ) {
168 \wp_die( \esc_html__( 'You do not have sufficient permissions to access this page.', 'wp-2fa' ) );
169 }
170
171 if ( isset( $_POST[ WP_2FA_SETTINGS_NAME ] ) ) {
172 check_admin_referer( 'wp_2fa_settings-options' );
173 $options = self::validate_and_sanitize( wp_unslash( $_POST[ WP_2FA_SETTINGS_NAME ] ) );
174 $settings_errors = get_settings_errors( WP_2FA_SETTINGS_NAME );
175 if ( ! empty( $settings_errors ) ) {
176 Settings_Page::set_network_admin_notice( 'error', $settings_errors[0]['message'] );
177 // redirect back to our options page.
178 wp_safe_redirect(
179 add_query_arg(
180 array(
181 'page' => 'wp-2fa-settings',
182 ),
183 network_admin_url( 'settings.php' )
184 )
185 );
186 exit;
187 }
188 WP2FA::update_plugin_settings( $options, false, WP_2FA_SETTINGS_NAME );
189
190 Settings_Page::set_network_admin_notice( 'success' );
191 // redirect back to our options page.
192 wp_safe_redirect(
193 add_query_arg(
194 array(
195 'page' => 'wp-2fa-settings',
196 'tab' => 'generic-settings',
197 ),
198 network_admin_url( 'admin.php' )
199 )
200 );
201 exit;
202 }
203 }
204
205 /**
206 * Limit settings setting.
207 *
208 * @return void
209 *
210 * @since 2.0.0
211 */
212 private static function remove_data_upon_uninstall() {
213 ?>
214 <div class="danger-zone-wrapper">
215 <h3><?php \esc_html_e( 'Do you want to delete the plugin data from the database upon uninstall', 'wp-2fa' ); ?></h3>
216 <p class="description">
217 <?php \esc_html_e( 'The plugin saves its settings in the WordPress database. By default the plugin settings are kept in the database so if it is installed again, you do not have to reconfigure the plugin. Enable this setting to delete the plugin settings from the database upon uninstall.', 'wp-2fa' ); ?>
218 </p>
219 <table class="form-table">
220 <tbody>
221 <tr>
222 <th><label for="delete_data"><?php \esc_html_e( 'Delete data', 'wp-2fa' ); ?></label></th>
223 <td>
224 <fieldset>
225 <input type="checkbox" id="delete_data" name="wp_2fa_settings[delete_data_upon_uninstall]" value="delete_data_upon_uninstall"
226 <?php checked( 1, WP2FA::get_wp2fa_general_setting( 'delete_data_upon_uninstall' ), true ); ?>
227 >
228 <?php \esc_html_e( 'Delete data upon uninstall', 'wp-2fa' ); ?>
229 </fieldset>
230 </td>
231 </tr>
232 </tbody>
233 </table>
234 </div>
235 <?php
236 $last_user_to_update_settings = get_current_user_id();
237 ?>
238 <input type="hidden" id="wp-2fa_main_user" name="wp_2fa_settings[2fa_settings_last_updated_by]" value="<?php echo \esc_attr( $last_user_to_update_settings ); ?>">
239 <?php
240 }
241
242 /**
243 * Limit settings access.
244 *
245 * @return void
246 *
247 * @since 2.0.0
248 */
249 private static function limit_settings_access() {
250 ?>
251 <br>
252 <h3><?php \esc_html_e( 'Limit 2FA settings access', 'wp-2fa' ); ?></h3>
253 <p class="description">
254 <?php \esc_html_e( 'Use this setting to hide this plugin configuration area from all other admins.', 'wp-2fa' ); ?>
255 </p>
256 <table class="form-table">
257 <tbody>
258 <tr>
259 <th><label for="limit_access"><?php \esc_html_e( 'Limit access to 2FA settings', 'wp-2fa' ); ?></label></th>
260 <td>
261 <fieldset>
262 <input type="checkbox" id="limit_access" name="wp_2fa_settings[limit_access]" value="limit_access"
263 <?php checked( 1, WP2FA::get_wp2fa_general_setting( 'limit_access' ), true ); ?>
264 >
265 <?php \esc_html_e( 'Hide settings from other administrators', 'wp-2fa' ); ?>
266 </fieldset>
267 </td>
268 </tr>
269 </tbody>
270 </table>
271 <?php
272 }
273
274 /**
275 * Enable REST API
276 *
277 * @return void
278 *
279 * @since 2.9.1
280 */
281 private static function disable_rest() {
282 ?>
283 <br>
284 <h3><?php \esc_html_e( 'Disable the REST API endpoints for 2FA', 'wp-2fa' ); ?></h3>
285 <p class="description">
286 <?php \esc_html_e( 'The WP 2FA REST API endpoints are enabled by default. They are used for integrations and do not impact your website’s performance, functionality, or security. If you prefer, you can disable these endpoints by using this setting.', 'wp-2fa' ); ?>
287 </p>
288 <table class="form-table">
289 <tbody>
290 <tr>
291 <th><label for="disable_rest"><?php \esc_html_e( 'Disable the REST API endpoints for 2FA', 'wp-2fa' ); ?></label></th>
292 <td>
293 <fieldset>
294 <input type="checkbox" id="disable_rest" name="wp_2fa_settings[disable_rest]" value="disable_rest"
295 <?php \checked( true, Settings_Utils::string_to_bool( WP2FA::get_wp2fa_general_setting( 'disable_rest' ) ) ); ?>
296 >
297 <label for="disable_rest"><?php \esc_html_e( 'Disable the REST API endpoints', 'wp-2fa' ); ?></label>
298 </fieldset>
299 </td>
300 </tr>
301 </tbody>
302 </table>
303 <script type="text/javascript">
304 //<![CDATA[
305 jQuery(document).ready(function( $ ) {
306 jQuery( 'body' ).on( 'click', '#disable_rest', function ( e ) {
307 // e.preventDefault();
308 if ( jQuery(this).is(":checked")) {
309 jQuery('#select_verification_method').addClass('disabled');
310 } else {
311 jQuery('#select_verification_method').removeClass('disabled');
312 }
313 });
314 });
315 //]]>
316 </script>
317 <?php
318 }
319
320 /**
321 * Enable REST API
322 *
323 * @return void
324 *
325 * @since 2.9.1
326 */
327 private static function enable_rest() {
328 ?>
329 <br>
330 <div id="select_verification_method" class=<?php echo \esc_attr( true === Settings_Utils::string_to_bool( WP2FA::get_wp2fa_general_setting( 'disable_rest' ) ) ? 'disabled' : '' ); ?>>
331 <h3><?php \esc_html_e( 'Select the 2FA verification mechanism', 'wp-2fa' ); ?></h3>
332 <p class="description">
333 <?php \esc_html_e( 'Choose how WP 2FA verifies the 2FA by default. The native method works for most setups, but you can switch to REST API verification if needed. Only change this setting if you are experiencing issues with the default method.', 'wp-2fa' ); ?>
334 </p>
335 <table class="form-table">
336 <tbody>
337 <tr>
338 <th><label for="enable_rest"><?php \esc_html_e( 'Select the default 2FA verification mechanism', 'wp-2fa' ); ?></label></th>
339 <td>
340 <fieldset>
341 <input type="radio" id="enable_native" name="wp_2fa_settings[enable_rest]" value=""
342 <?php \checked( false, WP2FA::get_wp2fa_general_setting( 'enable_rest' ), true ); ?>
343 >
344 <label for="enable_native"><?php \esc_html_e( 'Native', 'wp-2fa' ); ?></label>
345 <br><input type="radio" id="enable_rest" name="wp_2fa_settings[enable_rest]" value="enable_rest"
346 <?php \checked( true, WP2FA::get_wp2fa_general_setting( 'enable_rest' ), true ); ?>
347 >
348 <label for="enable_rest">
349 <?php \esc_html_e( 'REST API', 'wp-2fa' ); ?></label>
350 </fieldset>
351 </td>
352 </tr>
353 </tbody>
354 </table>
355 </div>
356 <?php
357 }
358
359 /**
360 * Skip 2FA for Passkey logins setting.
361 *
362 * @param string $suffix Suffix for the input names.
363 *
364 * @return void
365 *
366 * @since 3.1.0
367 */
368 public static function skip_2fa_for_passkeys( string $suffix = '' ) {
369 ?>
370 <br>
371 <h3><?php \esc_html_e( 'Additional settings', 'wp-2fa' ); ?></h3>
372 <table class="form-table">
373 <tbody>
374 <tr>
375 <th><label for="skip_2fa_for_passkeys"><?php \esc_html_e( 'Bypass 2FA after login with Passkey', 'wp-2fa' ); ?></label></th>
376 <td>
377 <fieldset>
378 <input type="checkbox" id="skip_2fa_for_passkeys" name="wp_2fa_settings<?php echo esc_attr( $suffix ); ?>[skip_2fa_for_passkeys]" value="skip_2fa_for_passkeys"
379 <?php checked( 1, WP2FA::get_wp2fa_general_setting( 'skip_2fa_for_passkeys' ), true ); ?>
380 >
381 </fieldset>
382 </td>
383 </tr>
384 </tbody>
385 </table>
386 <p class="description">
387 <?php \esc_html_e( 'When this setting is enabled, users who successfully sign in using a Passkey won\'t be asked for an additional 2FA step if they have 2FA already configured.', 'wp-2fa' ); ?>
388 </p>
389
390 <?php
391 }
392
393 /**
394 * Disable brute force protection setting.
395 *
396 * @return void
397 *
398 * @since 2.5.0
399 */
400 private static function disable_brute_force_settings() {
401 ?>
402 <br>
403 <h3><?php \esc_html_e( 'Disable 2FA code brute force protection', 'wp-2fa' ); ?></h3>
404 <p class="description">
405 <?php \esc_html_e( 'When using email and SMS 2FA, the plugin sends the users a new one-time code whenever they enter the wrong code when logging in. This is a security enhancement, a sort of brute force protection. You can disable this feature from the below setting, however, it is not recommended.', 'wp-2fa' ); ?>
406 </p>
407 <table class="form-table">
408 <tbody>
409 <tr>
410 <th><label for="brute_force_disable"><?php \esc_html_e( 'Disable one-time code brute force protection', 'wp-2fa' ); ?></label></th>
411 <td>
412 <fieldset>
413 <input type="checkbox" id="brute_force_disable" name="wp_2fa_settings[brute_force_disable]" value="brute_force_disable"
414 <?php checked( 1, WP2FA::get_wp2fa_general_setting( 'brute_force_disable' ), true ); ?>
415 >
416 </fieldset>
417 </td>
418 </tr>
419 </tbody>
420 </table>
421 <?php
422 }
423
424 /**
425 * Rendering settings when there are no methods.
426 *
427 * @return void
428 *
429 * @since 2.2.0
430 */
431 /**
432 * Use new interface setting.
433 *
434 * @return void
435 *
436 * @since 4.0.0
437 */
438 private static function use_new_interface_setting() {
439 $use_new_interface = WP2FA::get_wp2fa_general_setting( 'use_new_interface' );
440 // Default to enabled for fresh installs (setting not yet in DB).
441 if ( null === $use_new_interface || '' === $use_new_interface ) {
442 $use_new_interface = true;
443 }
444 ?>
445 <h3><?php \esc_html_e( 'Use new interface', 'wp-2fa' ); ?></h3>
446 <p class="description">
447 <?php \esc_html_e( 'Enable this setting to use the new settings and policies interface.', 'wp-2fa' ); ?>
448 </p>
449 <table class="form-table">
450 <tbody>
451 <tr>
452 <th><label for="use_new_interface"><?php \esc_html_e( 'Use new interface', 'wp-2fa' ); ?></label></th>
453 <td>
454 <fieldset>
455 <input type="checkbox" id="use_new_interface" name="wp_2fa_settings[use_new_interface]" value="use_new_interface"
456 <?php \checked( 1, Settings_Utils::string_to_bool( $use_new_interface ), true ); ?>
457 >
458 <?php \esc_html_e( 'Enable the new interface', 'wp-2fa' ); ?>
459 </fieldset>
460 </td>
461 </tr>
462 </tbody>
463 </table>
464 <?php
465 }
466
467 private static function no_method_exists() {
468 ?>
469 <p class="description">
470 <?php
471 printf(
472 // translators: support email.
473 \esc_html__( 'Use this setting below to configure the properties of the two-factor authentication on your website and how users use it. If you have any questions send us an email at %1$s.', 'wp-2fa' ),
474 '<a href="mailto:support@melapress.com">support@melapress.com</a>'
475 );
476 ?>
477 </p>
478 <h3><?php \esc_html_e( 'What should the plugin do if the 2FA method used during a user login is unavailable', 'wp-2fa' ); ?></h3>
479 <p class="description">
480 <?php \esc_html_e( 'There may be cases in which the 2FA service is unavailable when a user is trying to log in. For example, the service is unreachable or there are no credits to complete the action. In this case you can configure the plugin to either block the login process, or allow the user to log in without 2FA authentication.', 'wp-2fa' ); ?>
481 </p>
482 <table class="form-table">
483 <tbody>
484 <tr>
485 <th><label for="no-methods"><?php \esc_html_e( 'Select action', 'wp-2fa' ); ?></label></th>
486 <td>
487 <fieldset class="contains-hidden-inputs" id="no-methods">
488 <label for="login_block">
489 <input type="radio" name="wp_2fa_settings[method_invalid_setting]" id="login_block" value="login_block"
490 <?php
491 $tst = WP2FA::get_wp2fa_general_setting( 'method_invalid_setting', \true );
492
493 if ( ! $tst ) {
494 $tst = WP2FA::get_wp2fa_general_setting( 'method_invalid_setting', \true, \true );
495 }
496
497 checked( $tst, 'login_block' );
498 ?>
499 >
500 <span><?php \esc_html_e( 'Block the login.', 'wp-2fa' ); ?></span>
501 </label>
502
503 <br/>
504 <label for="allow_login_without_method">
505 <input type="radio" name="wp_2fa_settings[method_invalid_setting]" id="allow_login_without_method" value="allow_login_without_method"
506 <?php checked( $tst, 'allow_login_without_method' ); ?>
507 data-unhide-when-checked=".custom-from-inputs">
508 <span><?php \esc_html_e( 'Allow the login without 2FA', 'wp-2fa' ); ?></span>
509 </label>
510 </fieldset>
511 </td>
512 </tr>
513 </tbody>
514 </table>
515 <?php
516 }
517 }
518 }
519