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