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 / class-user-notices.php
wp-2fa / includes / classes / Admin Last commit date
AdminSettings 20 hours ago Controllers 20 hours ago Fly-Out 20 hours ago Helpers 20 hours ago Methods 20 hours ago Settings 20 hours ago SettingsPages 20 hours ago Views 20 hours ago class-about-us.php 20 hours ago class-docs-and-support.php 20 hours ago class-free-support-notice.php 20 hours ago class-help-contact-us.php 20 hours ago class-license-page.php 20 hours ago class-new-interface-notice.php 20 hours ago class-plugin-updated-notice.php 20 hours ago class-premium-features.php 20 hours ago class-profile-section-renderer.php 20 hours ago class-settings-page.php 20 hours ago class-setup-wizard.php 20 hours ago class-top-bar-banner.php 20 hours ago class-user-listing.php 20 hours ago class-user-notices.php 20 hours ago class-user-profile.php 20 hours ago class-user-registered.php 20 hours ago class-wizard-integration.php 20 hours ago index.php 20 hours ago
class-user-notices.php
354 lines
1 <?php
2 /**
3 * Responsible for WP2FA user's notifying.
4 *
5 * @package wp2fa
6 * @subpackage user-utils
7 * @copyright 2026 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;
13
14 use WP2FA\WP2FA;
15 use WP2FA\Extensions_Loader;
16 use WP2FA\Utils\Settings_Utils;
17 use WP2FA\Admin\Helpers\WP_Helper;
18 use WP2FA\Freemius\User_Licensing;
19 use WP2FA\Admin\Controllers\Methods;
20 use WP2FA\Admin\Helpers\User_Helper;
21 use WP2FA\Admin\Controllers\Settings;
22 use WP2FA\Admin\Views\Grace_Period_Notifications;
23 use WP2FA\Admin\SettingsPages\Settings_Page_White_Label;
24
25 /**
26 * User_Notices class with user notification filters
27 *
28 * @since 2.4.0
29 */
30 if ( ! class_exists( '\WP2FA\Admin\User_Notices' ) ) {
31 /**
32 * User_Notices - Class for displaying notices to our users.
33 */
34 class User_Notices {
35
36 /**
37 * Lets set things up
38 */
39 public static function init() {
40 $user = User_Helper::get_user_object();
41 $enforcement_policy = Settings_Utils::get_setting_role( User_Helper::get_user_role( $user ), 'enforcement-policy' );
42 if ( ! empty( $enforcement_policy ) ) {
43 // Check we are supposed to, before adding action to show nag.
44 if ( in_array( $enforcement_policy, array( 'all-users', 'certain-roles-only', 'certain-users-only', 'superadmins-only', 'superadmins-siteadmins-only', 'enforce-on-multisite', true ), true ) ) {
45
46 $global_methods = Methods::get_available_2fa_methods( User_Helper::get_user_role() );
47 $users_method = User_Helper::get_enabled_method_for_user( User_Helper::get_user_object() );
48
49 if ( Grace_Period_Notifications::notify_using_dashboard( User_Helper::get_user_object() ) ) {
50 add_action( 'admin_notices', array( __CLASS__, 'user_setup_2fa_nag' ) );
51 add_action( 'network_admin_notices', array( __CLASS__, 'user_setup_2fa_nag' ) );
52 }
53
54 // If enabled method is no longer available, show nag so users reconfigures using an available remaining method.
55 if ( User_Helper::is_enforced( $user ) && ! empty( $users_method ) && empty( \array_intersect( array( $users_method ), $global_methods ) ) ) {
56 }
57 } elseif ( 'do-not-enforce' === Settings_Utils::get_setting_role( User_Helper::get_user_role( $user ), 'enforcement-policy' ) ) {
58 add_action( 'admin_notices', array( __CLASS__, 'user_reconfigure_2fa_nag' ) );
59 add_action( 'network_admin_notices', array( __CLASS__, 'user_setup_2fa_nag' ) );
60 }
61 }
62 }
63
64 /**
65 * The nag content
66 *
67 * @param string $is_shortcode - Is that a call from shortcode.
68 * @param string $configure_2fa_url - The configuration url.
69 *
70 * @return void
71 */
72 public static function user_setup_2fa_nag( $is_shortcode = '', $configure_2fa_url = '' ) {
73
74 if ( isset( $_GET['user_id'] ) ) {
75 $current_profile_user_id = (int) $_GET['user_id'];
76 } elseif ( ! is_null( User_Helper::get_user_object() ) ) {
77 $current_profile_user_id = User_Helper::get_user_object()->ID;
78 } else {
79 $current_profile_user_id = false;
80 }
81
82 if ( ! $current_profile_user_id ||
83 isset( $_GET['user_id'] ) &&
84 $_GET['user_id'] !== User_Helper::get_user_object()->ID ) {
85 return;
86 }
87
88 // In admin context, enforced-instantly users get the wizard auto-opened,
89 // so we skip the nag. On FE (shortcode), we still need to show it.
90 if ( 'output_shortcode' !== $is_shortcode && User_Helper::get_user_enforced_instantly( User_Helper::get_user_object() ) ) {
91 return;
92 }
93
94 $grace_expiry = (int) User_Helper::get_user_expiry_date( User_Helper::get_user_object() );
95
96 $class = 'notice notice-info wp-2fa-nag wp-2fa-admin-notice';
97
98 if ( User_Helper::get_user_needs_to_reconfigure_2fa( User_Helper::get_user_object() ) ) {
99 $message = WP2FA::get_wp2fa_white_label_setting( 'default-2fa-resetup-required-notice', true );
100 } else {
101 $message = WP2FA::get_wp2fa_white_label_setting( 'default-2fa-required-notice', true );
102 }
103
104 $is_nag_dismissed = User_Helper::get_nag_status();
105 $is_nag_needed = User_Helper::is_enforced( User_Helper::get_user_object()->ID );
106 $is_user_excluded = User_Helper::is_excluded( User_Helper::get_user_object()->ID );
107 $enabled_methods = User_Helper::get_enabled_method_for_user( User_Helper::get_user_object() );
108 $new_page_id = Settings_Utils::get_setting_role( User_Helper::get_user_role(), 'custom-user-page-id' );
109
110 if ( empty( $new_page_id ) ) {
111 $new_page_id = Settings::get_custom_settings_page_id( '', User_Helper::get_user_object() );
112 }
113
114 $new_page_permalink = get_permalink( $new_page_id );
115
116 $setup_url = Settings::get_setup_page_link( true );
117
118 // Stop the page from being a link to a page this user cant access if needed.
119 if ( WP_Helper::is_multisite() && ! is_user_member_of_blog( User_Helper::get_user_object()->ID ) ) {
120 $new_page_id = false;
121 }
122
123 // If we have a custom page generated, lets use it.
124 if ( ! empty( $new_page_id ) && $new_page_permalink ) {
125 $setup_url = $new_page_permalink;
126 }
127
128 // Allow setup URL to be customized if outputting via shortcode.
129 if ( isset( $is_shortcode ) && 'output_shortcode' === $is_shortcode && ! empty( $configure_2fa_url ) ) {
130 $setup_url = $configure_2fa_url;
131 }
132
133 // If the nag has not already been dismissed, and of course if the user is eligible, lets show them something.
134 $is_enforced_instantly = User_Helper::get_user_enforced_instantly( User_Helper::get_user_object() );
135 if ( ! $is_nag_dismissed && $is_nag_needed && empty( $enabled_methods ) && ! $is_user_excluded && ( ! empty( $grace_expiry ) || $is_enforced_instantly ) ) {
136
137 $show = true;
138
139 if ( class_exists( '\WP2FA\Freemius\User_Licensing' ) ) {
140 if ( Extensions_Loader::use_proxytron() ) {
141 $show = User_Licensing::enable_2fa_user_setting( true );
142 // When quota is exceeded or no license, fall back to free
143 // version behaviour — still show the nag.
144 if ( ! $show && User_Licensing::quota_check() ) {
145 $show = true;
146 }
147 }
148 }
149
150 if ( $show ) {
151 $is_frontend = ( isset( $is_shortcode ) && 'output_shortcode' === $is_shortcode );
152 $nag_id = 'wp-2fa-nag-' . wp_unique_id();
153
154 echo '<div id="' . \esc_attr( $nag_id ) . '" class="' . \esc_attr( $class ) . '">';
155 echo wpautop( \wp_kses_post( WP2FA::replace_remaining_grace_period( $message, (int) $grace_expiry ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
156
157 if ( $is_frontend ) {
158 echo ' <a href="' . \esc_url( $setup_url ) . '" class="button button-primary" data-wp2fa-nag-configure="' . \esc_attr( $nag_id ) . '">' . \esc_html__( 'Configure 2FA now', 'wp-2fa' ) . '</a>';
159 } else {
160 echo ' <a href="' . \esc_url( $setup_url ) . '" class="button button-primary">' . \esc_html__( 'Configure 2FA now', 'wp-2fa' ) . '</a>';
161 }
162
163 echo ' <a href="#" class="button button-secondary wp-2fa-dismiss-nag" data-wp-2fa-nag-dismiss="' . \esc_attr( $nag_id ) . '">' . \esc_html__( 'Remind me on next login', 'wp-2fa' ) . '</a></p>';
164 echo '</div>';
165
166 self::inline_nag_scripts( $nag_id );
167 }
168 } else {
169 self::user_reconfigure_2fa_nag();
170 }
171 }
172
173 /**
174 * The nag content
175 *
176 * @since 3.0.0
177 */
178 public static function user_reconfigure_2fa_nag() {
179
180 // If the nag has not already been dismissed, and of course if the user is eligible, lets show them something.
181 if ( User_Helper::needs_to_reconfigure_method() ) {
182 $class = 'notice notice-info wp-2fa-nag wp-2fa-admin-notice';
183
184 $message = \esc_html__( 'The 2FA method you were using is no longer allowed on this website. Please reconfigure 2FA using one of the supported methods.', 'wp-2fa' );
185
186 echo '<div class="' . \esc_attr( $class ) . '"><p>' . \esc_html( $message );
187 echo ' <a href="' . \esc_url( Settings::get_setup_page_link() ) . '" class="button button-primary">' . \esc_html__( 'Configure 2FA now', 'wp-2fa' ) . '</a>';
188 echo ' <a href="#" class="button button-secondary wp-2fa-button-secondary dismiss-user-reconfigure-nag">' . \esc_html__( 'I\'ll do it later', 'wp-2fa' ) . '</a></p>';
189 echo '</div>';
190 }
191 }
192
193
194 /**
195 * Outputs inline vanilla JS for nag dismiss and configure buttons.
196 *
197 * @param string $nag_id - The unique ID of the nag element.
198 *
199 * @return void
200 *
201 * @since 2.10.0
202 */
203 private static function inline_nag_scripts( $nag_id ) {
204 $ajax_url = \esc_url( \admin_url( 'admin-ajax.php' ) );
205 $nag_nonce = \wp_create_nonce( 'wp-2fa-dismiss-nag' );
206 ?>
207 <script type="text/javascript">
208 (function() {
209 var nagEl = document.getElementById('<?php echo \esc_js( $nag_id ); ?>');
210 if (!nagEl) return;
211
212 var dismissBtn = nagEl.querySelector('[data-wp-2fa-nag-dismiss="<?php echo \esc_js( $nag_id ); ?>"]');
213 if (dismissBtn) {
214 dismissBtn.addEventListener('click', function(e) {
215 e.preventDefault();
216 var xhr = new XMLHttpRequest();
217 xhr.open('POST', '<?php echo $ajax_url; // phpcs:ignore ?>', true);
218 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
219 xhr.onreadystatechange = function() {
220 if (xhr.readyState === 4) {
221 nagEl.style.transition = 'opacity 0.3s, max-height 0.3s';
222 nagEl.style.overflow = 'hidden';
223 nagEl.style.opacity = '0';
224 nagEl.style.maxHeight = '0';
225 setTimeout(function() { nagEl.style.display = 'none'; }, 300);
226 }
227 };
228 xhr.send('action=dismiss_nag&nonce=<?php echo \esc_js( $nag_nonce ); ?>');
229 });
230 }
231
232 var configureBtn = nagEl.querySelector('[data-wp2fa-nag-configure="<?php echo \esc_js( $nag_id ); ?>"]');
233 if (configureBtn) {
234 configureBtn.addEventListener('click', function(e) {
235 e.preventDefault();
236 // New wizard system (v4+).
237 var wizardBtn = document.querySelector('[data-wp2fa-open-wizard]');
238 if (wizardBtn) {
239 wizardBtn.click();
240 return;
241 }
242 // Legacy wizard (MicroModal).
243 if (document.getElementById('configure-2fa') && typeof window.wp2fa_fireWizard === 'function') {
244 window.wp2fa_fireWizard();
245 return;
246 }
247 // Fallback: navigate to setup URL.
248 if (configureBtn.href && configureBtn.href !== '#') {
249 window.location.href = configureBtn.href;
250 }
251 });
252 }
253 })();
254 </script>
255 <?php
256 }
257
258 /**
259 * Dismiss notice and setup a user meta value so we know its been dismissed
260 *
261 * @since 3.0.0
262 */
263 public static function dismiss_nag() {
264 check_ajax_referer( 'wp-2fa-dismiss-nag', 'nonce' );
265 User_Helper::set_nag_status( true );
266 }
267
268 /**
269 * Reset the nag when the user logs out, so they get it again next time.
270 *
271 * @param int $user_id - The ID of the user.
272 *
273 * @return void
274 *
275 * @since 3.0.0
276 */
277 public static function reset_nag( $user_id ) {
278 User_Helper::remove_nag_status( $user_id );
279 }
280
281 /**
282 * Adds setting option in the white label settings page.
283 *
284 * @return void
285 *
286 * @since 2.5.0
287 */
288 public static function white_label_settings_text() {
289 ?>
290 <tr>
291 <th><label for="email-backup-method"><?php \esc_html_e( '2FA mandatory notice', 'wp-2fa' ); ?></label></th>
292 <td>
293 <?php
294 echo Settings_Page_White_Label::create_standard_editor( 'default-2fa-required-notice' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
295 ?>
296 <div style="margin-top: 5px;"><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>
297 </td>
298 </tr>
299 <tr>
300 <th><label for="email-backup-method"><?php \esc_html_e( '2FA reconfiguration mandatory notice', 'wp-2fa' ); ?></label></th>
301 <td>
302 <?php
303 echo Settings_Page_White_Label::create_standard_editor( 'default-2fa-resetup-required-notice' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
304 ?>
305 <div style="margin-top: 5px;"><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>
306 </td>
307 </tr>
308 <tr>
309 <th><label><?php \esc_html_e( 'User profile 2FA configuration area title', 'wp-2fa' ); ?></label></th>
310 <td>
311 <?php
312 echo Settings_Page_White_Label::create_standard_editor( 'user-profile-form-preamble-title' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
313 ?>
314 <div style="margin-top: 5px;"><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>
315 </td>
316 </tr>
317 <tr>
318 <th><label><?php \esc_html_e( 'User profile 2FA configuration area description', 'wp-2fa' ); ?></label></th>
319 <td>
320 <?php
321 echo Settings_Page_White_Label::create_standard_editor( 'user-profile-form-preamble-desc' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
322 ?>
323 <div style="margin-top: 5px;"><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>
324 </td>
325 </tr>
326 <?php
327 ?>
328 <?php
329 }
330
331 /**
332 * Adds and filters extension values in the settings store array ($output).
333 *
334 * @param array $output - Array with the currently stored settings.
335 * @param array $input - Array with the input ($_POST) values.
336 *
337 * @return array
338 *
339 * @since 2.5.0
340 */
341 public static function settings_store( array $output, array $input ) {
342 if ( isset( $input['default-2fa-required-notice'] ) ) {
343 $output['default-2fa-required-notice'] = \wp_kses_post( $input['default-2fa-required-notice'] );
344 }
345
346 if ( isset( $input['default-2fa-resetup-required-notice'] ) ) {
347 $output['default-2fa-resetup-required-notice'] = \wp_kses_post( $input['default-2fa-resetup-required-notice'] );
348 }
349
350 return $output;
351 }
352 }
353 }
354