class-shortcodes.php
306 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Responsible for rendering the short codes. |
| 4 | * |
| 5 | * @package wp2fa |
| 6 | * @subpackage short-codes |
| 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 | declare(strict_types=1); |
| 13 | |
| 14 | namespace WP2FA\Shortcodes; |
| 15 | |
| 16 | use WP2FA\Core; |
| 17 | use WP2FA\WP2FA; |
| 18 | use WP2FA\Admin\User_Notices; |
| 19 | use WP2FA\Admin\Wizard_Integration; |
| 20 | use WP2FA\Utils\Settings_Utils; |
| 21 | use WP2FA\Admin\Helpers\WP_Helper; |
| 22 | use WP2FA\Admin\Views\Re_Login_2FA; |
| 23 | use WP2FA\Admin\Helpers\User_Helper; |
| 24 | |
| 25 | if ( ! class_exists( '\WP2FA\Shortcodes\Shortcodes' ) ) { |
| 26 | /** |
| 27 | * Class for rendering shortcodes. |
| 28 | */ |
| 29 | class Shortcodes { |
| 30 | |
| 31 | /** |
| 32 | * Constructor. |
| 33 | */ |
| 34 | public static function init() { |
| 35 | \add_shortcode( 'wp-2fa-setup-form', array( __CLASS__, 'user_setup_2fa_form' ) ); |
| 36 | \add_shortcode( 'wp-2fa-setup-notice', array( __CLASS__, 'user_setup_2fa_notice' ) ); |
| 37 | //\add_action( 'wp_enqueue_scripts', array( __CLASS__, 'register_2fa_shortcode_scripts' ) ); |
| 38 | |
| 39 | \add_filter( 'the_content', array( __CLASS__, 'maybe_hide_generated_by_text' ) ); |
| 40 | } |
| 41 | |
| 42 | /** |
| 43 | * Strips the "Page generated by WP 2FA Plugin" attribution text from the |
| 44 | * custom frontend 2FA page when the hide_page_generated_by setting is enabled. |
| 45 | * |
| 46 | * @param string $content The post content. |
| 47 | * |
| 48 | * @return string |
| 49 | * |
| 50 | * @since 2.8.0 |
| 51 | */ |
| 52 | public static function maybe_hide_generated_by_text( $content ) { |
| 53 | if ( ! \is_page() ) { |
| 54 | return $content; |
| 55 | } |
| 56 | |
| 57 | if ( ! \has_shortcode( $content, 'wp-2fa-setup-form' ) ) { |
| 58 | return $content; |
| 59 | } |
| 60 | |
| 61 | if ( ! WP2FA::get_wp2fa_white_label_setting( 'hide_page_generated_by' ) ) { |
| 62 | return $content; |
| 63 | } |
| 64 | |
| 65 | // Remove the attribution paragraph that was embedded in the page content at creation time. |
| 66 | $content = \preg_replace( |
| 67 | '#<p>\s*' . \preg_quote( \esc_html__( 'Page generated by', 'wp-2fa' ), '#' ) . '\s*<a [^>]*>.*?</a>\s*</p>#s', |
| 68 | '', |
| 69 | $content |
| 70 | ); |
| 71 | |
| 72 | return $content; |
| 73 | } |
| 74 | |
| 75 | /** |
| 76 | * Register scripts and styles. |
| 77 | */ |
| 78 | public static function register_2fa_shortcode_scripts() { |
| 79 | // Add our front end stuff, which we only want to load when the shortcode is present. |
| 80 | \wp_register_script( 'wp_2fa_frontend_scripts', Core\script_url( 'wp-2fa', 'admin' ), array( 'jquery', 'wp_2fa_micro_modals', 'wp-i18n' ), WP_2FA_VERSION, true ); |
| 81 | \wp_register_script( 'wp_2fa_micro_modals', Core\script_url( 'micromodal', 'admin' ), array(), WP_2FA_VERSION, true ); |
| 82 | \wp_register_style( 'wp_2fa_styles', Core\style_url( 'styles', 'frontend' ), array(), WP_2FA_VERSION ); |
| 83 | |
| 84 | $data_array = array( |
| 85 | 'ajaxURL' => \admin_url( 'admin-ajax.php' ), |
| 86 | 'roles' => WP_Helper::get_roles_wp(), |
| 87 | 'nonce' => \wp_create_nonce( 'wp-2fa-settings-nonce' ), |
| 88 | 'codesPreamble' => \esc_html__( 'These are the 2FA backup codes for the user', 'wp-2fa' ), |
| 89 | 'readyText' => \esc_html__( 'I\'m ready', 'wp-2fa' ), |
| 90 | 'codeReSentText' => \esc_html__( 'New code sent', 'wp-2fa' ), |
| 91 | 'allDoneHeading' => \esc_html__( 'All done.', 'wp-2fa' ), |
| 92 | 'allDoneText' => \esc_html__( 'Your login just got more secure.', 'wp-2fa' ), |
| 93 | 'closeWizard' => \esc_html__( 'Close Wizard', 'wp-2fa' ), |
| 94 | 'invalidEmail' => \esc_html__( 'Please use a valid email address', 'wp-2fa' ), |
| 95 | ); |
| 96 | \wp_localize_script( 'wp_2fa_frontend_scripts', 'wp2faData', $data_array ); |
| 97 | |
| 98 | $role = User_Helper::get_user_role(); |
| 99 | |
| 100 | $re_login = Settings_Utils::get_setting_role( $role, Re_Login_2FA::RE_LOGIN_SETTINGS_NAME ); |
| 101 | |
| 102 | $data_array = array( |
| 103 | 'ajaxURL' => \admin_url( 'admin-ajax.php' ), |
| 104 | 'nonce' => \wp_create_nonce( 'wp2fa-verify-wizard-page' ), |
| 105 | 'codesPreamble' => \esc_html__( 'These are the 2FA backup codes for the user', 'wp-2fa' ), |
| 106 | 'readyText' => \esc_html__( 'I\'m ready', 'wp-2fa' ), |
| 107 | 'codeReSentText' => \esc_html__( 'New code sent', 'wp-2fa' ), |
| 108 | 'invalidEmail' => \esc_html__( 'Please use a valid email address', 'wp-2fa' ), |
| 109 | 'backupCodesSent' => \esc_html__( 'Backup codes sent', 'wp-2fa' ), |
| 110 | 'reLogin' => $re_login, |
| 111 | 'reLoginEnabled' => Re_Login_2FA::ENABLED_SETTING_VALUE, |
| 112 | 'loginUrl' => \wp_login_url(), |
| 113 | ); |
| 114 | $redirect_page = \sanitize_text_field( Settings_Utils::get_setting_role( $role, 'redirect-user-custom-page' ) ); |
| 115 | $redirect_page_global = \sanitize_text_field( Settings_Utils::get_setting_role( null, 'redirect-user-custom-page' ) ); |
| 116 | $redirect_page_global_setting = \sanitize_text_field( Settings_Utils::get_setting_role( $role, 'redirect-user-custom-page-global' ) ); |
| 117 | |
| 118 | // Priority: role-specific redirect-user-custom-page > global redirect-user-custom-page > redirect-user-custom-page-global > empty. |
| 119 | if ( '' !== trim( (string) $redirect_page ) ) { |
| 120 | $data_array['redirectToUrl'] = \trailingslashit( get_site_url() ) . $redirect_page; |
| 121 | } elseif ( '' !== trim( (string) $redirect_page_global ) ) { |
| 122 | $data_array['redirectToUrl'] = \trailingslashit( get_site_url() ) . $redirect_page_global; |
| 123 | } elseif ( '' !== trim( (string) $redirect_page_global_setting ) ) { |
| 124 | $data_array['redirectToUrl'] = \trailingslashit( get_site_url() ) . $redirect_page_global_setting; |
| 125 | } else { |
| 126 | $data_array['redirectToUrl'] = ''; |
| 127 | } |
| 128 | |
| 129 | // Check for shortcode parameter - if one is present use it to redirect the user - highest priority. |
| 130 | if ( isset( $redirect_after ) && ! empty( $redirect_after ) ) { |
| 131 | $candidate_url = \trailingslashit( \get_site_url() ) . \sanitize_text_field( $redirect_after ); |
| 132 | $validated = \wp_validate_redirect( $candidate_url, '' ); |
| 133 | if ( ! empty( $validated ) ) { |
| 134 | $data_array['redirectToUrl'] = $validated; |
| 135 | } |
| 136 | } elseif ( isset( $_GET['return'] ) && ! empty( $_GET['return'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 137 | $return_path = \sanitize_text_field( \wp_unslash( $_GET['return'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 138 | $candidate_url = \trailingslashit( \get_site_url() ) . $return_path; |
| 139 | $validated = \wp_validate_redirect( $candidate_url, '' ); |
| 140 | if ( ! empty( $validated ) ) { |
| 141 | $data_array['redirectToUrl'] = $validated; |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | \wp_localize_script( 'wp_2fa_frontend_scripts', 'wp2faWizardData', $data_array ); |
| 146 | } |
| 147 | |
| 148 | /** |
| 149 | * Output setup form. |
| 150 | * |
| 151 | * @param array $atts - Array with the attributes passed to shortcode. |
| 152 | * |
| 153 | * @return string |
| 154 | */ |
| 155 | public static function user_setup_2fa_form( $atts ) { |
| 156 | $atts = \shortcode_atts( |
| 157 | array( |
| 158 | 'show_preamble' => 'true', |
| 159 | 'redirect_after' => '', |
| 160 | 'do_not_show_enabled' => 'false', |
| 161 | ), |
| 162 | $atts |
| 163 | ); |
| 164 | |
| 165 | $show_preamble = filter_var( $atts['show_preamble'], FILTER_VALIDATE_BOOLEAN ); |
| 166 | $redirect_after = \sanitize_text_field( $atts['redirect_after'] ); |
| 167 | $do_not_show_enabled = filter_var( $atts['do_not_show_enabled'], FILTER_VALIDATE_BOOLEAN ); |
| 168 | |
| 169 | /** |
| 170 | * Fires when the FE shortcode scripts are registered. |
| 171 | * |
| 172 | * @param bool $shortcodes - True if called from the short codes method. |
| 173 | * |
| 174 | * @since 2.2.0 |
| 175 | */ |
| 176 | \do_action( WP_2FA_PREFIX . 'shortcode_scripts', true ); |
| 177 | |
| 178 | if ( \is_user_logged_in() ) { |
| 179 | |
| 180 | $additional_args = array( |
| 181 | 'is_shortcode' => true, |
| 182 | 'show_preamble' => $show_preamble, |
| 183 | 'options' => array( |
| 184 | 'do_not_show_enabled' => $do_not_show_enabled, |
| 185 | ), |
| 186 | ); |
| 187 | |
| 188 | $user = \wp_get_current_user(); |
| 189 | |
| 190 | ob_start(); |
| 191 | echo self::get_notification_style(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
| 192 | echo '<div class="wp-2fa-shortcode-wrapper">'; |
| 193 | User_Notices::user_setup_2fa_nag( 'output_shortcode' ); |
| 194 | Wizard_Integration::render_profile_section_frontend( $user, $additional_args ); |
| 195 | echo '</div>'; |
| 196 | $content = ob_get_contents(); |
| 197 | ob_end_clean(); |
| 198 | |
| 199 | return $content; |
| 200 | } elseif ( ! \is_admin() && ! \is_user_logged_in() ) { |
| 201 | ob_start(); |
| 202 | $new_page_id = WP2FA::get_wp2fa_setting( 'custom-user-page-id' ); |
| 203 | $redirect_to = ! empty( $new_page_id ) ? \get_permalink( $new_page_id ) : \get_home_url(); |
| 204 | $link_markup = '<a href="' . \esc_url( \wp_login_url( $redirect_to ) ) . '">' . \esc_html__( 'Login here.', 'wp-2fa' ) . '</a>'; |
| 205 | $message = '<p id="wp_2fa_login_to_view_text">' . str_replace( '{login_url}', $link_markup, WP2FA::get_wp2fa_white_label_setting( 'login-to-view-area', true ) ) . '</p>'; |
| 206 | echo \wp_kses_post( $message ); |
| 207 | $content = ob_get_contents(); |
| 208 | ob_end_clean(); |
| 209 | return $content; |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | /** |
| 214 | * Output setup nag. |
| 215 | * |
| 216 | * @param array $atts - Array with the attributes passed to shortcode. |
| 217 | * |
| 218 | * @return string |
| 219 | */ |
| 220 | public static function user_setup_2fa_notice( $atts ) { |
| 221 | $atts = \shortcode_atts( |
| 222 | array( |
| 223 | 'configure_2fa_url' => '', |
| 224 | ), |
| 225 | $atts |
| 226 | ); |
| 227 | |
| 228 | $configure_2fa_url = \esc_url_raw( $atts['configure_2fa_url'] ); |
| 229 | |
| 230 | // TODO: is that really necessary? |
| 231 | User_Notices::init(); |
| 232 | |
| 233 | if ( ! is_admin() && is_user_logged_in() ) { |
| 234 | \wp_enqueue_script( 'wp_2fa_micro_modals' ); |
| 235 | \wp_enqueue_script( 'wp_2fa_frontend_scripts' ); |
| 236 | \wp_enqueue_style( 'wp_2fa_styles' ); |
| 237 | |
| 238 | $data_array = array( |
| 239 | 'ajaxURL' => \admin_url( 'admin-ajax.php' ), |
| 240 | 'roles' => WP_Helper::get_roles_wp(), |
| 241 | 'nonce' => \wp_create_nonce( 'wp-2fa-settings-nonce' ), |
| 242 | 'codesPreamble' => \esc_html__( 'These are the 2FA backup codes for the user', 'wp-2fa' ), |
| 243 | 'readyText' => \esc_html__( 'I\'m ready', 'wp-2fa' ), |
| 244 | 'codeReSentText' => \esc_html__( 'New code sent', 'wp-2fa' ), |
| 245 | 'allDoneHeading' => \esc_html__( 'All done.', 'wp-2fa' ), |
| 246 | 'allDoneText' => \esc_html__( 'Your login just got more secure.', 'wp-2fa' ), |
| 247 | 'closeWizard' => \esc_html__( 'Close Wizard', 'wp-2fa' ), |
| 248 | 'redirectToUrl' => $configure_2fa_url, |
| 249 | ); |
| 250 | \wp_localize_script( 'wp_2fa_frontend_scripts', 'wp2faData', $data_array ); |
| 251 | |
| 252 | ob_start(); |
| 253 | User_Notices::user_setup_2fa_nag( 'output_shortcode', $configure_2fa_url ); |
| 254 | $content = ob_get_contents(); |
| 255 | ob_end_clean(); |
| 256 | |
| 257 | return $content; |
| 258 | } |
| 259 | |
| 260 | return ''; |
| 261 | } |
| 262 | |
| 263 | /** |
| 264 | * Output the style of the nags on FE pages. |
| 265 | * |
| 266 | * @return string |
| 267 | * |
| 268 | * @since 4.0.0 |
| 269 | */ |
| 270 | public static function get_notification_style(): string { |
| 271 | $styles = <<<CSS |
| 272 | <style media="screen"> |
| 273 | .wp-2fa-shortcode-wrapper .wp-2fa-nag, .woocommerce .wp-2fa-nag { |
| 274 | width: 100%; |
| 275 | border: 1px solid #eee; |
| 276 | border-left: 3px solid red; |
| 277 | padding: 12px 15px; |
| 278 | margin-bottom: 20px; |
| 279 | font-size: 13px; |
| 280 | } |
| 281 | .wp-2fa-shortcode-wrapper .wp-2fa-nag p, .woocommerce .wp-2fa-nag p { |
| 282 | margin-bottom: 5px |
| 283 | } |
| 284 | .wp-2fa-shortcode-wrapper .wp-2fa-nag br + span, .woocommerce .wp-2fa-nag br + span { |
| 285 | clear: both; |
| 286 | display: block; |
| 287 | margin-top: 10px; |
| 288 | margin-bottom: 10px; |
| 289 | } |
| 290 | .wp-2fa-shortcode-wrapper .wp-2fa-nag a.button, .woocommerce .wp-2fa-nag a.button { |
| 291 | font-size: 13px; |
| 292 | padding: 0 !important; |
| 293 | margin-right: 20px; |
| 294 | background-color: transparent; |
| 295 | } |
| 296 | .wp-2fa-shortcode-wrapper .wp-2fa-nag a.button:visited, .woocommerce .wp-2fa-nag a.button:visited { |
| 297 | color: #222; |
| 298 | } |
| 299 | </style> |
| 300 | CSS; |
| 301 | |
| 302 | return $styles; |
| 303 | } |
| 304 | } |
| 305 | } |
| 306 |