PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 1.6.0
WP 2FA – Two-factor authentication for WordPress v1.6.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 UserListing.php 5 years ago UserNotices.php 5 years ago UserProfile.php 5 years ago UserRegistered.php 5 years ago
SettingsPage.php
2316 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="enforcement-policy"><?php esc_html_e( 'Exclude the following sites', 'wp-2fa' ); ?></label></th>
646 <td>
647 <fieldset>
648 <input type="text" id="excluded_sites_search" placeholder="Search sites in your network">
649 <input type="hidden" id="excluded_sites" name="wp_2fa_settings[excluded_sites]"
650 value="<?php echo sanitize_text_field( WP2FA::get_wp2fa_setting( 'excluded_sites' ) ); ?>">
651 <div id="excluded_sites_buttons"></div>
652 </fieldset>
653 </td>
654 </tr>
655 </tbody>
656 </table>
657 <?php
658 }
659
660 /**
661 * Grace period settings
662 */
663 private function grace_period_setting() {
664 $user = wp_get_current_user();
665
666 $grace_period = (int) WP2FA::get_wp2fa_setting( 'grace-period' );
667 $testing = apply_filters( 'wp_2fa_allow_grace_period_in_seconds', false );
668 if ( $testing ) {
669 $grace_max = 600;
670 } else {
671 $grace_max = 10;
672 }
673 ?>
674 <br>
675 <h3><?php esc_html_e( 'Should users be asked to setup 2FA instantly or should they have a grace period?', 'wp-2fa' ); ?></h3>
676 <p class="description">
677 <?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>
678 </p>
679
680 <table class="form-table">
681 <tbody>
682 <tr>
683 <th><label for="grace-policy"><?php esc_html_e( 'Grace period', 'wp-2fa' ); ?></label></th>
684 <td>
685 <fieldset class="contains-hidden-inputs">
686 <label for="no-grace-period">
687 <input type="radio" name="wp_2fa_settings[grace-policy]" id="no-grace-period" value="no-grace-period"
688 <?php checked( WP2FA::get_wp2fa_setting( 'grace-policy' ), 'no-grace-period' ); ?>
689 >
690 <span><?php esc_html_e( 'Users have to configure 2FA straight away.', 'wp-2fa' ); ?></span>
691 </label>
692
693 <br/>
694 <label for="use-grace-period">
695 <input type="radio" name="wp_2fa_settings[grace-policy]" id="use-grace-period" value="use-grace-period"
696 <?php checked( WP2FA::get_wp2fa_setting( 'grace-policy' ), 'use-grace-period' ); ?>
697 data-unhide-when-checked=".grace-period-inputs">
698 <span><?php esc_html_e( 'Give users a grace period to configure 2FA', 'wp-2fa' ); ?></span>
699 </label>
700 <fieldset class="hidden grace-period-inputs">
701 <br/>
702 <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 ); ?>">
703 <label class="radio-inline">
704 <input class="js-nested" type="radio" name="wp_2fa_settings[grace-period-denominator]" value="hours"
705 <?php checked( WP2FA::get_wp2fa_setting( 'grace-period-denominator' ), 'hours' ); ?>
706 >
707 <?php esc_html_e( 'Hours', 'wp-2fa' ); ?>
708 </label>
709 <label class="radio-inline">
710 <input class="js-nested" type="radio" name="wp_2fa_settings[grace-period-denominator]" value="days"
711 <?php checked( WP2FA::get_wp2fa_setting( 'grace-period-denominator' ), 'days' ); ?>
712 >
713 <?php esc_html_e( 'Days', 'wp-2fa' ); ?>
714 </label>
715 <?php
716 $testing = apply_filters( 'wp_2fa_allow_grace_period_in_seconds', false );
717 if ( $testing ) {
718 ?>
719 <label class="radio-inline">
720 <input class="js-nested" type="radio" name="wp_2fa_settings[grace-period-denominator]" value="seconds"
721 <?php checked( WP2FA::get_wp2fa_setting( 'grace-period-denominator' ), 'seconds' ); ?>
722 >
723 <?php esc_html_e( 'Seconds', 'wp-2fa' ); ?>
724 </label>
725 <?php
726 }
727
728 $last_user_to_update_settings = $user->ID;
729
730 ?>
731 <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 ); ?>">
732 </fieldset>
733 <br/>
734 </fieldset>
735 </td>
736 </tr>
737 </tbody>
738 </table>
739 <?php
740 }
741
742 /**
743 * Grace period frequency
744 */
745 private function gracePeriodFrequency() {
746 ?>
747 <h3><?php esc_html_e( 'How often should the plugin check if a user\'s grace period is over?', 'wp-2fa' ); ?></h3>
748 <p class="description">
749 <?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' ); ?>
750 </p>
751 <table class="form-table">
752 <tbody>
753 <tr>
754 <th><label for="grace-period"><?php esc_html_e( 'Enable cron', 'wp-2fa' ); ?></label></th>
755 <td>
756 <fieldset>
757 <input type="checkbox" id="grace-cron" name="wp_2fa_settings[enable_grace_cron]" value="enable_grace_cron"
758 <?php checked( 1, WP2FA::get_wp2fa_setting( 'enable_grace_cron' ), true ); ?>
759 >
760 <?php esc_html_e( 'Use cron job to check grace periods', 'wp-2fa' ); ?>
761 </fieldset>
762 </td>
763 </tr>
764 <tr class="disabled destory-session-setting">
765 <th><label for="destory-session"><?php esc_html_e( 'Destroy session', 'wp-2fa' ); ?></label></th>
766 <td>
767 <fieldset>
768 <input type="checkbox" id="destory-session" name="wp_2fa_settings[enable_destroy_session]" value="enable_destroy_session"
769 <?php checked( 1, WP2FA::get_wp2fa_setting( 'enable_destroy_session' ), true ); ?>
770 >
771 <?php esc_html_e( 'Destroy user session when grace period expires?', 'wp-2fa' ); ?>
772 </fieldset>
773 </td>
774 </tr>
775 </tbody>
776 </table>
777 <?php
778 }
779
780 /**
781 * Disable removal of 2FA settings
782 */
783 private function disable_2fa_removal_setting() {
784 $user = wp_get_current_user();
785 ?>
786 <br>
787 <h3><?php esc_html_e( 'Should users be able to disable 2FA on their user profile?', 'wp-2fa' ); ?></h3>
788 <p class="description">
789 <?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' ); ?>
790 </p>
791 <table class="form-table">
792 <tbody>
793 <tr>
794 <th><label for="hide-remove-2fa"><?php esc_html_e( 'Hide the Remove 2FA button', 'wp-2fa' ); ?></label></th>
795 <td>
796 <fieldset>
797 <input type="checkbox" id="hide-remove-2fa" name="wp_2fa_settings[hide_remove_button]" value="hide_remove_button"
798 <?php checked( 1, WP2FA::get_wp2fa_setting( 'hide_remove_button' ), true ); ?>
799 >
800 <?php esc_html_e( 'Hide the Remove 2FA button on user profile pages', 'wp-2fa' ); ?>
801 </fieldset>
802 </td>
803 </tr>
804 </tbody>
805 </table>
806 <?php
807 }
808
809 /**
810 * Limit settings setting
811 */
812 private function limit_settings_access() {
813 ?>
814 <br>
815 <h3><?php esc_html_e( 'Limit 2FA settings access?', 'wp-2fa' ); ?></h3>
816 <p class="description">
817 <?php esc_html_e( 'Use this setting to hide this plugin configuration area from all other admins.', 'wp-2fa' ); ?>
818 </p>
819 <table class="form-table">
820 <tbody>
821 <tr>
822 <th><label for="grace-period"><?php esc_html_e( 'Limit access to 2FA settings', 'wp-2fa' ); ?></label></th>
823 <td>
824 <fieldset>
825 <input type="checkbox" id="limit_access" name="wp_2fa_settings[limit_access]" value="limit_access"
826 <?php checked( 1, WP2FA::get_wp2fa_setting( 'limit_access' ), true ); ?>
827 >
828 <?php esc_html_e( 'Hide settings from other administrators', 'wp-2fa' ); ?>
829 </fieldset>
830 </td>
831 </tr>
832 </tbody>
833 </table>
834 <?php
835 }
836
837 /**
838 * Limit settings setting
839 */
840 private function remove_data_upon_uninstall() {
841 ?>
842 <div class="danger-zone-wrapper">
843 <h3><?php esc_html_e( 'Do you want to delete the plugin data from the database upon uninstall?', 'wp-2fa' ); ?></h3>
844 <p class="description">
845 <?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' ); ?>
846 </p>
847 <table class="form-table">
848 <tbody>
849 <tr>
850 <th><label for="delete_data"><?php esc_html_e( 'Delete data', 'wp-2fa' ); ?></label></th>
851 <td>
852 <fieldset>
853 <input type="checkbox" id="elete_data" name="wp_2fa_settings[delete_data_upon_uninstall]" value="delete_data_upon_uninstall"
854 <?php checked( 1, WP2FA::get_wp2fa_setting( 'delete_data_upon_uninstall' ), true ); ?>
855 >
856 <?php esc_html_e( 'Delete data upon uninstall', 'wp-2fa' ); ?>
857 </fieldset>
858 </td>
859 </tr>
860 </tbody>
861 </table>
862 <table class="form-table hidden">
863 <tbody>
864 <tr>
865 <th></th>
866 <td>
867 <fieldset>
868 <input type="checkbox" id="notify_users" name="wp_2fa_settings[notify_users]" value="notify_users">
869 </fieldset>
870 </td>
871 </tr>
872 </tbody>
873 </table>
874 </div>
875 <?php
876 }
877
878 /**
879 * Get all users
880 */
881 public function get_all_users() {
882 // Die if user does not have permission to view.
883 if ( ! current_user_can( 'manage_options' ) ) {
884 die( 'Access Denied.' );
885 }
886 // Filter $_GET array for security.
887 $get_array = filter_input_array( INPUT_GET );
888
889 // Die if nonce verification failed.
890 if ( ! wp_verify_nonce( sanitize_text_field( $get_array['wp_2fa_nonce'] ), 'wp-2fa-settings-nonce' ) ) {
891 die( esc_html__( 'Nonce verification failed.', 'wp-2fa' ) );
892 }
893
894 $users_args = array(
895 'fields' => array( 'ID', 'user_login' ),
896 );
897 if ( WP2FA::is_this_multisite() ) {
898 $users_args['blog_id'] = 0;
899 }
900 $users_data = UserUtils::get_all_user_ids_and_login_names( 'query', $users_args );
901
902 // Create final array which we will fill in below.
903 $users = [];
904
905 foreach ( $users_data as $user ) {
906 if ( strpos( $user['user_login'], $get_array['term'] ) !== false ) {
907 array_push( $users, [
908 'value' => $user['user_login'],
909 'label' => $user['user_login']
910 ]);
911 }
912 }
913
914 echo wp_json_encode( $users );
915 exit;
916 }
917
918 /**
919 * Get all network sites
920 */
921 public function get_all_network_sites() {
922 // Die if user does not have permission to view.
923 if ( ! current_user_can( 'manage_options' ) ) {
924 die( 'Access Denied.' );
925 }
926 // Filter $_GET array for security.
927 $get_array = filter_input_array( INPUT_GET );
928 // Die if nonce verification failed.
929 if ( ! wp_verify_nonce( sanitize_text_field( $get_array['wp_2fa_nonce'] ), 'wp-2fa-settings-nonce' ) ) {
930 die( esc_html__( 'Nonce verification failed.', 'wp-2fa' ) );
931 }
932 // Fetch sites.
933 $sites_found = array();
934
935 foreach ( get_sites() as $site ) {
936 $subsite_id = get_object_vars( $site )['blog_id'];
937 $subsite_name = get_blog_details( $subsite_id )->blogname;
938 $site_details = '';
939 $site_details[ $subsite_id ] = $subsite_name;
940 if ( stripos( $subsite_name, $get_array['term'] ) !== false ) {
941 $site_details = $subsite_name . ':' . $subsite_id;
942 array_push( $sites_found, $site_details );
943 }
944 }
945 echo wp_json_encode( $sites_found );
946 exit;
947 }
948
949 /**
950 * Unlock users accounts if they have overrun grace period
951 *
952 * @param int $user_id User ID.
953 */
954 public function unlock_account( $user_id ) {
955 // Die if user does not have permission to view.
956 if ( ! current_user_can( 'manage_options' ) ) {
957 die( 'Access Denied.' );
958 }
959
960 $grace_period = WP2FA::get_wp2fa_setting( 'grace-period' );
961 $grace_period_denominator = WP2FA::get_wp2fa_setting( 'grace-period-denominator' );
962 $create_a_string = $grace_period . ' ' . $grace_period_denominator;
963 // Turn that string into a time.
964 $grace_expiry = strtotime( $create_a_string );
965
966 // Filter $_GET array for security.
967 $get_array = filter_input_array( INPUT_GET );
968 $nonce = sanitize_text_field( $get_array['wp_2fa_nonce'] );
969
970 // Die if nonce verification failed.
971 if ( ! wp_verify_nonce( $nonce, 'wp-2fa-unlock-account-nonce' ) ) {
972 die( esc_html__( 'Nonce verification failed.', 'wp-2fa' ) );
973 }
974
975 if ( isset( $get_array['user_id'] ) ) {
976 global $wpdb;
977 $wpdb->query(
978 $wpdb->prepare(
979 "
980 DELETE FROM $wpdb->usermeta
981 WHERE user_id = %d
982 AND meta_key IN ( %s, %s )
983 ",
984 [
985 intval( $get_array['user_id'] ),
986 'wp_2fa_user_grace_period_expired',
987 'wp_2fa_locked_account_notification',
988 ]
989 )
990 );
991 $update = update_user_meta( intval( $get_array['user_id'] ), 'wp_2fa_grace_period_expiry', $grace_expiry );
992 $this->send_account_unlocked_email( intval( $get_array['user_id'] ) );
993 add_action( 'admin_notices', array( $this, 'user_unlocked_notice' ) );
994 }
995 }
996
997 /**
998 * Remove user 2fa config
999 *
1000 * @param int $user_id User ID.
1001 */
1002 public function remove_user_2fa( $user_id ) {
1003 // Filter $_GET array for security.
1004 $get_array = filter_input_array( INPUT_GET );
1005 $nonce = sanitize_text_field( $get_array['wp_2fa_nonce'] );
1006
1007 if ( ! wp_verify_nonce( $nonce, 'wp-2fa-remove-user-2fa-nonce' ) ) {
1008 die( esc_html__( 'Nonce verification failed.', 'wp-2fa' ) );
1009 }
1010
1011 if ( isset( $get_array['user_id'] ) ) {
1012 $user_id = intval( $get_array['user_id'] );
1013 global $wpdb;
1014 $wpdb->query(
1015 $wpdb->prepare(
1016 "DELETE FROM $wpdb->usermeta
1017 WHERE user_id = %d
1018 AND meta_key LIKE %s",
1019 [
1020 $user_id,
1021 'wp_2fa_%'
1022 ]
1023 )
1024 );
1025
1026 $is_needed = Authentication::is_user_eligible_for_2fa( $user_id );
1027
1028 if ( $is_needed ) {
1029 if ( 'do-not-enforce' !== WP2FA::get_wp2fa_setting( 'enforcement-policy' ) ) {
1030 // Turn inputs into a useable string.
1031 $create_a_string = WP2FA::get_wp2fa_setting( 'grace-period' ) . ' ' . WP2FA::get_wp2fa_setting( 'grace-period-denominator' );
1032 // Turn that string into a time.
1033 $grace_expiry = strtotime( $create_a_string );
1034 update_user_meta( $user_id, 'wp_2fa_grace_period_expiry', $grace_expiry );
1035 update_user_meta( $user_id, 'wp_2fa_update_nag_dismissed', true );
1036 }
1037 $grace_policy = WP2FA::get_wp2fa_setting( 'grace-policy' );
1038 if ( 'no-grace-period' === $grace_policy ) {
1039 update_user_meta( $user_id, 'wp_2fa_user_enforced_instantly', true );
1040 // Get sessions for user with ID $user_id.
1041 $sessions = \WP_Session_Tokens::get_instance( $user_id );
1042 // Log them out.
1043 $sessions->destroy_all();
1044 }
1045 }
1046 if ( isset( $get_array['admin_reset'] ) ) {
1047 add_action( 'admin_notices', array( $this, 'admin_deleted_2fa_notice' ) );
1048 } else {
1049 add_action( 'admin_notices', array( $this, 'user_deleted_2fa_notice' ) );
1050 }
1051 }
1052 }
1053
1054 /**
1055 * Send account unlocked notification via email.
1056 *
1057 * @param int $user_id user ID.
1058 *
1059 * @return boolean
1060 */
1061 public static function send_account_unlocked_email( $user_id ) {
1062 // Bail if the user has not enabled this email.
1063 if ( 'enable_account_unlocked_email' !== WP2FA::get_wp2fa_email_templates( 'send_account_unlocked_email' ) ) {
1064 return false;
1065 }
1066
1067 // Grab user data.
1068 $user = get_userdata( $user_id );
1069 // Grab user email.
1070 $email = $user->user_email;
1071 // Setup the email contents.
1072 $subject = wp_strip_all_tags( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'user_account_unlocked_email_subject' ) ) );
1073 $message = wpautop( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'user_account_unlocked_email_body' ), $user_id ) );
1074
1075 self::send_email($email, $subject, $message);
1076 }
1077
1078 /**
1079 * Validate options before saving
1080 *
1081 * @param array $input The settings array.
1082 *
1083 * @return array|void
1084 */
1085 public function validate_and_sanitize( $input ) {
1086
1087 // Bail if user doesnt have permissions to be here.
1088 if ( ! current_user_can( 'manage_options' ) || ! isset( $_POST['action'] ) && ! check_admin_referer( 'wp2fa-step-choose-method' ) ) {
1089 return;
1090 }
1091
1092 // Setup args we may need, depending if this is a MS setup or not.
1093 $users = array();
1094 $users_args = array();
1095 if ( WP2FA::is_this_multisite() ) {
1096 $users_args['blog_id'] = 0;
1097 }
1098 $total_users = count_users();
1099
1100 // Allow user to override batch size.
1101 $batch_size = intval( apply_filters( 'wp_2fa_batch_size', 1000 ) );
1102
1103 $slices = ceil( $total_users['total_users'] / $batch_size );
1104
1105 $noMethodEnabled = false;
1106 if ( ! isset( $input['enable_totp'] ) && ! isset( $input['enable_email'] ) && ! isset( $_POST['save_step'] ) ) {
1107 add_settings_error(
1108 'wp_2fa_settings',
1109 esc_attr( 'enable_email_settings_error' ),
1110 esc_html__( 'At least one 2FA method should be enabled.', 'wp-2fa' ),
1111 'error'
1112 );
1113 $noMethodEnabled = true;
1114 }
1115
1116 $simple_settings_we_can_loop = array(
1117 'enable_totp',
1118 'enable_email',
1119 'backup_codes_enabled',
1120 'excluded_sites',
1121 'grace-policy',
1122 'notify_users',
1123 'enable_grace_cron',
1124 'enable_destroy_session',
1125 '2fa_settings_last_updated_by',
1126 'limit_access',
1127 'delete_data_upon_uninstall',
1128 'hide_remove_button',
1129 'redirect-user-custom-page',
1130 'redirect-user-custom-page-global',
1131 );
1132
1133 $settings_to_turn_into_bools = array(
1134 'notify_users',
1135 'enable_grace_cron',
1136 'enable_destroy_session',
1137 'limit_access',
1138 'delete_data_upon_uninstall',
1139 'hide_remove_button'
1140 );
1141
1142 $settings_to_turn_into_array = [
1143 'enforced_roles',
1144 'enforced_users',
1145 'excluded_users',
1146 'excluded_roles',
1147 ];
1148
1149 foreach ( $simple_settings_we_can_loop as $simple_setting ) {
1150 if ( ! in_array( $simple_setting, $settings_to_turn_into_bools ) ) {
1151 // Is item is not one of our possible settings we want to turn into a bool, process.
1152 $output[ $simple_setting ] = ( isset( $input[ $simple_setting ] ) && ! empty( $input[ $simple_setting ] ) ) ? trim( sanitize_text_field( $input[ $simple_setting ] ) ) : false;
1153 } else {
1154 // This item is one we treat as a bool, so process correctly.
1155 $output[ $simple_setting ] = ( isset( $input[ $simple_setting ] ) && ! empty( $input[ $simple_setting ] ) ) ? true : false;
1156 }
1157 }
1158
1159 if ( $noMethodEnabled ) {
1160 // No method is enabled, fall back to previous selected one - we don't want to break the logic
1161 $totpEnabled = WP2FA::get_wp2fa_setting( 'enable_totp' );
1162 $emailEnabled = WP2FA::get_wp2fa_setting( 'enable_email' );
1163
1164 if ( $totpEnabled ) {
1165 $output['enable_totp'] = $totpEnabled;
1166 }
1167 if ( $emailEnabled ) {
1168 $output['enable_email'] = $emailEnabled;
1169 }
1170 }
1171
1172 $output['included_sites'] = [];
1173 if ( isset( $input['included_sites'] ) && is_array($input['included_sites']) && ! empty( $input['included_sites'] ) ) {
1174 foreach ( $input['included_sites'] as &$site ) {
1175 if ( ! filter_var($site, FILTER_VALIDATE_INT) ) {
1176 unset( $site );
1177 continue;
1178 }
1179
1180 $output['included_sites'][] = $site;
1181 }
1182 }
1183 unset( $site );
1184
1185 foreach ($settings_to_turn_into_array as $setting) {
1186 if ( isset( $input[$setting] ) ) {
1187 $output[$setting] = $input[$setting];
1188 } else {
1189 $output[$setting] = [];
1190 }
1191 }
1192
1193 $output['default-text-code-page'] = WP2FA::get_wp2fa_setting('default-text-code-page', false, true);
1194
1195 if ( isset( $input['default-text-code-page'] ) && '' !== trim( $input['default-text-code-page'] ) ) {
1196 $output['default-text-code-page'] = \strip_tags( $input['default-text-code-page'] );
1197 }
1198
1199 $total_users = $total_users['total_users'];
1200 $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;
1201 Debugging::log( $log_content );
1202
1203 // Compare current to old value to see if a method which was once enabled, has now been disabled.
1204 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' ) ) {
1205
1206 if ( ! isset( $input['enable_totp'] ) ) {
1207 $removing = 'totp';
1208 } elseif ( ! isset( $input['enable_email'] ) ) {
1209 $removing = 'email';
1210 }
1211
1212 for ( $count = 0; $count < $slices; $count++ ) {
1213 $users_args = array(
1214 'number' => $batch_size,
1215 'offset' => $count * $batch_size,
1216 'fields' => array( 'ID' ),
1217 'count' => $count,
1218 'batch_size' => $batch_size,
1219 );
1220 if ( WP2FA::is_this_multisite() ) {
1221 $users_args['blog_id'] = 0;
1222 }
1223
1224 // Get users who have the method we are removeing enabled.
1225 $users = UserUtils::get_all_user_ids_based_on_enabled_2fa_method( $removing, $users_args );
1226
1227 // If we have found users using the method which has been disabled, remove the method.
1228 if ( ! empty( $users ) ) {
1229 $log_content = __( 'RemoveEnabledMethod happening on following IDs: ', 'wp-2fa' ) . "\n" . $users;
1230 Debugging::log( $log_content );
1231
1232 $background_process = new RemoveEnabledMethods();
1233 $item_to_process = array();
1234 $item_to_process['users'] = $users;
1235 $item_to_process['method_to_remove'] = $removing;
1236 $background_process->push_to_queue( $item_to_process );
1237 $background_process->save()->dispatch();
1238 }
1239
1240 }
1241 }
1242
1243 if ( isset( $input['grace-period'] ) ) {
1244 if ( 0 === (int) $input['grace-period'] ) {
1245 add_settings_error(
1246 'wp_2fa_settings',
1247 esc_attr( 'grace_settings_error' ),
1248 esc_html__( 'Grace period must be at least 1 day/hour', 'wp-2fa' ),
1249 'error'
1250 );
1251 $output['grace-period'] = 1;
1252 } else {
1253 $output['grace-period'] = (int) $input['grace-period'];
1254 }
1255 }
1256
1257 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'] ) {
1258 $output['grace-period-denominator'] = sanitize_text_field( $input['grace-period-denominator'] );
1259 }
1260
1261 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'] ) {
1262 $output['create-custom-user-page'] = sanitize_text_field( $input['create-custom-user-page'] );
1263 }
1264
1265 if ( isset( $input['custom-user-page-url'] ) ) {
1266 if ( $input['custom-user-page-url'] !== WP2FA::get_wp2fa_setting( 'custom-user-page-url' ) ) {
1267 if ( ! empty( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) ) ) {
1268 $updated_post = array(
1269 'ID' => WP2FA::get_wp2fa_setting( 'custom-user-page-id' ),
1270 'post_name' => sanitize_title_with_dashes( $input['custom-user-page-url'] ),
1271 );
1272 wp_update_post( $updated_post );
1273 $output['custom-user-page-url'] = sanitize_title_with_dashes( $input['custom-user-page-url'] );
1274 $output['custom-user-page-id'] = WP2FA::get_wp2fa_setting( 'custom-user-page-id' );
1275 } elseif ( 'yes' === $input['create-custom-user-page'] && ! empty( $input['custom-user-page-url'] ) ) {
1276 $output['custom-user-page-url'] = sanitize_title_with_dashes( $input['custom-user-page-url'] );
1277 $create_page = $this->generate_custom_user_profile_page( $output['custom-user-page-url'] );
1278 $output['custom-user-page-id'] = (int) $create_page;
1279 }
1280 } else {
1281 $output['custom-user-page-url'] = sanitize_title_with_dashes( $input['custom-user-page-url'] );
1282 $output['custom-user-page-id'] = WP2FA::get_wp2fa_setting( 'custom-user-page-id' );
1283 }
1284 }
1285
1286 if ( isset( $_REQUEST['page'] ) && 'wp-2fa-setup' !== $_REQUEST['page'] || isset( $_REQUEST['wp_2fa_settings']['create-custom-user-page'] ) ) {
1287
1288 if ( isset( $input['create-custom-user-page'] ) && 'no' === $input['create-custom-user-page'] ) {
1289 $output['custom-user-page-url'] = '';
1290 $output['custom-user-page-id'] = '';
1291 wp_delete_post( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ), true );
1292 }
1293 }
1294
1295 if ( isset( $input['create-custom-user-page'] ) && 'yes' === $input['create-custom-user-page'] && empty( $input['custom-user-page-url'] ) ) {
1296 add_settings_error(
1297 'wp_2fa_settings',
1298 esc_attr( 'no_page_slug_provided' ),
1299 esc_html__( 'You must provide a new page slug.', 'wp-2fa' ),
1300 'error'
1301 );
1302 }
1303
1304 if ( isset( $input['grace-period'] ) && isset( $input['grace-period-denominator'] ) ) {
1305 // Turn inputs into a useable string.
1306 $create_a_string = $output['grace-period'] . ' ' . $output['grace-period-denominator'];
1307 // Turn that string into a time.
1308 $grace_expiry = strtotime( $create_a_string );
1309 $output['grace-period-expiry-time'] = sanitize_text_field( $grace_expiry );
1310 }
1311
1312 // Process main policy.
1313 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" ] ) ) {
1314
1315 // Clear enforced roles/users if setting has changed.
1316 if ( 'all-users' === $input['enforcement-policy'] || 'do-not-enforce' === $input['enforcement-policy'] ) {
1317 $input['enforced_users'] = [];
1318 $input['enforced_roles'] = [];
1319 $output['enforced_users'] = [];
1320 $output['enforced_roles'] = [];
1321 }
1322
1323 $output['enforcement-policy'] = sanitize_text_field( $input['enforcement-policy'] );
1324
1325 if ( 'certain-roles-only' === $input['enforcement-policy'] && empty( $input['enforced_roles'] ) && empty( $input['enforced_users'] ) ) {
1326 add_settings_error(
1327 'wp_2fa_settings',
1328 esc_attr( 'enforced_roles_settings_error' ),
1329 esc_html__( 'You must specify at least one role or user', 'wp-2fa' ),
1330 'error'
1331 );
1332 }
1333
1334 // 2FA is applied to all users.
1335 if ( 'all-users' === $input['enforcement-policy'] ) {
1336
1337 $all_possible_roles = WP2FA::wp_2fa_get_roles();
1338 $excluded_roles = array_filter( $output['excluded_roles'] );
1339
1340 // Determine the roles we actually want to grab from the db.
1341 $roles_to_load = array_diff( array_keys( $all_possible_roles ), $excluded_roles );
1342
1343 $excluded_users = $output['excluded_users'];
1344
1345 for ( $count = 0; $count < $slices; $count++ ) {
1346 $users_args = array(
1347 'number' => $batch_size,
1348 'offset' => $count * $batch_size,
1349 'fields' => array( 'ID' ),
1350 'count' => $count,
1351 'batch_size' => $batch_size,
1352 'role__in' => $roles_to_load,
1353 'excluded_users' => $excluded_users,
1354 'skip_existing_2fa_users' => true,
1355 );
1356 if ( WP2FA::is_this_multisite() ) {
1357 $users_args['blog_id'] = 0;
1358 }
1359
1360 $users = UserUtils::get_all_user_ids( 'query', $users_args );
1361
1362 $log_content = __( 'Policy = All Users. Enforcing 2FA on following IDs', 'wp-2fa' ) . "\n" . $users;
1363 Debugging::log( $log_content );
1364
1365 if ( ! empty( $users ) ) {
1366 $background_process = new Enforce2FA();
1367 $item_to_process = array();
1368 $item_to_process['users'] = $users;
1369 $item_to_process['grace_expiry'] = $grace_expiry;
1370 $item_to_process['grace_policy'] = $output['grace-policy'];
1371 $item_to_process['notify_users'] = $output['notify_users'];
1372 $item_to_process['excluded_users'] = $output['excluded_users'];
1373 $item_to_process['excluded_roles'] = $output['excluded_roles'];
1374 $item_to_process['excluded_sites'] = $output['excluded_sites'];
1375 $background_process->push_to_queue( $item_to_process );
1376 $background_process->save()->dispatch();
1377 }
1378 }
1379 }
1380
1381 // 2FA to be applied to certain roles/users only.
1382 if ( 'certain-roles-only' === $input['enforcement-policy'] ) {
1383
1384 $excluded_users = $output['excluded_users'];
1385
1386 // Remove data from applicable users.
1387 for ( $count = 0; $count < $slices; $count++ ) {
1388 $users_args = array(
1389 'number' => $batch_size,
1390 'offset' => $count * $batch_size,
1391 'fields' => array( 'ID' ),
1392 'count' => $count,
1393 'batch_size' => $batch_size,
1394 'excluded_users' => $excluded_users,
1395 );
1396 if ( WP2FA::is_this_multisite() ) {
1397 $users_args['blog_id'] = 0;
1398 }
1399
1400 $users = UserUtils::get_all_user_ids_who_have_wp_2fa_metadata_present( $users_args );
1401
1402 $log_content = __( 'Policy = Certain Users/Roles. Deleting Grace period on following IDs', 'wp-2fa' ) . "\n" . $users;
1403 Debugging::log( $log_content );
1404
1405 if ( ! empty( $users ) ) {
1406 $background_process = new DeleteGracePeriod();
1407 $item_to_process = array();
1408 $item_to_process['users'] = $users;
1409 $background_process->push_to_queue( $item_to_process );
1410 $background_process->save()->dispatch();
1411 }
1412
1413 }
1414
1415 // We will get the IDs of users we want below.
1416 $users_to_enforce = '';
1417
1418 $users_we_want_to_enforce = ( isset( $input['enforced_users'] ) && ! empty( $input['enforced_users'] ) ) ? array_filter( $input['enforced_users'] ) : [];
1419 $roles_we_want_to_enforce = ( isset( $input['enforced_roles'] ) && ! empty( $input['enforced_roles'] ) ) ? SettingsPage::extract_roles_from_input( $output['enforced_roles'] ) : [];
1420
1421 // Enforced users have been provided or setting has changed since last update.
1422 if ( WP2FA::get_wp2fa_setting( 'enforced_users' ) !== $users_we_want_to_enforce ) {
1423
1424 foreach ( $users_we_want_to_enforce as $enforced_user ) {
1425 global $wpdb;
1426 $select = $wpdb->prepare(
1427 "
1428 SELECT ID
1429 FROM $wpdb->users
1430 WHERE user_login = %s
1431 ",
1432 [
1433 $enforced_user
1434 ]
1435 );
1436 $user_data = $wpdb->get_results( $select );
1437 $user = $user_data[0];
1438 $users_to_enforce .= $user->ID . ',';
1439 }
1440
1441 }
1442
1443 // Enforced roles have been provided or setting has changed since last update.
1444 if ( WP2FA::get_wp2fa_setting( 'enforced_roles' ) !== $roles_we_want_to_enforce ) {
1445
1446 $excluded_users = $output['excluded_users'];
1447
1448 if ( ! empty( $output['enforced_roles'] ) ) {
1449 for ( $count = 0; $count < $slices; $count++ ) {
1450 $users_args = array(
1451 'number' => $batch_size,
1452 'offset' => $count * $batch_size,
1453 'fields' => array( 'ID' ),
1454 'count' => $count,
1455 'batch_size' => $batch_size,
1456 'role__in' => $roles_we_want_to_enforce,
1457 'excluded_users' => $excluded_users,
1458 );
1459 if ( WP2FA::is_this_multisite() ) {
1460 $users_args['blog_id'] = 0;
1461 }
1462 $users = UserUtils::get_all_user_ids( 'query', $users_args );
1463 $users_to_enforce .= $users;
1464 }
1465 }
1466 }
1467
1468 // Remove duplicate IDs.
1469 $users_to_enforce = implode( ',', array_unique( explode( ',', $users_to_enforce ) ) );
1470
1471 $log_content = __( 'Policy = Certain Users/Roles. Enforce 2FA on following IDs', 'wp-2fa' ) . "\n" . $users_to_enforce;
1472 Debugging::log( $log_content );
1473
1474 if ( ! empty( $users_to_enforce ) ) {
1475 $background_process = new Enforce2FA();
1476 $item_to_process = array();
1477 $item_to_process['users'] = $users_to_enforce;
1478 $item_to_process['grace_expiry'] = $grace_expiry;
1479 $item_to_process['grace_policy'] = sanitize_text_field( $input['grace-policy'] );
1480 $item_to_process['notify_users'] = isset( $input['notify_users'] ) ? $input['notify_users'] : false;
1481 $background_process->push_to_queue( $item_to_process );
1482 $background_process->save()->dispatch();
1483 }
1484
1485 }
1486
1487 // 2FA is not enforced on users, wipe data.
1488 if ( 'do-not-enforce' === $input['enforcement-policy'] ) {
1489
1490 for ( $count = 0; $count < $slices; $count++ ) {
1491 $users_args = array(
1492 'number' => $batch_size,
1493 'offset' => $count * $batch_size,
1494 'fields' => array( 'ID' ),
1495 'count' => $count,
1496 'batch_size' => $batch_size,
1497 );
1498 if ( WP2FA::is_this_multisite() ) {
1499 $users_args['blog_id'] = 0;
1500 }
1501
1502 $users = UserUtils::get_all_user_ids_who_have_wp_2fa_metadata_present( $users_args );
1503
1504 $log_content = __( 'Policy = Do not enforce. Remove grace period from following IDs (users not obliged to setup 2FA)', 'wp-2fa' ) . "\n" . $users;
1505 Debugging::log( $log_content );
1506
1507 if ( ! empty( $users ) ) {
1508 $background_process = new DeleteGracePeriod();
1509 $item_to_process = array();
1510 $item_to_process['users'] = $users;
1511 $background_process->push_to_queue( $item_to_process );
1512 $background_process->save()->dispatch();
1513 }
1514 }
1515 }
1516
1517 // If any users are being exluded, delete any wp 2fa data.
1518 if ( isset( $output['excluded_users'] ) &&
1519 !empty( array_diff(WP2FA::get_wp2fa_setting( 'excluded_users' ), $output['excluded_users']) ) ) {
1520 // Wipe user 2fa data.
1521 $user_array = $output['excluded_users'];
1522 foreach ( $user_array as $user ) {
1523 if ( ! empty( $user ) ) {
1524 $user_to_wipe = get_user_by( 'login', $user );
1525 global $wpdb;
1526 $wpdb->query(
1527 $wpdb->prepare(
1528 "
1529 DELETE FROM $wpdb->usermeta
1530 WHERE user_id = %d
1531 AND meta_key LIKE %s
1532 ",
1533 [
1534 $user_to_wipe->ID,
1535 'wp_2fa_%'
1536 ]
1537 )
1538 );
1539
1540 }
1541 }
1542 }
1543
1544 if ( isset( $output['excluded_roles'] ) && ! empty( $output['excluded_roles'] ) &&
1545 !empty( array_diff(WP2FA::get_wp2fa_setting( 'excluded_roles' ), $output['excluded_roles'] ) ) )
1546 {
1547 $excluded_roles_array = array_filter( array_map('strtolower', $output['excluded_roles'] ) );
1548
1549 // Flush the old expiry away from ALL users, we will re-apply them based on the current setup at the end of this.
1550 for ( $count = 0; $count < $slices; $count++ ) {
1551 $users_args = array(
1552 'number' => $batch_size,
1553 'offset' => $count * $batch_size,
1554 'fields' => array( 'ID' ),
1555 'count' => $count,
1556 'batch_size' => $batch_size,
1557 'role__in' => $excluded_roles_array,
1558 );
1559 if ( WP2FA::is_this_multisite() ) {
1560 $users_args['blog_id'] = 0;
1561 }
1562
1563 // Get users who are in a role we are excluding.
1564 $users = UserUtils::get_all_user_ids( 'query', $users_args );
1565
1566 $log_content = __( 'We have excluded roles. RemoveAllUserData 2FA on following IDs', 'wp-2fa' ) . "\n" . $users;
1567 Debugging::log( $log_content );
1568
1569 if ( ! empty( $users ) ) {
1570 $background_process = new RemoveAllUserData();
1571 $item_to_process = array();
1572 $item_to_process['users'] = $users;
1573 $background_process->push_to_queue( $item_to_process );
1574 $background_process->save()->dispatch();
1575 }
1576 }
1577 }
1578
1579 // 2FA is applied to users which are members of specific site.
1580 if ( 'enforce-on-multisite' === $input['enforcement-policy'] ) {
1581
1582 if ( isset( $output['included_sites'] ) && ! empty( $output['included_sites'] ) && WP2FA::get_wp2fa_setting( 'included_sites' ) !== $output['included_sites'] ) {
1583
1584 foreach ( $output['included_sites'] as $blogId ) {
1585 for ( $count = 0; $count < $slices; $count++ ) {
1586 $users_args = array(
1587 'number' => $batch_size,
1588 'offset' => $count * $batch_size,
1589 'fields' => array( 'ID' ),
1590 'count' => $count,
1591 'batch_size' => $batch_size,
1592 'skip_existing_2fa_users' => true,
1593 'blog_id' => $blogId,
1594 );
1595
1596 $users = UserUtils::get_all_user_ids( 'get_users', $users_args );
1597
1598 $log_content = __( 'Policy = Enforce for these multisite users. Enforcing 2FA on following IDs', 'wp-2fa' ) . "\n" . $users;
1599 Debugging::log( $log_content );
1600
1601 if ( ! empty( $users ) ) {
1602 $background_process = new Enforce2FA();
1603 $item_to_process = array();
1604 $item_to_process['users'] = $users;
1605 $item_to_process['grace_expiry'] = $grace_expiry;
1606 $item_to_process['grace_policy'] = $output['grace-policy'];
1607 $item_to_process['notify_users'] = $output['notify_users'];
1608 $item_to_process['excluded_users'] = $output['excluded_users'];
1609 $item_to_process['excluded_roles'] = $output['excluded_roles'];
1610 $item_to_process['excluded_sites'] = $output['excluded_sites'];
1611 $item_to_process['included_sites'] = $output['included_sites'];
1612 $background_process->push_to_queue( $item_to_process );
1613 $background_process->save()->dispatch();
1614 }
1615 }
1616 }
1617 }
1618 }
1619 }
1620
1621 // 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.
1622 global $wp_settings_errors;
1623 if ( isset( $wp_settings_errors ) ) {
1624 $errors = array_map( 'unserialize', array_unique( array_map( 'serialize', $wp_settings_errors ) ) );
1625 $wp_settings_errors = $errors;
1626 }
1627
1628 $log_content = __( 'Settings saving processes complete', 'wp-2fa' );
1629 Debugging::log( $log_content );
1630
1631 return $output;
1632 }
1633
1634 /**
1635 * Hide settings menu item
1636 */
1637 public function hide_settings() {
1638 $user = wp_get_current_user();
1639
1640 // Check we have a user before doing anything else.
1641 if ( is_a( $user, '\WP_User' ) ) {
1642 $user_id = (int) $user->ID;
1643 if ( ! empty( WP2FA::get_wp2fa_setting( '2fa_settings_last_updated_by' ) ) ) {
1644 $main_user = (int) WP2FA::get_wp2fa_setting( '2fa_settings_last_updated_by' );
1645 } else {
1646 $main_user = '';
1647 }
1648 if ( ! empty( WP2FA::get_wp2fa_setting( 'limit_access' ) ) && $user->ID !== $main_user ) {
1649 // Remove admin menu item.
1650 remove_submenu_page( 'options-general.php', 'wp-2fa-settings' );
1651 }
1652 }
1653 }
1654
1655 /**
1656 * Add unlock user link to user actions.
1657 *
1658 * @param array $links Default row content.
1659 *
1660 * @return array
1661 */
1662 public function add_plugin_action_links( $links ) {
1663
1664 if ( WP2FA::is_this_multisite() ) {
1665 $url = network_admin_url( '/settings.php?page=wp-2fa-settings' );
1666 } else {
1667 $url = admin_url( '/options-general.php?page=wp-2fa-settings' );
1668 }
1669
1670 $links = array_merge(
1671 array(
1672 '<a href="' . esc_url( $url ) . '">' . esc_html__( 'Configure 2FA Settings', 'wp-2fa' ) . '</a>',
1673 ),
1674 $links
1675 );
1676
1677 return $links;
1678
1679 }
1680
1681 /**
1682 * User unlocked notice.
1683 */
1684 public function user_unlocked_notice() {
1685 ?>
1686 <div class="notice notice-success is-dismissible">
1687 <p><?php esc_html_e( 'User account successfully unlocked. User can login again.', 'wp-2fa' ); ?></p>
1688 <button type="button" class="notice-dismiss">
1689 <span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
1690 </button>
1691 </div>
1692 <?php
1693 }
1694
1695 /**
1696 * User deleted 2FA settings notification
1697 */
1698 public function user_deleted_2fa_notice() {
1699 ?>
1700 <div class="notice notice-success is-dismissible">
1701 <p><?php esc_html_e( 'Your 2FA settings have been removed.', 'wp-2fa' ); ?></p>
1702 <button type="button" class="notice-dismiss">
1703 <span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
1704 </button>
1705 </div>
1706 <?php
1707 }
1708
1709 /**
1710 * Admin deleted user 2FA settings notification
1711 */
1712 public function admin_deleted_2fa_notice() {
1713 ?>
1714 <div class="notice notice-success is-dismissible">
1715 <p><?php esc_html_e( 'User 2FA settings have been removed.', 'wp-2fa' ); ?></p>
1716 <button type="button" class="notice-dismiss">
1717 <span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
1718 </button>
1719 </div>
1720 <?php
1721 }
1722
1723 /**
1724 * Semd email to let user know they need to enabled 2FA
1725 *
1726 * @param int $user_id User ID.
1727 */
1728 public static function send_2fa_enforced_email( $user_id, $override_grace_period = '' ) {
1729 // Bail if the user has not enabled this email.
1730 if ( 'enable_enforced_email' !== WP2FA::get_wp2fa_email_templates( 'send_enforced_email' ) ) {
1731 return false;
1732 }
1733
1734 $user_id = (int) $user_id;
1735 // Grab user data.
1736 $user = get_user_by( 'id', $user_id );
1737 // Check if user has any enabled 2FA methods before sending.
1738 $enabled_methods = get_user_meta( $user->ID, 'wp_2fa_enabled_methods', true );
1739 if ( ! empty( $enabled_methods ) ) {
1740 return false;
1741 }
1742
1743 // Grab user email.
1744 $email = $user->user_email;
1745
1746 $subject = wp_strip_all_tags( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'enforced_email_subject' ), $user_id, '', $override_grace_period ) );
1747 $message = wpautop( WP2FA::replace_email_strings( WP2FA::get_wp2fa_email_templates( 'enforced_email_body' ), $user_id, '', $override_grace_period ) );
1748
1749 return self::send_email( $email, $subject, $message );
1750 }
1751
1752 public function update_wp2fa_network_options() {
1753 check_admin_referer( 'wp_2fa_settings-options' );
1754
1755 if ( isset( $_POST['wp_2fa_settings'] ) ) {
1756 $options = $this->validate_and_sanitize( wp_unslash( $_POST['wp_2fa_settings'] ) );
1757 $update_options = update_network_option( null, 'wp_2fa_settings', $options );
1758 }
1759
1760 // redirect back to our options page.
1761 wp_safe_redirect(
1762 add_query_arg(
1763 array(
1764 'page' => 'wp-2fa-settings',
1765 'wp_2fa_network_settings_updated' => 'true',
1766 ),
1767 network_admin_url( 'settings.php' )
1768 )
1769 );
1770 exit;
1771 }
1772
1773 /**
1774 * Handle saving email options to the network main site options.
1775 */
1776 public function update_wp2fa_network_email_options() {
1777 if ( isset( $_POST['email_from_setting'] ) ) {
1778 $options = $this->validate_and_sanitize_email( wp_unslash( $_POST ) );
1779
1780 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'] ) ) {
1781 // redirect back to our options page.
1782 wp_safe_redirect(
1783 add_query_arg(
1784 array(
1785 'page' => 'wp-2fa-settings',
1786 'wp_2fa_network_settings_updated' => 'false',
1787 'tab' => 'email-settings',
1788 ),
1789 network_admin_url( 'settings.php' )
1790 )
1791 );
1792 exit;
1793 }
1794
1795 $update_options = update_network_option( null, 'wp_2fa_email_settings', $options );
1796 }
1797
1798 // redirect back to our options page.
1799 wp_safe_redirect(
1800 add_query_arg(
1801 array(
1802 'page' => 'wp-2fa-settings',
1803 'wp_2fa_network_settings_updated' => 'true',
1804 'tab' => 'email-settings',
1805 ),
1806 network_admin_url( 'settings.php' )
1807 )
1808 );
1809 exit;
1810 }
1811
1812 /**
1813 * These are used instead of add_settings_error which in a network site. Used to show if settings have been updated or failed.
1814 */
1815 public function settings_saved_network_admin_notice() {
1816 if ( isset( $_GET['wp_2fa_network_settings_updated'] ) && $_GET['wp_2fa_network_settings_updated'] == 'true' ) :
1817 ?>
1818 <div class="notice notice-success is-dismissible">
1819 <p><?php esc_html_e( '2FA Settings Updated', 'wp-2fa' ); ?></p>
1820 <button type="button" class="notice-dismiss">
1821 <span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
1822 </button>
1823 </div>
1824 <?php
1825 endif;
1826 if ( isset( $_GET['wp_2fa_network_settings_updated'] ) && $_GET['wp_2fa_network_settings_updated'] == 'false' ) :
1827 ?>
1828 <div class="notice notice-error is-dismissible">
1829 <p><?php esc_html_e( 'Please ensure both custom email address and display name are provided.', 'wp-2fa' ); ?></p>
1830 <button type="button" class="notice-dismiss">
1831 <span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span>
1832 </button>
1833 </div>
1834 <?php
1835 endif;
1836 }
1837
1838 /**
1839 * Email settings
1840 */
1841 private function email_from_settings() {
1842 ?>
1843 <h3><?php esc_html_e( 'Which email address should the plugin use as a from address?', 'wp-2fa' ); ?></h3>
1844 <p class="description">
1845 <?php esc_html_e( 'Use these settings to customize the "from" name and email address for all correspondence sent from our plugin.', 'wp-2fa' ); ?>
1846 </p>
1847 <table class="form-table">
1848 <tbody>
1849 <tr>
1850 <th><label for="2fa-method"><?php esc_html_e( 'From email & name', 'wp-2fa' ); ?></label>
1851 </th>
1852 <td>
1853 <fieldset class="contains-hidden-inputs">
1854 <label for="use-defaults">
1855 <input type="radio" name="email_from_setting" id="use-defaults" value="use-defaults"
1856 <?php checked( WP2FA::get_wp2fa_email_templates( 'email_from_setting' ), 'use-defaults' ); ?>
1857 >
1858 <span><?php esc_html_e( 'Use the email address from the WordPress general settings.', 'wp-2fa' ); ?></span>
1859 </label>
1860
1861 <br/>
1862 <label for="use-custom-email">
1863 <input type="radio" name="email_from_setting" id="use-custom-email" value="use-custom-email"
1864 <?php checked( WP2FA::get_wp2fa_email_templates( 'email_from_setting' ), 'use-custom-email' ); ?>
1865 data-unhide-when-checked=".custom-from-inputs">
1866 <span><?php esc_html_e( 'Use another email address', 'wp-2fa' ); ?></span>
1867 </label>
1868 <fieldset class="hidden custom-from-inputs">
1869 <br/>
1870 <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>
1871 <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' ); ?>">
1872 </fieldset>
1873
1874 </fieldset>
1875 </td>
1876 </tr>
1877 </tbody>
1878 </table>
1879
1880 <br>
1881 <hr>
1882
1883 <h3><?php esc_html_e( 'Email delivery test', 'wp-2fa' ); ?></h3>
1884 <p class="description">
1885 <?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' ); ?>
1886 </p>
1887 <p>
1888 <button type="button" name="test_email_config_test"
1889 class="button js-button-test-email-trigger"
1890 data-email-id="config_test"
1891 data-nonce="<?php echo esc_attr( wp_create_nonce( 'wp-2fa-email-test-config_test' ) ); ?>">
1892 <?php esc_html_e( 'Test email delivery', 'wp-2fa' ); ?>
1893 </button>
1894 </p>
1895
1896 <br>
1897 <hr>
1898
1899 <?php
1900 }
1901
1902 /**
1903 * Creates the email notification definitions.
1904 *
1905 * @return EmailTemplate[]
1906 */
1907 public function get_email_notification_definitions( ) {
1908 $result = [
1909 new EmailTemplate(
1910 'enforced',
1911 esc_html__( 'Enforced 2FA email', 'wp-2fa' ),
1912 esc_html__( 'This is the email sent to applicable users when you enforce 2fa.', 'wp-2fa' )
1913 ),
1914 new EmailTemplate(
1915 'login_code',
1916 esc_html__( 'Login code email', 'wp-2fa' ),
1917 esc_html__( 'This is the email sent to a user when a login code is required.', 'wp-2fa' )
1918 ),
1919 new EmailTemplate(
1920 'account_locked',
1921 esc_html__( 'User account locked email', 'wp-2fa' ),
1922 esc_html__( 'This is the email sent to a user upon grace period expiry.', 'wp-2fa' )
1923 ),
1924 new EmailTemplate(
1925 'account_unlocked',
1926 esc_html__( 'User account unlocked email', 'wp-2fa' ),
1927 esc_html__( 'This is the email sent to a user when the user\'s account has been unlocked.', 'wp-2fa' )
1928 )
1929 ];
1930
1931 $result[1]->setCanBeToggled(false);
1932 $result[2]->setEmailContentId('user_account_locked');
1933 $result[3]->setEmailContentId('user_account_unlocked');
1934 return $result;
1935 }
1936 /**
1937 * Email settings
1938 */
1939 private function email_settings() {
1940 $custom_user_page_id = WP2FA::get_wp2fa_setting( 'custom-user-page-id' );
1941 $email_template_definitions = $this->get_email_notification_definitions();
1942 ?>
1943 <h1><?php esc_html_e( 'Email Templates', 'wp-2fa' ); ?></h1>
1944 <?php foreach ($email_template_definitions as $email_template) : ?>
1945 <?php $template_id = $email_template->getId(); ?>
1946 <h3><?php echo $email_template->getTitle(); ?></h3>
1947 <p class="description"><?php echo $email_template->getDescription(); ?></p>
1948 <table class="form-table">
1949 <tbody>
1950 <?php if ($email_template->canBeToggled()): ?>
1951 <tr>
1952 <th><label for="send_<?php echo $template_id; ?>_email"><?php esc_html_e( 'Send this email', 'wp-2fa' ); ?></label></th>
1953 <td>
1954 <fieldset>
1955 <input type="checkbox" id="send_<?php echo $template_id; ?>_email" name="send_<?php echo $template_id; ?>_email" value="enable_<?php echo $template_id; ?>_email"
1956 <?php checked( 'enable_' . $template_id . '_email', WP2FA::get_wp2fa_email_templates( 'send_' . $template_id . '_email' )); ?>
1957 >
1958 <label for="send_<?php echo $template_id; ?>_email"><?php esc_html_e( 'Uncheck to disable this message.', 'wp-2fa' ); ?></label>
1959 </fieldset>
1960 </td>
1961 </tr>
1962 <?php endif; ?>
1963 <?php $template_id = $email_template->getEmailContentId(); ?>
1964 <tr>
1965 <th><label for="<?php echo $template_id; ?>_email_subject"><?php esc_html_e( 'Email subject', 'wp-2fa' ); ?></label></th>
1966 <td>
1967 <fieldset>
1968 <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' ) ); ?>">
1969 </fieldset>
1970 </td>
1971 </tr>
1972 <tr>
1973 <th>
1974 <label for="<?php echo $template_id; ?>_email_body"><?php esc_html_e( 'Email body', 'wp-2fa' ); ?></label>
1975 </br>
1976 <label for="<?php echo $template_id; ?>_email_tags" style="font-weight: 400;"><?php esc_html_e( 'Available template tags:', 'wp-2fa' ); ?></label>
1977 </br>
1978 </br>
1979 <span style="font-weight: 400;">
1980 {site_url}</br>
1981 {site_name}</br>
1982 {grace_period}</br>
1983 {user_login_name}</br>
1984 {login_code}
1985 <?php
1986 if ( ! empty( $custom_user_page_id ) ) {
1987 echo '</br>{2fa_settings_page_url}';
1988 }
1989 ?>
1990 </span>
1991 </th>
1992 <td>
1993 <fieldset>
1994 <?php
1995 $message = WP2FA::get_wp2fa_email_templates( $template_id . '_email_body' );
1996 $content = $message;
1997 $editor_id = $template_id . '_email_body';
1998 $settings = array(
1999 'media_buttons' => false,
2000 'editor_height' => 200,
2001 );
2002 wp_editor( $content, $editor_id, $settings );
2003 ?>
2004 </fieldset>
2005 <p>
2006 <button type="button" name="test_email_<?php echo esc_attr( $template_id ); ?>"
2007 class="button js-button-test-email-trigger"
2008 data-email-id="<?php echo esc_attr( $template_id ); ?>"
2009 data-nonce="<?php echo esc_attr( wp_create_nonce( 'wp-2fa-email-test-' . $template_id ) ); ?>">
2010 <?php esc_html_e( 'Send test email', 'wp-2fa' ); ?>
2011 </button>
2012 </p>
2013 </td>
2014 </tr>
2015 </tbody>
2016 </table>
2017
2018 <br>
2019 <hr>
2020 <?php endforeach; ?>
2021 <?php
2022 }
2023
2024 /**
2025 * Validate email templates before saving
2026 *
2027 * @param array $input The settings array.
2028 */
2029 public function validate_and_sanitize_email( $input ) {
2030
2031 // Bail if user doesnt have permissions to be here.
2032 if ( ! current_user_can( 'manage_options' ) ) {
2033 return;
2034 }
2035
2036 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' ) ) {
2037 die( esc_html__( 'Nonce verification failed.', 'wp-2fa' ) );
2038 }
2039
2040 if ( isset( $_POST['email_from_setting'] ) && 'use-defaults' === $_POST['email_from_setting'] || isset( $_POST['email_from_setting'] ) && 'use-custom-email' === $_POST['email_from_setting'] ) {
2041 $output['email_from_setting'] = sanitize_text_field( wp_unslash( $_POST['email_from_setting'] ) );
2042 }
2043
2044 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'] ) ) {
2045 add_settings_error(
2046 'wp_2fa_settings',
2047 esc_attr( 'email_from_settings_error' ),
2048 esc_html__( 'Please provide an email address', 'wp-2fa' ),
2049 'error'
2050 );
2051 $output['custom_from_email_address'] = '';
2052 }
2053
2054 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'] ) ) {
2055 add_settings_error(
2056 'wp_2fa_settings',
2057 esc_attr( 'display_name_settings_error' ),
2058 esc_html__( 'Please provide a display name.', 'wp-2fa' ),
2059 'error'
2060 );
2061 $output['custom_from_email_address'] = '';
2062 }
2063
2064 if ( isset( $_POST['custom_from_email_address'] ) && ! empty( $_POST['custom_from_email_address'] ) ) {
2065 if ( ! filter_var( $_POST['custom_from_email_address'], FILTER_VALIDATE_EMAIL ) ) {
2066 add_settings_error(
2067 'wp_2fa_settings',
2068 esc_attr( 'email_invalid_settings_error' ),
2069 esc_html__( 'Please provide a valid email address. Your email address has not been updated.', 'wp-2fa' ),
2070 'error'
2071 );
2072 }
2073 $output['custom_from_email_address'] = sanitize_email( wp_unslash( $_POST['custom_from_email_address'] ) );
2074 }
2075
2076 if ( isset( $_POST['custom_from_display_name'] ) && ! empty( $_POST['custom_from_display_name'] ) ) {
2077 // Check if the string contains HTML/tags.
2078 preg_match( "/<\/?\w+((\s+\w+(\s*=\s*(?:\".*?\"|'.*?'|[^'\">\s]+))?)+\s*|\s*)\/?>/", $_POST['custom_from_display_name'], $matches );
2079 if ( count( $matches ) > 0 ) {
2080 add_settings_error(
2081 'wp_2fa_settings',
2082 esc_attr( 'display_name_invalid_settings_error' ),
2083 esc_html__( 'Please only use alphanumeric text. Your display name has not been updated.', 'wp-2fa' ),
2084 'error'
2085 );
2086 } else {
2087 $output['custom_from_display_name'] = sanitize_text_field( wp_unslash( $_POST['custom_from_display_name'] ) );
2088 }
2089 }
2090
2091 if ( isset( $_POST['enforced_email_subject'] ) ) {
2092 $output['enforced_email_subject'] = wp_kses_post( wp_unslash( $_POST['enforced_email_subject'] ) );
2093 }
2094
2095 if ( isset( $_POST['enforced_email_body'] ) ) {
2096 $output['enforced_email_body'] = wpautop( wp_kses_post( wp_unslash( $_POST['enforced_email_body'] ) ) );
2097 }
2098
2099 if ( isset( $_POST['login_code_email_subject'] ) ) {
2100 $output['login_code_email_subject'] = wp_kses_post( wp_unslash( $_POST['login_code_email_subject'] ) );
2101 }
2102
2103 if ( isset( $_POST['login_code_email_body'] ) ) {
2104 $output['login_code_email_body'] = wpautop( wp_kses_post( wp_unslash( $_POST['login_code_email_body'] ) ) );
2105 }
2106
2107 if ( isset( $_POST['user_account_locked_email_subject'] ) ) {
2108 $output['user_account_locked_email_subject'] = wp_kses_post( wp_unslash( $_POST['user_account_locked_email_subject'] ) );
2109 }
2110
2111 if ( isset( $_POST['user_account_locked_email_body'] ) ) {
2112 $output['user_account_locked_email_body'] = wpautop( wp_kses_post( wp_unslash( $_POST['user_account_locked_email_body'] ) ) );
2113 }
2114
2115 if ( isset( $_POST['user_account_unlocked_email_subject'] ) ) {
2116 $output['user_account_unlocked_email_subject'] = wp_kses_post( wp_unslash( $_POST['user_account_unlocked_email_subject'] ) );
2117 }
2118
2119 if ( isset( $_POST['user_account_unlocked_email_body'] ) ) {
2120 $output['user_account_unlocked_email_body'] = wpautop( wp_kses_post( wp_unslash( $_POST['user_account_unlocked_email_body'] ) ) );
2121 }
2122
2123 if ( isset( $_POST['send_enforced_email'] ) && 'enable_enforced_email' === $_POST['send_enforced_email'] ) {
2124 $output['send_enforced_email'] = sanitize_text_field( $_POST['send_enforced_email'] );
2125 }
2126
2127 if ( isset( $_POST['send_account_locked_email'] ) && 'enable_account_locked_email' === $_POST['send_account_locked_email'] ) {
2128 $output['send_account_locked_email'] = sanitize_text_field( $_POST['send_account_locked_email'] );
2129 }
2130
2131 if ( isset( $_POST['send_account_unlocked_email'] ) && 'enable_account_unlocked_email' === $_POST['send_account_unlocked_email'] ) {
2132 $output['send_account_unlocked_email'] = sanitize_text_field( $_POST['send_account_unlocked_email'] );
2133 }
2134
2135 // 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.
2136 global $wp_settings_errors;
2137 if ( isset( $wp_settings_errors ) ) {
2138 $errors = array_map( 'unserialize', array_unique( array_map( 'serialize', $wp_settings_errors ) ) );
2139 $wp_settings_errors = $errors;
2140 }
2141
2142 if ( isset( $output ) ) {
2143 return $output;
2144 } else {
2145 return;
2146 }
2147
2148 }
2149
2150 /**
2151 * Creates a new page with our shortcode present.
2152 */
2153 public function generate_custom_user_profile_page( $page_slug ) {
2154 // Bail if user doesnt have permissions to be here.
2155 if ( ! current_user_can( 'manage_options' ) ) {
2156 return;
2157 }
2158
2159 // Check if a page with slug exists.
2160 $page_exists = $this->get_post_by_post_name( $page_slug, 'page' );
2161 if ( $page_exists ) {
2162 // Seeing as the page exisits, return its ID.
2163 return $page_exists->ID;
2164 }
2165
2166 $generated_by_message = '<p>'.esc_html__( 'Page generated by', 'wp-2fa' );
2167 $generated_by_message .= ' <a href="https://www.wpwhitesecurity.com/wordpress-plugins/wp-2fa/" target="_blank">'.esc_html__( 'WP 2FA Plugin', 'wp-2fa' ).'</a>';
2168 $generated_by_message .= '</p>';
2169
2170 $user = wp_get_current_user();
2171 $post_data = array(
2172 'post_title' => 'WP 2FA User Profile',
2173 'post_name' => $page_slug,
2174 'post_content' => '[wp-2fa-setup-form] ' . $generated_by_message,
2175 'post_status' => 'publish',
2176 'post_author' => $user->ID,
2177 'post_type' => 'page',
2178 );
2179
2180 // Lets insert the post now.
2181 $result = wp_insert_post( $post_data );
2182
2183 if ( $result && ! is_wp_error( $result ) ) {
2184 $post_id = $result;
2185 set_transient( 'wp_2fa_new_custom_page_created', true, 60 );
2186 set_site_transient( 'wp_2fa_new_custom_page_created', true, 60 );
2187 return $post_id;
2188 }
2189 }
2190
2191 /**
2192 * Check if page with slug exisits.
2193 */
2194 public function get_post_by_post_name( $slug = '', $post_type = '' ) {
2195 if ( ! $slug || ! $post_type ) {
2196 return false;
2197 }
2198
2199 $post_object = get_page_by_path( $slug, OBJECT, $post_type );
2200
2201 if ( ! $post_object ) {
2202 return false;
2203 }
2204
2205 return $post_object;
2206 }
2207
2208 /**
2209 * Add our custom state to our created page.
2210 */
2211 public function add_display_post_states( $post_states, $post ) {
2212 if ( ! empty( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) ) ) {
2213 if ( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) === $post->ID ) {
2214 $post_states['wp_2fa_page_for_user'] = __( 'WP 2FA User Page', 'wp-2fa' );
2215 }
2216 }
2217
2218 return $post_states;
2219 }
2220
2221 /**
2222 * Handles sending of an email. It sets necessary header such as content type and custom from email address and name.
2223 *
2224 * @param string $recipient_email Email address to send message to.
2225 * @param string $subject Email subject.
2226 * @param string $message Message contents.
2227 *
2228 * @return bool Whether the email contents were sent successfully.
2229 */
2230 public static function send_email( $recipient_email, $subject, $message ) {
2231
2232 // Specify our desired headers.
2233 $headers = 'Content-type: text/html;charset=utf-8' . "\r\n";
2234
2235 if ( 'use-custom-email' === WP2FA::get_wp2fa_email_templates( 'email_from_setting' ) ) {
2236 $headers .= 'From: ' . WP2FA::get_wp2fa_email_templates( 'custom_from_display_name' ) . ' <' . WP2FA::get_wp2fa_email_templates( 'custom_from_email_address' ) . '>' . "\r\n";
2237 } else {
2238 $headers .= 'From: ' . get_bloginfo( 'name' ) . ' <' . get_bloginfo('admin_email') . '>' . "\r\n";
2239 }
2240
2241 // Fire our email.
2242 return wp_mail( $recipient_email, $subject, $message, $headers );
2243
2244 }
2245
2246 /**
2247 * Turns user roles data in any form and shape to an array of strings.
2248 *
2249 * @param mixed $value User role names (slugs) as raw value.
2250 *
2251 * @return string[] List of user role names (slugs).
2252 */
2253 public static function extract_roles_from_input( $value ) {
2254 if ( is_array( $value ) ) {
2255 return $value;
2256 }
2257
2258 if ( is_string( $value ) && ! empty( $value ) ) {
2259 return explode( ',', $value );
2260 }
2261
2262 return [];
2263 }
2264
2265 /**
2266 * Determine if any BG processes are currently running.
2267 *
2268 * @return int|false Number of jobs.
2269 */
2270 public function get_current_number_of_active_bg_processes() {
2271 global $wpdb;
2272
2273 $bg_jobs = $wpdb->get_results(
2274 "SELECT option_value FROM $wpdb->options
2275 WHERE option_name LIKE '%_2fa_bg_%'"
2276 );
2277
2278 return count( $bg_jobs );
2279 }
2280
2281 /**
2282 * Cancel BG processes.
2283 *
2284 */
2285 public function cancel_bg_processes() {
2286 global $wpdb;
2287 $wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE '%_2fa_bg_%'");
2288
2289 $cron_hook_identifiers = [ '2fa_check_grace_period_status', '2fa_bg_update_user_meta', '2fa_bg_wipe_all_user_data', '2fa_bg_remove_enabled_methods' ];
2290
2291 foreach ( $cron_hook_identifiers as $cron_hook_identifier ) {
2292 $cleared_jobs = wp_clear_scheduled_hook( $wpdb->prefix.$cron_hook_identifier );
2293 }
2294
2295 wp_send_json_success( $cleared_jobs );
2296 }
2297
2298 /**
2299 * Checks if the backup codes option is globally enabled
2300 *
2301 * @return bool
2302 */
2303 public static function are_backup_codes_enabled() {
2304
2305 if ( null === self::$backupCodesEnabled ) {
2306 self::$backupCodesEnabled = false;
2307
2308 if ( 'yes' === WP2FA::get_wp2fa_setting( 'backup_codes_enabled' ) ) {
2309 self::$backupCodesEnabled = true;
2310 }
2311 }
2312
2313 return self::$backupCodesEnabled;
2314 }
2315 }
2316