isPreview() ) { $this->renderMinimal( sanitize_key( wp_unslash( $_GET['skin'] ?? '' ) ), true ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only preview, restricted to admins in isPreview(). exit; } if ( self::isLoginPage() ) { if ( is_user_logged_in() ) { wp_safe_redirect( '' !== self::returnTo() ? self::returnTo() : self::accountUrl() ); exit; } if ( Settings::get( OtpAuthModule::SLUG . '.page_minimal', true ) ) { $this->renderMinimal( '', false ); exit; } return; } if ( $this->shouldRedirectFromAccount() ) { wp_safe_redirect( self::url( self::currentUrl() ) ); exit; } // With sign-in by code the only way in and no login page of its own, the // guest's "my account" is the store's login screen, and follows the "no // header and footer" choice like a login page would. if ( self::pageId() <= 0 && $this->isGuestAccountLogin() && Settings::get( OtpAuthModule::SLUG . '.page_minimal', true ) ) { $this->renderMinimal( '', false ); exit; } } /** * Is this a guest on "my account" being asked to sign in, while sign-in by * code is the only way in? * * The lost-password endpoint is left alone, as everywhere: it is how someone * who signed up by email gets back in. So is everything while the failsafe * holds, when "my account" has to show the theme's password form. */ private function isGuestAccountLogin(): bool { if ( is_user_logged_in() || ! OtpAuthModule::codeOnly() || Failsafe::isActive() ) { return false; } if ( ! function_exists( 'is_account_page' ) || ! is_account_page() ) { return false; } return ! ( function_exists( 'is_wc_endpoint_url' ) && is_wc_endpoint_url( 'lost-password' ) ); } /** * Place the form on the login page when its content does not. * * Done by adding the shortcode to the page in memory rather than by * appending output to `the_content`: block themes render an empty page body * as nothing at all — the filter never runs — while a shortcode in the * content is rendered by every theme, block or classic, in its own layout. * Nothing is written to the database. * * @param mixed $posts Posts the main query found. * * @return mixed * * @internal Hooked to `the_posts`. */ public function injectShortcode( $posts ) { $id = self::pageId(); if ( $id <= 0 || ! is_array( $posts ) ) { return $posts; } foreach ( $posts as $post ) { if ( $post instanceof \WP_Post && $post->ID === $id && ! has_shortcode( $post->post_content, 'rahkar_login' ) ) { $post->post_content .= "\n\n[rahkar_login]"; } } return $posts; } /** * The chosen login page, or 0 when none is chosen or the choice is unusable. * * A page WooCommerce or WordPress already uses for something else cannot be * the login page. "My account" as the login page sends a signed-in customer * to their account — the same page — and round again, until the browser * gives up; the checkout or the cart would lose the page's real job to a * login form. Such a choice is ignored here rather than trusted, so a store * that picked one before the list excluded them keeps working. */ public static function pageId(): int { $id = (int) Settings::get( OtpAuthModule::SLUG . '.login_page', 0 ); return $id > 0 && ! in_array( $id, self::reservedPageIds(), true ) ? $id : 0; } /** * Pages that already have a job: WooCommerce's account, checkout, cart and * shop pages, and the site's front and posts pages. * * @return list */ public static function reservedPageIds(): array { $ids = [ (int) get_option( 'page_on_front' ), (int) get_option( 'page_for_posts' ) ]; if ( function_exists( 'wc_get_page_id' ) ) { foreach ( [ 'myaccount', 'checkout', 'cart', 'shop' ] as $page ) { $ids[] = (int) wc_get_page_id( $page ); } } return array_values( array_filter( $ids, static fn( int $id ): bool => $id > 0 ) ); } /** * Take over WordPress's own login screen, for guests signing in or signing up. * * Sends them to the login page when there is one, or shows the code form on * wp-login.php itself. Everything else wp-login.php does is left alone: the * lost-password and reset links, log-out, the session-expired box inside the * dashboard (`interim-login`), and a POST from the password form, so * `wp-login.php?rahkar_password=1` stays a way in for an administrator whose * account has no mobile number. While the failsafe holds, the password form * is what everyone gets. * * @internal Hooked to `login_init`. */ public function replaceWpLogin(): void { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Routing only; nothing is changed here. $action = isset( $_REQUEST['action'] ) ? sanitize_key( wp_unslash( $_REQUEST['action'] ) ) : 'login'; if ( ! in_array( $action, [ 'login', 'register' ], true ) || isset( $_GET[ self::PASSWORD_ARG ] ) || isset( $_REQUEST['interim-login'] ) || 'POST' === strtoupper( sanitize_key( wp_unslash( $_SERVER['REQUEST_METHOD'] ?? '' ) ) ) || is_user_logged_in() || Failsafe::isActive() ) { return; } // phpcs:enable WordPress.Security.NonceVerification.Recommended if ( self::pageId() > 0 ) { wp_safe_redirect( self::url( self::returnTo() ) ); exit; } $this->renderMinimal( '', false ); exit; } /** * Is this request the chosen login page? */ public static function isLoginPage(): bool { $id = self::pageId(); return $id > 0 && is_page( $id ); } /** * The login page URL, optionally with where to return afterwards. * * @param string $returnTo Same-site URL to come back to. */ public static function url( string $returnTo = '' ): string { $id = self::pageId(); $url = $id > 0 ? (string) get_permalink( $id ) : ''; if ( '' === $url ) { return wp_login_url( $returnTo ); } return '' !== $returnTo ? add_query_arg( 'redirect_to', rawurlencode( $returnTo ), $url ) : $url; } /** * Where to go after signing in, from the `redirect_to` argument, when it * points at this site. */ public static function returnTo(): string { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- A navigation target, validated below. $raw = isset( $_GET['redirect_to'] ) ? esc_url_raw( wp_unslash( $_GET['redirect_to'] ) ) : ''; return '' !== $raw ? wp_validate_redirect( $raw, '' ) : ''; } /** * Render the focused page and its form. * * @param string $skinId Skin to show; empty uses the chosen one. * @param bool $preview Whether this is an admin preview. */ private function renderMinimal( string $skinId, bool $preview ): void { $widget = Plugin::make( LoginWidget::class ); $widget->enqueue( $skinId ); status_header( 200 ); nocache_headers(); $logo = get_custom_logo(); ?> > >

output( LoginWidget::PURPOSE_PAGE, $skinId, self::returnTo() ); ?>