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 );
?>
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' ] ); ?>