Traits
10 months ago
class-backup-codes.php
10 months ago
class-email-wizard-steps.php
10 months ago
class-email.php
10 months ago
class-totp-wizard-steps.php
10 months ago
class-totp.php
10 months ago
index.php
10 months ago
class-email-wizard-steps.php
349 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 2025 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\Helpers\WP_Helper; |
| 23 | use WP2FA\Admin\Views\Wizard_Steps; |
| 24 | use WP2FA\Admin\Controllers\Methods; |
| 25 | use WP2FA\Admin\Helpers\User_Helper; |
| 26 | use WP2FA\Admin\Methods\Traits\Methods_Wizards_Trait; |
| 27 | |
| 28 | /** |
| 29 | * Class for handling email codes. |
| 30 | * |
| 31 | * @since 2.6.0 |
| 32 | * |
| 33 | * @package WP2FA |
| 34 | */ |
| 35 | if ( ! class_exists( '\WP2FA\Methods\Wizards\Email_Wizard_Steps' ) ) { |
| 36 | /** |
| 37 | * Email code class, for handling email code generation and such. |
| 38 | * |
| 39 | * @since 2.6.0 |
| 40 | */ |
| 41 | class Email_Wizard_Steps extends Wizard_Steps { |
| 42 | |
| 43 | use Methods_Wizards_Trait; |
| 44 | |
| 45 | /** |
| 46 | * Keeps the main class method name, so we can call it when needed. |
| 47 | * |
| 48 | * @var string |
| 49 | * |
| 50 | * @since 2.6.0 |
| 51 | */ |
| 52 | private static $main_class = Email::class; |
| 53 | |
| 54 | /** |
| 55 | * The default value of the method order in the wizards. |
| 56 | * |
| 57 | * @var integer |
| 58 | * |
| 59 | * @since 2.6.0 |
| 60 | */ |
| 61 | private static $order = 2; |
| 62 | |
| 63 | /** |
| 64 | * Inits the class hooks |
| 65 | * |
| 66 | * @return void |
| 67 | * |
| 68 | * @since 2.4.0 |
| 69 | */ |
| 70 | public static function init() { |
| 71 | \add_filter( WP_2FA_PREFIX . 'methods_modal_options', array( __CLASS__, 'email_option' ), 10, 2 ); |
| 72 | \add_action( WP_2FA_PREFIX . 'modal_methods', array( __CLASS__, 'email_modal_configure' ) ); |
| 73 | \add_filter( WP_2FA_PREFIX . 'methods_re_configure', array( __CLASS__, 'email_re_configure' ), 10, 2 ); |
| 74 | \add_filter( WP_2FA_PREFIX . 'methods_settings', array( __CLASS__, 'email_wizard_settings' ), 10, 4 ); |
| 75 | } |
| 76 | |
| 77 | /** |
| 78 | * Shows the option for email method reconfiguring (if applicable) |
| 79 | * |
| 80 | * @param array $methods - Array of methods collected. |
| 81 | * @param string $role - The name of the role to show option to. |
| 82 | * |
| 83 | * @since 2.6.0 - Parameter $methods is added, parameter $role (name) is added and array is now returned |
| 84 | * |
| 85 | * @return array |
| 86 | */ |
| 87 | public static function email_re_configure( array $methods, string $role ): array { |
| 88 | |
| 89 | if ( ! Email::is_enabled( $role ) ) { |
| 90 | return $methods; |
| 91 | } |
| 92 | \ob_start(); |
| 93 | ?> |
| 94 | <div class="option-pill"> |
| 95 | <?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, 'hotp' ) ); ?> |
| 96 | <div class="wp2fa-setup-actions"> |
| 97 | <a 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="2fa-wizard-email"><?php \esc_html_e( 'Change email address', 'wp-2fa' ); ?></a> |
| 98 | </div> |
| 99 | </div> |
| 100 | <?php |
| 101 | $output = ob_get_clean(); |
| 102 | |
| 103 | $methods[ self::get_order( $role, $methods ) ] = array( |
| 104 | 'name' => self::$main_class::METHOD_NAME, |
| 105 | 'output' => $output, |
| 106 | ); |
| 107 | |
| 108 | return $methods; |
| 109 | } |
| 110 | |
| 111 | /** |
| 112 | * Shows the initial email setup options based on enabled methods |
| 113 | * |
| 114 | * @param array $methods - Array of methods collected. |
| 115 | * @param string $role - The name of the role to show option to. |
| 116 | * |
| 117 | * @since 2.6.0 - Parameter $methods is added, parameter $role (name) is added and array is now returned |
| 118 | * |
| 119 | * @return array |
| 120 | */ |
| 121 | public static function email_option( array $methods, string $role ): array { |
| 122 | if ( Email::is_enabled( $role ) && Methods::is_method_enabled_for_role( Email::METHOD_NAME, $role ) ) { |
| 123 | \ob_start(); |
| 124 | ?> |
| 125 | <div class="option-pill"> |
| 126 | <label for="geek"> |
| 127 | <input id="geek" name="wp_2fa_enabled_methods" type="radio" value="email"> |
| 128 | <?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'email-option-label', true ) ); ?> |
| 129 | <?php |
| 130 | $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; |
| 131 | if ( $show ) { |
| 132 | echo '<br><span class="wizard-tooltip" data-tooltip-content="data-' . \esc_attr( Email::METHOD_NAME ) . '-tooltip-content-wrapper">i</span>'; |
| 133 | } |
| 134 | ?> |
| 135 | </label> |
| 136 | <?php |
| 137 | if ( $show ) { |
| 138 | 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>'; |
| 139 | } |
| 140 | ?> |
| 141 | </div> |
| 142 | <?php |
| 143 | $output = ob_get_clean(); |
| 144 | |
| 145 | $methods[ self::get_order( $role, $methods ) ] = $output; |
| 146 | } |
| 147 | |
| 148 | return $methods; |
| 149 | } |
| 150 | |
| 151 | /** |
| 152 | * Settings page and first time wizard settings render |
| 153 | * |
| 154 | * @param array $methods - Array with all the methods in which we have to add this one. |
| 155 | * @param boolean $setup_wizard - Is that the first time setup wizard. |
| 156 | * @param string $data_role - Additional HTML data attribute. |
| 157 | * @param mixed $role - Name of the role. |
| 158 | * |
| 159 | * @return array - The array with the methods with all the methods wizard steps. |
| 160 | * |
| 161 | * @since 2.6.0 |
| 162 | */ |
| 163 | public static function email_wizard_settings( array $methods, bool $setup_wizard, string $data_role, $role = null ) { |
| 164 | $name_prefix = \WP_2FA_POLICY_SETTINGS_NAME; |
| 165 | $role_id = ''; |
| 166 | if ( null !== $role && '' !== trim( (string) $role ) ) { |
| 167 | $name_prefix .= "[{$role}]"; |
| 168 | $data_role = 'data-role="' . $role . '"'; |
| 169 | $role_id = '-' . $role; |
| 170 | } |
| 171 | \ob_start(); |
| 172 | ?> |
| 173 | <div id="<?php echo \esc_attr( Email::METHOD_NAME ); ?>-method-wrapper" class="method-wrapper"> |
| 174 | <?php echo self::hidden_order_setting( $role ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> |
| 175 | <label for="hotp<?php echo \esc_attr( $role_id ); ?>" style="margin-bottom: 0 !important;"> |
| 176 | <input type="checkbox" id="hotp<?php echo \esc_attr( $role_id ); ?>" name="<?php echo \esc_attr( $name_prefix ); ?>[enable_email]" value="enable_email" |
| 177 | <?php echo $data_role; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> |
| 178 | |
| 179 | <?php \checked( Email::POLICY_SETTINGS_NAME, Settings_Utils::get_setting_role( $role, Email::POLICY_SETTINGS_NAME ), true ); ?> |
| 180 | |
| 181 | > |
| 182 | <?php |
| 183 | \esc_html_e( 'One-time code via email (HOTP)', 'wp-2fa' ); |
| 184 | \esc_html_e( ' - ensure email deliverability with the free plugin ', 'wp-2fa' ); |
| 185 | echo '<a href="https://wordpress.org/plugins/wp-mail-smtp/" target="_blank" rel="nofollow">WP Mail SMTP</a>.'; |
| 186 | ?> |
| 187 | </label> |
| 188 | <?php |
| 189 | if ( $setup_wizard ) { |
| 190 | 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 set up a secondary 2FA method is highly recommended. You can do this in the next step of the wizard. This will allow users to log in using an alternative method should they, for example lose access to their phone.', 'wp-2fa' ) . '</p>'; |
| 191 | } |
| 192 | ?> |
| 193 | <?php |
| 194 | |
| 195 | $enabled_settings = Settings_Utils::get_setting_role( $role, 'enable_email', true ); |
| 196 | |
| 197 | ?> |
| 198 | <?php |
| 199 | ?> |
| 200 | <?php if ( ! $setup_wizard ) { ?> |
| 201 | <div class="use-different-hotp-mail<?php echo \esc_attr( ( false === $enabled_settings ? ' disabled' : '' ) ); ?>"> |
| 202 | <p class="description"> |
| 203 | <?php \esc_html_e( 'Allow user to specify the email address of choice', 'wp-2fa' ); ?> |
| 204 | </p> |
| 205 | |
| 206 | <fieldset class="email-hotp-options"> |
| 207 | <?php |
| 208 | $options = array( |
| 209 | 'yes' => array( |
| 210 | 'label' => \esc_html__( 'Yes', 'wp-2fa' ), |
| 211 | 'value' => 'specify-email_hotp', |
| 212 | ), |
| 213 | 'no' => array( |
| 214 | 'label' => \esc_html__( 'No', 'wp-2fa' ), |
| 215 | 'value' => '', |
| 216 | ), |
| 217 | ); |
| 218 | |
| 219 | foreach ( $options as $option_key => $option_settings ) { |
| 220 | ?> |
| 221 | <label for="specify-email_hotp-<?php echo \esc_attr( $option_key ); ?><?php echo \esc_attr( $role_id ); ?>"> |
| 222 | <input type="radio" |
| 223 | name="<?php echo \esc_attr( $name_prefix ); ?>[specify-email_hotp]" |
| 224 | <?php echo $data_role; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> |
| 225 | id="specify-email_hotp-<?php echo \esc_attr( $option_key ); ?><?php echo \esc_attr( $role_id ); ?>" |
| 226 | value="<?php echo \esc_attr( $option_settings['value'] ); ?>" class="js-nested" |
| 227 | <?php echo \checked( Settings_Utils::get_setting_role( $role, 'specify-email_hotp', \true ), $option_settings['value'] ); ?> |
| 228 | > |
| 229 | <span><?php echo $option_settings['label']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span> |
| 230 | </label> |
| 231 | <?php |
| 232 | } |
| 233 | ?> |
| 234 | </fieldset> |
| 235 | </div> |
| 236 | <script> |
| 237 | document.querySelector('.enforce-email-hotp #enforce-email<?php echo \esc_attr( $role_id ); ?>').onclick = function() { |
| 238 | if (this.checked == true){ |
| 239 | this.closest('.use-different-hotp-mail').querySelector('.email-hotp-options').classList.add('disabled'); |
| 240 | document.querySelector('#specify-email_hotp-<?php echo \esc_attr( $option_key ); ?><?php echo \esc_attr( $role_id ); ?>').checked = true; |
| 241 | } else { |
| 242 | this.closest('.use-different-hotp-mail').querySelector('.email-hotp-options').classList.remove('disabled'); |
| 243 | } |
| 244 | }; |
| 245 | </script> |
| 246 | <?php } ?> |
| 247 | </div> |
| 248 | <?php |
| 249 | $output = ob_get_clean(); |
| 250 | |
| 251 | $methods[ self::get_order( $role, $methods ) ] = $output; |
| 252 | |
| 253 | return $methods; |
| 254 | } |
| 255 | |
| 256 | /** |
| 257 | * Reconfigures email form |
| 258 | * |
| 259 | * @since 2.6.0 |
| 260 | * |
| 261 | * @return void |
| 262 | */ |
| 263 | public static function email_modal_configure() { |
| 264 | |
| 265 | if ( ! Email::is_enabled() ) { |
| 266 | return; |
| 267 | } |
| 268 | ?> |
| 269 | <div class="wizard-step" id="2fa-wizard-email"> |
| 270 | <fieldset> |
| 271 | <div class="step-setting-wrapper active"> |
| 272 | <div class="mb-20"> |
| 273 | <?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_hotp_intro', true ) ); ?> |
| 274 | </div> |
| 275 | <fieldset class="radio-cells"> |
| 276 | <div class="option-pill"> |
| 277 | <label for="use_wp_email"> |
| 278 | <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> |
| 279 | <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> |
| 280 | </label> |
| 281 | </div> |
| 282 | <?php |
| 283 | if ( Settings_Utils::get_setting_role( User_Helper::get_user_role(), 'specify-email_hotp' ) ) { |
| 284 | ?> |
| 285 | <div class="option-pill"> |
| 286 | <label for="use_custom_email"> |
| 287 | <input type="radio" name="wp_2fa_email_address" id="use_custom_email" value="use_custom_email"> |
| 288 | <span><?php \esc_html_e( 'Use a different email address:', 'wp-2fa' ); ?></span> |
| 289 | <?php \esc_html_e( 'Email address', 'wp-2fa' ); ?> |
| 290 | <input type="email" name="custom-email-address" id="custom-email-address" class="input" value=""/> |
| 291 | </label> |
| 292 | </div> |
| 293 | <?php |
| 294 | } |
| 295 | ?> |
| 296 | </fieldset> |
| 297 | <p class="description"><?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_hotp_help', true ) ); ?></p><br> |
| 298 | |
| 299 | <?php |
| 300 | $from_email = \get_option( 'admin_email' ); |
| 301 | |
| 302 | $custom_mail = WP2FA::get_wp2fa_email_templates( 'custom_from_email_address' ); |
| 303 | |
| 304 | if ( isset( $custom_mail ) && ! empty( (string) $custom_mail ) ) { |
| 305 | $from_email = $custom_mail; |
| 306 | } else { |
| 307 | $from_email = Settings_Page::get_default_email_address(); |
| 308 | } |
| 309 | |
| 310 | echo \wp_kses_post( str_replace( '{from_email}', \esc_html( $from_email ), WP2FA::get_wp2fa_white_label_setting( 'method_help_hotp_help_email', true ) ) ); |
| 311 | ?> |
| 312 | |
| 313 | <div class="wp2fa-setup-actions"> |
| 314 | <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> |
| 315 | <a class="button button-primary wp-2fa-button-primary modal_cancel"><?php \esc_attr_e( 'Cancel', 'wp-2fa' ); ?></a> |
| 316 | </div> |
| 317 | </div> |
| 318 | |
| 319 | <div class="step-setting-wrapper" data-step-title="<?php \esc_html_e( 'Verify configuration', 'wp-2fa' ); ?>" id="2fa-wizard-email"> |
| 320 | <div class="mb-20"> |
| 321 | <?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_verification_hotp_pre', true ) ); ?> |
| 322 | </div> |
| 323 | <fieldset> |
| 324 | <label for="2fa-email-authcode"> |
| 325 | <?php \esc_html_e( 'Verification Code:', 'wp-2fa' ); ?> |
| 326 | <input type="tel" name="wp-2fa-email-authcode" id="wp-2fa-email-authcode" class="input" value="" size="20" pattern="[0-9]*" autocomplete="off"/> |
| 327 | <script> |
| 328 | const email_authcode = document.getElementById('wp-2fa-email-authcode'); |
| 329 | email_authcode.addEventListener('input', function() { |
| 330 | this.value = this.value.trim(); |
| 331 | }); |
| 332 | </script> |
| 333 | </label> |
| 334 | <div class="verification-response"></div> |
| 335 | </fieldset> |
| 336 | <br /> |
| 337 | <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> |
| 338 | <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 ?>> |
| 339 | <span class="resend-inner"><?php \esc_html_e( 'Send me another code', 'wp-2fa' ); ?></span> |
| 340 | </a> |
| 341 | <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> |
| 342 | </div> |
| 343 | </fieldset> |
| 344 | </div> |
| 345 | <?php |
| 346 | } |
| 347 | } |
| 348 | } |
| 349 |