PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 2.7.0
WP 2FA – Two-factor authentication for WordPress v2.7.0
4.1.0 4.0.0 1.7.1 2.0.0 2.0.1 2.1.0 2.2.0 2.2.1 2.3.0 2.4.0 2.4.1 2.4.2 2.5.0 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.8.0 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0 3.0.1 3.1.0 3.1.1 3.1.1.2 trunk 1.2.0 1.3.0 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.6.0 1.6.1 1.6.2 1.7.0
wp-2fa / includes / classes / Admin / SettingsPages / class-settings-page-email.php
wp-2fa / includes / classes / Admin / SettingsPages Last commit date
class-settings-page-email.php 2 years ago class-settings-page-general.php 2 years ago class-settings-page-policies.php 2 years ago class-settings-page-render.php 2 years ago class-settings-page-white-label.php 2 years ago index.php 2 years ago
class-settings-page-email.php
501 lines
1 <?php
2 /**
3 * Email settings class.
4 *
5 * @package wp2fa
6 * @subpackage settings-pages
7 * @copyright 2024 Melapress
8 * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
9 * @link https://wordpress.org/plugins/wp-2fa/
10 */
11
12 namespace WP2FA\Admin\SettingsPages;
13
14 use WP2FA\Email_Template;
15 use WP2FA\WP2FA;
16 use WP2FA\Utils\Debugging;
17 use WP2FA\Admin\Helpers\WP_Helper;
18 use WP2FA\Admin\Controllers\Settings;
19 use WP2FA\Utils\Settings_Utils;
20 use WP2FA\Admin\Settings_Page;
21
22 /**
23 * Email settings tab
24 */
25 if ( ! class_exists( '\WP2FA\Admin\SettingsPages\Settings_Page_Email' ) ) {
26 /**
27 * Settings_Page_Email - Class for handling email settings
28 *
29 * @since 2.0.0
30 */
31 class Settings_Page_Email {
32
33 /**
34 * Render the settings
35 *
36 * @return void
37 *
38 * @since 2.0.0
39 */
40 public static function render() {
41 \settings_fields( WP_2FA_EMAIL_SETTINGS_NAME );
42 self::email_from_settings();
43 self::email_settings();
44 \submit_button( \esc_html__( 'Save email settings and templates', 'wp-2fa' ) );
45 }
46
47 /**
48 * Handle saving email options to the network main site options.
49 *
50 * @return void
51 *
52 * @since 2.0.0
53 *
54 * @SuppressWarnings(PHPMD.ExitExpressions)
55 */
56 public static function update_wp2fa_network_options() {
57 if ( isset( $_POST['email_from_setting'] ) ) { // phpcs:ignore
58 $options = self::validate_and_sanitize( wp_unslash( $_POST ) ); // phpcs:ignore
59
60 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'] ) ) { // phpcs:ignore
61 // redirect back to our options page.
62 \wp_safe_redirect(
63 \add_query_arg(
64 array(
65 'page' => 'wp-2fa-settings',
66 'wp_2fa_network_settings_updated' => 'false',
67 'tab' => 'email-settings',
68 ),
69 \network_admin_url( 'admin.php' )
70 )
71 );
72 exit;
73 }
74
75 Settings_Utils::update_option( WP_2FA_EMAIL_SETTINGS_NAME, $options );
76 }
77
78 // redirect back to our options page.
79 \wp_safe_redirect(
80 \add_query_arg(
81 array(
82 'page' => 'wp-2fa-settings',
83 'wp_2fa_network_settings_updated' => 'true',
84 'tab' => 'email-settings',
85 ),
86 \network_admin_url( 'admin.php' )
87 )
88 );
89 exit;
90 }
91
92 /**
93 * Email settings
94 *
95 * @return void
96 *
97 * @since 2.0.0
98 */
99 private static function email_from_settings() {
100 ?>
101 <h3><?php \esc_html_e( 'Which email address should the plugin use as a from address?', 'wp-2fa' ); ?></h3>
102 <p class="description">
103 <?php \esc_html_e( 'Use these settings to customize the "from" name and email address for all correspondence sent from our plugin.', 'wp-2fa' ); ?>
104 </p>
105 <table class="form-table">
106 <tbody>
107 <tr>
108 <th><label for="2fa-method"><?php \esc_html_e( 'From email & name', 'wp-2fa' ); ?></label>
109 </th>
110 <td>
111 <fieldset class="contains-hidden-inputs">
112 <label for="use-defaults">
113 <input type="radio" name="email_from_setting" id="use-defaults" value="use-defaults"
114 <?php \checked( WP2FA::get_wp2fa_email_templates( 'email_from_setting' ), 'use-defaults' ); ?>
115 >
116 <span><?php \esc_html_e( 'Use the email address ', 'wp-2fa' ); ?> <?php echo Settings_Page::get_default_email_address(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span>
117 </label>
118 <br/>
119 <label for="use-custom-email">
120 <input type="radio" name="email_from_setting" id="use-custom-email" value="use-custom-email"
121 <?php \checked( WP2FA::get_wp2fa_email_templates( 'email_from_setting' ), 'use-custom-email' ); ?>
122 data-unhide-when-checked=".custom-from-inputs">
123 <span><?php \esc_html_e( 'Use another email address', 'wp-2fa' ); ?></span>
124 </label>
125 <fieldset class="hidden custom-from-inputs">
126 <p class="description">
127 <?php \esc_html_e( 'A \'From email\' address with a domain different than that of your website domain name, or with a domain that the hosting does not relay might cause the notification emails to be blocked, marked as spam, or not delivered at all. If you are not 100% sure about this change, consult with your web host.', 'wp-2fa' ); ?>
128 </p>
129 <br/>
130 <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 \esc_attr( WP2FA::get_wp2fa_email_templates( 'custom_from_email_address' ) ); ?>"><br><br>
131 <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 \esc_attr( WP2FA::get_wp2fa_email_templates( 'custom_from_display_name' ) ); ?>">
132 </fieldset>
133
134 </fieldset>
135 </td>
136 </tr>
137 </tbody>
138 </table>
139 <div class="description"><i><?php \esc_html_e( 'Tip: The \'From email\' address should match your website domain. If the "from address" does not match your website domain, the emails may be blocked or marked as spam. If you are not sure about this please consult with your website administrator / developer or ', 'wp-2fa' ); ?><a href="<?php echo \esc_url( 'https://melapress.com/contact/?utm_source=plugins&utm_medium=link&utm_campaign=wp2fa' ); ?>" target="_blank"><?php \esc_html_e( 'contact us', 'wp-2fa' ); ?></a> <?php \esc_html_e( 'for more information.', 'wp-2fa' ); ?></i></div>
140 <br>
141 <hr>
142
143 <h3><?php \esc_html_e( 'Email delivery test', 'wp-2fa' ); ?></h3>
144 <p class="description">
145 <?php \esc_html_e( 'The plugin sends emails with one-time codes, blocked account notifications and more. Use the button below to confirm the plugin can successfully send emails.', 'wp-2fa' ); ?>
146 </p>
147 <p>
148 <button type="button" name="test_email_config_test"
149 class="button js-button-test-email-trigger"
150 data-email-id="config_test"
151 <?php echo WP_Helper::create_data_nonce( 'wp-2fa-email-test-config_test' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
152 <?php \esc_html_e( 'Test email delivery', 'wp-2fa' ); ?>
153 </button>
154 </p>
155
156 <br>
157 <hr>
158
159 <?php
160 }
161
162 /**
163 * Creates the email notification definitions.
164 *
165 * @return Email_Template[]
166 *
167 * @since 2.0.0
168 */
169 public static function get_email_notification_definitions() {
170
171 $backup_codes = new Email_Template(
172 'user_backup_codes',
173 \esc_html__( 'User backup codes email', 'wp-2fa' ),
174 \esc_html__( 'This email can be sent a user once backup codes are generated.', 'wp-2fa' )
175 );
176 $backup_codes->set_can_be_toggled( false );
177
178 $result = array(
179 new Email_Template(
180 'login_code_setup',
181 \esc_html__( '2FA setup code email', 'wp-2fa' ),
182 \esc_html__( 'This is the email sent to a user when setting up 2FA via email.', 'wp-2fa' )
183 ),
184 new Email_Template(
185 'login_code',
186 \esc_html__( 'Login code email', 'wp-2fa' ),
187 \esc_html__( 'This is the email sent to a user when a login code is required.', 'wp-2fa' )
188 ),
189 new Email_Template(
190 'account_locked',
191 \esc_html__( 'User account locked email', 'wp-2fa' ),
192 \esc_html__( 'This is the email sent to a user upon grace period expiry.', 'wp-2fa' )
193 ),
194 new Email_Template(
195 'account_unlocked',
196 \esc_html__( 'User account unlocked email', 'wp-2fa' ),
197 \esc_html__( 'This is the email sent to a user when the user\'s account has been unlocked.', 'wp-2fa' )
198 ),
199 new Email_Template(
200 'reset_password_code',
201 \esc_html__( 'User reset password code email', 'wp-2fa' ),
202 \esc_html__( 'This is the email sent to a user when a password reset is requested.', 'wp-2fa' )
203 ),
204 $backup_codes,
205 );
206
207 /**
208 * Add an option for external providers to implement their own email template settings for the settings tab.
209 *
210 * @param array $result - The array with all the email templates.
211 *
212 * @since 2.0.0
213 */
214 $result = \apply_filters( WP_2FA_PREFIX . 'email_notification_definitions', $result );
215
216 if ( count( $result ) > 6 ) {
217 $result[0]->set_can_be_toggled( false );
218 $result[1]->set_can_be_toggled( false );
219 $result[2]->set_can_be_toggled( false );
220 $result[3]->set_email_content_id( 'user_account_locked' );
221 $result[4]->set_email_content_id( 'user_account_unlocked' );
222 $result[5]->set_can_be_toggled( false );
223 } else {
224 $result[0]->set_can_be_toggled( false );
225 $result[1]->set_can_be_toggled( false );
226 $result[2]->set_email_content_id( 'user_account_locked' );
227 $result[3]->set_email_content_id( 'user_account_unlocked' );
228 $result[4]->set_can_be_toggled( false );
229 }
230 return $result;
231 }
232
233 /**
234 * Validate email templates before saving
235 *
236 * @since 2.0.0
237 *
238 * @SuppressWarnings(PHPMD.ExitExpressions)
239 */
240 public static function validate_and_sanitize() {
241
242 // Bail if user doesn't have permissions to be here.
243 if ( ! \current_user_can( 'manage_options' ) ) {
244 return;
245 }
246
247 Debugging::log( 'The following settings will be processed (E-mail): ' . "\n" . wp_json_encode( $_POST ) ); // phpcs:ignore
248
249 if ( empty( $_POST ) || ! isset( $_POST['_wpnonce'] ) || empty( $_POST['_wpnonce'] ) || ! \wp_verify_nonce( $_POST['_wpnonce'], WP_2FA_PREFIX . 'email_settings-options' ) && ! \wp_verify_nonce( $_POST['_wpnonce'], WP_2FA_PREFIX . 'settings-options' ) || ! \wp_verify_nonce( $_POST['_wpnonce'], WP_2FA_PREFIX . 'email_settings-options' ) && ! \wp_verify_nonce( $_POST['_wpnonce'], WP_2FA_PREFIX . 'settings-options' ) ) { // phpcs:ignore
250 die( \esc_html__( 'Nonce verification failed.', 'wp-2fa' ) );
251 }
252
253 $output = array();
254
255 if ( isset( $_POST['email_from_setting'] ) && 'use-defaults' === $_POST['email_from_setting'] || isset( $_POST['email_from_setting'] ) && 'use-custom-email' === $_POST['email_from_setting'] ) {
256 $output['email_from_setting'] = \sanitize_text_field( \wp_unslash( $_POST['email_from_setting'] ) );
257 }
258
259 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'] ) ) {
260 \add_settings_error(
261 WP_2FA_SETTINGS_NAME,
262 \esc_attr( 'email_from_settings_error' ),
263 \esc_html__( 'Please provide an email address', 'wp-2fa' ),
264 'error'
265 );
266 $output['custom_from_email_address'] = '';
267 }
268
269 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'] ) ) {
270 \add_settings_error(
271 WP_2FA_SETTINGS_NAME,
272 \esc_attr( 'display_name_settings_error' ),
273 \esc_html__( 'Please provide a display name.', 'wp-2fa' ),
274 'error'
275 );
276 $output['custom_from_email_address'] = '';
277 }
278
279 if ( isset( $_POST['custom_from_email_address'] ) && ! empty( $_POST['custom_from_email_address'] ) ) {
280 if ( ! filter_var( \wp_unslash( $_POST['custom_from_email_address'] ), FILTER_VALIDATE_EMAIL ) ) {
281 \add_settings_error(
282 WP_2FA_SETTINGS_NAME,
283 \esc_attr( 'email_invalid_settings_error' ),
284 \esc_html__( 'Please provide a valid email address. Your email address has not been updated.', 'wp-2fa' ),
285 'error'
286 );
287 }
288 $output['custom_from_email_address'] = \sanitize_email( \wp_unslash( $_POST['custom_from_email_address'] ) );
289
290 Settings_Utils::delete_option( 'dismiss_notice_mail_domain' );
291 }
292
293 if ( ! isset( $_POST['email_from_setting'] ) ) {
294 Settings_Utils::delete_option( 'dismiss_notice_mail_domain' );
295 }
296
297 if ( isset( $_POST['custom_from_display_name'] ) && ! empty( $_POST['custom_from_display_name'] ) ) {
298 // Check if the string contains HTML/tags.
299 preg_match( "/<\/?\w+((\s+\w+(\s*=\s*(?:\".*?\"|'.*?'|[^'\">\s]+))?)+\s*|\s*)\/?>/", sanitize_text_field( wp_unslash( $_POST['custom_from_display_name'] ) ), $matches );
300 if ( count( $matches ) > 0 ) {
301 \add_settings_error(
302 WP_2FA_SETTINGS_NAME,
303 \esc_attr( 'display_name_invalid_settings_error' ),
304 \esc_html__( 'Please only use alphanumeric text. Your display name has not been updated.', 'wp-2fa' ),
305 'error'
306 );
307 } else {
308 $output['custom_from_display_name'] = \sanitize_text_field( \wp_unslash( $_POST['custom_from_display_name'] ) );
309 }
310 }
311
312 if ( isset( $_POST['login_code_email_subject'] ) ) {
313 $output['login_code_email_subject'] = \wp_kses_post( \wp_unslash( $_POST['login_code_email_subject'] ) );
314 }
315
316 if ( isset( $_POST['login_code_email_body'] ) ) {
317 $output['login_code_email_body'] = \wpautop( \wp_kses_post( \wp_unslash( $_POST['login_code_email_body'] ) ) );
318 }
319
320 if ( isset( $_POST['login_code_setup_email_subject'] ) ) {
321 $output['login_code_setup_email_subject'] = \wp_kses_post( \wp_unslash( $_POST['login_code_setup_email_subject'] ) );
322 }
323
324 if ( isset( $_POST['login_code_setup_email_body'] ) ) {
325 $output['login_code_setup_email_body'] = \wpautop( \wp_kses_post( \wp_unslash( $_POST['login_code_setup_email_body'] ) ) );
326 }
327
328 if ( isset( $_POST['user_account_locked_email_subject'] ) ) {
329 $output['user_account_locked_email_subject'] = \wp_kses_post( \wp_unslash( $_POST['user_account_locked_email_subject'] ) );
330 }
331
332 if ( isset( $_POST['user_account_locked_email_body'] ) ) {
333 $output['user_account_locked_email_body'] = \wpautop( \wp_kses_post( \wp_unslash( $_POST['user_account_locked_email_body'] ) ) );
334 }
335
336 if ( isset( $_POST['user_account_unlocked_email_subject'] ) ) {
337 $output['user_account_unlocked_email_subject'] = \wp_kses_post( \wp_unslash( $_POST['user_account_unlocked_email_subject'] ) );
338 }
339
340 if ( isset( $_POST['user_account_unlocked_email_body'] ) ) {
341 $output['user_account_unlocked_email_body'] = \wpautop( \wp_kses_post( \wp_unslash( $_POST['user_account_unlocked_email_body'] ) ) );
342 }
343
344 if ( isset( $_POST['reset_password_code_email_body'] ) ) {
345 $output['reset_password_code_email_body'] = \wpautop( \wp_kses_post( \wp_unslash( $_POST['reset_password_code_email_body'] ) ) );
346 }
347
348 if ( isset( $_POST['reset_password_code_email_subject'] ) ) {
349 $output['reset_password_code_email_subject'] = \wp_kses_post( \wp_unslash( $_POST['reset_password_code_email_subject'] ) );
350 }
351
352 $output['send_account_locked_email'] = '';
353 if ( isset( $_POST['send_account_locked_email'] ) && 'enable_account_locked_email' === $_POST['send_account_locked_email'] ) {
354 $output['send_account_locked_email'] = \sanitize_text_field( \wp_unslash( $_POST['send_account_locked_email'] ) );
355 }
356
357 $output['send_account_unlocked_email'] = '';
358 if ( isset( $_POST['send_account_unlocked_email'] ) && 'enable_account_unlocked_email' === $_POST['send_account_unlocked_email'] ) {
359 $output['send_account_unlocked_email'] = \sanitize_text_field( \wp_unslash( $_POST['send_account_unlocked_email'] ) );
360 }
361
362 $output['send_login_code_email'] = '';
363 if ( isset( $_POST['send_login_code_email'] ) && 'enable_send_login_code_email' === $_POST['send_login_code_email'] ) {
364 $output['send_login_code_email'] = \sanitize_text_field( \wp_unslash( $_POST['send_login_code_email'] ) );
365 }
366
367 if ( isset( $_POST['user_backup_codes_email_subject'] ) ) {
368 $output['user_backup_codes_email_subject'] = \wp_kses_post( \wp_unslash( $_POST['user_backup_codes_email_subject'] ) );
369 }
370
371 if ( isset( $_POST['user_backup_codes_email_body'] ) ) {
372 $output['user_backup_codes_email_body'] = \wpautop( \wp_kses_post( \wp_unslash( $_POST['user_backup_codes_email_body'] ) ) );
373 }
374
375 /**
376 * Filter the values we are about to store in the plugin settings.
377 *
378 * @param array $output - The output array with all the data we will store in the settings.
379 *
380 * @since 2.0.0
381 */
382 $output = \apply_filters( WP_2FA_PREFIX . 'filter_output_email_template_content', $output );
383
384 Debugging::log( 'The following settings are being saved (E-mail): ' . "\n" . \wp_json_encode( $output ) );
385
386 // 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.
387 global $wp_settings_errors;
388 if ( isset( $wp_settings_errors ) ) {
389 $errors = array_map( 'unserialize', array_unique( array_map( 'serialize', $wp_settings_errors ) ) );
390 $wp_settings_errors = $errors; // phpcs:ignore
391 }
392
393 if ( isset( $output ) ) {
394 return $output;
395 } else {
396 return;
397 }
398 }
399
400 /**
401 * Email settings
402 *
403 * @return void
404 *
405 * @since 2.0.0
406 */
407 private static function email_settings() {
408 $custom_user_page_id = Settings::check_setting_in_all_roles( 'custom-user-page-id' );
409 if ( empty( $custom_user_page_id ) ) {
410 $custom_user_page_id = Settings::check_setting_in_all_roles( 'custom-user-page-url' );
411 }
412 $email_template_definitions = self::get_email_notification_definitions();
413 ?>
414 <h1><?php \esc_html_e( 'Email Templates', 'wp-2fa' ); ?></h1>
415 <?php foreach ( $email_template_definitions as $email_template ) : ?>
416 <?php $template_id = $email_template->get_id(); ?>
417 <h3><?php echo \esc_html( $email_template->get_title() ); ?></h3>
418 <p class="description"><?php echo $email_template->get_description(); // phpcs:ignore ?></p>
419 <table class="form-table">
420 <tbody>
421 <?php if ( $email_template->can_be_toggled() ) : ?>
422 <tr>
423 <th><label for="send_<?php echo \esc_attr( $template_id ); ?>_email"><?php \esc_html_e( 'Send this email', 'wp-2fa' ); ?></label></th>
424 <td>
425 <fieldset>
426 <input type="checkbox" id="send_<?php echo \esc_attr( $template_id ); ?>_email" name="send_<?php echo \esc_attr( $template_id ); ?>_email" value="enable_<?php echo \esc_attr( $template_id ); ?>_email"
427 <?php \checked( 'enable_' . $template_id . '_email', WP2FA::get_wp2fa_email_templates( 'send_' . $template_id . '_email' ) ); ?>
428 >
429 <label for="send_<?php echo \esc_attr( $template_id ); ?>_email"><?php \esc_html_e( 'Uncheck to disable this message.', 'wp-2fa' ); ?></label>
430 </fieldset>
431 </td>
432 </tr>
433 <?php endif; ?>
434 <?php $template_id = $email_template->get_email_content_id(); ?>
435 <tr>
436 <th><label for="<?php echo \esc_attr( $template_id ); ?>_email_subject"><?php \esc_html_e( 'Email subject', 'wp-2fa' ); ?></label></th>
437 <td>
438 <fieldset>
439 <input type="text" id="<?php echo \esc_attr( $template_id ); ?>_email_subject" name="<?php echo \esc_attr( $template_id ); ?>_email_subject" class="large-text" value="<?php echo \esc_attr( WP2FA::get_wp2fa_email_templates( $template_id . '_email_subject' ) ); ?>">
440 </fieldset>
441 </td>
442 </tr>
443 <tr>
444 <th>
445 <label for="<?php echo \esc_attr( $template_id ); ?>_email_body"><?php \esc_html_e( 'Email body', 'wp-2fa' ); ?></label>
446 </br>
447 <label for="<?php echo \esc_attr( $template_id ); ?>_email_tags" style="font-weight: 400;"><?php \esc_html_e( 'Available template tags:', 'wp-2fa' ); ?></label>
448 </br>
449 </br>
450 <span style="font-weight: 400;">
451 {site_url}</br>
452 {site_name}</br>
453 {grace_period}</br>
454 {user_login_name}</br>
455 {user_first_name}</br>
456 {user_last_name}</br>
457 {user_display_name}</br>
458 {login_code}</br>
459 {user_ip_address}</br>
460 {backup_codes}
461 <?php
462 if ( ! empty( $custom_user_page_id ) ) {
463 echo '</br>{2fa_settings_page_url}';
464 }
465 ?>
466 </span>
467 </th>
468 <td>
469 <fieldset>
470 <?php
471 $message = WP2FA::get_wp2fa_email_templates( $template_id . '_email_body' );
472 $content = $message;
473 $editor_id = $template_id . '_email_body';
474 $settings = array(
475 'media_buttons' => false,
476 'editor_height' => 200,
477 );
478 \wp_editor( $content, $editor_id, $settings );
479 ?>
480 </fieldset>
481 <p>
482 <button type="button" name="test_email_<?php echo \esc_attr( $template_id ); ?>"
483 class="button js-button-test-email-trigger"
484 data-email-id="<?php echo \esc_attr( $template_id ); ?>"
485 <?php echo WP_Helper::create_data_nonce( 'wp-2fa-email-test-' . $template_id ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
486 <?php \esc_html_e( 'Send test email', 'wp-2fa' ); ?>
487 </button>
488 </p>
489 </td>
490 </tr>
491 </tbody>
492 </table>
493
494 <br>
495 <hr>
496 <?php endforeach; ?>
497 <?php
498 }
499 }
500 }
501