PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 4.0.0
WP 2FA – Two-factor authentication for WordPress v4.0.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 / Methods / class-email-wizard-steps.php
wp-2fa / includes / classes / Admin / Methods Last commit date
Traits 1 day ago passkeys 1 day ago class-backup-codes.php 1 day ago class-email-wizard-steps.php 1 day ago class-email.php 1 day ago class-totp-wizard-steps.php 1 day ago class-totp.php 1 day ago index.php 1 day ago
class-email-wizard-steps.php
459 lines
1 <?php
2 /**
3 * Responsible for WP2FA user's Email manipulation.
4 *
5 * @package wp2fa
6 * @subpackage methods-wizard
7 * @since 2.6.0
8 * @copyright 2026 Melapress
9 * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
10 *
11 * @see https://wordpress.org/plugins/wp-2fa/
12 */
13
14 declare(strict_types=1);
15
16 namespace WP2FA\Methods\Wizards;
17
18 use WP2FA\WP2FA;
19 use WP2FA\Methods\Email;
20 use WP2FA\Admin\Settings_Page;
21 use WP2FA\Utils\Settings_Utils;
22 use WP2FA\Admin\Settings_Builder;
23 use WP2FA\Admin\Helpers\WP_Helper;
24 use WP2FA\Admin\Views\Wizard_Steps;
25 use WP2FA\Admin\Controllers\Methods;
26 use WP2FA\Admin\Helpers\User_Helper;
27 use WP2FA\Admin\Helpers\Email_Templates;
28 use WP2FA\Admin\Methods\Traits\Methods_Wizards_Trait;
29
30 /**
31 * Class for handling email codes.
32 *
33 * @since 2.6.0
34 *
35 * @package WP2FA
36 */
37 if ( ! class_exists( '\WP2FA\Methods\Wizards\Email_Wizard_Steps' ) ) {
38 /**
39 * Email code class, for handling email code generation and such.
40 *
41 * @since 2.6.0
42 */
43 class Email_Wizard_Steps extends Wizard_Steps {
44
45 use Methods_Wizards_Trait;
46
47 /**
48 * Keeps the main class method name, so we can call it when needed.
49 *
50 * @var string
51 *
52 * @since 2.6.0
53 */
54 private static $main_class = Email::class;
55
56 /**
57 * The default value of the method order in the wizards.
58 *
59 * @var integer
60 *
61 * @since 2.6.0
62 */
63 private static $order = 2;
64
65 /**
66 * Inits the class hooks
67 *
68 * @return void
69 *
70 * @since 2.4.0
71 */
72 public static function init() {
73 \add_filter( WP_2FA_PREFIX . 'methods_modal_options', array( __CLASS__, 'email_option' ), 10, 2 );
74 \add_action( WP_2FA_PREFIX . 'modal_methods', array( __CLASS__, 'modal_configure' ) );
75 \add_filter( WP_2FA_PREFIX . 'methods_re_configure', array( __CLASS__, 'email_re_configure' ), 10, 2 );
76 \add_filter( WP_2FA_PREFIX . 'methods_settings', array( __CLASS__, 'email_wizard_settings' ), 10, 4 );
77
78 self::common_init();
79 }
80
81 /**
82 * Returns extra policy settings HTML for the Email (HOTP) method.
83 *
84 * Renders code validity period (number) and allow-custom-email (radio)
85 * controls using Settings_Builder.
86 *
87 * @param null|string $role - The name of the role (if present).
88 *
89 * @return string
90 *
91 * @since 3.2.0
92 */
93 public static function get_extra_policy_settings_html( $role = null ): string {
94 $name_prefix = \WP_2FA_POLICY_SETTINGS_NAME;
95 $role_id = '';
96 if ( null !== $role && '' !== trim( (string) $role ) ) {
97 $name_prefix .= "[{$role}]";
98 $role_id = '-' . $role;
99 }
100
101 $mins = (int) Settings_Utils::get_setting_role( $role, 'email-code-period', true );
102
103 $specify_email = Settings_Utils::get_setting_role( $role, 'specify-email_hotp', true );
104
105 \ob_start();
106 ?>
107 <div class="wp2fa-extra-inline">
108 <?php
109 ?>
110 </div>
111
112 <div class="wp2fa-extra-inline">
113 <?php
114 Settings_Builder::build_option(
115 array(
116 'text' => \esc_html__( 'Allow users to specify their email address of choice?', 'wp-2fa' ),
117 'id' => 'specify-email-hotp-label' . $role_id,
118 'type' => 'simple-text',
119 )
120 );
121
122 Settings_Builder::build_option(
123 array(
124 'id' => 'specify-email-hotp' . $role_id,
125 'type' => 'toggle-checkbox',
126 'not_bool' => true,
127 'not_id' => true,
128 'value' => 'specify-email_hotp',
129 'option_name' => $name_prefix . '[specify-email_hotp]',
130 'default' => ! empty( $specify_email ) ? $specify_email : '',
131 // 'options' => array(
132 // 'specify-email_hotp' => \esc_html__( 'Yes', 'wp-2fa' ),
133 // '' => \esc_html__( 'No', 'wp-2fa' ),
134 // ),
135 )
136 );
137 ?>
138 </div>
139 <?php
140
141 return \ob_get_clean();
142 }
143
144 /**
145 * Shows the option for email method reconfiguring (if applicable)
146 *
147 * @param array $methods - Array of methods collected.
148 * @param string $role - The name of the role to show option to.
149 *
150 * @since 2.6.0 - Parameter $methods is added, parameter $role (name) is added and array is now returned
151 *
152 * @return array
153 */
154 public static function email_re_configure( array $methods, string $role ): array {
155
156 if ( ! Email::is_enabled( $role ) ) {
157 return $methods;
158 }
159 \ob_start();
160 ?>
161 <div class="option-pill">
162 <?php echo \wp_kses_post( WP2FA::contextual_reconfigure_text( WP2FA::get_wp2fa_white_label_setting( 'hotp_reconfigure_intro', true ), User_Helper::get_user_object()->ID, Email::METHOD_NAME ) ); ?>
163 <div class="wp2fa-setup-actions">
164 <a href="#" class="button button-primary wp-2fa-button-primary" data-name="next_step_setting_modal_wizard" value="<?php \esc_attr_e( 'I\'m Ready', 'wp-2fa' ); ?>" data-user-id="<?php echo \esc_attr( User_Helper::get_user_object()->ID ); ?>" <?php echo WP_Helper::create_data_nonce( 'wp-2fa-send-setup-email' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> data-next-step="wp-2fa-wizard-email"><?php \esc_html_e( 'Change email address', 'wp-2fa' ); ?></a>
165 </div>
166 </div>
167 <?php
168 $output = ob_get_clean();
169
170 $methods[ self::get_order( $role, $methods ) ] = array(
171 'name' => self::$main_class::METHOD_NAME,
172 'output' => $output,
173 );
174
175 return $methods;
176 }
177
178 /**
179 * Shows the initial email setup options based on enabled methods
180 *
181 * @param array $methods - Array of methods collected.
182 * @param string $role - The name of the role to show option to.
183 *
184 * @since 2.6.0 - Parameter $methods is added, parameter $role (name) is added and array is now returned
185 *
186 * @return array
187 */
188 public static function email_option( array $methods, string $role ): array {
189 if ( Email::is_enabled( $role ) && Methods::is_method_enabled_for_role( Email::METHOD_NAME, $role ) ) {
190 \ob_start();
191 ?>
192 <div class="option-pill">
193 <label for="geek">
194 <input id="geek" name="wp_2fa_enabled_methods" type="radio" value="email">
195 <?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'email-option-label', true ) ); ?>
196 <?php
197 $show = ( 'show_help_text' === WP2FA::get_wp2fa_white_label_setting( 'show_help_text' ) && WP2FA::get_wp2fa_white_label_setting( \esc_attr( Email::METHOD_NAME ) . '-option-label-hint', true ) ) ?? false;
198 if ( $show ) {
199 echo '<br><span class="wizard-tooltip" data-tooltip-content="data-' . \esc_attr( Email::METHOD_NAME ) . '-tooltip-content-wrapper">i</span>';
200 }
201 ?>
202 </label>
203 <?php
204 if ( $show ) {
205 echo '<p class="description tooltip-content-wrapper" data-' . \esc_attr( Email::METHOD_NAME ) . '-tooltip-content-wrapper>' . \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( \esc_attr( Email::METHOD_NAME ) . '-option-label-hint', true ) ) . '</p>';
206 }
207 ?>
208 </div>
209 <?php
210 $output = ob_get_clean();
211
212 $methods[ self::get_order( $role, $methods ) ] = $output;
213 }
214
215 return $methods;
216 }
217
218 /**
219 * Settings page and first time wizard settings render
220 *
221 * @param array $methods - Array with all the methods in which we have to add this one.
222 * @param boolean $setup_wizard - Is that the first time setup wizard.
223 * @param string $data_role - Additional HTML data attribute.
224 * @param mixed $role - Name of the role.
225 *
226 * @return array - The array with the methods with all the methods wizard steps.
227 *
228 * @since 2.6.0
229 */
230 public static function email_wizard_settings( array $methods, bool $setup_wizard, string $data_role, $role = null ) {
231 $name_prefix = \WP_2FA_POLICY_SETTINGS_NAME;
232 $role_id = '';
233 if ( null !== $role && '' !== trim( (string) $role ) ) {
234 $name_prefix .= "[{$role}]";
235 $data_role = 'data-role="' . $role . '"';
236 $role_id = '-' . $role;
237 }
238
239 /**
240 * Filter to check if the interface has to disable this method.
241 *
242 * @param bool - Should we disable the method - default false.
243 * @param Providers - The current method class.
244 * @param null|string - The role name for which the status must be checked.
245 *
246 * @since 2.9.2
247 */
248 $method_disabled_class = \apply_filters( WP_2FA_PREFIX . 'method_settings_disabled', false, self::$main_class, $role );
249
250 if ( $method_disabled_class ) {
251 $method_disabled_class = 'disabled';
252 }
253
254 \ob_start();
255 ?>
256 <div id="<?php echo \esc_attr( Email::METHOD_NAME ); ?>-method-wrapper" class="method-wrapper">
257 <?php echo self::hidden_order_setting( $role ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
258 <label class="<?php echo \esc_html( $method_disabled_class ); ?>" for="hotp<?php echo \esc_attr( $role_id ); ?>" style="margin-bottom: 0 !important;">
259 <input type="checkbox" id="hotp<?php echo \esc_attr( $role_id ); ?>" name="<?php echo \esc_attr( $name_prefix ); ?>[enable_email]" value="enable_email"
260 <?php echo $data_role; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
261
262 <?php \checked( Email::POLICY_SETTINGS_NAME, Settings_Utils::get_setting_role( $role, Email::POLICY_SETTINGS_NAME ), true ); ?>
263
264 >
265 <?php
266 echo \esc_html( WP2FA::get_wp2fa_white_label_setting( 'email-option-label', true ) );
267 \esc_html_e( ' - ensure email deliverability with the free plugin ', 'wp-2fa' );
268 echo '<a href="https://wordpress.org/plugins/wp-mail-smtp/" target="_blank" rel="nofollow">WP Mail SMTP</a>.';
269 ?>
270 </label>
271 <?php
272 if ( $setup_wizard ) {
273 echo '<p class="description">' . \esc_html__( 'When using this method, users will receive the one-time login code over email. Therefore, email deliverability is very important. Users using this method should whitelist the address from which the codes are sent. By default, this is the email address configured in your WordPress. You can run an email test from the plugin\'s settings to confirm email deliverability. If you have had email deliverability / reliability issues, we highly recommend you to install the free plugin ', 'wp-2fa' ) . '<a href="https://wordpress.org/plugins/wp-mail-smtp/" target="_blank" rel="nofollow">WP Mail SMTP</a><br><br>' . \esc_html__( 'Allowing users to configure a secondary 2FA method is highly recommended. You can configure this in the next step of the wizard. This allows users to log in using an alternative method if they lose access to their primary 2FA device, such as their phone.', 'wp-2fa' ) . '</p>';
274 }
275 ?>
276 <?php
277
278 $enabled_settings = Settings_Utils::get_setting_role( $role, 'enable_email', true );
279
280 ?>
281 <?php
282 ?>
283 <?php if ( ! $setup_wizard ) { ?>
284 <div class="use-different-hotp-mail<?php echo \esc_attr( ( false === $enabled_settings ? ' disabled' : '' ) ); ?>">
285 <?php
286 ?>
287 <p class="description">
288 <?php \esc_html_e( 'Allow user to specify the email address of choice', 'wp-2fa' ); ?>
289 </p>
290
291 <fieldset class="email-hotp-options">
292 <?php
293 $options = array(
294 'yes' => array(
295 'label' => \esc_html__( 'Yes', 'wp-2fa' ),
296 'value' => 'specify-email_hotp',
297 ),
298 'no' => array(
299 'label' => \esc_html__( 'No', 'wp-2fa' ),
300 'value' => '',
301 ),
302 );
303
304 foreach ( $options as $option_key => $option_settings ) {
305 ?>
306 <label for="specify-email_hotp-<?php echo \esc_attr( $option_key ); ?><?php echo \esc_attr( $role_id ); ?>">
307 <input type="radio"
308 name="<?php echo \esc_attr( $name_prefix ); ?>[specify-email_hotp]"
309 <?php echo $data_role; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
310 id="specify-email_hotp-<?php echo \esc_attr( $option_key ); ?><?php echo \esc_attr( $role_id ); ?>"
311 value="<?php echo \esc_attr( $option_settings['value'] ); ?>" class="js-nested"
312 <?php echo \checked( Settings_Utils::get_setting_role( $role, 'specify-email_hotp', \true ), $option_settings['value'] ); ?>
313 >
314 <span><?php echo $option_settings['label']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span>
315 </label>
316 <?php
317 }
318 ?>
319 </fieldset>
320 </div>
321 <script>
322 if ( document.querySelector('.enforce-email-hotp #enforce-email<?php echo \esc_attr( $role_id ); ?>') ) {
323 document.querySelector('.enforce-email-hotp #enforce-email<?php echo \esc_attr( $role_id ); ?>').onclick = function() {
324 if (this.checked == true){
325 this.closest('.use-different-hotp-mail').querySelector('.email-hotp-options').classList.add('disabled');
326 document.querySelector('#specify-email_hotp-<?php echo \esc_attr( $option_key ); ?><?php echo \esc_attr( $role_id ); ?>').checked = true;
327 } else {
328 this.closest('.use-different-hotp-mail').querySelector('.email-hotp-options').classList.remove('disabled');
329 }
330 };
331 }
332 </script>
333 <?php } ?>
334 </div>
335 <?php
336 $output = ob_get_clean();
337
338 $methods[ self::get_order( $role, $methods ) ] = $output;
339
340 return $methods;
341 }
342
343 /**
344 * Reconfigures email form
345 *
346 * @param \WP_User $user - WP User object.
347 *
348 * @return void
349 *
350 * @since 2.6.0
351 * @since 3.0.0 - Added $user parameter.
352 */
353 public static function modal_configure( $user ) {
354
355 if ( ! Email::is_enabled( User_Helper::get_user_role( $user ) ) ) {
356 return;
357 }
358 ?>
359 <div class="wizard-step" id="wp-2fa-wizard-email">
360 <fieldset>
361 <div class="step-setting-wrapper active">
362 <div class="mb-20">
363 <?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_hotp_intro', true ) ); ?>
364 </div>
365 <fieldset class="radio-cells">
366 <div class="option-pill">
367 <label for="use_wp_email">
368 <input type="radio" name="wp_2fa_email_address" id="use_wp_email" value="<?php echo \esc_attr( User_Helper::get_user_object()->user_email ); ?>" checked>
369 <span><?php \esc_html_e( 'Use my user email (', 'wp-2fa' ); ?><small><?php echo \esc_attr( User_Helper::get_user_object()->user_email ); ?></small><?php \esc_html_e( ')', 'wp-2fa' ); ?></span>
370 </label>
371 </div>
372 <?php
373 if ( Settings_Utils::get_setting_role( User_Helper::get_user_role(), 'specify-email_hotp' ) ) {
374 ?>
375 <div class="option-pill">
376 <label for="use_custom_email">
377 <input type="radio" name="wp_2fa_email_address" id="use_custom_email" value="use_custom_email">
378 <span><?php \esc_html_e( 'Use a different email address:', 'wp-2fa' ); ?></span>
379 <?php \esc_html_e( 'Email address', 'wp-2fa' ); ?>
380 <input type="email" name="custom-email-address" id="custom-email-address" class="input" value=""/>
381 </label>
382 </div>
383 <?php
384 }
385 ?>
386 </fieldset>
387 <p class="description"><?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_hotp_help', true ) ); ?></p><br>
388
389 <?php
390 $from_email = \get_option( 'admin_email' );
391
392 $custom_mail = Email_Templates::get_wp2fa_email_templates( 'custom_from_email_address' );
393
394 if ( isset( $custom_mail ) && ! empty( (string) $custom_mail ) ) {
395 $from_email = $custom_mail;
396 } else {
397 $from_email = Settings_Page::get_default_email_address();
398 }
399
400 echo \wp_kses_post( str_replace( '{from_email}', \esc_html( $from_email ), WP2FA::get_wp2fa_white_label_setting( 'method_help_hotp_help_email', true ) ) );
401 ?>
402
403 <div class="wp2fa-setup-actions">
404 <button class="button button-primary wp-2fa-button-primary" name="next_step_setting_email_verify" value="<?php \esc_attr_e( 'I\'m Ready', 'wp-2fa' ); ?>" data-trigger-setup-email data-user-id="<?php echo \esc_attr( User_Helper::get_user_object()->ID ); ?>" <?php echo WP_Helper::create_data_nonce( 'wp-2fa-send-setup-email' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> type="button"><?php \esc_html_e( 'I\'m Ready', 'wp-2fa' ); ?></button>
405 <a class="button button-primary wp-2fa-button-primary modal_cancel"><?php \esc_attr_e( 'Cancel', 'wp-2fa' ); ?></a>
406 <?php
407 /*
408 if ( User_Helper::is_enforced( User_Helper::get_user_object()->ID ) ) {
409 ?>
410 <a class="button button-primary wp-2fa-button-primary modal_logout" <?php echo WP_Helper::create_data_nonce( 'wp-2fa-logout' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>><?php \esc_attr_e( 'Log out', 'wp-2fa' ); ?></a>
411 <?php
412 }
413 */
414 ?>
415 </div>
416 </div>
417
418 <div class="step-setting-wrapper" data-step-title="<?php \esc_html_e( 'Verify configuration', 'wp-2fa' ); ?>" id="wp-2fa-wizard-email">
419 <div class="mb-20">
420 <?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_verification_hotp_pre', true ) ); ?>
421 </div>
422 <fieldset>
423 <label for="wp-2fa-email-authcode">
424 <?php \esc_html_e( 'Verification Code:', 'wp-2fa' ); ?>
425 <input type="tel" name="wp-2fa-email-authcode" id="wp-2fa-email-authcode" class="input" value="" size="20" pattern="[0-9]*" autocomplete="off"/>
426 <script>
427 const email_authcode = document.getElementById('wp-2fa-email-authcode');
428 email_authcode.addEventListener('input', function() {
429 this.value = this.value.trim();
430 });
431 </script>
432 </label>
433 <div class="verification-response"></div>
434 </fieldset>
435 <br />
436 <a href="#" class="button wp-2fa-button-primary" data-validate-authcode-ajax <?php echo WP_Helper::create_data_nonce( 'wp-2fa-validate-authcode' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>><?php \esc_html_e( 'Validate & Save', 'wp-2fa' ); ?></a>
437 <a href="#" class="button wp-2fa-button-primary resend-email-code" data-trigger-setup-email data-user-id="<?php echo \esc_attr( User_Helper::get_user_object()->ID ); ?>" <?php echo WP_Helper::create_data_nonce( 'wp-2fa-send-setup-email' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
438 <span class="resend-inner"><?php \esc_html_e( 'Send me another code', 'wp-2fa' ); ?></span>
439 </a>
440 <button class="wp-2fa-button-secondary button" data-close-2fa-modal aria-label="Close this dialog window"><?php \esc_html_e( 'Cancel', 'wp-2fa' ); ?></button>
441 </div>
442 </fieldset>
443 </div>
444 <?php
445 }
446
447 /**
448 * Returns an inline hint shown next to the Email method title.
449 *
450 * @return string
451 *
452 * @since 4.0.0
453 */
454 public static function get_method_title_hint(): string {
455 return ' - ' . \esc_html__( 'ensure email deliverability with the free plugin ', 'wp-2fa' ) . '<a href="https://wordpress.org/plugins/wp-mail-smtp/" target="_blank" rel="nofollow">WP Mail SMTP</a>.';
456 }
457 }
458 }
459