renderPanel( __( 'ورود با کد پیامکی', 'rahkar-toolkit' ), __( 'ورود با رمز عبور', 'rahkar-toolkit' ) ); } /** * Inject the panel into a register form. * * The same flow serves both: verifying a number that has no account creates * one, so there is nothing extra to register. * * @internal Hooked callback. */ public function renderRegisterPanel(): void { $this->renderPanel( __( 'ثبت‌نام با شماره موبایل', 'rahkar-toolkit' ), __( 'ثبت‌نام با ایمیل', 'rahkar-toolkit' ) ); } /** * Which login methods this render should offer. * * Not memoised: a page can render several login forms — the header popup * paints long before the checkout content — and {@see self::forceOpen()} may * be called in between. A cached value would let the header decide what the * checkout shows. */ private function mode(): string { $stored = (string) Settings::get( OtpAuthModule::SLUG . '.login_mode', self::MODE_PASSWORD_FIRST ); $mode = in_array( $stored, [ self::MODE_PASSWORD_FIRST, self::MODE_OTP_FIRST, self::MODE_OTP_ONLY ], true ) ? $stored : self::MODE_PASSWORD_FIRST; // The store asked for no password forms anywhere. if ( OtpAuthModule::codeOnly() ) { return self::MODE_OTP_ONLY; } // Promote, never demote: a store that chose "code only" keeps it. if ( self::$forcedOpen && self::MODE_PASSWORD_FIRST === $mode ) { $mode = self::MODE_OTP_FIRST; } return $mode; } /** * Emit one panel. * * @param string $switchLabel Text for the button that opens the panel. * @param string $backLabel Text for the link back to the theme's form. */ private function renderPanel( string $switchLabel, string $backLabel ): void { // Codes are not getting through: the theme's own form, untouched, is the // way in until they are. See Failsafe. if ( Failsafe::isActive() ) { return; } $mode = $this->mode(); $otpFirst = self::MODE_PASSWORD_FIRST !== $mode; $otpOnly = self::MODE_OTP_ONLY === $mode; // The active class is rendered by PHP rather than added by script: adding // it on load would let the theme's password form paint first and then // vanish — a visible flash on every page for a store that chose code // login as its default. $classes = 'rahkar-wd' . ( $otpFirst ? ' is-active' : '' ) . ( $otpOnly ? ' is-only' : '' ); $this->printCriticalStylesOnce(); if ( $otpFirst ) { $this->printStylesOnce(); } $purpose = self::$forcedOpen ? LoginWidget::PURPOSE_CHECKOUT : LoginWidget::PURPOSE_EMBEDDED; $widget = Plugin::make( LoginWidget::class ); ?>
> output( $purpose ); ?>

printBootstrapOnce(); } /** * Hide the theme's own fields behind an open panel, before first paint, and * lay out the panel's own chrome. * * Inline, because a panel that renders open needs the theme's password * fields gone before the page is shown; waiting for a stylesheet makes them * flash into view and vanish. */ private function printCriticalStylesOnce(): void { if ( self::$criticalPrinted ) { return; } self::$criticalPrinted = true; ?> .rahkar-wd.is-active~p,.rahkar-wd.is-active~.login-form-footer,.rahkar-wd.is-active~.wd-login-divider,.rahkar-wd.is-active~.wd-social-login{display:none} .rahkar-wd__panel[hidden]{display:none} .rahkar-wd__switch{width:100%;margin-bottom:1rem} .rahkar-wd__foot{margin:1rem 0 0;text-align:center} .rahkar-wd__back{background:none;border:0;padding:0;font:inherit;color:inherit;text-decoration:underline;cursor:pointer} $url ) { // Registered and printed here, beside a panel that is shown open, rather // than enqueued: enqueueing would put the stylesheet on every page. wp_register_style( $handle, $url, [], RAHKAR_WP_VERSION ); wp_print_styles( $handle ); } } /** * Print the on-demand loader, once per request. */ private function printBootstrapOnce(): void { if ( self::$bootstrapPrinted ) { return; } self::$bootstrapPrinted = true; $assets = [ 'script' => add_query_arg( 'ver', RAHKAR_WP_VERSION, LoginWidget::scriptUrl() ), 'styles' => array_map( static fn( string $url ): string => add_query_arg( 'ver', RAHKAR_WP_VERSION, $url ), array_values( LoginWidget::styleUrls() ) ), ]; ?> ( function () { var assets = ; var loaded = false; // Fetched once, the first time anyone engages with a panel. Woodmart // puts a login form on every page, so loading these up front would // charge every visitor for a form most of them never open. function loadAssets() { if ( loaded ) { if ( window.rahkarLoginBoot ) { window.rahkarLoginBoot( document ); } return; } loaded = true; assets.styles.forEach( function ( href ) { if ( document.querySelector( 'link[href="' + href + '"]' ) ) { return; } var link = document.createElement( 'link' ); link.rel = 'stylesheet'; link.href = href; document.head.appendChild( link ); } ); var script = document.createElement( 'script' ); script.src = assets.script; script.defer = true; document.body.appendChild( script ); } function open( wrap ) { if ( ! wrap ) { return; } wrap.classList.add( 'is-active' ); var panel = wrap.querySelector( '[data-rahkar-panel]' ); if ( panel ) { panel.hidden = false; } loadAssets(); } function close( wrap ) { // In code-only mode there is nothing to go back to. if ( ! wrap || wrap.classList.contains( 'is-only' ) ) { return; } wrap.classList.remove( 'is-active' ); var panel = wrap.querySelector( '[data-rahkar-panel]' ); if ( panel ) { panel.hidden = true; } } // One delegated listener for the whole document: Woodmart injects login // forms over AJAX, and delegation keeps working for markup that // appears after this ran. document.addEventListener( 'click', function ( event ) { if ( ! event.target.closest ) { return; } var opener = event.target.closest( '[data-rahkar-open]' ); if ( opener ) { event.preventDefault(); open( opener.closest( '[data-rahkar-wd]' ) ); return; } var closer = event.target.closest( '[data-rahkar-close]' ); if ( closer ) { event.preventDefault(); close( closer.closest( '[data-rahkar-wd]' ) ); } } ); // With code login as the default there is no button to press, so the // trigger is touching the panel at all. `focusin` fires before any // keystroke, so the script is ready by the time a number is typed. document.addEventListener( 'focusin', function ( event ) { if ( event.target.closest && event.target.closest( '[data-rahkar-wd].is-active' ) ) { loadAssets(); } } ); } )(); 'rahkar-otp-boot' ] ); ?>