PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 1.5.2
WP 2FA – Two-factor authentication for WordPress v1.5.2
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 / SettingsPage.php
wp-2fa / includes / classes / Admin Last commit date
SettingsPage.php 5 years ago SetupWizard.php 5 years ago UserNotices.php 5 years ago UserProfile.php 5 years ago UserRegistered.php 5 years ago
SettingsPage.php
2026 lines
1 <?php // phpcs:ignore
2
3 namespace WP2FA\Admin;
4
5 use WP2FA\EmailTemplate;
6 use WP2FA\Utils\UserUtils;
7 use \WP2FA\WP2FA as WP2FA;
8 use \WP2FA\Authenticator\Authentication as Authentication;
9 use \WP2FA\Utils\GenerateModal as GenerateModal;
10 use \WP2FA\BackgroundProcessing\Enforce2FA as Enforce2FA;
11 use \WP2FA\BackgroundProcessing\DeleteGracePeriod as DeleteGracePeriod;
12 use \WP2FA\BackgroundProcessing\RemoveEnabledMethods as RemoveEnabledMethods;
13 use \WP2FA\BackgroundProcessing\RemoveAllUserData as RemoveAllUserData;
14 use \WP2FA\Utils\Debugging as Debugging;
15
16 /**
17 * SettingsPage - Class for handling settings
18 */
19 class SettingsPage {
20
21 /**
22 * Create admin menu entru and settings page
23 */
24 public function create_settings_admin_menu() {
25 // Create sub menu item.
26 add_options_page(
27 esc_html__( 'WP 2FA Settings', 'wp-2fa' ),
28 esc_html__( 'Two-factor Authentication', 'wp-2fa' ),
29 'manage_options',
30 'wp-2fa-settings',
31 array( $this, 'settings_page_render' )
32 );
33
34 // Register our settings page.
35 register_setting(
36 'wp_2fa_settings',
37 'wp_2fa_settings',
38 array( $this, 'validate_and_sanitize' )
39 );
40
41 register_setting(
42 'wp_2fa_email_settings',
43 'wp_2fa_email_settings',
44 array( $this, 'validate_and_sanitize_email' )
45 );
46 }
47
48 /**
49 * Create admin menu entru and settings page
50 */
51 public function create_settings_admin_menu_multisite() {
52 // Create sub menu item.
53 add_submenu_page(
54 'settings.php',
55 esc_html__( 'WP 2FA Settings', 'wp-2fa' ),
56 esc_html__( 'Two-factor Authentication', 'wp-2fa' ),
57 'manage_options',
58 'wp-2fa-settings',
59 array( $this, 'settings_page_render' )
60 );
61 // Register our settings page.
62 register_setting(
63 'wp_2fa_settings',
64 'wp_2fa_settings',
65 array( $this, 'validate_and_sanitize' )
66 );
67
68 register_setting(
69 'wp_2fa_email_settings',
70 'wp_2fa_email_settings',
71 array( $this, 'validate_and_sanitize_email' )
72 );
73 }
74
75 /**
76 * Render the settings
77 */
78 public function settings_page_render() {
79
80 $bg_process_status = $this->get_current_number_of_active_bg_processes();
81
82 $user = wp_get_current_user();
83 if ( ! empty( WP2FA::get_wp2fa_setting( '2fa_settings_last_updated_by' ) ) ) {
84 $main_user = (int) WP2FA::get_wp2fa_setting( '2fa_settings_last_updated_by' );
85 } else {
86 $main_user = '';
87 }
88
89 // Check if new user page has been published.
90 if ( ! empty( get_transient( 'wp_2fa_new_custom_page_created' ) ) ) {
91 delete_transient( 'wp_2fa_new_custom_page_created' );
92 $new_page_id = WP2FA::get_wp2fa_setting( 'custom-user-page-id' );
93 $new_page_permalink = get_permalink( $new_page_id );
94
95 $new_page_modal_content = sprintf(
96 '<h3>%1$s</h3><h4><a target="_blank" href="%2$s">%2$s</a></h4><p>%3$s</p><p>%4$s <strong>%5$s</strong> %6$s</p>',
97 esc_html__( 'The plugin created the 2FA settings page with the URL:', 'wp-2fa' ),
98 esc_url( $new_page_permalink ),
99 esc_html__( 'You can edit this page using the page editor, like you do with all other pages.', 'wp-2fa' ),
100 esc_html__( 'Use the', 'wp-2fa' ),
101 esc_html__( '{2fa_settings_page_url}', 'wp-2fa' ),
102 esc_html__( 'html tag in the email templates to include the URL of the 2FA configuration page when notifying the users to configure two-factor authentication.', 'wp-2fa' )
103 );
104
105 echo GenerateModal::generate_modal(
106 'new-page-created',
107 false,
108 $new_page_modal_content,
109 [
110 '<a href="#" class="modal__btn modal__btn-primary button-primary" data-close-2fa-modal>'. __( 'OK', 'wp-2fa' ) .'</a>',
111 ],
112 true,
113 '560px'
114 );
115 ?>
116 <?php
117 }
118 ?>
119
120 <?php
121 echo GenerateModal::generate_modal(
122 'notify-users',
123 __( 'Notify users?', 'wp-2fa' ),
124 __( 'Would you like to notify all applicable users based on your changes?', 'wp-2fa' ),
125 [
126 '<a href="#" id="send-notification-email" class="modal__btn modal__btn button-secondary">'.__( 'Notify users & save settings', 'wp-2fa' ). '</a>',
127 '<a href="#" id="save-settings" class="modal__btn modal__btn-primary button-primary">'. __( 'Save settings only', 'wp-2fa' ) .'</a>',
128 ],
129 '',
130 '430px'
131 );
132
133 if ( $bg_process_status && $bg_process_status > 0 ) {
134 echo GenerateModal::generate_modal(
135 'bg-processes-running',
136 __( 'Settings are still being propagated.', 'wp-2fa' ),
137 __( 'Some settings are still being applied in the background, you may continue to use your site as normal whilst this takes place.', 'wp-2fa' ) . ' <br> ' . __( 'Number of jobs left', 'wp_2fa' ) . ' <strong><span id="current-active-jobs-count">'. $bg_process_status .'</span></strong>',
138 [
139 '<a href="#" class="modal__btn modal__btn-primary button-primary" data-close-2fa-modal>'. __( 'Continue', 'wp-2fa' ) .'</a>',
140 ],
141 true,
142 '430px'
143 );
144 }
145 ?>
146
147 <div class="wrap wp-2fa-settings-wrapper">
148 <h2><?php esc_html_e( 'WP 2FA Settings', 'wp-2fa' ); ?></h2>
149 <hr>
150 <?php if ( ! empty( WP2FA::get_wp2fa_setting( 'limit_access' ) ) && $main_user !== $user->ID ) { ?>
151
152 <?php
153 echo esc_html__( 'These settings have been disabled by your site administrator, please contact them for further assistance.', 'wp-2fa' );
154 ?>
155
156 <?php } else { ?>
157
158 <div class="nav-tab-wrapper">
159 <a href="<?php echo esc_url( add_query_arg( array( 'page' => 'wp-2fa-settings' ), network_admin_url( 'admin.php' ) ) ); ?>" class="nav-tab <?php echo ! isset( $_REQUEST['tab'] ) ? 'nav-tab-active' : ''; ?>"><?php _e( '2FA Settings', 'wp-2fa' ); ?></a>
160 <a href="
161 <?php
162 echo esc_url(
163 add_query_arg(
164 array(
165 'page' => 'wp-2fa-settings',
166 'tab' => 'email-settings',
167 ),
168 network_admin_url( 'admin.php' )
169 )
170 );
171 ?>
172 " class="nav-tab <?php echo isset( $_REQUEST['tab'] ) && 'email-settings' === $_REQUEST['tab'] ? 'nav-tab-active' : ''; ?>"><?php _e( 'Email Settings & Templates', 'wp-2fa' ); ?></a>
173 </div>
174 <?php
175 if ( ! current_user_can( 'manage_options' ) ) {
176 return;
177 }
178 if ( WP2FA::is_this_multisite() ) {
179 $action = 'edit.php?action=update_wp2fa_network_options';
180 } else {
181 $action = 'options.php';
182 }
183 if ( ! isset( $_REQUEST['tab'] ) || isset( $_REQUEST['tab'] ) && '2fa-settings' === $_REQUEST['tab'] ) :
184 ?>
185 <br/>
186 <?php
187 printf( '<p class="description">%1$s <a href="mailto:support@wpwhitesecurity.com">%2$s</a></p>', esc_html__( 'Use the settings 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', 'wp-2fa' ), esc_html__( 'support@wpwhitesecurity.com', 'wp-2fa' ) );
188 ?>
189 <br/>
190 <?php $total_users = count_users(); ?>
191 <form id="wp-2fa-admin-settings" action='<?php echo esc_attr( $action ); ?>' method='post' autocomplete="off" data-2fa-total-users="<?php echo $total_users['total_users']; ?>">
192 <?php
193 if ( ! current_user_can( 'manage_options' ) ) {
194 return;
195 }
196
197 settings_fields( 'wp_2fa_settings' );
198 $this->select_method_setting();
199 $this->select_enforcement_policy_setting();
200 $this->user_profile_settings();
201 $this->excluded_roles_or_users_setting();
202 if ( WP2FA::is_this_multisite() ) {
203 $this->excluded_network_sites();
204 }
205 $this->grace_period_setting();
206 $this->disable_2fa_removal_setting();
207 $this->limit_settings_access();
208 $this->remove_data_upon_uninstall();
209 submit_button();
210 ?>
211 </form>
212 <?php endif; ?>
213
214 <?php
215 if ( WP2FA::is_this_multisite() ) {
216 $action = 'edit.php?action=update_wp2fa_network_email_options';
217 } else {
218 $action = 'options.php';
219 }
220 ?>
221
222 <?php if ( isset( $_REQUEST['tab'] ) && 'email-settings' === $_REQUEST['tab'] ) : ?>
223 <br/>
224 <?php
225 printf( '<p class="description">%1$s <a href="mailto:support@wpwhitesecurity.com">%2$s</a></p>', esc_html__( 'Use the settings below to configure the emails which are sent to users as part of the 2FA plugin. If you have any questions send us an email at', 'wp-2fa' ), esc_html__( 'support@wpwhitesecurity.com', 'wp-2fa' ) );
226 ?>
227 <br/>
228 <form action='<?php echo esc_attr( $action ); ?>' method='post' autocomplete="off">
229 <?php
230 if ( ! current_user_can( 'manage_options' ) ) {
231 return;
232 }
233
234 settings_fields( 'wp_2fa_email_settings' );
235 $this->email_from_settings();
236 $this->email_settings();
237 submit_button( 'Save email settings and templates' );
238 ?>
239 </form>
240 <?php endif; ?>
241
242 <?php } ?>
243 </div>
244 <?php
245 }
246
247 /**
248 * General settings
249 */
250 private function select_method_setting() {
251 ?>
252 <h3><?php esc_html_e( 'Which two-factor authentication methods can your users use on this website?', 'wp-2fa' ); ?></h3>
253 <p class="description">
254 <?php esc_html_e( 'When you disable one of the below 2FA methods none of your users can use it.', 'wp-2fa' ); ?>
255 </p>
256 <table class="form-table">
257 <tbody>
258 <tr>
259 <th><label for="2fa-method"><?php esc_html_e( 'Select the methods:', 'wp-2fa' ); ?></label></th>
260 <td>
261 <fieldset>
262 <label for="totp">
263 <input type="checkbox" id="totp" name="wp_2fa_settings[enable_totp]" value="enable_totp"
264 <?php checked( 'enable_totp', WP2FA::get_wp2fa_setting( 'enable_totp' ), true ); ?>
265 >
266 <?php esc_html_e( 'one-time code via 2FA App (TOTP) - ', 'wp-2fa' ); ?><a href="https://www.wpwhitesecurity.com/support/kb/configuring-2fa-apps/?utm_source=plugin&utm_medium=referral&utm_campaign=wp2fa&utm_content=settings+pages" target="_blank"><?php esc_html_e( 'complete list of supported 2FA apps.', 'wp-2fa' ); ?></a>
267 </label>
268 <br/>
269 <label for="email">
270 <input type="checkbox" id="hotp" name="wp_2fa_settings[enable_email]" value="enable_email"
271 <?php checked( WP2FA::get_wp2fa_setting( 'enable_email' ), 'enable_email' ); ?>
272 >
273 <?php esc_html_e( 'one-time code via email (HOTP)', 'wp-2fa' ); ?>
274 </label>
275 <br />
276 </fieldset>
277 </td>
278 </tr>
279 </tbody>
280 </table>
281 <?php
282 }
283
284 /**
285 * Policy settings
286 */
287 private function select_enforcement_policy_setting() {
288 ?>
289 <h3><?php esc_html_e( 'Do you want to enforce 2FA for some, or all the users? ', 'wp-2fa' ); ?></h3>
290 <p class="description">
291 <?php esc_html_e( 'When you enforce 2FA the users will be prompted to configure 2FA the next time they login. Users have a grace period for configuring 2FA. You can configure the grace period and also exclude user(s) or role(s) in this settings page. ', 'wp-2fa' ); ?> <a href="https://www.wpwhitesecurity.com/support/kb/configure-2fa-policies-enforce/?utm_source=plugin&utm_medium=referral&utm_campaign=wp2fa&utm_content=settings+pages" target="_blank"><?php esc_html_e( 'Learn more.', 'wp-2fa' ); ?></a>
292 </p>
293 <table class="form-table">
294 <tbody>
295 <tr>
296 <th><label for="enforcement-policy"><?php esc_html_e( 'Enforce 2FA on:', 'wp-2fa' ); ?></label></th>
297 <td>
298 <fieldset class="contains-hidden-inputs">
299 <label for="all-users">
300 <input type="radio" name="wp_2fa_settings[enforcement-policy]" id="all-users" value="all-users"
301 <?php checked( WP2FA::get_wp2fa_setting( 'enforcement-policy' ), 'all-users' ); ?>
302 >
303 <span><?php esc_html_e( 'All users', 'wp-2fa' ); ?></span>
304 </label>
305 <br/>
306
307 <?php if ( WP2FA::is_this_multisite() ): ?>
308 <label for="superadmins-only">
309 <input type="radio" name="wp_2fa_settings[enforcement-policy]" id="superadmins-only" value="superadmins-only"
310 <?php checked( WP2FA::get_wp2fa_setting( 'enforcement-policy' ), 'superadmins-only' ); ?> />
311 <span><?php esc_html_e( 'Only super admins', 'wp-2fa' ); ?></span>
312 </label>
313 <br/>
314 <?php endif; ?>
315
316 <label for="certain-roles-only">
317 <?php $checked = in_array( WP2FA::get_wp2fa_setting( 'enforcement-policy' ), [ 'certain-roles-only', 'certain-users-only' ] ); ?>
318 <input type="radio" name="wp_2fa_settings[enforcement-policy]" id="certain-roles-only" value="certain-roles-only"
319 <?php checked( $checked ); ?>
320 data-unhide-when-checked=".certain-roles-only-inputs, .certain-users-only-inputs">
321 <span><?php esc_html_e( 'Only for specific users and roles', 'wp-2fa' ); ?></span>
322 </label>
323 <fieldset class="hidden certain-users-only-inputs">
324 <br/>
325 <input type="text" id="enforced_users_search" placeholder="<?php esc_html_e( 'Search users', 'wp-2fa' ); ?>">
326 <input type="hidden" id="enforced_users" name="wp_2fa_settings[enforced_users]" value="<?php echo esc_attr( WP2FA::get_wp2fa_setting( 'enforced_users' ) ); ?>">
327 <div id="enforced_users_buttons"></div>
328 </fieldset>
329 <fieldset class="hidden certain-roles-only-inputs">
330 <br/>
331 <input type="text" id="enforced_roles_search" placeholder="<?php esc_html_e( 'Search roles', 'wp-2fa' ); ?>">
332 <input type="hidden" id="enforced_roles" name="wp_2fa_settings[enforced_roles]" value="<?php echo esc_attr( WP2FA::get_wp2fa_setting( 'enforced_roles' ) ); ?>">
333 <div id="enforced_roles_buttons"></div>
334 </fieldset>
335
336 <br/>
337 <label for="do-not-enforce">
338 <input type="radio" name="wp_2fa_settings[enforcement-policy]" id="do-not-enforce" value="do-not-enforce"
339 <?php checked( WP2FA::get_wp2fa_setting( 'enforcement-policy' ), 'do-not-enforce' ); ?>
340 >
341 <span><?php esc_html_e( 'Do not enforce on any users', 'wp-2fa' ); ?></span>
342 </label>
343 <br/>
344 </fieldset>
345 </td>
346 </tr>
347 </tbody>
348 </table>
349 <?php
350 }
351
352 /**
353 * User profile settings
354 */
355 private function user_profile_settings() {
356 ?>
357 <h3><?php esc_html_e( 'Can users access the WordPress dashboard or you have custom profile pages? ', 'wp-2fa' ); ?></h3>
358 <p class="description">
359 <?php esc_html_e( 'If your users do not have access to the WordPress dashboard (because you use custom user profile pages) enable this option. Once enabled, the plugin creates a page which ONLY authenticated users can access to configure their user 2FA settings. A link to this page is sent in the 2FA welcome email.', 'wp-2fa' ); ?></a>
360 </p>
361 <table class="form-table">
362 <tbody>
363 <tr>
364 <th><label for="enforcement-policy"><?php esc_html_e( 'Create custom 2FA settings page', 'wp-2fa' ); ?></label></th>
365 <td>
366 <fieldset>
367 <label class="radio-inline">
368 <input id="use_custom_page" type="radio" name="wp_2fa_settings[create-custom-user-page]" value="yes"
369 <?php checked( WP2FA::get_wp2fa_setting( 'create-custom-user-page' ), 'yes' ); ?>
370 >
371 <?php esc_html_e( 'Yes', 'wp-2fa' ); ?>
372 </label>
373 <label class="radio-inline">
374 <input id="dont_use_custom_page" type="radio" name="wp_2fa_settings[create-custom-user-page]" value="no"
375 <?php checked( WP2FA::get_wp2fa_setting( 'create-custom-user-page' ), 'no' ); ?>
376 <?php checked( WP2FA::get_wp2fa_setting( 'create-custom-user-page' ), '' ); ?>
377 >
378 <?php esc_html_e( 'No', 'wp-2fa' ); ?>
379 </label>
380 </fieldset>
381 </td>
382 </tr>
383 <tr class="custom-user-page-setting disabled">
384 <th><label for="enforcement-policy"><?php esc_html_e( 'Custom 2FA settings page', 'wp-2fa' ); ?></label></th>
385 <td>
386 <fieldset>
387 <?php
388 if ( ! empty( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) ) ) {
389 $custom_slug = get_post_field( 'post_name', get_post( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) ) );
390 } else {
391 $custom_slug = WP2FA::get_wp2fa_setting( 'custom-user-page-url' );
392 }
393
394 $has_error = false;
395 $settings_errors = get_settings_errors( 'wp_2fa_settings' );
396 if (!empty($settings_errors)) {
397 foreach ( $settings_errors as $error ) {
398 if ($error['code'] == 'no_page_slug_provided') {
399 $has_error = true;
400 break;
401 }
402 }
403 }
404
405 ?>
406 <?php esc_html_e( 'Specify a URL for the Custom 2FA settings page URL:', 'wp-2fa' ); ?> <?php echo trailingslashit( get_site_url() ); ?>
407 <input type="text" id="custom-user-page-url" name="wp_2fa_settings[custom-user-page-url]" value="<?php echo sanitize_text_field( $custom_slug ); ?>"<?php if ($has_error): ?> class="error"<?php endif; ?>>
408 </fieldset>
409 <?php
410 if ( ! empty( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) ) ) {
411 $edit_post_link = get_edit_post_link( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) );
412 $view_post_link = get_permalink( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) );
413 ?>
414 <br>
415 <a href="<?php echo esc_url( $edit_post_link ); ?>" target="_blank" class="button button-secondary" style="margin-right: 5px;"><?php esc_html_e( 'Edit Page', 'wp-2fa' ); ?></a> <a href="<?php echo esc_url( $view_post_link ); ?>" target="_blank" class="button button-primary"><?php esc_html_e( 'View Page', 'wp-2fa' ); ?></a>
416 <?php
417 }
418 ?>
419 </td>
420 </tr>
421 </tbody>
422 </table>
423 <?php
424 }
425
426 /**
427 * Role and users exclusion settings
428 */
429 private function excluded_roles_or_users_setting() {
430 ?>
431 <br>
432 <h3><?php esc_html_e( 'Do you want to exclude any users or roles from 2FA? ', 'wp-2fa' ); ?></h3>
433 <p class="description">
434 <?php esc_html_e( 'If you are enforcing 2FA on all users but for some reason you would like to exclude individual user(s) or users with a specific role, you can exclude them below', 'wp-2fa' ); ?>
435 </p>
436 <table class="form-table">
437 <tbody>
438 <tr>
439 <th><label for="enforcement-policy"><?php esc_html_e( 'Exclude the following users', 'wp-2fa' ); ?></label></th>
440 <td>
441 <fieldset>
442 <input type="text" id="excluded_users_search" placeholder="Search user name">
443 <input type="hidden" id="excluded_users" name="wp_2fa_settings[excluded_users]"
444 value="<?php echo sanitize_text_field( WP2FA::get_wp2fa_setting( 'excluded_users' ) ); ?>">
445 <div id="excluded_users_buttons"></div>
446 </fieldset>
447 </td>
448 </tr>
449 <tr>
450 <th><label for="enforcement-policy"><?php esc_html_e( 'Exclude the following roles:', 'wp-2fa' ); ?></label></th>
451 <td>
452 <fieldset>
453 <input type="text" id="excluded_roles_search" placeholder="Search roles">
454 <input type="hidden" id="excluded_roles" name="wp_2fa_settings[excluded_roles]"
455 value="<?php echo sanitize_text_field( WP2FA::get_wp2fa_setting( 'excluded_roles' ) ); ?>">
456 <div id="excluded_roles_buttons"></div>
457 </fieldset>
458 </td>
459 </tr>
460 </tbody>
461 </table>
462 <?php
463 }
464
465 /**
466 * Role and users exclusion settings
467 */
468 private function excluded_network_sites() {
469 ?>
470 <br>
471 <h3><?php esc_html_e( 'Do you want to exclude all the users of a site from 2FA? ', 'wp-2fa' ); ?></h3>
472 <p class="description">
473 <?php esc_html_e( 'If you are enforcing 2FA on all users but for some reason you do not want to enforce it on a specific sub site, specify the sub site name below:', 'wp-2fa' ); ?>
474 </p>
475 <table class="form-table">
476 <tbody>
477 <tr>
478 <th><label for="enforcement-policy"><?php esc_html_e( 'Exclude the following sites', 'wp-2fa' ); ?></label></th>
479 <td>
480 <fieldset>
481 <input type="text" id="excluded_sites_search" placeholder="Search sites in your network">
482 <input type="hidden" id="excluded_sites" name="wp_2fa_settings[excluded_sites]"
483 value="<?php echo sanitize_text_field( WP2FA::get_wp2fa_setting( 'excluded_sites' ) ); ?>">
484 <div id="excluded_sites_buttons"></div>
485 </fieldset>
486 </td>
487 </tr>
488 </tbody>
489 </table>
490 <?php
491 }
492
493 /**
494 * Grace period settings
495 */
496 private function grace_period_setting() {
497 $user = wp_get_current_user();
498
499 $grace_period = (int) WP2FA::get_wp2fa_setting( 'grace-period' );
500 $testing = get_option( 'wp_2fa_test_grace' );
501 if ( '1' === $testing ) {
502 $grace_max = 600;
503 } else {
504 $grace_max = 10;
505 }
506 ?>
507 <br>
508 <h3><?php esc_html_e( 'Should users be asked to setup 2FA instantly or should they have a grace period?', 'wp-2fa' ); ?></h3>
509 <p class="description">
510 <?php esc_html_e( 'When you enforce 2FA on user(s) they have a grace period to configure 2FA. If they fail to configure it within the configured stipulated time, their account will be locked and have to be unlocked manually. Maximum grace period is 10 days.', 'wp-2fa' ); ?> <a href="https://www.wpwhitesecurity.com/support/kb/configure-grace-period-2fa/?utm_source=plugin&utm_medium=referral&utm_campaign=wp2fa&utm_content=settings+pages" target="_blank"><?php esc_html_e( 'Learn more.', 'wp-2fa' ); ?></a>
511 </p>
512
513 <table class="form-table">
514 <tbody>
515 <tr>
516 <th><label for="grace-policy"><?php esc_html_e( 'Grace period:', 'wp-2fa' ); ?></label></th>
517 <td>
518 <fieldset class="contains-hidden-inputs">
519 <label for="no-grace-period">
520 <input type="radio" name="wp_2fa_settings[grace-policy]" id="no-grace-period" value="no-grace-period"
521 <?php checked( WP2FA::get_wp2fa_setting( 'grace-policy' ), 'no-grace-period' ); ?>
522 >
523 <span><?php esc_html_e( 'Users have to configure 2FA straight away.', 'wp-2fa' ); ?></span>
524 </label>
525
526 <br/>
527 <label for="use-grace-period">
528 <input type="radio" name="wp_2fa_settings[grace-policy]" id="use-grace-period" value="use-grace-period"
529 <?php checked( WP2FA::get_wp2fa_setting( 'grace-policy' ), 'use-grace-period' ); ?>
530 data-unhide-when-checked=".grace-period-inputs">
531 <span><?php esc_html_e( 'Give users a grace period to configure 2FA', 'wp-2fa' ); ?></span>
532 </label>
533 <fieldset class="hidden grace-period-inputs">
534 <br/>
535 <input type="number" id="grace-period" name="wp_2fa_settings[grace-period]" value="<?php echo esc_attr( $grace_period ); ?>" min="1" max="<?php echo esc_attr( $grace_max ); ?>">
536 <label class="radio-inline">
537 <input class="js-nested" type="radio" name="wp_2fa_settings[grace-period-denominator]" value="hours"
538 <?php checked( WP2FA::get_wp2fa_setting( 'grace-period-denominator' ), 'hours' ); ?>
539 >
540 <?php esc_html_e( 'Hours', 'wp-2fa' ); ?>
541 </label>
542 <label class="radio-inline">
543 <input class="js-nested" type="radio" name="wp_2fa_settings[grace-period-denominator]" value="days"
544 <?php checked( WP2FA::get_wp2fa_setting( 'grace-period-denominator' ), 'days' ); ?>
545 >
546 <?php esc_html_e( 'Days', 'wp-2fa' ); ?>
547 </label>
548 <?php
549 $testing = get_option( 'wp_2fa_test_grace' );
550 if ( '1' === $testing ) {
551 ?>
552 <label class="radio-inline">
553 <input class="js-nested" type="radio" name="wp_2fa_settings[grace-period-denominator]" value="seconds"
554 <?php checked( WP2FA::get_wp2fa_setting( 'grace-period-denominator' ), 'seconds' ); ?>
555 >
556 <?php esc_html_e( 'Seconds', 'wp-2fa' ); ?>
557 </label>
558 <?php
559 }
560
561 $last_user_to_update_settings = $user->ID;
562
563 ?>
564 <input type="hidden" id="2fa_main_user" name="wp_2fa_settings[2fa_settings_last_updated_by]" value="<?php echo esc_attr( $last_user_to_update_settings ); ?>">
565 </fieldset>
566 <br/>
567 </fieldset>
568 </td>
569 </tr>
570 </tbody>
571 </table>
572
573
574 <h3><?php esc_html_e( 'How often should the plugin check if a user\'s grace period is over?', 'wp-2fa' ); ?></h3>
575 <p class="description">
576 <?php esc_html_e( 'By default the plugin checks if a users grace periods to setup 2FA has passed when the user tries to login. If you would like the plugin to advise the user within an hour, enable the below option to add a cron job that runs every hour.', 'wp-2fa' ); ?>
577 </p>
578 <table class="form-table">
579 <tbody>
580 <tr>
581 <th><label for="grace-period"><?php esc_html_e( 'Enable cron', 'wp-2fa' ); ?></label></th>
582 <td>
583 <fieldset>
584 <input type="checkbox" id="grace-cron" name="wp_2fa_settings[enable_grace_cron]" value="enable_grace_cron"
585 <?php checked( 1, WP2FA::get_wp2fa_setting( 'enable_grace_cron' ), true ); ?>
586 >
587 <?php esc_html_e( 'Use cron job to check grace periods', 'wp-2fa' ); ?>
588 </fieldset>
589 </td>
590 </tr>
591 <tr class="disabled destory-session-setting">
592 <th><label for="destory-session"><?php esc_html_e( 'Destroy session', 'wp-2fa' ); ?></label></th>
593 <td>
594 <fieldset>
595 <input type="checkbox" id="destory-session" name="wp_2fa_settings[enable_destroy_session]" value="enable_destroy_session"
596 <?php checked( 1, WP2FA::get_wp2fa_setting( 'enable_destroy_session' ), true ); ?>
597 >
598 <?php esc_html_e( 'Destroy user session when grace period expires?', 'wp-2fa' ); ?>
599 </fieldset>
600 </td>
601 </tr>
602 </tbody>
603 </table>
604 <?php
605 }
606
607 /**
608 * Disable removal of 2FA settings
609 */
610 private function disable_2fa_removal_setting() {
611 $user = wp_get_current_user();
612 ?>
613 <br>
614 <h3><?php esc_html_e( 'Should users be able to disable 2FA on their user profile?', 'wp-2fa' ); ?></h3>
615 <p class="description">
616 <?php esc_html_e( 'Users can configure and also disable 2FA on their profile by clicking the "Remove 2FA" button. Enable this setting to disable the Remove 2FA button so users cannot disable 2FA from their user profile.', 'wp-2fa' ); ?>
617 </p>
618 <table class="form-table">
619 <tbody>
620 <tr>
621 <th><label for="hide-remove-2fa"><?php esc_html_e( 'Hide the Remove 2FA button', 'wp-2fa' ); ?></label></th>
622 <td>
623 <fieldset>
624 <input type="checkbox" id="hide-remove-2fa" name="wp_2fa_settings[hide_remove_button]" value="hide_remove_button"
625 <?php checked( 1, WP2FA::get_wp2fa_setting( 'hide_remove_button' ), true ); ?>
626 >
627 <?php esc_html_e( 'Hide the Remove 2FA button on user profile pages', 'wp-2fa' ); ?>
628 </fieldset>
629 </td>
630 </tr>
631 </tbody>
632 </table>
633 <?php
634 }
635
636 /**
637 * Limit settings setting
638 */
639 private function limit_settings_access() {
640 ?>
641 <br>
642 <h3><?php esc_html_e( 'Limit 2FA settings access?', 'wp-2fa' ); ?></h3>
643 <p class="description">
644 <?php esc_html_e( 'Use this setting to hide this plugin configuration area from all other admins.', 'wp-2fa' ); ?>
645 </p>
646 <table class="form-table">
647 <tbody>
648 <tr>
649 <th><label for="grace-period"><?php esc_html_e( 'Limited access', 'wp-2fa' ); ?></label></th>
650 <td>
651 <fieldset>
652 <input type="checkbox" id="limit_access" name="wp_2fa_settings[limit_access]" value="limit_access"
653 <?php checked( 1, WP2FA::get_wp2fa_setting( 'limit_access' ), true ); ?>
654 >
655 <?php esc_html_e( 'Hide settings from other administrators', 'wp-2fa' ); ?>
656 </fieldset>
657 </td>
658 </tr>
659 </tbody>
660 </table>
661 <?php
662 }
663
664 /**
665 * Limit settings setting
666 */
667 private function remove_data_upon_uninstall() {
668 ?>
669 <div class="danger-zone-wrapper">
670 <h3><?php esc_html_e( 'Do you want to delete the plugin data from the database upon uninstall?', 'wp-2fa' ); ?></h3>
671 <p class="description">
672 <?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' ); ?>
673 </p>
674 <table class="form-table">
675 <tbody>
676 <tr>
677 <th><label for="delete_data"><?php esc_html_e( 'Delete data', 'wp-2fa' ); ?></label></th>
678 <td>
679 <fieldset>
680 <input type="checkbox" id="elete_data" name="wp_2fa_settings[delete_data_upon_uninstall]" value="delete_data_upon_uninstall"
681 <?php checked( 1, WP2FA::get_wp2fa_setting( 'delete_data_upon_uninstall' ), true ); ?>
682 >
683 <?php esc_html_e( 'Delete data upon uninstall', 'wp-2fa' ); ?>
684 </fieldset>
685 </td>
686 </tr>
687 </tbody>
688 </table>
689 <table class="form-table hidden">
690 <tbody>
691 <tr>
692 <th></th>
693 <td>
694 <fieldset>
695 <input type="checkbox" id="notify_users" name="wp_2fa_settings[notify_users]" value="notify_users">
696 </fieldset>
697 </td>
698 </tr>
699 </tbody>
700 </table>
701 </div>
702 <?php
703 }
704
705 /**
706 * Get all users
707 */
708 public function get_all_users() {
709 // Die if user does not have permission to view.
710 if ( ! current_user_can( 'manage_options' ) ) {
711 die( 'Access Denied.' );
712 }
713 // Filter $_GET array for security.
714 $get_array = filter_input_array( INPUT_GET );
715
716 // Die if nonce verification failed.
717 if ( ! wp_verify_nonce( sanitize_text_field( $get_array['wp_2fa_nonce'] ), 'wp-2fa-settings-nonce' ) ) {
718 die( esc_html__( 'Nonce verification failed.', 'wp-2fa' ) );
719 }
720
721 $users_args = array(
722 'fields' => array( 'ID', 'user_login' ),
723 );
724 if ( WP2FA::is_this_multisite() ) {
725 $users_args['blog_id'] = 0;
726 }
727 $users_data = UserUtils::get_all_user_ids_and_login_names( 'query', $users_args );
728
729 // Create final array which we will fill in below.
730 $users = [];
731
732 foreach ( $users_data as $user ) {
733 if ( strpos( $user['user_login'], $get_array['term'] ) !== false ) {
734 array_push( $users, [
735 'value' => $user['user_login'],
736 'label' => $user['user_login']
737 ]);
738 }
739 }
740
741 echo wp_json_encode( $users );
742 exit;
743 }
744
745 /**
746 * Get all network sites
747 */
748 public function get_all_network_sites() {
749 // Die if user does not have permission to view.
750 if ( ! current_user_can( 'manage_options' ) ) {
751 die( 'Access Denied.' );
752 }
753 // Filter $_GET array for security.
754 $get_array = filter_input_array( INPUT_GET );
755 // Die if nonce verification failed.
756 if ( ! wp_verify_nonce( sanitize_text_field( $get_array['wp_2fa_nonce'] ), 'wp-2fa-settings-nonce' ) ) {
757 die( esc_html__( 'Nonce verification failed.', 'wp-2fa' ) );
758 }
759 // Fetch sites.
760 $sites_found = array();
761
762 foreach ( get_sites() as $site ) {
763 $subsite_id = get_object_vars( $site )['blog_id'];
764 $subsite_name = get_blog_details( $subsite_id )->blogname;
765 $site_details = '';
766 $site_details[ $subsite_id ] = $subsite_name;
767 if ( stripos( $subsite_name, $get_array['term'] ) !== false ) {
768 $site_details = $subsite_name . ':' . $subsite_id;
769 array_push( $sites_found, $site_details );
770 }
771 }
772 echo wp_json_encode( $sites_found );
773 exit;
774 }
775
776 /**
777 * Unlock users accounts if they have overrun grace period
778 *
779 * @param int $user_id User ID.
780 */
781 public function unlock_account( $user_id ) {
782 // Die if user does not have permission to view.
783 if ( ! current_user_can( 'manage_options' ) ) {
784 die( 'Access Denied.' );
785 }
786
787 $grace_period = WP2FA::get_wp2fa_setting( 'grace-period' );
788 $grace_period_denominator = WP2FA::get_wp2fa_setting( 'grace-period-denominator' );
789 $create_a_string = $grace_period . ' ' . $grace_period_denominator;
790 // Turn that string into a time.
791 $grace_expiry = strtotime( $create_a_string );
792
793 // Filter $_GET array for security.
794 $get_array = filter_input_array( INPUT_GET );
795 $nonce = sanitize_text_field( $get_array['wp_2fa_nonce'] );
796
797 // Die if nonce verification failed.
798 if ( ! wp_verify_nonce( $nonce, 'wp-2fa-unlock-account-nonce' ) ) {
799 die( esc_html__( 'Nonce verification failed.', 'wp-2fa' ) );
800 }
801
802 if ( isset( $get_array['user_id'] ) ) {
803 global $wpdb;
804 $wpdb->query(
805 $wpdb->prepare(
806 "
807 DELETE FROM $wpdb->usermeta
808 WHERE user_id = %d
809 AND meta_key IN ( %s, %s )
810 ",
811 [
812 intval( $get_array['user_id'] ),
813 'wp_2fa_user_grace_period_expired',
814 'wp_2fa_locked_account_notification',
815 ]
816 )
817 );
818 $update = update_user_meta( intval( $get_array['user_id'] ), 'wp_2fa_grace_period_expiry', $grace_expiry );
819 $this->send_account_unlocked_email( intval( $get_array['user_id'] ) );
820 add_action( 'admin_notices', array( $this, 'user_unlocked_notice' ) );
821 }
822 }
823
824 /**
825 * Remove user 2fa config
826 *
827 * @param int $user_id User ID.
828 */
829 public function remove_user_2fa( $user_id ) {
830 // Filter $_GET array for security.
831 $get_array = filter_input_array( INPUT_GET );
832 $nonce = sanitize_text_field( $get_array['wp_2fa_nonce'] );
833
834 if ( ! wp_verify_nonce( $nonce, 'wp-2fa-remove-user-2fa-nonce' ) ) {
835 die( esc_html__( 'Nonce verification failed.', 'wp-2fa' ) );
836 }
837
838 if ( isset( $get_array['user_id'] ) ) {
839 $user_id = intval( $get_array['user_id'] );
840 global $wpdb;
841 $wpdb->query(
842 $wpdb->prepare(
843 "DELETE FROM $wpdb->usermeta
844 WHERE user_id = %d
845 AND meta_key LIKE %s",
846 [
847 $user_id,
848 'wp_2fa_%'
849 ]
850 )
851 );
852
853 $is_needed = Authentication::is_user_eligible_for_2fa( $user_id );
854
855 if ( $is_needed ) {
856 if ( 'do-not-enforce' !== WP2FA::get_wp2fa_setting( 'enforcement-policy' ) ) {
857 // Turn inputs into a useable string.
858 $create_a_string = WP2FA::get_wp2fa_setting( 'grace-period' ) . ' ' . WP2FA::get_wp2fa_setting( 'grace-period-denominator' );
859 // Turn that string into a time.
860 $grace_expiry = strtotime( $create_a_string );
861 update_user_meta( $user_id, 'wp_2fa_grace_period_expiry', $grace_expiry );
862 update_user_meta( $user_id, 'wp_2fa_update_nag_dismissed', true );
863 }
864 $grace_policy = WP2FA::get_wp2fa_setting( 'grace-policy' );
865 if ( 'no-grace-period' === $grace_policy ) {
866 update_user_meta( $user_id, 'wp_2fa_user_enforced_instantly', true );
867 // Get sessions for user with ID $user_id.
868 $sessions = \WP_Session_Tokens::get_instance( $user_id );
869 // Log them out.
870 $sessions->destroy_all();
871 }
872 }
873 if ( isset( $get_array['admin_reset'] ) ) {
874 add_action( 'admin_notices', array( $this, 'admin_deleted_2fa_notice' ) );
875 } else {
876 add_action( 'admin_notices', array( $this, 'user_deleted_2fa_notice' ) );
877 }
878 }
879 }
880
881 /**
882 * Send account unlocked notification via email.
883 *
884 * @param int $user_id user ID.
885 *
886 * @return boolean
887 */
888 public static function send_account_unlocked_email( $user_id ) {
889 // Bail if the user has not enabled this email.
890 if ( 'enable_account_unlocked_email' !== WP2FA::get_wp2fa_email_templates( 'send_account_unlocked_email' ) ) {
891 return false;
892 }
893
894 // Grab user data.
895 $user = get_userdata( $user_id );
896 // Grab user email.
897 $email = $user->user_email;
898 // Setup the email contents.
899 $subject = wp_strip_all_tags( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'user_account_unlocked_email_subject' ) ) );
900 $message = wpautop( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'user_account_unlocked_email_body' ), $user_id ) );
901
902 self::send_email($email, $subject, $message);
903 }
904
905 /**
906 * Validate options before saving
907 *
908 * @param array $input The settings array.
909 *
910 * @return array|void
911 */
912 public function validate_and_sanitize( $input ) {
913
914 // Bail if user doesnt have permissions to be here.
915 if ( ! current_user_can( 'manage_options' ) || ! isset( $_POST['action'] ) && ! check_admin_referer( 'wp2fa-step-choose-method' ) ) {
916 return;
917 }
918
919 // Setup args we may need, depending if this is a MS setup or not.
920 $users = array();
921 $users_args = array();
922 if ( WP2FA::is_this_multisite() ) {
923 $users_args['blog_id'] = 0;
924 }
925 $total_users = count_users();
926
927 // Allow user to override batch size.
928 $custom_batch_size = get_site_option( 'wp_2fa_batch_size' );
929 $batch_size = ( ! empty( $custom_batch_size ) && is_int( $custom_batch_size ) ) ? $custom_batch_size : 1000;
930
931 $slices = ceil( $total_users['total_users'] / $batch_size );
932
933 if ( ! isset( $input['enable_totp'] ) && ! isset( $input['enable_email'] ) ) {
934 add_settings_error(
935 'wp_2fa_settings',
936 esc_attr( 'enable_email_settings_error' ),
937 esc_html__( 'At least one 2FA method should be enabled.', 'wp-2fa' ),
938 'error'
939 );
940 }
941
942 $simple_settings_we_can_loop = array(
943 'enable_totp',
944 'enable_email',
945 'enforced_roles',
946 'enforced_users',
947 'excluded_users',
948 'excluded_roles',
949 'excluded_sites',
950 'grace-policy',
951 'notify_users',
952 'enable_grace_cron',
953 'enable_destroy_session',
954 '2fa_settings_last_updated_by',
955 'limit_access',
956 'delete_data_upon_uninstall',
957 'hide_remove_button'
958 );
959
960 $settings_to_turn_into_bools = array(
961 'notify_users',
962 'enable_grace_cron',
963 'enable_destroy_session',
964 'limit_access',
965 'delete_data_upon_uninstall',
966 'hide_remove_button'
967 );
968
969 foreach ( $simple_settings_we_can_loop as $simple_setting ) {
970 if ( ! in_array( $simple_setting, $settings_to_turn_into_bools ) ) {
971 // Is item is not one of our possible settings we want to turn into a bool, process.
972 $output[ $simple_setting ] = ( isset( $input[ $simple_setting ] ) && ! empty( $input[ $simple_setting ] ) ) ? trim( sanitize_text_field( $input[ $simple_setting ] ) ) : false;
973 } else {
974 // This item is one we treat as a bool, so process correctly.
975 $output[ $simple_setting ] = ( isset( $input[ $simple_setting ] ) && ! empty( $input[ $simple_setting ] ) ) ? true : false;
976 }
977 }
978
979 $total_users = $total_users['total_users'];
980 $log_content = __( 'The following setting are being saved: ', 'wp-2fa' ) . "\n" . json_encode( $input ) . "\n" . __( 'Total Users/Batch size: ', 'wp-2fa' ) . $total_users . '/' . $batch_size;
981 Debugging::log( $log_content );
982
983 // Compare current to old value to see if a method which was once enabled, has now been disabled.
984 if ( ! isset( $input['enable_totp'] ) && 'enable_totp' === WP2FA::get_wp2fa_setting( 'enable_totp' ) || ! isset( $input['enable_email'] ) && 'enable_email' === WP2FA::get_wp2fa_setting( 'enable_email' ) ) {
985
986 if ( ! isset( $input['enable_totp'] ) ) {
987 $removing = 'totp';
988 } elseif ( ! isset( $input['enable_email'] ) ) {
989 $removing = 'email';
990 }
991
992 for ( $count = 0; $count < $slices; $count++ ) {
993 $users_args = array(
994 'number' => $batch_size,
995 'offset' => $count * $batch_size,
996 'fields' => array( 'ID' ),
997 'count' => $count,
998 'batch_size' => $batch_size,
999 );
1000 if ( WP2FA::is_this_multisite() ) {
1001 $users_args['blog_id'] = 0;
1002 }
1003
1004 // Get users who have the method we are removeing enabled.
1005 $users = UserUtils::get_all_user_ids_based_on_enabled_2fa_method( $removing, $users_args );
1006
1007 // If we have found users using the method which has been disabled, remove the method.
1008 if ( ! empty( $users ) ) {
1009 $log_content = __( 'RemoveEnabledMethod happening on following IDs: ', 'wp-2fa' ) . "\n" . $users;
1010 Debugging::log( $log_content );
1011
1012 $background_process = new RemoveEnabledMethods();
1013 $item_to_process = array();
1014 $item_to_process['users'] = $users;
1015 $item_to_process['method_to_remove'] = $removing;
1016 $background_process->push_to_queue( $item_to_process );
1017 $background_process->save()->dispatch();
1018 }
1019
1020 }
1021 }
1022
1023 if ( isset( $input['grace-period'] ) ) {
1024 if ( 0 === (int) $input['grace-period'] ) {
1025 add_settings_error(
1026 'wp_2fa_settings',
1027 esc_attr( 'grace_settings_error' ),
1028 esc_html__( 'Grace period must be at least 1 day/hour', 'wp-2fa' ),
1029 'error'
1030 );
1031 $output['grace-period'] = 1;
1032 } else {
1033 $output['grace-period'] = (int) $input['grace-period'];
1034 }
1035 }
1036
1037 if ( isset( $input['grace-period-denominator'] ) && 'days' === $input['grace-period-denominator'] || isset( $input['grace-period-denominator'] ) && 'hours' === $input['grace-period-denominator'] || isset( $input['grace-period-denominator'] ) && 'seconds' === $input['grace-period-denominator'] ) {
1038 $output['grace-period-denominator'] = sanitize_text_field( $input['grace-period-denominator'] );
1039 }
1040
1041 if ( isset( $input['create-custom-user-page'] ) && 'yes' === $input['create-custom-user-page'] || isset( $input['create-custom-user-page'] ) && 'no' === $input['create-custom-user-page'] ) {
1042 $output['create-custom-user-page'] = sanitize_text_field( $input['create-custom-user-page'] );
1043 }
1044
1045 if ( isset( $input['custom-user-page-url'] ) ) {
1046 if ( $input['custom-user-page-url'] !== WP2FA::get_wp2fa_setting( 'custom-user-page-url' ) ) {
1047 if ( ! empty( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) ) ) {
1048 $updated_post = array(
1049 'ID' => WP2FA::get_wp2fa_setting( 'custom-user-page-id' ),
1050 'post_name' => sanitize_title_with_dashes( $input['custom-user-page-url'] ),
1051 );
1052 wp_update_post( $updated_post );
1053 $output['custom-user-page-url'] = sanitize_title_with_dashes( $input['custom-user-page-url'] );
1054 $output['custom-user-page-id'] = WP2FA::get_wp2fa_setting( 'custom-user-page-id' );
1055 } elseif ( 'yes' === $input['create-custom-user-page'] && ! empty( $input['custom-user-page-url'] ) ) {
1056 $output['custom-user-page-url'] = sanitize_title_with_dashes( $input['custom-user-page-url'] );
1057 $create_page = $this->generate_custom_user_profile_page( $output['custom-user-page-url'] );
1058 $output['custom-user-page-id'] = (int) $create_page;
1059 }
1060 } else {
1061 $output['custom-user-page-url'] = sanitize_title_with_dashes( $input['custom-user-page-url'] );
1062 $output['custom-user-page-id'] = WP2FA::get_wp2fa_setting( 'custom-user-page-id' );
1063 }
1064 }
1065
1066 if ( isset( $_REQUEST['page'] ) && 'wp-2fa-setup' !== $_REQUEST['page'] || isset( $_REQUEST['wp_2fa_settings']['create-custom-user-page'] ) ) {
1067
1068 if ( isset( $input['create-custom-user-page'] ) && 'no' === $input['create-custom-user-page'] ) {
1069 $output['custom-user-page-url'] = '';
1070 $output['custom-user-page-id'] = '';
1071 wp_delete_post( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ), true );
1072 }
1073 }
1074
1075 if ( isset( $input['create-custom-user-page'] ) && 'yes' === $input['create-custom-user-page'] && empty( $input['custom-user-page-url'] ) ) {
1076 add_settings_error(
1077 'wp_2fa_settings',
1078 esc_attr( 'no_page_slug_provided' ),
1079 esc_html__( 'You must provide a new page slug.', 'wp-2fa' ),
1080 'error'
1081 );
1082 }
1083
1084 if ( isset( $input['grace-period'] ) && isset( $input['grace-period-denominator'] ) ) {
1085 // Turn inputs into a useable string.
1086 $create_a_string = $output['grace-period'] . ' ' . $output['grace-period-denominator'];
1087 // Turn that string into a time.
1088 $grace_expiry = strtotime( $create_a_string );
1089 $output['grace-period-expiry-time'] = sanitize_text_field( $grace_expiry );
1090 }
1091
1092 // Process main policy.
1093 if ( isset( $input['enforcement-policy'] ) && in_array( $input['enforcement-policy'], [ 'all-users', 'certain-users-only', 'certain-roles-only', 'do-not-enforce', 'superadmins-only' ] ) ) {
1094
1095 // Clear enforced roles/users if setting has changed.
1096 if ( 'all-users' === $input['enforcement-policy'] || 'do-not-enforce' === $input['enforcement-policy'] ) {
1097 $input['enforced_users'] = '';
1098 $input['enforced_roles'] = '';
1099 $output['enforced_users'] = '';
1100 $output['enforced_roles'] = '';
1101 }
1102
1103 $output['enforcement-policy'] = sanitize_text_field( $input['enforcement-policy'] );
1104
1105 if ( 'certain-roles-only' === $input['enforcement-policy'] && empty( $input['enforced_roles'] ) && empty( $input['enforced_users'] ) ) {
1106 add_settings_error(
1107 'wp_2fa_settings',
1108 esc_attr( 'enforced_roles_settings_error' ),
1109 esc_html__( 'You must specify at least one role or user', 'wp-2fa' ),
1110 'error'
1111 );
1112 }
1113
1114 // 2FA is applied to all users.
1115 if ( 'all-users' === $input['enforcement-policy'] ) {
1116
1117 $all_possible_roles = WP2FA::wp_2fa_get_roles();
1118 $excluded_roles = array_filter( explode( ',', strtolower( $output['excluded_roles'] ) ) );
1119
1120 // Determine the roles we actually want to grab from the db.
1121 $roles_to_load = array_diff( array_keys( $all_possible_roles ), $excluded_roles );
1122
1123 $excluded_users = ( ! empty( $output['excluded_users'] ) ) ? explode( ',', substr( $output['excluded_users'], 0, -1 ) ) : [];
1124
1125 for ( $count = 0; $count < $slices; $count++ ) {
1126 $users_args = array(
1127 'number' => $batch_size,
1128 'offset' => $count * $batch_size,
1129 'fields' => array( 'ID' ),
1130 'count' => $count,
1131 'batch_size' => $batch_size,
1132 'role__in' => $roles_to_load,
1133 'excluded_users' => $excluded_users,
1134 'skip_existing_2fa_users' => true,
1135 );
1136 if ( WP2FA::is_this_multisite() ) {
1137 $users_args['blog_id'] = 0;
1138 }
1139
1140 $users = UserUtils::get_all_user_ids( 'query', $users_args );
1141
1142 $log_content = __( 'Policy = All Users. Enforcing 2FA on following IDs', 'wp-2fa' ) . "\n" . $users;
1143 Debugging::log( $log_content );
1144
1145 if ( ! empty( $users ) ) {
1146 $background_process = new Enforce2FA();
1147 $item_to_process = array();
1148 $item_to_process['users'] = $users;
1149 $item_to_process['grace_expiry'] = $grace_expiry;
1150 $item_to_process['grace_policy'] = $output['grace-policy'];
1151 $item_to_process['notify_users'] = $output['notify_users'];
1152 $item_to_process['excluded_users'] = $output['excluded_users'];
1153 $item_to_process['excluded_roles'] = $output['excluded_roles'];
1154 $item_to_process['excluded_sites'] = $output['excluded_sites'];
1155 $background_process->push_to_queue( $item_to_process );
1156 $background_process->save()->dispatch();
1157 }
1158 }
1159 }
1160
1161 // 2FA to be applied to certain roles/users only.
1162 if ( 'certain-roles-only' === $input['enforcement-policy'] ) {
1163
1164 // Remove data from applicable users.
1165 for ( $count = 0; $count < $slices; $count++ ) {
1166 $users_args = array(
1167 'number' => $batch_size,
1168 'offset' => $count * $batch_size,
1169 'fields' => array( 'ID' ),
1170 'count' => $count,
1171 'batch_size' => $batch_size,
1172 );
1173 if ( WP2FA::is_this_multisite() ) {
1174 $users_args['blog_id'] = 0;
1175 }
1176
1177 $users = UserUtils::get_all_user_ids_who_have_wp_2fa_metadata_present( $users_args );
1178
1179 $log_content = __( 'Policy = Certain Users/Roles. Deleting Grace period on following IDs', 'wp-2fa' ) . "\n" . $users;
1180 Debugging::log( $log_content );
1181
1182 if ( ! empty( $users ) ) {
1183 $background_process = new DeleteGracePeriod();
1184 $item_to_process = array();
1185 $item_to_process['users'] = $users;
1186 $background_process->push_to_queue( $item_to_process );
1187 $background_process->save()->dispatch();
1188 }
1189
1190 }
1191
1192 // We will get the IDs of users we want below.
1193 $users_to_enforce = '';
1194
1195 $users_we_want_to_enforce = ( isset( $input['enforced_users'] ) && ! empty( $input['enforced_users'] ) ) ? array_filter( explode( ',', $input['enforced_users'] ) ) : 'none';
1196 $roles_we_want_to_enforce = ( isset( $input['enforced_roles'] ) && ! empty( $input['enforced_roles'] ) ) ? SettingsPage::extract_roles_from_input( $output['enforced_roles'] ) : 'none';
1197
1198 // Enforced users have been provided or setting has changed since last update.
1199 if ( WP2FA::get_wp2fa_setting( 'enforced_users' ) !== $users_we_want_to_enforce ) {
1200
1201 foreach ( $users_we_want_to_enforce as $enforced_user ) {
1202 global $wpdb;
1203 $select = $wpdb->prepare(
1204 "
1205 SELECT ID
1206 FROM $wpdb->users
1207 WHERE user_login = %s
1208 ",
1209 [
1210 $enforced_user
1211 ]
1212 );
1213 $user_data = $wpdb->get_results( $select );
1214 $user = $user_data[0];
1215 $users_to_enforce .= $user->ID . ',';
1216 }
1217
1218 }
1219
1220 // Enforced roles have been provided or setting has changed since last update.
1221 if ( WP2FA::get_wp2fa_setting( 'enforced_roles' ) !== $roles_we_want_to_enforce ) {
1222 for ( $count = 0; $count < $slices; $count++ ) {
1223 $users_args = array(
1224 'number' => $batch_size,
1225 'offset' => $count * $batch_size,
1226 'fields' => array( 'ID' ),
1227 'count' => $count,
1228 'batch_size' => $batch_size,
1229 'role__in' => $roles_we_want_to_enforce
1230 );
1231 if ( WP2FA::is_this_multisite() ) {
1232 $users_args['blog_id'] = 0;
1233 }
1234 $users = UserUtils::get_all_user_ids( 'query', $users_args );
1235 $users_to_enforce .= $users;
1236 }
1237 }
1238
1239 // Remove duplicate IDs.
1240 $users_to_enforce = implode( ',', array_unique( explode( ',', $users_to_enforce ) ) );
1241
1242 $log_content = __( 'Policy = Certain Users/Roles. Enforce 2FA on following IDs', 'wp-2fa' ) . "\n" . $users_to_enforce;
1243 Debugging::log( $log_content );
1244
1245 if ( ! empty( $users_to_enforce ) ) {
1246 $background_process = new Enforce2FA();
1247 $item_to_process = array();
1248 $item_to_process['users'] = $users_to_enforce;
1249 $item_to_process['grace_expiry'] = $grace_expiry;
1250 $item_to_process['grace_policy'] = sanitize_text_field( $input['grace-policy'] );
1251 $item_to_process['notify_users'] = isset( $input['notify_users'] ) ? $input['notify_users'] : false;
1252 $background_process->push_to_queue( $item_to_process );
1253 $background_process->save()->dispatch();
1254 }
1255
1256 }
1257
1258 // 2FA is not enforced on users, wipe data.
1259 if ( 'do-not-enforce' === $input['enforcement-policy'] ) {
1260
1261 for ( $count = 0; $count < $slices; $count++ ) {
1262 $users_args = array(
1263 'number' => $batch_size,
1264 'offset' => $count * $batch_size,
1265 'fields' => array( 'ID' ),
1266 'count' => $count,
1267 'batch_size' => $batch_size,
1268 );
1269 if ( WP2FA::is_this_multisite() ) {
1270 $users_args['blog_id'] = 0;
1271 }
1272
1273 $users = UserUtils::get_all_user_ids_who_have_wp_2fa_metadata_present( $users_args );
1274
1275 $log_content = __( 'Policy = Do not enforce. Remove grace period from following IDs (users not obliged to setup 2FA)', 'wp-2fa' ) . "\n" . $users;
1276 Debugging::log( $log_content );
1277
1278 if ( ! empty( $users ) ) {
1279 $background_process = new DeleteGracePeriod();
1280 $item_to_process = array();
1281 $item_to_process['users'] = $users;
1282 $background_process->push_to_queue( $item_to_process );
1283 $background_process->save()->dispatch();
1284 }
1285 }
1286 }
1287
1288 // If any users are being exluded, delete any wp 2fa data.
1289 if ( isset( $output['excluded_users'] ) && WP2FA::get_wp2fa_setting( 'excluded_users' ) !== $output['excluded_users'] ) {
1290 // Wipe user 2fa data.
1291 $user_array = explode( ',', $output['excluded_users'] );
1292 foreach ( $user_array as $user ) {
1293 if ( ! empty( $user ) ) {
1294 $user_to_wipe = get_user_by( 'login', $user );
1295 global $wpdb;
1296 $wpdb->query(
1297 $wpdb->prepare(
1298 "
1299 DELETE FROM $wpdb->usermeta
1300 WHERE user_id = %d
1301 AND meta_key LIKE %s
1302 ",
1303 [
1304 $user_to_wipe->ID,
1305 'wp_2fa_%'
1306 ]
1307 )
1308 );
1309
1310 }
1311 }
1312 }
1313
1314 if ( isset( $output['excluded_roles'] ) && ! empty( $output['excluded_roles'] ) && WP2FA::get_wp2fa_setting( 'excluded_roles' ) !== $output['excluded_roles'] ) {
1315 $excluded_roles_array = array_filter( explode( ',', strtolower( $output['excluded_roles'] ) ) );
1316
1317 // Flush the old expiry away from ALL users, we will re-apply them based on the current setup at the end of this.
1318 for ( $count = 0; $count < $slices; $count++ ) {
1319 $users_args = array(
1320 'number' => $batch_size,
1321 'offset' => $count * $batch_size,
1322 'fields' => array( 'ID' ),
1323 'count' => $count,
1324 'batch_size' => $batch_size,
1325 'role__in' => $excluded_roles_array,
1326 );
1327 if ( WP2FA::is_this_multisite() ) {
1328 $users_args['blog_id'] = 0;
1329 }
1330
1331 // Get users who are in a role we are excluding.
1332 $users = UserUtils::get_all_user_ids( 'query', $users_args );
1333
1334 $log_content = __( 'We have excluded roles. RemoveAllUserData 2FA on following IDs', 'wp-2fa' ) . "\n" . $users;
1335 Debugging::log( $log_content );
1336
1337 if ( ! empty( $users ) ) {
1338 $background_process = new RemoveAllUserData();
1339 $item_to_process = array();
1340 $item_to_process['users'] = $users;
1341 $background_process->push_to_queue( $item_to_process );
1342 $background_process->save()->dispatch();
1343 }
1344 }
1345 }
1346 }
1347
1348 // 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.
1349 global $wp_settings_errors;
1350 if ( isset( $wp_settings_errors ) ) {
1351 $errors = array_map( 'unserialize', array_unique( array_map( 'serialize', $wp_settings_errors ) ) );
1352 $wp_settings_errors = $errors;
1353 }
1354
1355 $log_content = __( 'Settings saving processes complete', 'wp-2fa' );
1356 Debugging::log( $log_content );
1357
1358 return $output;
1359 }
1360
1361 /**
1362 * Hide settings menu item
1363 */
1364 public function hide_settings() {
1365 $user = wp_get_current_user();
1366
1367 // Check we have a user before doing anything else.
1368 if ( is_a( $user, '\WP_User' ) ) {
1369 $user_id = (int) $user->ID;
1370 if ( ! empty( WP2FA::get_wp2fa_setting( '2fa_settings_last_updated_by' ) ) ) {
1371 $main_user = (int) WP2FA::get_wp2fa_setting( '2fa_settings_last_updated_by' );
1372 } else {
1373 $main_user = '';
1374 }
1375 if ( ! empty( WP2FA::get_wp2fa_setting( 'limit_access' ) ) && $user->ID !== $main_user ) {
1376 // Remove admin menu item.
1377 remove_submenu_page( 'options-general.php', 'wp-2fa-settings' );
1378 }
1379 }
1380 }
1381
1382 /**
1383 * Add unlock user link to user actions.
1384 *
1385 * @param array $links Default row content.
1386 *
1387 * @return array
1388 */
1389 public function add_plugin_action_links( $links ) {
1390
1391 if ( WP2FA::is_this_multisite() ) {
1392 $url = network_admin_url( '/settings.php?page=wp-2fa-settings' );
1393 } else {
1394 $url = admin_url( '/options-general.php?page=wp-2fa-settings' );
1395 }
1396
1397 $links = array_merge(
1398 array(
1399 '<a href="' . esc_url( $url ) . '">' . esc_html__( 'Configure 2FA Settings', 'wp-2fa' ) . '</a>',
1400 ),
1401 $links
1402 );
1403
1404 return $links;
1405
1406 }
1407
1408 /**
1409 * User unlocked notice.
1410 */
1411 public function user_unlocked_notice() {
1412 ?>
1413 <div class="notice notice-success is-dismissible">
1414 <p><?php esc_html_e( 'User account successfully unlocked. User can login again.', 'wp-2fa' ); ?></p>
1415 <button type="button" class="notice-dismiss">
1416 <span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
1417 </button>
1418 </div>
1419 <?php
1420 }
1421
1422 /**
1423 * User deleted 2FA settings notification
1424 */
1425 public function user_deleted_2fa_notice() {
1426 ?>
1427 <div class="notice notice-success is-dismissible">
1428 <p><?php esc_html_e( 'Your 2FA settings have been removed.', 'wp-2fa' ); ?></p>
1429 <button type="button" class="notice-dismiss">
1430 <span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
1431 </button>
1432 </div>
1433 <?php
1434 }
1435
1436 /**
1437 * Admin deleted user 2FA settings notification
1438 */
1439 public function admin_deleted_2fa_notice() {
1440 ?>
1441 <div class="notice notice-success is-dismissible">
1442 <p><?php esc_html_e( 'User 2FA settings have been removed.', 'wp-2fa' ); ?></p>
1443 <button type="button" class="notice-dismiss">
1444 <span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
1445 </button>
1446 </div>
1447 <?php
1448 }
1449
1450 /**
1451 * Semd email to let user know they need to enabled 2FA
1452 *
1453 * @param int $user_id User ID.
1454 */
1455 public static function send_2fa_enforced_email( $user_id, $override_grace_period = '' ) {
1456 // Bail if the user has not enabled this email.
1457 if ( 'enable_enforced_email' !== WP2FA::get_wp2fa_email_templates( 'send_enforced_email' ) ) {
1458 return false;
1459 }
1460
1461 $user_id = (int) $user_id;
1462 // Grab user data.
1463 $user = get_user_by( 'id', $user_id );
1464 // Check if user has any enabled 2FA methods before sending.
1465 $enabled_methods = get_user_meta( $user->ID, 'wp_2fa_enabled_methods', true );
1466 if ( ! empty( $enabled_methods ) ) {
1467 return false;
1468 }
1469
1470 // Grab user email.
1471 $email = $user->user_email;
1472
1473 $subject = wp_strip_all_tags( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'enforced_email_subject' ), $user_id, '', $override_grace_period ) );
1474 $message = wpautop( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'enforced_email_body' ), $user_id, '', $override_grace_period ) );
1475
1476 return self::send_email( $email, $subject, $message );
1477 }
1478
1479 public function update_wp2fa_network_options() {
1480 check_admin_referer( 'wp_2fa_settings-options' );
1481
1482 if ( isset( $_POST['wp_2fa_settings'] ) ) {
1483 $options = $this->validate_and_sanitize( wp_unslash( $_POST['wp_2fa_settings'] ) );
1484 $update_options = update_network_option( null, 'wp_2fa_settings', $options );
1485 }
1486
1487 // redirect back to our options page.
1488 wp_safe_redirect(
1489 add_query_arg(
1490 array(
1491 'page' => 'wp-2fa-settings',
1492 'wp_2fa_network_settings_updated' => 'true',
1493 ),
1494 network_admin_url( 'settings.php' )
1495 )
1496 );
1497 exit;
1498 }
1499
1500 /**
1501 * Handle saving email options to the network main site options.
1502 */
1503 public function update_wp2fa_network_email_options() {
1504 if ( isset( $_POST['email_from_setting'] ) ) {
1505 $options = $this->validate_and_sanitize_email( wp_unslash( $_POST ) );
1506
1507 if ( isset( $_POST['email_from_setting'] ) && 'use-custom-email' === $_POST['email_from_setting'] && isset( $_POST['custom_from_display_name'] ) && empty( $_POST['custom_from_display_name'] ) || isset( $_POST['email_from_setting'] ) && 'use-custom-email' === $_POST['email_from_setting'] && isset( $_POST['custom_from_email_address'] ) && empty( $_POST['custom_from_email_address'] ) ) {
1508 // redirect back to our options page.
1509 wp_safe_redirect(
1510 add_query_arg(
1511 array(
1512 'page' => 'wp-2fa-settings',
1513 'wp_2fa_network_settings_updated' => 'false',
1514 'tab' => 'email-settings',
1515 ),
1516 network_admin_url( 'settings.php' )
1517 )
1518 );
1519 exit;
1520 }
1521
1522 $update_options = update_network_option( null, 'wp_2fa_email_settings', $options );
1523 }
1524
1525 // redirect back to our options page.
1526 wp_safe_redirect(
1527 add_query_arg(
1528 array(
1529 'page' => 'wp-2fa-settings',
1530 'wp_2fa_network_settings_updated' => 'true',
1531 'tab' => 'email-settings',
1532 ),
1533 network_admin_url( 'settings.php' )
1534 )
1535 );
1536 exit;
1537 }
1538
1539 /**
1540 * These are used instead of add_settings_error which in a network site. Used to show if settings have been updated or failed.
1541 */
1542 public function settings_saved_network_admin_notice() {
1543 if ( isset( $_GET['wp_2fa_network_settings_updated'] ) && $_GET['wp_2fa_network_settings_updated'] == 'true' ) :
1544 ?>
1545 <div class="notice notice-success is-dismissible">
1546 <p><?php esc_html_e( '2FA Settings Updated', 'wp-2fa' ); ?></p>
1547 <button type="button" class="notice-dismiss">
1548 <span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
1549 </button>
1550 </div>
1551 <?php
1552 endif;
1553 if ( isset( $_GET['wp_2fa_network_settings_updated'] ) && $_GET['wp_2fa_network_settings_updated'] == 'false' ) :
1554 ?>
1555 <div class="notice notice-error is-dismissible">
1556 <p><?php esc_html_e( 'Please ensure both custom email address and display name are provided.', 'wp-2fa' ); ?></p>
1557 <button type="button" class="notice-dismiss">
1558 <span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
1559 </button>
1560 </div>
1561 <?php
1562 endif;
1563 }
1564
1565 /**
1566 * Email settings
1567 */
1568 private function email_from_settings() {
1569 ?>
1570 <h3><?php esc_html_e( 'Which email address should the plugin use as a from address?', 'wp-2fa' ); ?></h3>
1571 <p class="description">
1572 <?php esc_html_e( 'Use these settings to customize the "from" name and email address for all correspondence sent from our plugin.', 'wp-2fa' ); ?>
1573 </p>
1574 <table class="form-table">
1575 <tbody>
1576 <tr>
1577 <th><label for="2fa-method"><?php esc_html_e( 'From email & name', 'wp-2fa' ); ?></label>
1578 </th>
1579 <td>
1580 <fieldset class="contains-hidden-inputs">
1581 <label for="use-defaults">
1582 <input type="radio" name="email_from_setting" id="use-defaults" value="use-defaults"
1583 <?php checked( WP2FA::get_wp2fa_email_templates( 'email_from_setting' ), 'use-defaults' ); ?>
1584 >
1585 <span><?php esc_html_e( 'Use the email address from the WordPress general settings.', 'wp-2fa' ); ?></span>
1586 </label>
1587
1588 <br/>
1589 <label for="use-custom-email">
1590 <input type="radio" name="email_from_setting" id="use-custom-email" value="use-custom-email"
1591 <?php checked( WP2FA::get_wp2fa_email_templates( 'email_from_setting' ), 'use-custom-email' ); ?>
1592 data-unhide-when-checked=".custom-from-inputs">
1593 <span><?php esc_html_e( 'Use another email address', 'wp-2fa' ); ?></span>
1594 </label>
1595 <fieldset class="hidden custom-from-inputs">
1596 <br/>
1597 <span><?php esc_html_e( 'Email Address:', 'wp-2fa' ); ?></span> <input type="text" id="custom_from_email_address" name="custom_from_email_address" value="<?php echo WP2FA::get_wp2fa_email_templates( 'custom_from_email_address' ); ?>"><br><br>
1598 <span><?php esc_html_e( 'Display Name:', 'wp-2fa' ); ?></span> <input type="text" id="custom_from_display_name" name="custom_from_display_name" value="<?php echo WP2FA::get_wp2fa_email_templates( 'custom_from_display_name' ); ?>">
1599 </fieldset>
1600
1601 </fieldset>
1602 </td>
1603 </tr>
1604 </tbody>
1605 </table>
1606
1607 <br>
1608 <hr>
1609
1610 <h3><?php esc_html_e( 'Email delivery test', 'wp-2fa' ); ?></h3>
1611 <p class="description">
1612 <?php esc_html_e( 'The plugin sends emails to notify users to setup 2FA when the policies are enabled, to send the one-time codes and more. Use the button below to confirm the plugin can successfully send emails.', 'wp-2fa' ); ?>
1613 </p>
1614 <p>
1615 <button type="button" name="test_email_config_test"
1616 class="button js-button-test-email-trigger"
1617 data-email-id="config_test"
1618 data-nonce="<?php echo esc_attr( wp_create_nonce( 'wp-2fa-email-test-config_test' ) ); ?>">
1619 <?php esc_html_e( 'Test email delivery', 'wp-2fa' ); ?>
1620 </button>
1621 </p>
1622
1623 <br>
1624 <hr>
1625
1626 <?php
1627 }
1628
1629 /**
1630 * Creates the email notification definitions.
1631 *
1632 * @return EmailTemplate[]
1633 */
1634 public function get_email_notification_definitions( ) {
1635 $result = [
1636 new EmailTemplate(
1637 'enforced',
1638 esc_html__( 'Enforced 2FA email', 'wp-2fa' ),
1639 esc_html__( 'This is the email sent to applicable users when you enforce 2fa.', 'wp-2fa' )
1640 ),
1641 new EmailTemplate(
1642 'login_code',
1643 esc_html__( 'Login code email', 'wp-2fa' ),
1644 esc_html__( 'This is the email sent to a user when a login code is required.', 'wp-2fa' )
1645 ),
1646 new EmailTemplate(
1647 'account_locked',
1648 esc_html__( 'User account locked email', 'wp-2fa' ),
1649 esc_html__( 'This is the email sent to a user upon grace period expiry.', 'wp-2fa' )
1650 ),
1651 new EmailTemplate(
1652 'account_unlocked',
1653 esc_html__( 'User account unlocked email', 'wp-2fa' ),
1654 esc_html__( 'This is the email sent to a user when the user\'s account has been unlocked.', 'wp-2fa' )
1655 )
1656 ];
1657
1658 $result[1]->setCanBeToggled(false);
1659 $result[2]->setEmailContentId('user_account_locked');
1660 $result[3]->setEmailContentId('user_account_unlocked');
1661 return $result;
1662 }
1663 /**
1664 * Email settings
1665 */
1666 private function email_settings() {
1667 $custom_user_page_id = WP2FA::get_wp2fa_setting( 'custom-user-page-id' );
1668 $email_template_definitions = $this->get_email_notification_definitions();
1669 ?>
1670 <h1><?php esc_html_e( 'Email Templates', 'wp-2fa' ); ?></h1>
1671 <?php foreach ($email_template_definitions as $email_template) : ?>
1672 <?php $template_id = $email_template->getId(); ?>
1673 <h3><?php echo $email_template->getTitle(); ?></h3>
1674 <p class="description"><?php echo $email_template->getDescription(); ?></p>
1675 <table class="form-table">
1676 <tbody>
1677 <?php if ($email_template->canBeToggled()): ?>
1678 <tr>
1679 <th><label for="send_<?php echo $template_id; ?>_email"><?php esc_html_e( 'Send this email', 'wp-2fa' ); ?></label></th>
1680 <td>
1681 <fieldset>
1682 <input type="checkbox" id="send_<?php echo $template_id; ?>_email" name="send_<?php echo $template_id; ?>_email" value="enable_<?php echo $template_id; ?>_email"
1683 <?php checked( 'enable_' . $template_id . '_email', WP2FA::get_wp2fa_email_templates( 'send_' . $template_id . '_email' )); ?>
1684 >
1685 <label for="send_<?php echo $template_id; ?>_email"><?php esc_html_e( 'Uncheck to disable this message.', 'wp-2fa' ); ?></label>
1686 </fieldset>
1687 </td>
1688 </tr>
1689 <?php endif; ?>
1690 <?php $template_id = $email_template->getEmailContentId(); ?>
1691 <tr>
1692 <th><label for="<?php echo $template_id; ?>_email_subject"><?php esc_html_e( 'Email subject', 'wp-2fa' ); ?></label></th>
1693 <td>
1694 <fieldset>
1695 <input type="text" id="<?php echo $template_id; ?>_email_subject" name="<?php echo $template_id; ?>_email_subject" class="large-text" value="<?php esc_html_e( WP2FA::get_wp2fa_email_templates( $template_id . '_email_subject' ) ); ?>">
1696 </fieldset>
1697 </td>
1698 </tr>
1699 <tr>
1700 <th>
1701 <label for="<?php echo $template_id; ?>_email_body"><?php esc_html_e( 'Email body', 'wp-2fa' ); ?></label>
1702 </br>
1703 <label for="<?php echo $template_id; ?>_email_tags" style="font-weight: 400;"><?php esc_html_e( 'Available template tags:', 'wp-2fa' ); ?></label>
1704 </br>
1705 </br>
1706 <span style="font-weight: 400;">
1707 {site_url}</br>
1708 {site_name}</br>
1709 {grace_period}</br>
1710 {user_login_name}</br>
1711 {login_code}
1712 <?php
1713 if ( ! empty( $custom_user_page_id ) ) {
1714 echo '</br>{2fa_settings_page_url}';
1715 }
1716 ?>
1717 </span>
1718 </th>
1719 <td>
1720 <fieldset>
1721 <?php
1722 $message = WP2FA::get_wp2fa_email_templates( $template_id . '_email_body' );
1723 $content = $message;
1724 $editor_id = $template_id . '_email_body';
1725 $settings = array(
1726 'media_buttons' => false,
1727 'editor_height' => 200,
1728 );
1729 wp_editor( $content, $editor_id, $settings );
1730 ?>
1731 </fieldset>
1732 <p>
1733 <button type="button" name="test_email_<?php echo esc_attr( $template_id ); ?>"
1734 class="button js-button-test-email-trigger"
1735 data-email-id="<?php echo esc_attr( $template_id ); ?>"
1736 data-nonce="<?php echo esc_attr( wp_create_nonce( 'wp-2fa-email-test-' . $template_id ) ); ?>">
1737 <?php esc_html_e( 'Send test email', 'wp-2fa' ); ?>
1738 </button>
1739 </p>
1740 </td>
1741 </tr>
1742 </tbody>
1743 </table>
1744
1745 <br>
1746 <hr>
1747 <?php endforeach; ?>
1748 <?php
1749 }
1750
1751 /**
1752 * Validate email templates before saving
1753 *
1754 * @param array $input The settings array.
1755 */
1756 public function validate_and_sanitize_email( $input ) {
1757
1758 // Bail if user doesnt have permissions to be here.
1759 if ( ! current_user_can( 'manage_options' ) ) {
1760 return;
1761 }
1762
1763 if ( empty( $_POST ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'wp_2fa_email_settings-options' ) && ! wp_verify_nonce( $_POST['_wpnonce'], 'wp_2fa_settings-options' ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'wp_2fa_email_settings-options' ) && ! wp_verify_nonce( $_POST['_wpnonce'], 'wp_2fa_settings-options' ) ) {
1764 die( esc_html__( 'Nonce verification failed.', 'wp-2fa' ) );
1765 }
1766
1767 if ( isset( $_POST['email_from_setting'] ) && 'use-defaults' === $_POST['email_from_setting'] || isset( $_POST['email_from_setting'] ) && 'use-custom-email' === $_POST['email_from_setting'] ) {
1768 $output['email_from_setting'] = sanitize_text_field( wp_unslash( $_POST['email_from_setting'] ) );
1769 }
1770
1771 if ( isset( $_POST['email_from_setting'] ) && 'use-custom-email' === $_POST['email_from_setting'] && isset( $_POST['custom_from_email_address'] ) && empty( $_POST['custom_from_email_address'] ) ) {
1772 add_settings_error(
1773 'wp_2fa_settings',
1774 esc_attr( 'email_from_settings_error' ),
1775 esc_html__( 'Please provide an email address', 'wp-2fa' ),
1776 'error'
1777 );
1778 $output['custom_from_email_address'] = '';
1779 }
1780
1781 if ( isset( $_POST['email_from_setting'] ) && 'use-custom-email' === $_POST['email_from_setting'] && isset( $_POST['custom_from_display_name'] ) && empty( $_POST['custom_from_display_name'] ) ) {
1782 add_settings_error(
1783 'wp_2fa_settings',
1784 esc_attr( 'display_name_settings_error' ),
1785 esc_html__( 'Please provide a display name.', 'wp-2fa' ),
1786 'error'
1787 );
1788 $output['custom_from_email_address'] = '';
1789 }
1790
1791 if ( isset( $_POST['custom_from_email_address'] ) && ! empty( $_POST['custom_from_email_address'] ) ) {
1792 if ( ! filter_var( $_POST['custom_from_email_address'], FILTER_VALIDATE_EMAIL ) ) {
1793 add_settings_error(
1794 'wp_2fa_settings',
1795 esc_attr( 'email_invalid_settings_error' ),
1796 esc_html__( 'Please provide a valid email address. Your email address has not been updated.', 'wp-2fa' ),
1797 'error'
1798 );
1799 }
1800 $output['custom_from_email_address'] = sanitize_email( wp_unslash( $_POST['custom_from_email_address'] ) );
1801 }
1802
1803 if ( isset( $_POST['custom_from_display_name'] ) && ! empty( $_POST['custom_from_display_name'] ) ) {
1804 // Check if the string contains HTML/tags.
1805 preg_match( "/<\/?\w+((\s+\w+(\s*=\s*(?:\".*?\"|'.*?'|[^'\">\s]+))?)+\s*|\s*)\/?>/", $_POST['custom_from_display_name'], $matches );
1806 if ( count( $matches ) > 0 ) {
1807 add_settings_error(
1808 'wp_2fa_settings',
1809 esc_attr( 'display_name_invalid_settings_error' ),
1810 esc_html__( 'Please only use alphanumeric text. Your display name has not been updated.', 'wp-2fa' ),
1811 'error'
1812 );
1813 } else {
1814 $output['custom_from_display_name'] = sanitize_text_field( wp_unslash( $_POST['custom_from_display_name'] ) );
1815 }
1816 }
1817
1818 if ( isset( $_POST['enforced_email_subject'] ) ) {
1819 $output['enforced_email_subject'] = wp_kses_post( wp_unslash( $_POST['enforced_email_subject'] ) );
1820 }
1821
1822 if ( isset( $_POST['enforced_email_body'] ) ) {
1823 $output['enforced_email_body'] = wpautop( wp_kses_post( wp_unslash( $_POST['enforced_email_body'] ) ) );
1824 }
1825
1826 if ( isset( $_POST['login_code_email_subject'] ) ) {
1827 $output['login_code_email_subject'] = wp_kses_post( wp_unslash( $_POST['login_code_email_subject'] ) );
1828 }
1829
1830 if ( isset( $_POST['login_code_email_body'] ) ) {
1831 $output['login_code_email_body'] = wpautop( wp_kses_post( wp_unslash( $_POST['login_code_email_body'] ) ) );
1832 }
1833
1834 if ( isset( $_POST['user_account_locked_email_subject'] ) ) {
1835 $output['user_account_locked_email_subject'] = wp_kses_post( wp_unslash( $_POST['user_account_locked_email_subject'] ) );
1836 }
1837
1838 if ( isset( $_POST['user_account_locked_email_body'] ) ) {
1839 $output['user_account_locked_email_body'] = wpautop( wp_kses_post( wp_unslash( $_POST['user_account_locked_email_body'] ) ) );
1840 }
1841
1842 if ( isset( $_POST['user_account_unlocked_email_subject'] ) ) {
1843 $output['user_account_unlocked_email_subject'] = wp_kses_post( wp_unslash( $_POST['user_account_unlocked_email_subject'] ) );
1844 }
1845
1846 if ( isset( $_POST['user_account_unlocked_email_body'] ) ) {
1847 $output['user_account_unlocked_email_body'] = wpautop( wp_kses_post( wp_unslash( $_POST['user_account_unlocked_email_body'] ) ) );
1848 }
1849
1850 if ( isset( $_POST['send_enforced_email'] ) && 'enable_enforced_email' === $_POST['send_enforced_email'] ) {
1851 $output['send_enforced_email'] = sanitize_text_field( $_POST['send_enforced_email'] );
1852 }
1853
1854 if ( isset( $_POST['send_account_locked_email'] ) && 'enable_account_locked_email' === $_POST['send_account_locked_email'] ) {
1855 $output['send_account_locked_email'] = sanitize_text_field( $_POST['send_account_locked_email'] );
1856 }
1857
1858 if ( isset( $_POST['send_account_unlocked_email'] ) && 'enable_account_unlocked_email' === $_POST['send_account_unlocked_email'] ) {
1859 $output['send_account_unlocked_email'] = sanitize_text_field( $_POST['send_account_unlocked_email'] );
1860 }
1861
1862 // 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.
1863 global $wp_settings_errors;
1864 if ( isset( $wp_settings_errors ) ) {
1865 $errors = array_map( 'unserialize', array_unique( array_map( 'serialize', $wp_settings_errors ) ) );
1866 $wp_settings_errors = $errors;
1867 }
1868
1869 if ( isset( $output ) ) {
1870 return $output;
1871 } else {
1872 return;
1873 }
1874
1875 }
1876
1877 /**
1878 * Creates a new page with our shortcode present.
1879 */
1880 public function generate_custom_user_profile_page( $page_slug ) {
1881 // Bail if user doesnt have permissions to be here.
1882 if ( ! current_user_can( 'manage_options' ) ) {
1883 return;
1884 }
1885
1886 // Check if a page with slug exists.
1887 $page_exists = $this->get_post_by_post_name( $page_slug, 'page' );
1888 if ( $page_exists ) {
1889 // Seeing as the page exisits, return its ID.
1890 return $page_exists->ID;
1891 }
1892
1893 $generated_by_message = sprintf(
1894 /* translators: %1$s: is the user name, %2$s is the website address */
1895 '<p>%1$s <a href="https://www.wpwhitesecurity.com/wordpress-plugins/wp-2fa/" target="_blank">%2$s</a></p>',
1896 esc_html__( 'Page generated by', 'wp-2fa' ),
1897 esc_html__( 'WP 2FA Plugin', 'wp-2fa' )
1898 );
1899
1900 $user = wp_get_current_user();
1901 $post_data = array(
1902 'post_title' => 'WP 2FA User Profile',
1903 'post_name' => $page_slug,
1904 'post_content' => '[wp-2fa-setup-form] ' . $generated_by_message,
1905 'post_status' => 'publish',
1906 'post_author' => $user->ID,
1907 'post_type' => 'page',
1908 );
1909
1910 // Lets insert the post now.
1911 $result = wp_insert_post( $post_data );
1912
1913 if ( $result && ! is_wp_error( $result ) ) {
1914 $post_id = $result;
1915 set_transient( 'wp_2fa_new_custom_page_created', true, 60 );
1916 set_site_transient( 'wp_2fa_new_custom_page_created', true, 60 );
1917 return $post_id;
1918 }
1919 }
1920
1921 /**
1922 * Check if page with slug exisits.
1923 */
1924 public function get_post_by_post_name( $slug = '', $post_type = '' ) {
1925 if ( ! $slug || ! $post_type ) {
1926 return false;
1927 }
1928
1929 $post_object = get_page_by_path( $slug, OBJECT, $post_type );
1930
1931 if ( ! $post_object ) {
1932 return false;
1933 }
1934
1935 return $post_object;
1936 }
1937
1938 /**
1939 * Add our custom state to our created page.
1940 */
1941 public function add_display_post_states( $post_states, $post ) {
1942 if ( ! empty( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) ) ) {
1943 if ( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) === $post->ID ) {
1944 $post_states['wp_2fa_page_for_user'] = __( 'WP 2FA User Page', 'wp-2fa' );
1945 }
1946 }
1947
1948 return $post_states;
1949 }
1950
1951 /**
1952 * Handles sending of an email. It sets necessary header such as content type and custom from email address and name.
1953 *
1954 * @param string $recipient_email Email address to send message to.
1955 * @param string $subject Email subject.
1956 * @param string $message Message contents.
1957 *
1958 * @return bool Whether the email contents were sent successfully.
1959 */
1960 public static function send_email( $recipient_email, $subject, $message ) {
1961
1962 // Specify our desired headers.
1963 $headers = 'Content-type: text/html;charset=utf-8' . "\r\n";
1964
1965 if ( 'use-custom-email' === WP2FA::get_wp2fa_email_templates( 'email_from_setting' ) ) {
1966 $headers .= 'From: ' . WP2FA::get_wp2fa_email_templates( 'custom_from_display_name' ) . ' <' . WP2FA::get_wp2fa_email_templates( 'custom_from_email_address' ) . '>' . "\r\n";
1967 }
1968
1969 // Fire our email.
1970 return wp_mail( $recipient_email, $subject, $message, $headers );
1971
1972 }
1973
1974 /**
1975 * Turns user roles data in any form and shape to an array of strings.
1976 *
1977 * @param mixed $value User role names (slugs) as raw value.
1978 *
1979 * @return string[] List of user role names (slugs).
1980 */
1981 public static function extract_roles_from_input( $value ) {
1982 if ( is_array( $value ) ) {
1983 return $value;
1984 }
1985
1986 if ( is_string( $value ) && ! empty( $value ) ) {
1987 return explode( ',', $value );
1988 }
1989
1990 return [];
1991 }
1992
1993 /**
1994 * Determine if any BG processes are currently running.
1995 *
1996 * @return int|false Number of jobs.
1997 */
1998 public function get_current_number_of_active_bg_processes() {
1999 global $wpdb;
2000
2001 $bg_jobs = $wpdb->get_results(
2002 "SELECT option_value FROM $wpdb->options
2003 WHERE option_name LIKE '%_2fa_bg_%'"
2004 );
2005
2006 return count( $bg_jobs );
2007 }
2008
2009 /**
2010 * Cancel BG processes.
2011 *
2012 */
2013 public function cancel_bg_processes() {
2014 global $wpdb;
2015 $wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE '%_2fa_bg_%'");
2016
2017 $cron_hook_identifiers = [ '2fa_check_grace_period_status', '2fa_bg_update_user_meta', '2fa_bg_wipe_all_user_data', '2fa_bg_remove_enabled_methods' ];
2018
2019 foreach ( $cron_hook_identifiers as $cron_hook_identifier ) {
2020 $cleared_jobs = wp_clear_scheduled_hook( $wpdb->prefix.$cron_hook_identifier );
2021 }
2022
2023 wp_send_json_success( $cleared_jobs );
2024 }
2025 }
2026