AdminSettings
18 hours ago
Controllers
18 hours ago
Fly-Out
18 hours ago
Helpers
18 hours ago
Methods
18 hours ago
Settings
18 hours ago
SettingsPages
18 hours ago
Views
18 hours ago
class-about-us.php
18 hours ago
class-docs-and-support.php
18 hours ago
class-free-support-notice.php
18 hours ago
class-help-contact-us.php
18 hours ago
class-license-page.php
18 hours ago
class-new-interface-notice.php
18 hours ago
class-plugin-updated-notice.php
18 hours ago
class-premium-features.php
18 hours ago
class-profile-section-renderer.php
18 hours ago
class-settings-page.php
18 hours ago
class-setup-wizard.php
18 hours ago
class-top-bar-banner.php
18 hours ago
class-user-listing.php
18 hours ago
class-user-notices.php
18 hours ago
class-user-profile.php
18 hours ago
class-user-registered.php
18 hours ago
class-wizard-integration.php
18 hours ago
index.php
18 hours ago
class-settings-page.php
1103 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Settings rendering class. |
| 4 | * |
| 5 | * @package wp2fa |
| 6 | * @subpackage settings |
| 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\Admin\SettingsPages\{ |
| 16 | Settings_Page_Policies, |
| 17 | Settings_Page_Policies_New, |
| 18 | Settings_Page_General, |
| 19 | Settings_Page_Email, |
| 20 | Settings_Page_New |
| 21 | }; |
| 22 | use WP2FA\Utils\Settings_Utils; |
| 23 | use WP2FA\Admin\Controllers\Settings; |
| 24 | use WP2FA\Licensing\Licensing_Factory; |
| 25 | use WP2FA\Admin\Helpers\Email_Templates; |
| 26 | use WP2FA\Admin\SettingsPages\Settings_Page_Render; |
| 27 | use WP2FA\Admin\SettingsPages\Settings_Page_Passkeys; |
| 28 | use WP2FA\Admin\SettingsPages\Settings_Page_White_Label; |
| 29 | use WP2FA\Admin\SettingsPages\Settings_Page_White_Labeling_New; |
| 30 | |
| 31 | /** |
| 32 | * Class for handling settings |
| 33 | */ |
| 34 | if ( ! class_exists( '\WP2FA\Admin\Settings_Page' ) ) { |
| 35 | /** |
| 36 | * Class for handling settings |
| 37 | */ |
| 38 | class Settings_Page { |
| 39 | |
| 40 | const TOP_MENU_SLUG = 'wp-2fa-policies'; |
| 41 | |
| 42 | /** |
| 43 | * Checks if the new interface is enabled. |
| 44 | * |
| 45 | * Returns true for fresh installs (setting not yet stored). |
| 46 | * Returns false for upgrades (migration sets it to false). |
| 47 | * |
| 48 | * @return bool |
| 49 | * |
| 50 | * @since 4.0.0 |
| 51 | */ |
| 52 | public static function is_new_interface_enabled(): bool { |
| 53 | $setting = WP2FA::get_wp2fa_general_setting( 'use_new_interface', true ); |
| 54 | |
| 55 | // Fresh installs: setting is not in DB yet → default to enabled. |
| 56 | if ( null === $setting || '' === $setting ) { |
| 57 | return true; |
| 58 | } |
| 59 | |
| 60 | return Settings_Utils::string_to_bool( $setting ); |
| 61 | } |
| 62 | |
| 63 | /** |
| 64 | * Redirects users to the correct settings page when the interface mode changes. |
| 65 | * |
| 66 | * After toggling "Use new interface", WordPress redirects back to the |
| 67 | * previous page slug which no longer exists – causing a white-screen. |
| 68 | * This method runs on admin_init (before the page-access check) and |
| 69 | * sends the user to the matching page. |
| 70 | * |
| 71 | * @return void |
| 72 | * |
| 73 | * @since 4.0.0 |
| 74 | */ |
| 75 | public static function redirect_on_interface_switch() { |
| 76 | if ( ! isset( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 77 | return; |
| 78 | } |
| 79 | |
| 80 | $page = \sanitize_text_field( wp_unslash( $_GET['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 81 | $use_new_interface = self::is_new_interface_enabled(); |
| 82 | $admin_url_func = \is_network_admin() ? 'network_admin_url' : 'admin_url'; |
| 83 | |
| 84 | // Old settings page requested but new interface is active → redirect. |
| 85 | if ( 'wp-2fa-settings' === $page && $use_new_interface ) { |
| 86 | \wp_safe_redirect( $admin_url_func( 'admin.php?page=wp-2fa-settings-new&settings-updated=true' ) ); |
| 87 | exit; |
| 88 | } |
| 89 | |
| 90 | // New settings page requested but old interface is active → redirect. |
| 91 | if ( 'wp-2fa-settings-new' === $page && ! $use_new_interface ) { |
| 92 | \wp_safe_redirect( $admin_url_func( 'admin.php?page=wp-2fa-settings&settings-updated=true' ) ); |
| 93 | exit; |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | /** |
| 98 | * Fallback redirect callback for a settings page whose slug is still |
| 99 | * registered but belongs to the inactive interface mode. |
| 100 | * |
| 101 | * Registered as the render callback of the hidden page so that if |
| 102 | * admin_init redirect did not fire (e.g. cached redirect from options.php), |
| 103 | * the user is still sent to the correct page instead of seeing a blank screen. |
| 104 | * |
| 105 | * @return void |
| 106 | * |
| 107 | * @since 4.0.0 |
| 108 | */ |
| 109 | public static function redirect_to_active_settings_page() { |
| 110 | $use_new_interface = self::is_new_interface_enabled(); |
| 111 | $target_page = $use_new_interface ? 'wp-2fa-settings-new' : 'wp-2fa-settings'; |
| 112 | $admin_url_func = \is_network_admin() ? 'network_admin_url' : 'admin_url'; |
| 113 | |
| 114 | \wp_safe_redirect( $admin_url_func( 'admin.php?page=' . $target_page . '&settings-updated=true' ) ); |
| 115 | exit; |
| 116 | } |
| 117 | |
| 118 | /** |
| 119 | * Create admin menu entry and settings page |
| 120 | */ |
| 121 | public static function create_settings_admin_menu() { |
| 122 | $use_new_interface = self::is_new_interface_enabled(); |
| 123 | |
| 124 | // Create admin menu item. |
| 125 | \add_menu_page( |
| 126 | \esc_html__( 'WP 2FA', 'wp-2fa' ), |
| 127 | \esc_html__( 'WP 2FA', 'wp-2fa' ), |
| 128 | 'manage_options', |
| 129 | self::TOP_MENU_SLUG, |
| 130 | null, |
| 131 | ' data:image/svg+xml;base64,' . base64_encode( file_get_contents( WP_2FA_PATH . 'dist/images/wp-2fa-white-icon20x28.svg' ) ), |
| 132 | 81 |
| 133 | ); |
| 134 | |
| 135 | if ( $use_new_interface ) { |
| 136 | // New interface: "Policies New" takes the place of the default policies page. |
| 137 | \add_submenu_page( |
| 138 | self::TOP_MENU_SLUG, |
| 139 | \esc_html__( '2FA policies', 'wp-2fa' ), |
| 140 | \esc_html__( '2FA policies', 'wp-2fa' ), |
| 141 | 'manage_options', |
| 142 | self::TOP_MENU_SLUG, |
| 143 | array( Settings_Page_Policies_New::class, 'render' ), |
| 144 | 1 |
| 145 | ); |
| 146 | } else { |
| 147 | // Old interface: original policies page. |
| 148 | \add_submenu_page( |
| 149 | self::TOP_MENU_SLUG, |
| 150 | \esc_html__( '2FA policies', 'wp-2fa' ), |
| 151 | \esc_html__( '2FA policies', 'wp-2fa' ), |
| 152 | 'manage_options', |
| 153 | self::TOP_MENU_SLUG, |
| 154 | array( Settings_Page_Policies::class, 'render' ), |
| 155 | 1 |
| 156 | ); |
| 157 | } |
| 158 | |
| 159 | \add_submenu_page( |
| 160 | self::TOP_MENU_SLUG, |
| 161 | \esc_html__( 'Passkeys', 'wp-2fa' ), |
| 162 | \esc_html__( 'Passkeys', 'wp-2fa' ), |
| 163 | 'manage_options', |
| 164 | Settings_Page_Passkeys::TOP_MENU_SLUG, |
| 165 | array( Settings_Page_Passkeys::class, 'render' ), |
| 166 | 1 |
| 167 | ); |
| 168 | |
| 169 | if ( ! $use_new_interface ) { |
| 170 | // Old interface: show old settings page. |
| 171 | \add_submenu_page( |
| 172 | self::TOP_MENU_SLUG, |
| 173 | \esc_html__( 'WP 2FA Settings', 'wp-2fa' ), |
| 174 | \esc_html__( 'Settings', 'wp-2fa' ), |
| 175 | 'manage_options', |
| 176 | 'wp-2fa-settings', |
| 177 | array( Settings_Page_Render::class, 'render' ), |
| 178 | 2 |
| 179 | ); |
| 180 | |
| 181 | // Old interface: also show new pages as separate items. |
| 182 | // \add_submenu_page( |
| 183 | // self::TOP_MENU_SLUG, |
| 184 | // \esc_html__( 'Settings New', 'wp-2fa' ), |
| 185 | // \esc_html__( 'Settings New', 'wp-2fa' ), |
| 186 | // 'manage_options', |
| 187 | // 'wp-2fa-settings-new', |
| 188 | // array( Settings_Page_New::class, 'render' ), |
| 189 | // 3 |
| 190 | // ); |
| 191 | |
| 192 | // \add_submenu_page( |
| 193 | // self::TOP_MENU_SLUG, |
| 194 | // \esc_html__( '2FA policies New', 'wp-2fa' ), |
| 195 | // \esc_html__( '2FA policies New', 'wp-2fa' ), |
| 196 | // 'manage_options', |
| 197 | // Settings_Page_Policies_New::PAGE_SLUG, |
| 198 | // array( Settings_Page_Policies_New::class, 'render' ), |
| 199 | // 4 |
| 200 | // ); |
| 201 | } else { |
| 202 | // New interface: separate White labeling sub-menu. |
| 203 | \add_submenu_page( |
| 204 | self::TOP_MENU_SLUG, |
| 205 | \esc_html__( 'White labeling', 'wp-2fa' ), |
| 206 | \esc_html__( 'White labeling', 'wp-2fa' ), |
| 207 | 'manage_options', |
| 208 | Settings_Page_White_Labeling_New::PAGE_SLUG, |
| 209 | array( Settings_Page_White_Labeling_New::class, 'render' ), |
| 210 | 2 |
| 211 | ); |
| 212 | |
| 213 | // New interface: show Settings New as the main "Settings" page. |
| 214 | \add_submenu_page( |
| 215 | self::TOP_MENU_SLUG, |
| 216 | \esc_html__( 'WP 2FA Settings', 'wp-2fa' ), |
| 217 | \esc_html__( 'Settings', 'wp-2fa' ), |
| 218 | 'manage_options', |
| 219 | 'wp-2fa-settings-new', |
| 220 | array( Settings_Page_New::class, 'render' ), |
| 221 | 4 |
| 222 | ); |
| 223 | |
| 224 | // Keep old slug registered (hidden) so options.php redirects |
| 225 | // back to it don't hit a "not allowed" dead end. |
| 226 | \add_submenu_page( |
| 227 | '', |
| 228 | '', |
| 229 | '', |
| 230 | 'manage_options', |
| 231 | 'wp-2fa-settings', |
| 232 | array( __CLASS__, 'redirect_to_active_settings_page' ) |
| 233 | ); |
| 234 | } |
| 235 | |
| 236 | // Register our policy settings. |
| 237 | \register_setting( |
| 238 | WP_2FA_POLICY_SETTINGS_NAME, |
| 239 | WP_2FA_POLICY_SETTINGS_NAME, |
| 240 | array( Settings_Page_Policies::class, 'validate_and_sanitize' ) |
| 241 | ); |
| 242 | |
| 243 | // Register our passkeys settings. |
| 244 | \register_setting( |
| 245 | WP_2FA_PASSKEYS_SETTINGS_NAME, |
| 246 | WP_2FA_PASSKEYS_SETTINGS_NAME, |
| 247 | array( Settings_Page_Passkeys::class, 'validate_and_sanitize' ) |
| 248 | ); |
| 249 | |
| 250 | // Register our white label settings. |
| 251 | \register_setting( |
| 252 | WP_2FA_WHITE_LABEL_SETTINGS_NAME, |
| 253 | WP_2FA_WHITE_LABEL_SETTINGS_NAME, |
| 254 | array( Settings_Page_White_Label::class, 'validate_and_sanitize' ) |
| 255 | ); |
| 256 | |
| 257 | // Register our settings page. |
| 258 | \register_setting( |
| 259 | WP_2FA_SETTINGS_NAME, |
| 260 | WP_2FA_SETTINGS_NAME, |
| 261 | array( Settings_Page_General::class, 'validate_and_sanitize' ) |
| 262 | ); |
| 263 | |
| 264 | \register_setting( |
| 265 | WP_2FA_EMAIL_SETTINGS_NAME, |
| 266 | WP_2FA_EMAIL_SETTINGS_NAME, |
| 267 | array( Settings_Page_Email::class, 'validate_and_sanitize' ) |
| 268 | ); |
| 269 | |
| 270 | // Register our "Settings New" option so Settings API calls on this page work. |
| 271 | \register_setting( |
| 272 | WP_2FA_NEW_SETTINGS_NAME, |
| 273 | WP_2FA_NEW_SETTINGS_NAME, |
| 274 | array( Settings_Page_New::class, 'validate_and_sanitize' ) |
| 275 | ); |
| 276 | |
| 277 | /** |
| 278 | * Fires after the main menu settings are registered. |
| 279 | * |
| 280 | * @param string - The menu slug. |
| 281 | * @param bool - Is that multisite install or not. |
| 282 | * |
| 283 | * @since 2.0.0 |
| 284 | */ |
| 285 | \do_action( WP_2FA_PREFIX . 'after_admin_menu_created', self::TOP_MENU_SLUG, false ); |
| 286 | |
| 287 | self::maybe_add_locked_reports_menu(); |
| 288 | |
| 289 | \add_action( WP_2FA_PREFIX . 'before_plugin_settings', array( __CLASS__, 'check_email' ) ); |
| 290 | } |
| 291 | |
| 292 | /** |
| 293 | * Create admin menu entry and settings page |
| 294 | */ |
| 295 | public static function create_settings_admin_menu_multisite() { |
| 296 | $use_new_interface = self::is_new_interface_enabled(); |
| 297 | |
| 298 | // Create admin menu item. |
| 299 | \add_menu_page( |
| 300 | \esc_html__( 'WP 2FA Settings', 'wp-2fa' ), |
| 301 | \esc_html__( 'WP 2FA', 'wp-2fa' ), |
| 302 | 'manage_options', |
| 303 | self::TOP_MENU_SLUG, |
| 304 | null, |
| 305 | 'data:image/svg+xml;base64,' . base64_encode( file_get_contents( WP_2FA_PATH . 'dist/images/wp-2fa-white-icon20x28.svg' ) ), |
| 306 | 81 |
| 307 | ); |
| 308 | |
| 309 | if ( $use_new_interface ) { |
| 310 | // New interface: "Policies New" takes the place of the default policies page. |
| 311 | \add_submenu_page( |
| 312 | self::TOP_MENU_SLUG, |
| 313 | \esc_html__( '2FA policies', 'wp-2fa' ), |
| 314 | \esc_html__( '2FA policies', 'wp-2fa' ), |
| 315 | 'manage_options', |
| 316 | self::TOP_MENU_SLUG, |
| 317 | array( Settings_Page_Policies_New::class, 'render' ), |
| 318 | 1 |
| 319 | ); |
| 320 | } else { |
| 321 | // Old interface: original policies page. |
| 322 | \add_submenu_page( |
| 323 | self::TOP_MENU_SLUG, |
| 324 | \esc_html__( '2FA policies', 'wp-2fa' ), |
| 325 | \esc_html__( '2FA policies', 'wp-2fa' ), |
| 326 | 'manage_options', |
| 327 | self::TOP_MENU_SLUG, |
| 328 | array( Settings_Page_Policies::class, 'render' ), |
| 329 | 1 |
| 330 | ); |
| 331 | } |
| 332 | |
| 333 | \add_submenu_page( |
| 334 | self::TOP_MENU_SLUG, |
| 335 | \esc_html__( 'Passkeys', 'wp-2fa' ), |
| 336 | \esc_html__( 'Passkeys', 'wp-2fa' ), |
| 337 | 'manage_options', |
| 338 | Settings_Page_Passkeys::TOP_MENU_SLUG, |
| 339 | array( Settings_Page_Passkeys::class, 'render' ), |
| 340 | 1 |
| 341 | ); |
| 342 | |
| 343 | if ( ! $use_new_interface ) { |
| 344 | // Old interface: show old settings page. |
| 345 | \add_submenu_page( |
| 346 | self::TOP_MENU_SLUG, |
| 347 | \esc_html__( 'WP 2FA Settings', 'wp-2fa' ), |
| 348 | \esc_html__( 'Settings', 'wp-2fa' ), |
| 349 | 'manage_options', |
| 350 | 'wp-2fa-settings', |
| 351 | array( Settings_Page_Render::class, 'render' ), |
| 352 | 2 |
| 353 | ); |
| 354 | |
| 355 | // Old interface: also show new pages as separate items. |
| 356 | // \add_submenu_page( |
| 357 | // self::TOP_MENU_SLUG, |
| 358 | // \esc_html__( 'Settings New', 'wp-2fa' ), |
| 359 | // \esc_html__( 'Settings New', 'wp-2fa' ), |
| 360 | // 'manage_options', |
| 361 | // 'wp-2fa-settings-new', |
| 362 | // array( Settings_Page_New::class, 'render' ), |
| 363 | // 3 |
| 364 | // ); |
| 365 | |
| 366 | // \add_submenu_page( |
| 367 | // self::TOP_MENU_SLUG, |
| 368 | // \esc_html__( '2FA policies New', 'wp-2fa' ), |
| 369 | // \esc_html__( '2FA policies New', 'wp-2fa' ), |
| 370 | // 'manage_options', |
| 371 | // Settings_Page_Policies_New::PAGE_SLUG, |
| 372 | // array( Settings_Page_Policies_New::class, 'render' ), |
| 373 | // 4 |
| 374 | // ); |
| 375 | } else { |
| 376 | // New interface: separate White labeling sub-menu. |
| 377 | \add_submenu_page( |
| 378 | self::TOP_MENU_SLUG, |
| 379 | \esc_html__( 'White labeling', 'wp-2fa' ), |
| 380 | \esc_html__( 'White labeling', 'wp-2fa' ), |
| 381 | 'manage_options', |
| 382 | Settings_Page_White_Labeling_New::PAGE_SLUG, |
| 383 | array( Settings_Page_White_Labeling_New::class, 'render' ), |
| 384 | 2 |
| 385 | ); |
| 386 | |
| 387 | // New interface: show Settings New as the main "Settings" page. |
| 388 | \add_submenu_page( |
| 389 | self::TOP_MENU_SLUG, |
| 390 | \esc_html__( 'WP 2FA Settings', 'wp-2fa' ), |
| 391 | \esc_html__( 'Settings', 'wp-2fa' ), |
| 392 | 'manage_options', |
| 393 | 'wp-2fa-settings-new', |
| 394 | array( Settings_Page_New::class, 'render' ), |
| 395 | 4 |
| 396 | ); |
| 397 | |
| 398 | // Keep old slug registered (hidden) so options.php redirects |
| 399 | // back to it don't hit a "not allowed" dead end. |
| 400 | \add_submenu_page( |
| 401 | null, |
| 402 | '', |
| 403 | '', |
| 404 | 'manage_options', |
| 405 | 'wp-2fa-settings', |
| 406 | array( __CLASS__, 'redirect_to_active_settings_page' ) |
| 407 | ); |
| 408 | } |
| 409 | |
| 410 | /** |
| 411 | * Fires after the main menu settings are registered. |
| 412 | * |
| 413 | * @param string - The menu slug. |
| 414 | * @param bool - Is that multisite install or not. |
| 415 | * |
| 416 | * @since 2.0.0 |
| 417 | */ |
| 418 | \do_action( WP_2FA_PREFIX . 'after_admin_menu_created', self::TOP_MENU_SLUG, true ); |
| 419 | |
| 420 | self::maybe_add_locked_reports_menu(); |
| 421 | } |
| 422 | /** |
| 423 | * Send account unlocked notification via email. |
| 424 | * |
| 425 | * @param int $user_id user ID. |
| 426 | * |
| 427 | * @return boolean |
| 428 | */ |
| 429 | public static function send_account_unlocked_email( $user_id ) { |
| 430 | // Bail if the user has not enabled this email. |
| 431 | if ( 'enable_account_unlocked_email' !== Email_Templates::get_wp2fa_email_templates( 'send_account_unlocked_email' ) ) { |
| 432 | return false; |
| 433 | } |
| 434 | |
| 435 | // Grab user data. |
| 436 | $user = get_userdata( $user_id ); |
| 437 | // Grab user email. |
| 438 | $email = $user->user_email; |
| 439 | // Setup the email contents. |
| 440 | $subject = wp_strip_all_tags( Email_Templates::replace_email_strings( Email_Templates::get_wp2fa_email_templates( 'user_account_unlocked_email_subject' ) ) ); |
| 441 | $message = wpautop( Email_Templates::replace_email_strings( Email_Templates::get_wp2fa_email_templates( 'user_account_unlocked_email_body' ), $user_id ) ); |
| 442 | |
| 443 | // @free:start |
| 444 | $message .= '<p>' . \esc_html__( 'Email sent by', 'wp-2fa' ); |
| 445 | $message .= ' <a href="https://melapress.com/wordpress-2fa/?&utm_source=plugin&utm_medium=wp2fa&utm_campaign=melapress_wp_2fa_plugin_link" target="_blank">' . \esc_html__( 'WP 2FA plugin.', 'wp-2fa' ) . '</a>'; |
| 446 | $message .= '</p>'; |
| 447 | // @free:end |
| 448 | |
| 449 | return self::send_email( $email, $subject, $message ); |
| 450 | } |
| 451 | |
| 452 | /** |
| 453 | * Hide settings menu item |
| 454 | */ |
| 455 | public static function hide_settings() { |
| 456 | $user = wp_get_current_user(); |
| 457 | |
| 458 | // Check we have a user before doing anything else. |
| 459 | if ( is_a( $user, '\WP_User' ) ) { |
| 460 | if ( ! empty( WP2FA::get_wp2fa_setting( '2fa_settings_last_updated_by' ) ) ) { |
| 461 | $main_user = (int) WP2FA::get_wp2fa_setting( '2fa_settings_last_updated_by' ); |
| 462 | } else { |
| 463 | $main_user = get_current_user_id(); |
| 464 | } |
| 465 | if ( ! empty( WP2FA::get_wp2fa_general_setting( 'limit_access' ) ) && $user->ID !== $main_user ) { |
| 466 | // Remove legacy admin menu item. |
| 467 | remove_submenu_page( 'options-general.php', self::TOP_MENU_SLUG ); |
| 468 | |
| 469 | // Remove all plugin submenu pages. |
| 470 | remove_submenu_page( self::TOP_MENU_SLUG, self::TOP_MENU_SLUG ); |
| 471 | remove_submenu_page( self::TOP_MENU_SLUG, Settings_Page_Passkeys::TOP_MENU_SLUG ); |
| 472 | remove_submenu_page( self::TOP_MENU_SLUG, 'wp-2fa-settings' ); |
| 473 | remove_submenu_page( self::TOP_MENU_SLUG, 'wp-2fa-settings-new' ); |
| 474 | remove_submenu_page( self::TOP_MENU_SLUG, Settings_Page_White_Labeling_New::PAGE_SLUG ); |
| 475 | remove_submenu_page( self::TOP_MENU_SLUG, Docs_And_Support::TOP_MENU_SLUG ); |
| 476 | remove_submenu_page( self::TOP_MENU_SLUG, License_Page::PAGE_SLUG ); |
| 477 | |
| 478 | // Remove the top-level menu item itself. |
| 479 | remove_menu_page( self::TOP_MENU_SLUG ); |
| 480 | } |
| 481 | } |
| 482 | } |
| 483 | |
| 484 | /** |
| 485 | * Add unlock user link to user actions. |
| 486 | * |
| 487 | * @param array $links Default row content. |
| 488 | * |
| 489 | * @return array |
| 490 | * @throws \Freemius_Exception - freemius exception. |
| 491 | */ |
| 492 | public static function add_plugin_action_links( $links ) { |
| 493 | // add link to the external free trial page in free version and also in premium version if license is not active. |
| 494 | if ( ! Licensing_Factory::has_active_valid_license() ) { |
| 495 | $trial_link = 'https://melapress.com/wordpress-2fa/pricing/?utm_source=plugin&utm_medium=wp2fa&utm_campaign=upgrade_to_premium_menu'; |
| 496 | $links = array_merge( |
| 497 | array( |
| 498 | '<a style="font-weight:bold" href="' . $trial_link . '" target="_blank">' . __( 'Upgrade to Premium', 'wp-2fa' ) . '</a>', |
| 499 | ), |
| 500 | $links |
| 501 | ); |
| 502 | } |
| 503 | |
| 504 | // add link to the plugin settings page. |
| 505 | $url = Settings::get_settings_page_link(); |
| 506 | $links = array_merge( |
| 507 | array( |
| 508 | '<a href="' . \esc_url( $url ) . '">' . \esc_html__( 'Configure 2FA Settings', 'wp-2fa' ) . '</a>', |
| 509 | ), |
| 510 | $links |
| 511 | ); |
| 512 | |
| 513 | return $links; |
| 514 | } |
| 515 | |
| 516 | /** |
| 517 | * Updates options for multisite |
| 518 | * |
| 519 | * @return void |
| 520 | * |
| 521 | * @since 2.0.0 |
| 522 | */ |
| 523 | public static function update_wp2fa_network_options() { |
| 524 | |
| 525 | Settings_Page_Policies::update_wp2fa_network_options(); |
| 526 | |
| 527 | Settings_Page_General::update_wp2fa_network_options(); |
| 528 | |
| 529 | Settings_Page_White_Label::update_wp2fa_network_options(); |
| 530 | |
| 531 | Settings_Page_Passkeys::update_wp2fa_network_options(); |
| 532 | |
| 533 | /** |
| 534 | * Gives the ability for extensions to set their settings in the plugin. |
| 535 | * |
| 536 | * @since 2.2.0 |
| 537 | */ |
| 538 | \do_action( WP_2FA_PREFIX . 'update_network_settings' ); |
| 539 | } |
| 540 | |
| 541 | /** |
| 542 | * Handle saving email options to the network main site options. |
| 543 | */ |
| 544 | public static function update_wp2fa_network_email_options() { |
| 545 | Settings_Page_Email::update_wp2fa_network_options(); |
| 546 | } |
| 547 | |
| 548 | /** |
| 549 | * Stores a network admin notice in a site transient keyed to the current user. |
| 550 | * This replaces passing messages via URL parameters to prevent reflected content injection. |
| 551 | * |
| 552 | * @param string $type Notice type: 'success' or 'error'. |
| 553 | * @param string $message The notice message (already translated). Empty string for default error message. |
| 554 | * |
| 555 | * @return void |
| 556 | * |
| 557 | * @since 2.8.0 |
| 558 | */ |
| 559 | public static function set_network_admin_notice( string $type, string $message = '' ) { |
| 560 | \set_site_transient( |
| 561 | 'wp_2fa_notice_' . \get_current_user_id(), |
| 562 | array( |
| 563 | 'type' => $type, |
| 564 | 'message' => $message, |
| 565 | ), |
| 566 | 30 |
| 567 | ); |
| 568 | } |
| 569 | |
| 570 | /** |
| 571 | * These are used instead of add_settings_error which in a network site. Used to show if settings have been updated or failed. |
| 572 | */ |
| 573 | public static function settings_saved_network_admin_notice() { |
| 574 | $transient_key = 'wp_2fa_notice_' . \get_current_user_id(); |
| 575 | $notice = \get_site_transient( $transient_key ); |
| 576 | |
| 577 | if ( ! $notice || ! is_array( $notice ) || ! isset( $notice['type'] ) ) { |
| 578 | return; |
| 579 | } |
| 580 | |
| 581 | \delete_site_transient( $transient_key ); |
| 582 | |
| 583 | $type = $notice['type']; |
| 584 | $message = isset( $notice['message'] ) ? $notice['message'] : ''; |
| 585 | |
| 586 | if ( 'success' === $type ) { |
| 587 | ?> |
| 588 | <div class="notice notice-success is-dismissible wp-2fa-admin-notice"> |
| 589 | <p><?php \esc_html_e( '2FA Settings Updated', 'wp-2fa' ); ?></p> |
| 590 | <button type="button" class="notice-dismiss"> |
| 591 | <span class="screen-reader-text"><?php \esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span> |
| 592 | </button> |
| 593 | </div> |
| 594 | <?php |
| 595 | } elseif ( 'error' === $type ) { |
| 596 | ?> |
| 597 | <div class="notice notice-error is-dismissible wp-2fa-admin-notice"> |
| 598 | <?php if ( ! empty( $message ) ) { ?> |
| 599 | <p><?php echo \esc_html( $message ); ?></p> |
| 600 | <?php } else { ?> |
| 601 | <p><?php \esc_html_e( 'Please ensure both custom email address and display name are provided.', 'wp-2fa' ); ?></p> |
| 602 | <?php } ?> |
| 603 | <button type="button" class="notice-dismiss"> |
| 604 | <span class="screen-reader-text"><?php \esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span> |
| 605 | </button> |
| 606 | </div> |
| 607 | <?php |
| 608 | } |
| 609 | } |
| 610 | |
| 611 | /** |
| 612 | * These are used instead of add_settings_error which in a network site. Used to show if settings have been updated or failed. |
| 613 | * |
| 614 | * @return void |
| 615 | * |
| 616 | * @since 2.0.0 |
| 617 | */ |
| 618 | public static function settings_saved_admin_notice() { |
| 619 | if ( isset( $_GET['page'] ) && 0 === strpos( \sanitize_text_field( \wp_unslash( $_GET['page'] ) ), 'wp-2fa-' ) ) { |
| 620 | if ( isset( $_GET['settings-updated'] ) && 'true' === $_GET['settings-updated'] ) { |
| 621 | $wp_settings_errors = get_settings_errors(); |
| 622 | |
| 623 | if ( count( $wp_settings_errors ) ) { |
| 624 | foreach ( $wp_settings_errors as $error ) { |
| 625 | ?> |
| 626 | <div class="notice notice-<?php echo \esc_attr( $error['type'] ); ?> is-dismissible wp-2fa-admin-notice"> |
| 627 | <p><?php echo \esc_html( $error['message'] ); ?></p> |
| 628 | <button type="button" class="notice-dismiss"> |
| 629 | <span class="screen-reader-text"><?php \esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span> |
| 630 | </button> |
| 631 | </div> |
| 632 | <?php |
| 633 | } |
| 634 | } else { |
| 635 | ?> |
| 636 | <div class="notice notice-success is-dismissible wp-2fa-admin-notice"> |
| 637 | <p><?php \esc_html_e( '2FA Settings Updated', 'wp-2fa' ); ?></p> |
| 638 | <button type="button" class="notice-dismiss"> |
| 639 | <span class="screen-reader-text"><?php \esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span> |
| 640 | </button> |
| 641 | </div> |
| 642 | <?php |
| 643 | } |
| 644 | } |
| 645 | if ( isset( $_GET['settings-updated'] ) && 'false' === $_GET['settings-updated'] ) { |
| 646 | ?> |
| 647 | <div class="notice notice-error is-dismissible wp-2fa-admin-notice"> |
| 648 | <p><?php \esc_html_e( 'Please ensure both custom email address and display name are provided.', 'wp-2fa' ); ?></p> |
| 649 | <button type="button" class="notice-dismiss"> |
| 650 | <span class="screen-reader-text"><?php \esc_html_e( 'Dismiss this notice.', 'wp-2fa' ); ?></span> |
| 651 | </button> |
| 652 | </div> |
| 653 | <?php |
| 654 | } |
| 655 | } |
| 656 | } |
| 657 | |
| 658 | /** |
| 659 | * Add our custom state to our created page. |
| 660 | * |
| 661 | * @param array $post_states - array with the post states. |
| 662 | * @param WP_Post $post - the WP post. |
| 663 | * |
| 664 | * @return array |
| 665 | */ |
| 666 | public static function add_display_post_states( $post_states, $post ) { |
| 667 | if ( ! empty( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) ) ) { |
| 668 | if ( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) === $post->ID ) { |
| 669 | $post_states['wp_2fa_page_for_user'] = __( 'WP 2FA User Page', 'wp-2fa' ); |
| 670 | } |
| 671 | } |
| 672 | |
| 673 | return $post_states; |
| 674 | } |
| 675 | |
| 676 | /** |
| 677 | * Handles sending of an email. It sets necessary header such as content type and custom from email address and name. |
| 678 | * |
| 679 | * @param string $recipient_email Email address to send message to. |
| 680 | * @param string $subject Email subject. |
| 681 | * @param string $message Message contents. |
| 682 | * |
| 683 | * @return bool Whether the email contents were sent successfully. |
| 684 | */ |
| 685 | public static function send_email( $recipient_email, $subject, $message ) { |
| 686 | |
| 687 | // Sanitize email inputs. |
| 688 | $recipient_email = sanitize_email( $recipient_email ); |
| 689 | $subject = str_replace( array( "\r", "\n" ), '', \sanitize_text_field( $subject ) ); |
| 690 | $message = wp_kses_post( $message ); |
| 691 | |
| 692 | // Specify our desired headers. |
| 693 | $headers = 'Content-type: text/html;charset=utf-8' . "\r\n"; |
| 694 | |
| 695 | if ( 'use-custom-email' === Email_Templates::get_wp2fa_email_templates( 'email_from_setting' ) ) { |
| 696 | $from_name = sanitize_text_field( Email_Templates::get_wp2fa_email_templates( 'custom_from_display_name' ) ); |
| 697 | $from_email = sanitize_email( Email_Templates::get_wp2fa_email_templates( 'custom_from_email_address' ) ); |
| 698 | $headers .= 'From: ' . $from_name . ' <' . $from_email . '>' . "\r\n"; |
| 699 | } else { |
| 700 | $headers .= 'From: WP 2FA from Melapress <' . self::get_default_email_address() . '>' . "\r\n"; |
| 701 | } |
| 702 | |
| 703 | // Fire our email. |
| 704 | return \wp_mail( $recipient_email, stripslashes_deep( html_entity_decode( $subject, ENT_QUOTES, 'UTF-8' ) ), $message, $headers ); |
| 705 | } |
| 706 | |
| 707 | /** |
| 708 | * Builds and returns the default email address used for the "from" email address when email is send |
| 709 | * |
| 710 | * @return string |
| 711 | * |
| 712 | * @since 2.6.4 |
| 713 | */ |
| 714 | public static function get_default_email_address(): string { |
| 715 | $sitename = wp_parse_url( network_home_url(), PHP_URL_HOST ); |
| 716 | $from_email = 'wp2fa@'; |
| 717 | |
| 718 | if ( ! empty( $sitename ) ) { |
| 719 | if ( str_starts_with( $sitename, 'www.' ) ) { |
| 720 | $sitename = substr( $sitename, 4 ); |
| 721 | } |
| 722 | $from_email .= sanitize_text_field( $sitename ); |
| 723 | } |
| 724 | |
| 725 | return sanitize_email( $from_email ); |
| 726 | } |
| 727 | |
| 728 | /** |
| 729 | * Turns user roles data in any form and shape to an array of strings. |
| 730 | * |
| 731 | * @param mixed $value User role names (slugs) as raw value. |
| 732 | * |
| 733 | * @return string[] List of user role names (slugs). |
| 734 | * |
| 735 | * @since 3.0.0 |
| 736 | */ |
| 737 | public static function extract_roles_from_input( $value ) { |
| 738 | if ( is_array( $value ) ) { |
| 739 | return array_map( 'sanitize_text_field', $value ); |
| 740 | } |
| 741 | |
| 742 | if ( is_string( $value ) && ! empty( $value ) ) { |
| 743 | $roles = explode( ',', $value ); |
| 744 | return array_map( 'sanitize_text_field', $roles ); |
| 745 | } |
| 746 | |
| 747 | return array(); |
| 748 | } |
| 749 | |
| 750 | /** |
| 751 | * Checks the email against the current domain and shows an error message if they do not match. |
| 752 | * |
| 753 | * @return void |
| 754 | * |
| 755 | * @since 2.6.0 |
| 756 | */ |
| 757 | public static function check_email() { |
| 758 | $is_dismissed = (bool) Settings_Utils::get_option( 'dismiss_notice_mail_domain', false ); |
| 759 | if ( ! $is_dismissed ) { |
| 760 | $admin_email = null; |
| 761 | if ( 'use-custom-email' === Email_Templates::get_wp2fa_email_templates( 'email_from_setting' ) ) { |
| 762 | $admin_email = sanitize_email( Email_Templates::get_wp2fa_email_templates( 'custom_from_email_address' ) ); |
| 763 | } else { |
| 764 | $admin_email = self::get_default_email_address(); |
| 765 | } |
| 766 | |
| 767 | if ( '' === trim( (string) $admin_email ) ) { |
| 768 | $email_settings_url = esc_url( |
| 769 | add_query_arg( |
| 770 | array( |
| 771 | 'page' => 'wp-2fa-settings', |
| 772 | 'tab' => 'email-settings', |
| 773 | ), |
| 774 | network_admin_url( 'admin.php' ) |
| 775 | ) |
| 776 | ); |
| 777 | ?> |
| 778 | <div class="notice notice-error wp-2fa-admin-notice"> |
| 779 | <p class="description"><?php esc_html_e( 'By default, the plugin uses ', 'wp-2fa' ); ?> <b><?php echo sanitize_email( self::get_default_email_address() ); ?></b> <?php esc_html_e( 'as the "from address" when sending emails with the 2FA code for users to log in. Do you want to keep using this or change it?', 'wp-2fa' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></p> |
| 780 | <p> |
| 781 | <a class="button button-primary" href="<?php echo esc_url( $email_settings_url ); ?>"><?php esc_html_e( 'Change it', 'wp-2fa' ); ?></a> |
| 782 | <a class="button button-secondary wp-2fa-email-notice" style="margin-left:20px" href="#"> |
| 783 | <?php esc_html_e( 'Keep using it', 'wp-2fa' ); ?> |
| 784 | </a> |
| 785 | </p> |
| 786 | |
| 787 | <?php wp_nonce_field( 'wp2fa_dismiss_notice_mail_domain', 'wp2fa_dismiss_notice_mail_domain', false ); ?> |
| 788 | </div> |
| 789 | <?php |
| 790 | } else { |
| 791 | Settings_Utils::update_option( 'dismiss_notice_mail_domain', true ); |
| 792 | } |
| 793 | } |
| 794 | } |
| 795 | |
| 796 | /** |
| 797 | * Sets the email domain do not match setting as dismissed. |
| 798 | * |
| 799 | * @return void |
| 800 | * |
| 801 | * @since 2.6.0 |
| 802 | */ |
| 803 | public static function dismiss_notice_mail_domain() { |
| 804 | // Verify nonce. |
| 805 | if ( isset( $_POST['nonce'] ) && \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_POST['nonce'] ) ), 'wp2fa_dismiss_notice_mail_domain' ) ) { |
| 806 | Settings_Utils::update_option( 'dismiss_notice_mail_domain', true ); |
| 807 | die(); |
| 808 | } |
| 809 | |
| 810 | die( 'Nonce verification failed!' ); |
| 811 | } |
| 812 | |
| 813 | /** |
| 814 | * Adds a locked "Reports" submenu item when no valid premium license is present. |
| 815 | * |
| 816 | * The menu item displays a lock icon and does nothing when clicked. |
| 817 | * |
| 818 | * @return void |
| 819 | * |
| 820 | * @since 4.0.0 |
| 821 | */ |
| 822 | public static function maybe_add_locked_reports_menu() { |
| 823 | if ( Licensing_Factory::has_active_valid_license() ) { |
| 824 | return; |
| 825 | } |
| 826 | |
| 827 | $lock_icon = '<span class="wp2fa-menu-lock-icon" aria-hidden="true"></span>'; |
| 828 | |
| 829 | \add_submenu_page( |
| 830 | self::TOP_MENU_SLUG, |
| 831 | \esc_html__( 'Reports', 'wp-2fa' ), |
| 832 | \esc_html__( 'Reports', 'wp-2fa' ) . $lock_icon, |
| 833 | 'manage_options', |
| 834 | 'wp-2fa-reports-locked', |
| 835 | array( __CLASS__, 'render_locked_reports_page' ), |
| 836 | 3 |
| 837 | ); |
| 838 | |
| 839 | \add_action( 'admin_head', array( __CLASS__, 'locked_reports_menu_styles' ) ); |
| 840 | } |
| 841 | |
| 842 | /** |
| 843 | * Outputs CSS for the locked Reports menu item lock icon. |
| 844 | * |
| 845 | * @return void |
| 846 | * |
| 847 | * @since 4.0.0 |
| 848 | */ |
| 849 | public static function locked_reports_menu_styles() { |
| 850 | ?> |
| 851 | <style> |
| 852 | #adminmenu .wp-submenu a[href*="wp-2fa-reports-locked"] { |
| 853 | cursor: pointer; |
| 854 | position: relative; |
| 855 | display: flex !important; |
| 856 | align-items: center; |
| 857 | justify-content: space-between; |
| 858 | } |
| 859 | #adminmenu .wp-submenu a[href*="wp-2fa-reports-locked"]:hover { |
| 860 | color: #72aee6; |
| 861 | } |
| 862 | .wp2fa-menu-lock-icon { |
| 863 | display: inline-flex; |
| 864 | align-items: center; |
| 865 | justify-content: center; |
| 866 | width: 20px; |
| 867 | height: 20px; |
| 868 | background: #2c3338; |
| 869 | border-radius: 4px; |
| 870 | flex-shrink: 0; |
| 871 | margin-left: 8px; |
| 872 | } |
| 873 | #adminmenu .wp-submenu li.current a[href*="wp-2fa-reports-locked"] .wp2fa-menu-lock-icon, |
| 874 | #adminmenu .wp-submenu a.current[href*="wp-2fa-reports-locked"] .wp2fa-menu-lock-icon { |
| 875 | background: #2271b1; |
| 876 | } |
| 877 | .wp2fa-menu-lock-icon::before { |
| 878 | content: ''; |
| 879 | display: block; |
| 880 | width: 12px; |
| 881 | height: 12px; |
| 882 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23c3c4c7'%3E%3Cpath d='M18 10h-1V7c0-2.8-2.2-5-5-5S7 4.2 7 7v3H6c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-8c0-1.1-.9-2-2-2zM9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v3H9V7zm9 13H6v-8h12v8zm-6-3c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z'/%3E%3C/svg%3E"); |
| 883 | background-size: contain; |
| 884 | background-repeat: no-repeat; |
| 885 | background-position: center; |
| 886 | } |
| 887 | #adminmenu .wp-submenu li.current a[href*="wp-2fa-reports-locked"] .wp2fa-menu-lock-icon::before, |
| 888 | #adminmenu .wp-submenu a.current[href*="wp-2fa-reports-locked"] .wp2fa-menu-lock-icon::before { |
| 889 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M18 10h-1V7c0-2.8-2.2-5-5-5S7 4.2 7 7v3H6c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-8c0-1.1-.9-2-2-2zM9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v3H9V7zm9 13H6v-8h12v8zm-6-3c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z'/%3E%3C/svg%3E"); |
| 890 | } |
| 891 | |
| 892 | .wp2fa-reports-teaser-wrap { |
| 893 | max-width: 100%; |
| 894 | margin-top: 20px; |
| 895 | margin-right: 20px; |
| 896 | } |
| 897 | .wp2fa-reports-teaser-card { |
| 898 | display: grid; |
| 899 | grid-template-columns: 1fr 1fr; |
| 900 | gap: 36px; |
| 901 | background: #fff; |
| 902 | border: 1px solid #dcdcde; |
| 903 | border-radius: 8px; |
| 904 | padding: 28px; |
| 905 | } |
| 906 | .wp2fa-reports-teaser-title-wrap { |
| 907 | display: flex; |
| 908 | align-items: center; |
| 909 | gap: 14px; |
| 910 | margin-bottom: 18px; |
| 911 | } |
| 912 | .wp2fa-reports-teaser-icon { |
| 913 | width: 52px; |
| 914 | height: 52px; |
| 915 | border-radius: 8px; |
| 916 | object-fit: contain; |
| 917 | } |
| 918 | .wp2fa-reports-teaser-title { |
| 919 | margin: 0; |
| 920 | font-size: 22px; |
| 921 | line-height: 1.22; |
| 922 | font-weight: 700; |
| 923 | color: #3c434a; |
| 924 | } |
| 925 | .wp2fa-reports-teaser-content { |
| 926 | font-size: 14px; |
| 927 | } |
| 928 | .wp2fa-reports-teaser-title span { |
| 929 | color: #2271b1; |
| 930 | } |
| 931 | .wp2fa-reports-teaser-description { |
| 932 | margin: 0 0 26px; |
| 933 | font-size: 14px; |
| 934 | line-height: 1.45; |
| 935 | font-weight: 400; |
| 936 | color: #50575e; |
| 937 | } |
| 938 | .wp2fa-reports-teaser-list { |
| 939 | list-style: none; |
| 940 | margin: 0 0 28px; |
| 941 | padding: 0; |
| 942 | display: flex; |
| 943 | flex-direction: column; |
| 944 | gap: 14px; |
| 945 | } |
| 946 | .wp2fa-reports-teaser-list li { |
| 947 | display: flex; |
| 948 | align-items: flex-start; |
| 949 | gap: 10px; |
| 950 | } |
| 951 | .wp2fa-reports-teaser-list-icon { |
| 952 | width: 32px; |
| 953 | height: 32px; |
| 954 | border-radius: 50%; |
| 955 | background: #2271b1; |
| 956 | flex-shrink: 0; |
| 957 | margin-top: 2px; |
| 958 | position: relative; |
| 959 | } |
| 960 | .wp2fa-reports-teaser-list-icon::before { |
| 961 | content: ''; |
| 962 | position: absolute; |
| 963 | top: 50%; |
| 964 | left: 50%; |
| 965 | width: 17px; |
| 966 | height: 17px; |
| 967 | transform: translate(-50%, -50%); |
| 968 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z'/%3E%3C/svg%3E"); |
| 969 | background-repeat: no-repeat; |
| 970 | background-size: contain; |
| 971 | } |
| 972 | .wp2fa-reports-teaser-point-title { |
| 973 | display: block; |
| 974 | font-size: 16px; |
| 975 | line-height: 1.2; |
| 976 | font-weight: 600; |
| 977 | color: #3c434a; |
| 978 | } |
| 979 | .wp2fa-reports-teaser-point-description { |
| 980 | display: block; |
| 981 | margin-top: 2px; |
| 982 | font-size: 14px; |
| 983 | line-height: 1.34; |
| 984 | color: #646970; |
| 985 | } |
| 986 | .wp2fa-reports-teaser-cta { |
| 987 | display: inline-block; |
| 988 | padding: 9px 18px; |
| 989 | border-radius: 7px; |
| 990 | background: #2271b1; |
| 991 | border: 1px solid #2271b1; |
| 992 | color: #fff; |
| 993 | font-size: 17px; |
| 994 | font-weight: 500; |
| 995 | text-decoration: none; |
| 996 | } |
| 997 | .wp2fa-reports-teaser-cta:hover, |
| 998 | .wp2fa-reports-teaser-cta:focus { |
| 999 | background: #135e96; |
| 1000 | border-color: #135e96; |
| 1001 | color: #fff; |
| 1002 | } |
| 1003 | .wp2fa-reports-teaser-preview { |
| 1004 | background: #efeff0; |
| 1005 | border-radius: 10px; |
| 1006 | padding: 16px; |
| 1007 | overflow: hidden; |
| 1008 | } |
| 1009 | .wp2fa-reports-teaser-preview img { |
| 1010 | width: 100%; |
| 1011 | height: auto; |
| 1012 | display: block; |
| 1013 | border-radius: 8px; |
| 1014 | opacity: 0.94; |
| 1015 | margin-left: 60px; |
| 1016 | } |
| 1017 | |
| 1018 | @media screen and (max-width: 980px) { |
| 1019 | .wp2fa-reports-teaser-card { |
| 1020 | grid-template-columns: 1fr; |
| 1021 | } |
| 1022 | .wp2fa-reports-teaser-preview img { |
| 1023 | margin-left: 0; |
| 1024 | } |
| 1025 | } |
| 1026 | </style> |
| 1027 | <?php |
| 1028 | } |
| 1029 | |
| 1030 | /** |
| 1031 | * Renders teaser page for the locked reports menu item. |
| 1032 | * |
| 1033 | * @return void |
| 1034 | * |
| 1035 | * @since 4.0.0 |
| 1036 | */ |
| 1037 | public static function render_locked_reports_page() { |
| 1038 | if ( ! \current_user_can( 'manage_options' ) ) { |
| 1039 | return; |
| 1040 | } |
| 1041 | |
| 1042 | ?> |
| 1043 | <div class="wp2fa-reports-teaser-wrap"> |
| 1044 | <div class="wp2fa-reports-teaser-card"> |
| 1045 | <div class="wp2fa-reports-teaser-content"> |
| 1046 | <div class="wp2fa-reports-teaser-title-wrap"> |
| 1047 | <svg class="wp2fa-reports-teaser-icon" xmlns="http://www.w3.org/2000/svg" width="33" height="49" viewBox="0 0 33 49" fill="none" aria-hidden="true" focusable="false"> |
| 1048 | <path d="M10.7607 16.2846L18.7944 8.17949L26.9017 0H13.414H0V8.17949V16.2846L2.65332 13.6077L5.38035 10.8564L8.03367 13.6077L10.7607 16.2846Z" fill="#99FFFF"/> |
| 1049 | <path d="M32.2094 48.9278V37.997V27.1406L21.5224 37.997L10.7617 48.7791L21.5224 48.8534L32.2094 48.9278Z" fill="#3E6BFF"/> |
| 1050 | <path d="M10.7607 27.1406L8.03367 24.3893L5.38035 21.7124L2.65332 18.9611L0 16.2841V27.1406V37.997L2.65332 35.2457L5.38035 32.5688L8.03367 35.2457L10.7607 37.997L18.7944 29.8175L26.9017 21.7124L29.555 24.3893L32.2084 27.1406V16.2841V5.42773L21.5214 16.2841L10.7607 27.1406Z" fill="#40D3F0"/> |
| 1051 | </svg> |
| 1052 | <h1 class="wp2fa-reports-teaser-title"><?php echo \esc_html__( 'Unlock 2FA Reports & Statistics with Premium', 'wp-2fa' ); ?></h1> |
| 1053 | </div> |
| 1054 | |
| 1055 | <p class="wp2fa-reports-teaser-description"><?php echo \esc_html__( 'Get a clear overview of two-factor authentication adoption across your website and identify users who are not yet protected.', 'wp-2fa' ); ?></p> |
| 1056 | <p class="wp2fa-reports-teaser-description"><?php echo \esc_html__( 'WP 2FA Premium includes detailed reports and statistics that help you monitor 2FA usage, track adoption by user role, review authentication methods in use, and export data for auditing and compliance purposes.', 'wp-2fa' ); ?></p> |
| 1057 | |
| 1058 | <ul class="wp2fa-reports-teaser-list"> |
| 1059 | <li> |
| 1060 | <span class="wp2fa-reports-teaser-list-icon" aria-hidden="true"></span> |
| 1061 | <span> |
| 1062 | <span class="wp2fa-reports-teaser-point-description"><?php echo \esc_html__( 'Monitor 2FA adoption across your entire website', 'wp-2fa' ); ?></span> |
| 1063 | </span> |
| 1064 | </li> |
| 1065 | <li> |
| 1066 | <span class="wp2fa-reports-teaser-list-icon" aria-hidden="true"></span> |
| 1067 | <span> |
| 1068 | <span class="wp2fa-reports-teaser-point-description"><?php echo \esc_html__( 'Identify users and roles that have not yet configured 2FA', 'wp-2fa' ); ?></span> |
| 1069 | </span> |
| 1070 | </li> |
| 1071 | <li> |
| 1072 | <span class="wp2fa-reports-teaser-list-icon" aria-hidden="true"></span> |
| 1073 | <span> |
| 1074 | <span class="wp2fa-reports-teaser-point-description"><?php echo \esc_html__( 'Track which authentication methods users are relying on', 'wp-2fa' ); ?></span> |
| 1075 | </span> |
| 1076 | </li> |
| 1077 | <li> |
| 1078 | <span class="wp2fa-reports-teaser-list-icon" aria-hidden="true"></span> |
| 1079 | <span> |
| 1080 | <span class="wp2fa-reports-teaser-point-description"><?php echo \esc_html__( 'Export reports as CSV for auditing, compliance, and internal reviews', 'wp-2fa' ); ?></span> |
| 1081 | </span> |
| 1082 | </li> |
| 1083 | <li> |
| 1084 | <span class="wp2fa-reports-teaser-list-icon" aria-hidden="true"></span> |
| 1085 | <span> |
| 1086 | <span class="wp2fa-reports-teaser-point-description"><?php echo \esc_html__( 'Measure the impact of your 2FA rollout and security policies', 'wp-2fa' ); ?></span> |
| 1087 | </span> |
| 1088 | </li> |
| 1089 | </ul> |
| 1090 | |
| 1091 | <a class="wp2fa-reports-teaser-cta" href="<?php echo \esc_url( 'https://melapress.com/wordpress-2fa/?utm_source=plugin&utm_medium=wp2fa&utm_campaign=free-reports-page' ); ?>" target="_blank" rel="noopener noreferrer"><?php echo \esc_html__( 'Unlock 2FA Reports & Statistics', 'wp-2fa' ); ?></a> |
| 1092 | </div> |
| 1093 | |
| 1094 | <div class="wp2fa-reports-teaser-preview" aria-hidden="true"> |
| 1095 | <img src="<?php echo \esc_url( WP_2FA_URL . 'includes/assets/images/reports-teaser-preview.png' ); ?>" alt="<?php echo \esc_attr__( 'Reports page preview', 'wp-2fa' ); ?>" /> |
| 1096 | </div> |
| 1097 | </div> |
| 1098 | </div> |
| 1099 | <?php |
| 1100 | } |
| 1101 | } |
| 1102 | } |
| 1103 |