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