PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 2.8.0
WP 2FA – Two-factor authentication for WordPress v2.8.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 / class-settings-page.php
wp-2fa / includes / classes / Admin Last commit date
Controllers 1 year ago Fly-Out 1 year ago Helpers 1 year ago Methods 1 year ago SettingsPages 1 year ago Views 1 year ago class-help-contact-us.php 1 year ago class-plugin-updated-notice.php 1 year ago class-premium-features.php 1 year ago class-settings-page.php 1 year ago class-setup-wizard.php 1 year ago class-user-listing.php 1 year ago class-user-notices.php 1 year ago class-user-profile.php 1 year ago class-user-registered.php 1 year ago index.php 1 year ago
class-settings-page.php
540 lines
1 <?php
2 /**
3 * Settings rendering class.
4 *
5 * @package wp2fa
6 * @subpackage settings
7 * @copyright 2024 Melapress
8 * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
9 * @link https://wordpress.org/plugins/wp-2fa/
10 */
11
12 namespace WP2FA\Admin;
13
14 use WP2FA\WP2FA;
15 use WP2FA\Admin\SettingsPages\{
16 Settings_Page_Policies,
17 Settings_Page_General,
18 Settings_Page_Email
19 };
20 use WP2FA\Admin\Controllers\Settings;
21 use WP2FA\Utils\Settings_Utils;
22
23 /**
24 * Class for handling settings
25 */
26 if ( ! class_exists( '\WP2FA\Admin\Settings_Page' ) ) {
27 /**
28 * Class for handling settings
29 */
30 class Settings_Page {
31
32 const TOP_MENU_SLUG = 'wp-2fa-policies';
33
34 /**
35 * Create admin menu entry and settings page
36 */
37 public static function create_settings_admin_menu() {
38 // Create admin menu item.
39 \add_menu_page(
40 \esc_html__( 'WP 2FA', 'wp-2fa' ),
41 \esc_html__( 'WP 2FA', 'wp-2fa' ),
42 'manage_options',
43 self::TOP_MENU_SLUG,
44 null,
45 'data:image/svg+xml;base64,' . base64_encode( file_get_contents( WP_2FA_PATH . 'dist/images/wp-2fa-white-icon20x28.svg' ) ), // phpcs:ignore
46 81
47 );
48
49 \add_submenu_page(
50 self::TOP_MENU_SLUG,
51 \esc_html__( '2FA Policies', 'wp-2fa' ),
52 \esc_html__( '2FA Policies', 'wp-2fa' ),
53 'manage_options',
54 self::TOP_MENU_SLUG,
55 array( \WP2FA\Admin\SettingsPages\Settings_Page_Policies::class, 'render' ),
56 1
57 );
58
59 \add_submenu_page(
60 self::TOP_MENU_SLUG,
61 \esc_html__( 'WP 2FA Settings', 'wp-2fa' ),
62 \esc_html__( 'Settings', 'wp-2fa' ),
63 'manage_options',
64 'wp-2fa-settings',
65 array( \WP2FA\Admin\SettingsPages\Settings_Page_Render::class, 'render' ),
66 2
67 );
68
69 // Register our policy settings.
70 \register_setting(
71 WP_2FA_POLICY_SETTINGS_NAME,
72 WP_2FA_POLICY_SETTINGS_NAME,
73 array( \WP2FA\Admin\SettingsPages\Settings_Page_Policies::class, 'validate_and_sanitize' )
74 );
75
76 // Register our white label settings.
77 \register_setting(
78 WP_2FA_WHITE_LABEL_SETTINGS_NAME,
79 WP_2FA_WHITE_LABEL_SETTINGS_NAME,
80 array( \WP2FA\Admin\SettingsPages\Settings_Page_White_Label::class, 'validate_and_sanitize' )
81 );
82
83 // Register our settings page.
84 \register_setting(
85 WP_2FA_SETTINGS_NAME,
86 WP_2FA_SETTINGS_NAME,
87 array( \WP2FA\Admin\SettingsPages\Settings_Page_General::class, 'validate_and_sanitize' )
88 );
89
90 \register_setting(
91 WP_2FA_EMAIL_SETTINGS_NAME,
92 WP_2FA_EMAIL_SETTINGS_NAME,
93 array( \WP2FA\Admin\SettingsPages\Settings_Page_Email::class, 'validate_and_sanitize' )
94 );
95
96 /**
97 * Fires after the main menu settings are registered.
98 *
99 * @param string - The menu slug.
100 * @param bool - Is that multisite install or not.
101 *
102 * @since 2.0.0
103 */
104 \do_action( WP_2FA_PREFIX . 'after_admin_menu_created', self::TOP_MENU_SLUG, false );
105
106 \add_action( WP_2FA_PREFIX . 'before_plugin_settings', array( __CLASS__, 'check_email' ) );
107 }
108
109 /**
110 * Create admin menu entry and settings page
111 */
112 public static function create_settings_admin_menu_multisite() {
113 // Create admin menu item.
114 \add_menu_page(
115 \esc_html__( 'WP 2FA Settings', 'wp-2fa' ),
116 \esc_html__( 'WP 2FA', 'wp-2fa' ),
117 'manage_options',
118 self::TOP_MENU_SLUG,
119 null,
120 'data:image/svg+xml;base64,' . base64_encode( file_get_contents( WP_2FA_PATH . 'dist/images/wp-2fa-white-icon20x28.svg' ) ), // phpcs:ignore
121 81
122 );
123
124 \add_submenu_page(
125 self::TOP_MENU_SLUG,
126 \esc_html__( '2FA Policies', 'wp-2fa' ),
127 \esc_html__( '2FA Policies', 'wp-2fa' ),
128 'manage_options',
129 self::TOP_MENU_SLUG,
130 array( \WP2FA\Admin\SettingsPages\Settings_Page_Policies::class, 'render' ),
131 1
132 );
133
134 \add_submenu_page(
135 self::TOP_MENU_SLUG,
136 \esc_html__( 'WP 2FA Settings', 'wp-2fa' ),
137 \esc_html__( 'Settings', 'wp-2fa' ),
138 'manage_options',
139 'wp-2fa-settings',
140 array( \WP2FA\Admin\SettingsPages\Settings_Page_Render::class, 'render' ),
141 2
142 );
143
144 /**
145 * Fires after the main menu settings are registered.
146 *
147 * @param string - The menu slug.
148 * @param bool - Is that multisite install or not.
149 *
150 * @since 2.0.0
151 */
152 \do_action( WP_2FA_PREFIX . 'after_admin_menu_created', self::TOP_MENU_SLUG, true );
153 }
154 /**
155 * Send account unlocked notification via email.
156 *
157 * @param int $user_id user ID.
158 *
159 * @return boolean
160 */
161 public static function send_account_unlocked_email( $user_id ) {
162 // Bail if the user has not enabled this email.
163 if ( 'enable_account_unlocked_email' !== WP2FA::get_wp2fa_email_templates( 'send_account_unlocked_email' ) ) {
164 return false;
165 }
166
167 // Grab user data.
168 $user = get_userdata( $user_id );
169 // Grab user email.
170 $email = $user->user_email;
171 // Setup the email contents.
172 $subject = wp_strip_all_tags( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'user_account_unlocked_email_subject' ) ) );
173 $message = wpautop( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'user_account_unlocked_email_body' ), $user_id ) );
174
175 return self::send_email( $email, $subject, $message );
176 }
177
178 /**
179 * Hide settings menu item
180 */
181 public static function hide_settings() {
182 $user = wp_get_current_user();
183
184 // Check we have a user before doing anything else.
185 if ( is_a( $user, '\WP_User' ) ) {
186 if ( ! empty( WP2FA::get_wp2fa_setting( '2fa_settings_last_updated_by' ) ) ) {
187 $main_user = (int) WP2FA::get_wp2fa_setting( '2fa_settings_last_updated_by' );
188 } else {
189 $main_user = get_current_user_id();
190 }
191 if ( ! empty( WP2FA::get_wp2fa_general_setting( 'limit_access' ) ) && $user->ID !== $main_user ) {
192 // Remove admin menu item.
193 remove_submenu_page( 'options-general.php', self::TOP_MENU_SLUG );
194 }
195 }
196 }
197
198 /**
199 * Add unlock user link to user actions.
200 *
201 * @param array $links Default row content.
202 *
203 * @return array
204 * @throws \Freemius_Exception - freemius exception.
205 */
206 public static function add_plugin_action_links( $links ) {
207 // add link to the external free trial page in free version and also in premium version if license is not active.
208 if ( ! function_exists( 'wp2fa_freemius' ) || ! wp2fa_freemius()->has_active_valid_license() ) {
209 $trial_link = 'https://melapress.com/wordpress-2fa/pricing/?utm_source=plugin&utm_medium=link&utm_campaign=wp2fa';
210 $links = array_merge(
211 array(
212 '<a style="font-weight:bold" href="' . $trial_link . '" target="_blank">' . __( 'Upgrade to Premium', 'wp-2fa' ) . '</a>',
213 ),
214 $links
215 );
216 }
217
218 // add link to the plugin settings page.
219 $url = Settings::get_settings_page_link();
220 $links = array_merge(
221 array(
222 '<a href="' . \esc_url( $url ) . '">' . \esc_html__( 'Configure 2FA Settings', 'wp-2fa' ) . '</a>',
223 ),
224 $links
225 );
226
227 return $links;
228 }
229
230 /**
231 * Updates options for multisite
232 *
233 * @return void
234 *
235 * @since 2.0.0
236 */
237 public static function update_wp2fa_network_options() {
238
239 Settings_Page_Policies::update_wp2fa_network_options();
240
241 Settings_Page_General::update_wp2fa_network_options();
242
243 \WP2FA\Admin\SettingsPages\Settings_Page_White_Label::update_wp2fa_network_options();
244
245 /**
246 * Gives the ability for extensions to set their settings in the plugin.
247 *
248 * @since 2.2.0
249 */
250 do_action( WP_2FA_PREFIX . 'update_network_settings' );
251 }
252
253 /**
254 * Handle saving email options to the network main site options.
255 */
256 public static function update_wp2fa_network_email_options() {
257 Settings_Page_Email::update_wp2fa_network_options();
258 }
259
260 /**
261 * These are used instead of add_settings_error which in a network site. Used to show if settings have been updated or failed.
262 */
263 public static function settings_saved_network_admin_notice() {
264 if ( isset( $_GET['wp_2fa_network_settings_updated'] ) && 'true' === $_GET['wp_2fa_network_settings_updated'] ) {
265 ?>
266 <div class="notice notice-success is-dismissible">
267 <p><?php \esc_html_e( '2FA Settings Updated', 'wp-2fa' ); ?></p>
268 <button type="button" class="notice-dismiss">
269 <span class="screen-reader-text"><?php \esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
270 </button>
271 </div>
272 <?php
273 }
274 if ( isset( $_GET['wp_2fa_network_settings_updated'] ) && 'false' === $_GET['wp_2fa_network_settings_updated'] ) { // phpcs:ignore
275 ?>
276 <div class="notice notice-error is-dismissible">
277 <?php
278 if ( isset( $_GET['wp_2fa_network_settings_custom_error_message'] ) ) { // phpcs:ignore
279 $error = \sanitize_text_field( \wp_unslash( $_GET['wp_2fa_network_settings_custom_error_message'] ) );
280 ?>
281 <p><?php echo \esc_attr( \esc_url_raw( \urldecode_deep( $error ) ) ); ?></p>
282 <button type="button" class="notice-dismiss">
283 <span class="screen-reader-text"><?php \esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
284 </button>
285 <?php
286 } else {
287 ?>
288 <p><?php \esc_html_e( 'Please ensure both custom email address and display name are provided.', 'wp-2fa' ); ?></p>
289 <button type="button" class="notice-dismiss">
290 <span class="screen-reader-text"><?php \esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
291 </button>
292 <?php
293 }
294 ?>
295 </div>
296 <?php
297 }
298 if ( isset( $_GET['wp_2fa_network_settings_error'] ) ) { // phpcs:ignore
299 ?>
300 <div class="notice notice-error is-dismissible">
301 <?php
302 $error = \sanitize_text_field( \wp_unslash( $_GET['wp_2fa_network_settings_error'] ) );
303
304 if ( true === \strpos( $error, 'http' ) ) {
305 ?>
306 <p><?php echo \esc_attr( \esc_url_raw( \urldecode_deep( $error ) ) ); ?></p>
307 <?php
308 } else {
309 ?>
310 <p><?php echo \esc_attr( ( $error ) ); ?></p>
311 <?php } ?>
312 <button type="button" class="notice-dismiss">
313 <span class="screen-reader-text"><?php \esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
314 </button>
315 </div>
316 <?php
317 }
318 }
319
320 /**
321 * These are used instead of add_settings_error which in a network site. Used to show if settings have been updated or failed.
322 *
323 * @return void
324 *
325 * @since 2.0.0
326 */
327 public static function settings_saved_admin_notice() {
328 if ( isset( $_GET['page'] ) && 0 === strpos( \sanitize_text_field( \wp_unslash( $_GET['page'] ) ), 'wp-2fa-' ) ) {
329 if ( isset( $_GET['settings-updated'] ) && 'true' === $_GET['settings-updated'] ) {
330 $wp_settings_errors = get_settings_errors();
331
332 if ( count( $wp_settings_errors ) ) {
333 foreach ( $wp_settings_errors as $error ) {
334 ?>
335 <div class="notice notice-<?php echo \esc_attr( $error['type'] ); ?> is-dismissible">
336 <p><?php echo \esc_html( $error['message'] ); ?></p>
337 <button type="button" class="notice-dismiss">
338 <span class="screen-reader-text"><?php \esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
339 </button>
340 </div>
341 <?php
342 }
343 } else {
344 ?>
345 <div class="notice notice-success is-dismissible">
346 <p><?php \esc_html_e( '2FA Settings Updated', 'wp-2fa' ); ?></p>
347 <button type="button" class="notice-dismiss">
348 <span class="screen-reader-text"><?php \esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
349 </button>
350 </div>
351 <?php
352 }
353 }
354 if ( isset( $_GET['settings-updated'] ) && 'false' === $_GET['settings-updated'] ) {
355 ?>
356 <div class="notice notice-error is-dismissible">
357 <p><?php \esc_html_e( 'Please ensure both custom email address and display name are provided.', 'wp-2fa' ); ?></p>
358 <button type="button" class="notice-dismiss">
359 <span class="screen-reader-text"><?php \esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
360 </button>
361 </div>
362 <?php
363 }
364 if ( isset( $_GET['settings_error'] ) ) {
365 ?>
366 <div class="notice notice-error is-dismissible">
367 <p><?php echo \esc_attr( \esc_url_raw( \urldecode_deep( \sanitize_text_field( \wp_unslash( $_GET['settings_error'] ) ) ) ) ); ?></p>
368 <button type="button" class="notice-dismiss">
369 <span class="screen-reader-text"><?php \esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
370 </button>
371 </div>
372 <?php
373 }
374 }
375 }
376
377 /**
378 * Add our custom state to our created page.
379 *
380 * @param array $post_states - array with the post states.
381 * @param WP_Post $post - the WP post.
382 *
383 * @return array
384 */
385 public static function add_display_post_states( $post_states, $post ) {
386 if ( ! empty( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) ) ) {
387 if ( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) === $post->ID ) {
388 $post_states['wp_2fa_page_for_user'] = __( 'WP 2FA User Page', 'wp-2fa' );
389 }
390 }
391
392 return $post_states;
393 }
394
395 /**
396 * Handles sending of an email. It sets necessary header such as content type and custom from email address and name.
397 *
398 * @param string $recipient_email Email address to send message to.
399 * @param string $subject Email subject.
400 * @param string $message Message contents.
401 *
402 * @return bool Whether the email contents were sent successfully.
403 */
404 public static function send_email( $recipient_email, $subject, $message ) {
405
406 // Specify our desired headers.
407 $headers = 'Content-type: text/html;charset=utf-8' . "\r\n";
408
409 if ( 'use-custom-email' === WP2FA::get_wp2fa_email_templates( 'email_from_setting' ) ) {
410 $headers .= 'From: ' . WP2FA::get_wp2fa_email_templates( 'custom_from_display_name' ) . ' <' . WP2FA::get_wp2fa_email_templates( 'custom_from_email_address' ) . '>' . "\r\n";
411 } else {
412
413 $headers .= 'From: wp2fa <' . self::get_default_email_address() . '>' . "\r\n";
414 // $headers .= 'From: ' . get_bloginfo( 'name' ) . ' <' . get_bloginfo( 'admin_email' ) . '>' . "\r\n";
415 }
416
417 // Fire our email.
418 return wp_mail( $recipient_email, stripslashes_deep( html_entity_decode( $subject, ENT_QUOTES, 'UTF-8' ) ), $message, $headers );
419 }
420
421 /**
422 * Builds and returns the default email address used for the "from" email address when email is send
423 *
424 * @return string
425 *
426 * @since 2.6.4
427 */
428 public static function get_default_email_address(): string {
429 $sitename = wp_parse_url( network_home_url(), PHP_URL_HOST );
430 $from_email = 'wp2fa@';
431
432 if ( null !== $sitename ) {
433 if ( str_starts_with( $sitename, 'www.' ) ) {
434 $sitename = substr( $sitename, 4 );
435 }
436
437 $from_email .= $sitename;
438 }
439
440 return $from_email;
441 }
442
443 /**
444 * Turns user roles data in any form and shape to an array of strings.
445 *
446 * @param mixed $value User role names (slugs) as raw value.
447 *
448 * @return string[] List of user role names (slugs).
449 */
450 public static function extract_roles_from_input( $value ) {
451 if ( is_array( $value ) ) {
452 return $value;
453 }
454
455 if ( is_string( $value ) && ! empty( $value ) ) {
456 return explode( ',', $value );
457 }
458
459 return array();
460 }
461
462 /**
463 * Determine if any BG processes are currently running.
464 *
465 * @return int|false Number of jobs.
466 */
467 public static function get_current_number_of_active_bg_processes() {
468 global $wpdb;
469
470 $bg_jobs = $wpdb->get_results( // phpcs:ignore
471 "SELECT option_value FROM $wpdb->options
472 WHERE option_name LIKE '%_2fa_bg_%'"
473 );
474
475 return count( $bg_jobs );
476 }
477
478 /**
479 * Checks the email against the current domain and shows an error message if they do not match.
480 *
481 * @return void
482 *
483 * @since 2.6.0
484 */
485 public static function check_email() {
486 $is_dismissed = (bool) Settings_Utils::get_option( 'dismiss_notice_mail_domain', false );
487 if ( ! $is_dismissed ) {
488 $admin_email = null;
489 if ( 'use-custom-email' === WP2FA::get_wp2fa_email_templates( 'email_from_setting' ) ) {
490 $admin_email = WP2FA::get_wp2fa_email_templates( 'custom_from_email_address' );
491 }
492
493 if ( '' === trim( (string) $admin_email ) ) {
494 $email_settings_url = \esc_url(
495 add_query_arg(
496 array(
497 'page' => 'wp-2fa-settings',
498 'tab' => 'email-settings',
499 ),
500 network_admin_url( 'admin.php' )
501 )
502 );
503 ?>
504 <div class="notice notice-error" style="padding-top: 10px; padding-bottom: 10px;">
505 <p class="description" ><?php \esc_html_e( 'By default, the plugin uses ', 'wp-2fa' ); ?> <b><?php echo \sanitize_email( self::get_default_email_address() ); ?></b> <?php \esc_html_e( 'as the "from address" when sending emails with the 2FA code for users to log in. Do you want to keep using this or change it?', 'wp-2fa' ); ?></p>
506 <p>
507 <a class="button button-primary" href="<?php echo \esc_url( $email_settings_url ); ?>"><?php \esc_html_e( 'Change it', 'wp-2fa' ); ?></a>
508 <a class="button button-secondary 2fa-email-notice" style="margin-left:20px" href="#">
509 <?php \esc_html_e( 'Keep using it', 'wp-2fa' ); ?>
510 </a>
511 </p>
512
513 <?php wp_nonce_field( 'wp2fa_dismiss_notice_mail_domain', 'wp2fa_dismiss_notice_mail_domain', false ); ?>
514 </div>
515 <?php
516 } else {
517 Settings_Utils::update_option( 'dismiss_notice_mail_domain', true );
518 }
519 }
520 }
521
522 /**
523 * Sets the email domain do not match setting as dismissed.
524 *
525 * @return void
526 *
527 * @since 2.6.0
528 */
529 public static function dismiss_notice_mail_domain() {
530 // Verify nonce.
531 if ( isset( $_POST['nonce'] ) && \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_POST['nonce'] ) ), 'wp2fa_dismiss_notice_mail_domain' ) ) {
532 Settings_Utils::update_option( 'dismiss_notice_mail_domain', true );
533 die();
534 }
535
536 die( 'Nonce verification failed!' );
537 }
538 }
539 }
540