Plugin Directory

Changeset 3391214


Ignore:
Timestamp:
11/06/2025 02:47:08 PM (5 months ago)
Author:
heymehedi
Message:

Update to version 1.11 from GitHub

Location:
login-me-now
Files:
2 added
18 edited
1 copied

Legend:

Unmodified
Added
Removed
  • login-me-now/tags/1.11/app/Controllers/PhoneOTPController.php

    r3384718 r3391214  
    44
    55use LoginMeNow\Repositories\PhoneOTPRepository;
     6use LoginMeNow\Repositories\SettingsRepository;
     7use LoginMeNow\User\PhoneMeta;
    68use WP_REST_Request;
    79use WP_REST_Response;
     
    2224
    2325                $phone           = (string) $request->get_param( 'phone' );
     26                $country         = strtoupper( (string) $request->get_param( 'country' ) );
     27                $dial_code_param = (string) $request->get_param( 'dialCode' );
    2428                $recaptcha_token = (string) $request->get_param( 'recaptchaToken' );
    25                 $result          = $this->repo->send_otp( $phone, $recaptcha_token );
     29                $normalized      = PhoneMeta::normalize( $phone );
    2630
    27         return $this->respond( $result );
    28     }
     31                if ( empty( $normalized ) ) {
     32                        return $this->error( __( 'Invalid phone number.', 'login-me-now' ), 400 );
     33                }
     34
     35                $allowed = SettingsRepository::get( 'phone_otp_allowed_countries', [] );
     36                $allowed = array_values( array_unique( array_filter( array_map( 'strtoupper', is_array( $allowed ) ? $allowed : (array) $allowed ) ) ) );
     37
     38                if ( ! empty( $allowed ) && ( empty( $country ) || ! in_array( $country, $allowed, true ) ) ) {
     39                        return $this->error( __( 'Phone verification is not available for the selected country.', 'login-me-now' ), 400 );
     40                }
     41
     42                $dial_code = preg_replace( '/[^0-9]/', '', $dial_code_param );
     43                if ( ! empty( $dial_code ) ) {
     44                        $expected_prefix = '+' . ltrim( $dial_code, '+' );
     45                        if ( 0 !== strpos( $normalized, $expected_prefix ) ) {
     46                                return $this->error( __( 'The phone number does not match the selected country code.', 'login-me-now' ), 400 );
     47                        }
     48                }
     49
     50                $result = $this->repo->send_otp( $normalized, $recaptcha_token );
     51
     52                return $this->respond( $result );
     53        }
    2954
    3055    public function verify_phone_otp( WP_REST_Request $request ): WP_REST_Response {
  • login-me-now/tags/1.11/app/Frontend/Enqueuer.php

    r3147204 r3391214  
    1313class Enqueuer extends EnqueuerBase {
    1414
    15     public function wp_enqueue_scripts(): void {
    16         $this->register_script( 'login-me-now-google-api', '//apis.google.com/js/api:client.js' );
    17         $this->register_script( 'login-me-now-social-login-main', LOGIN_ME_NOW_PUBLIC . 'js/main.js', ['login-me-now-google-api'] );
     15        public function wp_enqueue_scripts(): void {
     16                $handles = $this->register_phone_input_assets();
    1817
    19         $this->register_style( 'login-me-now-social-login-main', LOGIN_ME_NOW_PUBLIC . 'css/main.css' );
    20         $this->local_script();
    21     }
     18                $this->register_script( 'login-me-now-google-api', '//apis.google.com/js/api:client.js' );
     19                $this->register_script(
     20                        'login-me-now-social-login-main',
     21                        LOGIN_ME_NOW_PUBLIC . 'js/main.js',
     22                        ['login-me-now-google-api', $handles['utils']]
     23                );
    2224
    23     public function login_enqueue_scripts(): void {
    24         $this->register_script( 'login-me-now-google-api', '//apis.google.com/js/api:client.js' );
    25         $this->register_script( 'login-me-now-social-login-main', LOGIN_ME_NOW_PUBLIC . 'js/main.js', ['login-me-now-google-api'] );
     25                $this->register_style(
     26                        'login-me-now-social-login-main',
     27                        LOGIN_ME_NOW_PUBLIC . 'css/main.css',
     28                        [$handles['style']]
     29                );
     30                $this->local_script();
     31        }
    2632
    27         $this->register_style( 'login-me-now-social-login-main', LOGIN_ME_NOW_PUBLIC . 'css/main.css' );
    28         $this->local_script();
    29     }
     33        public function login_enqueue_scripts(): void {
     34                $handles = $this->register_phone_input_assets();
     35
     36                $this->register_script( 'login-me-now-google-api', '//apis.google.com/js/api:client.js' );
     37                $this->register_script(
     38                        'login-me-now-social-login-main',
     39                        LOGIN_ME_NOW_PUBLIC . 'js/main.js',
     40                        ['login-me-now-google-api', $handles['utils']]
     41                );
     42
     43                $this->register_style(
     44                        'login-me-now-social-login-main',
     45                        LOGIN_ME_NOW_PUBLIC . 'css/main.css',
     46                        [$handles['style']]
     47                );
     48                $this->local_script();
     49        }
    3050
    3151    public function local_script() {
     
    4565        }
    4666
    47         wp_localize_script( 'login-me-now-social-login-main', 'login_me_now_social_login_main_obj', $data );
    48     }
     67                wp_localize_script( 'login-me-now-social-login-main', 'login_me_now_social_login_main_obj', $data );
     68        }
     69
     70        private function register_phone_input_assets(): array {
     71                $version   = apply_filters( 'login_me_now_phone_otp_phone_input_version', '18.2.1' );
     72                $base_url  = apply_filters(
     73                        'login_me_now_phone_otp_phone_input_base_url',
     74                        sprintf( 'https://cdn.jsdelivr.net/npm/intl-tel-input@%s/build/', $version )
     75                );
     76                $base_url  = trailingslashit( $base_url );
     77                $style     = 'login-me-now-intl-tel-input-style';
     78                $script    = 'login-me-now-intl-tel-input';
     79                $utils     = 'login-me-now-intl-tel-input-utils';
     80
     81                $utils_src = apply_filters( 'login_me_now_phone_otp_utils_url', $base_url . 'js/utils.js' );
     82
     83                $this->register_style( $style, $base_url . 'css/intlTelInput.css' );
     84                $this->register_script( $script, $base_url . 'js/intlTelInput.min.js' );
     85                $this->register_script( $utils, $utils_src, [$script] );
     86
     87                return [
     88                        'style' => $style,
     89                        'script' => $script,
     90                        'utils'  => $utils,
     91                ];
     92        }
    4993}
  • login-me-now/tags/1.11/app/Logins/PhoneOTPLogin/Settings.php

    r3384718 r3391214  
    215215
    216216                $fields[] = [
     217                        'title'         => __( 'Default Country', 'login-me-now' ),
     218                        'description'   => __( 'Choose the country that should be preselected when the phone field loads.', 'login-me-now' ),
     219                        'id'            => 'phone_otp_default_country',
     220                        'type'          => 'select',
     221                        'options'       => $this->get_country_options(),
     222                        'tab'           => 'phone-otp',
     223                        'previous_data' => SettingsRepository::get( 'phone_otp_default_country', 'US' ),
     224                        'if_has'        => ['phone_otp_enabled'],
     225                ];
     226
     227                $fields[] = [
     228                        'title'         => __( 'Allowed Countries', 'login-me-now' ),
     229                        'description'   => __( 'Limit the dropdown to specific countries. Leave empty to allow all countries.', 'login-me-now' ),
     230                        'id'            => 'phone_otp_allowed_countries',
     231                        'type'          => 'multi-select',
     232                        'options'       => $this->get_country_options(),
     233                        'tab'           => 'phone-otp',
     234                        'previous_data' => array_values( array_filter( array_map( 'strtoupper', (array) SettingsRepository::get( 'phone_otp_allowed_countries', [] ) ) ) ),
     235                        'if_has'        => ['phone_otp_enabled'],
     236                ];
     237
     238                $fields[] = [
     239                        'type'   => 'separator',
     240                        'tab'    => 'phone-otp',
     241                        'if_has' => ['phone_otp_enabled'],
     242                ];
     243
     244                $fields[] = [
    217245                        'title'         => __( 'Button Label', 'login-me-now' ),
    218246                        'description'   => __( 'Text displayed on the Phone OTP button.', 'login-me-now' ),
     
    249277                return $fields;
    250278        }
     279
     280        private function get_country_options(): array {
     281                static $options = null;
     282
     283                if ( null === $options ) {
     284                        $options = include __DIR__ . '/countries.php';
     285
     286                        $options = array_map(
     287                                static function ( array $option ): array {
     288                                        $option['value'] = strtoupper( $option['value'] );
     289                                        $option['label'] = __( $option['label'], 'login-me-now' );
     290
     291                                        return $option;
     292                                },
     293                                $options
     294                        );
     295                }
     296
     297                return apply_filters( 'login_me_now_phone_otp_country_options', $options );
     298        }
    251299}
  • login-me-now/tags/1.11/app/Logins/PhoneOTPLogin/Views/Button.php

    r3384718 r3391214  
    1717$verify_endpoint = get_rest_url( null, 'login-me-now/verify-phone-otp' );
    1818$nonce         = wp_create_nonce( 'lmn-phone-otp-nonce' );
     19$default_country_setting = SettingsRepository::get( 'phone_otp_default_country', 'US' );
     20$default_country        = strtolower( is_string( $default_country_setting ) ? $default_country_setting : 'US' );
     21
     22$allowed_countries_setting = SettingsRepository::get( 'phone_otp_allowed_countries', [] );
     23$allowed_countries         = array_values(
     24        array_unique(
     25                array_filter(
     26                        array_map(
     27                                static function ( $value ): string {
     28                                        return strtolower( (string) $value );
     29                                },
     30                                is_array( $allowed_countries_setting ) ? $allowed_countries_setting : (array) $allowed_countries_setting
     31                        )
     32                )
     33        )
     34);
     35
     36if ( '' === $default_country ) {
     37        $default_country = 'us';
     38}
     39
     40if ( ! empty( $allowed_countries ) && ! in_array( $default_country, $allowed_countries, true ) ) {
     41        $default_country = $allowed_countries[0];
     42}
     43
     44$allowed_countries = apply_filters( 'login_me_now_phone_otp_allowed_countries', $allowed_countries );
     45$allowed_countries = array_values(
     46        array_unique(
     47                array_filter(
     48                        array_map(
     49                                static function ( $value ): string {
     50                                        return strtolower( (string) $value );
     51                                },
     52                                (array) $allowed_countries
     53                        )
     54                )
     55        )
     56);
     57$phone_input_version = apply_filters( 'login_me_now_phone_otp_phone_input_version', '18.2.1' );
     58$phone_input_base_url = trailingslashit(
     59        apply_filters(
     60                'login_me_now_phone_otp_phone_input_base_url',
     61                sprintf( 'https://cdn.jsdelivr.net/npm/intl-tel-input@%s/build/', $phone_input_version )
     62        )
     63);
     64$phone_input_utils_url = apply_filters( 'login_me_now_phone_otp_utils_url', $phone_input_base_url . 'js/utils.js' );
    1965?>
    2066
     
    2571        data-lmn-phone-otp-provider="<?php echo esc_attr( $provider ); ?>"
    2672        data-lmn-phone-otp-recaptcha-key="<?php echo esc_attr( $recaptcha_key ); ?>"
     73        data-lmn-phone-otp-default-country="<?php echo esc_attr( $default_country ); ?>"
     74        data-lmn-phone-otp-utils-url="<?php echo esc_url( $phone_input_utils_url ); ?>"
     75        data-lmn-phone-otp-allowed-countries='<?php echo esc_attr( wp_json_encode( array_values( $allowed_countries ) ) ); ?>'
    2776        <?php if ( ! empty( $help_url ) ) : ?>data-lmn-phone-otp-help-url="<?php echo esc_url( $help_url ); ?>"<?php endif; ?>
    2877>
     
    4998                        <div class="lmn_phone_otp_step is-active" data-lmn-phone-otp-step="request">
    5099                                <label class="lmn_phone_otp_label" for="lmn_phone_number">
    51                                         <span><?php esc_html_e( 'Phone number', 'login-me-now' ); ?></span>
    52                                         <input type="tel" name="lmn_phone_number" id="lmn_phone_number" autocomplete="tel" placeholder="<?php esc_attr_e( '+1 234 567 8900', 'login-me-now' ); ?>" aria-required="true">
     100                                        <p><?php esc_html_e( 'Phone number', 'login-me-now' ); ?></p>
     101                                        <input type="tel" name="lmn_phone_number" id="lmn_phone_number" autocomplete="tel" placeholder="<?php esc_attr_e( '234 567 8900', 'login-me-now' ); ?>" aria-required="true">
    53102                                </label>
    54103                                <button type="button" class="lmn_btn lmn_phone_otp_primary" data-lmn-phone-otp-send>
     
    90139<script>
    91140        (function () {
    92                 const container = document.querySelector('[data-lmn-phone-otp]');
    93                 if (!container) {
    94                         return;
    95                 }
    96 
    97                 const triggerButton = container.querySelector('.lmn_phone_otp_trigger');
    98                 const modal = container.querySelector('.lmn_phone_otp_modal');
    99                 const overlay = container.querySelector('[data-lmn-phone-otp-overlay]');
    100                 const closeButtons = container.querySelectorAll('[data-lmn-phone-otp-close]');
    101                 const messageBox = container.querySelector('[data-lmn-phone-otp-message]');
    102                 const phoneInput = container.querySelector('#lmn_phone_number');
    103                 const sendButton = container.querySelector('[data-lmn-phone-otp-send]');
    104                 const verifyButton = container.querySelector('[data-lmn-phone-otp-verify]');
    105                 const resendButton = container.querySelector('[data-lmn-phone-otp-resend]');
    106                 const timerLabel = container.querySelector('[data-lmn-phone-otp-timer]');
    107                 const selectedPhoneLabel = container.querySelector('[data-lmn-phone-otp-selected]');
    108                 const helpButton = container.querySelector('[data-lmn-phone-otp-help]');
    109                 const steps = {
    110                         request: container.querySelector('[data-lmn-phone-otp-step="request"]'),
    111                         verify: container.querySelector('[data-lmn-phone-otp-step="verify"]')
    112                 };
    113                 const digitInputs = Array.from(container.querySelectorAll('[data-lmn-phone-otp-digit]'));
     141                const initPhoneOtp = () => {
     142                        const container = document.querySelector('[data-lmn-phone-otp]');
     143                        if (!container) {
     144                                return;
     145                        }
     146
     147                        const triggerButton = container.querySelector('.lmn_phone_otp_trigger');
     148                        const modal = container.querySelector('.lmn_phone_otp_modal');
     149                        const overlay = container.querySelector('[data-lmn-phone-otp-overlay]');
     150                        const closeButtons = container.querySelectorAll('[data-lmn-phone-otp-close]');
     151                        const messageBox = container.querySelector('[data-lmn-phone-otp-message]');
     152                        const phoneInput = container.querySelector('#lmn_phone_number');
     153                        const sendButton = container.querySelector('[data-lmn-phone-otp-send]');
     154                        const verifyButton = container.querySelector('[data-lmn-phone-otp-verify]');
     155                        const resendButton = container.querySelector('[data-lmn-phone-otp-resend]');
     156                        const timerLabel = container.querySelector('[data-lmn-phone-otp-timer]');
     157                        const selectedPhoneLabel = container.querySelector('[data-lmn-phone-otp-selected]');
     158                        const helpButton = container.querySelector('[data-lmn-phone-otp-help]');
     159                        const steps = {
     160                                request: container.querySelector('[data-lmn-phone-otp-step="request"]'),
     161                                verify: container.querySelector('[data-lmn-phone-otp-step="verify"]')
     162                        };
     163                        const digitInputs = Array.from(container.querySelectorAll('[data-lmn-phone-otp-digit]'));
    114164
    115165                const endpoints = {
     
    121171                const provider = container.dataset.lmnPhoneOtpProvider || 'firebase';
    122172                const recaptchaKey = container.dataset.lmnPhoneOtpRecaptchaKey || '';
     173                const defaultCountry = (container.dataset.lmnPhoneOtpDefaultCountry || 'us').toLowerCase();
     174                const utilsUrl = container.dataset.lmnPhoneOtpUtilsUrl || '';
     175                const allowedCountries = (() => {
     176                        const raw = container.dataset.lmnPhoneOtpAllowedCountries || '[]';
     177                        try {
     178                                const parsed = JSON.parse(raw);
     179                                if (Array.isArray(parsed)) {
     180                                        return parsed
     181                                                .map((value) => (typeof value === 'string' ? value.toLowerCase() : ''))
     182                                                .filter((value, index, self) => value && self.indexOf(value) === index);
     183                                }
     184                        } catch (error) {
     185                                // Ignore malformed configuration and fall back to all countries.
     186                        }
     187
     188                        return [];
     189                })();
     190                const fallbackCountry = allowedCountries.length ? allowedCountries[0] : defaultCountry || 'us';
    123191
    124192                let countdownId = null;
     
    127195                let cooldown = 60;
    128196                let lastPhone = '';
    129 
    130                 const formatPhone = (value) => value.replace(/\s+/g, '');
     197                        let lastCountry = defaultCountry || fallbackCountry;
     198                        let iti = null;
     199                        let intlInitAttempts = 0;
     200
     201                        const maxIntlInitAttempts = 40;
     202
     203                        const getSelectedCountry = () => {
     204                                if (iti && typeof iti.getSelectedCountryData === 'function') {
     205                                        const data = iti.getSelectedCountryData();
     206                                        if (data && typeof data === 'object') {
     207                                                return data;
     208                                        }
     209                                }
     210
     211                                return null;
     212                        };
     213
     214                        if (utilsUrl && window.intlTelInputGlobals && typeof window.intlTelInputGlobals.loadUtils === 'function') {
     215                                window.intlTelInputGlobals.loadUtils(utilsUrl);
     216                        }
     217
     218                        const configureIntlTelInput = () => {
     219                                if (!phoneInput || typeof window.intlTelInput !== 'function' || iti) {
     220                                        return Boolean(iti);
     221                                }
     222
     223                                try {
     224                                        const config = {
     225                                                initialCountry: defaultCountry || 'us',
     226                                                separateDialCode: true,
     227                                                nationalMode: false,
     228                                                autoPlaceholder: 'polite',
     229                                        };
     230
     231                                        if (allowedCountries.length) {
     232                                                config.onlyCountries = allowedCountries;
     233                                        }
     234
     235                                        iti = window.intlTelInput(phoneInput, config);
     236
     237                                        const selected = getSelectedCountry();
     238                                        if (selected?.iso2) {
     239                                                lastCountry = selected.iso2.toLowerCase();
     240                                        }
     241
     242                                        phoneInput.addEventListener('countrychange', () => {
     243                                                const country = getSelectedCountry();
     244                                                if (country?.iso2) {
     245                                                        lastCountry = country.iso2.toLowerCase();
     246                                                }
     247                                        });
     248
     249                                        return Boolean(iti);
     250                                } catch (error) {
     251                                        iti = null;
     252                                        return false;
     253                                }
     254                        };
     255
     256                        const ensureIntlTelInput = () => {
     257                                if (configureIntlTelInput()) {
     258                                        return;
     259                                }
     260
     261                                if (intlInitAttempts >= maxIntlInitAttempts) {
     262                                        return;
     263                                }
     264
     265                                intlInitAttempts += 1;
     266                                window.setTimeout(ensureIntlTelInput, 150);
     267                        };
     268
     269                        ensureIntlTelInput();
     270                        const handleWindowLoad = () => {
     271                                window.removeEventListener('load', handleWindowLoad);
     272                                ensureIntlTelInput();
     273                        };
     274                        window.addEventListener('load', handleWindowLoad);
     275
     276                        const getRawInputValue = () => (phoneInput?.value || '').trim();
     277
     278                const getFormattedNumber = () => {
     279                        if (iti && typeof iti.getNumber === 'function') {
     280                                const value = iti.getNumber();
     281                                if (value) {
     282                                        return value;
     283                                }
     284                        }
     285
     286                        return getRawInputValue().replace(/\s+/g, '');
     287                };
     288
     289                const getDisplayNumber = () => {
     290                        if (iti && typeof iti.getNumber === 'function') {
     291                                if (window.intlTelInputUtils && window.intlTelInputUtils.numberFormat && typeof window.intlTelInputUtils.numberFormat.INTERNATIONAL !== 'undefined') {
     292                                        const formatted = iti.getNumber(window.intlTelInputUtils.numberFormat.INTERNATIONAL);
     293                                        if (formatted) {
     294                                                return formatted;
     295                                        }
     296                                }
     297
     298                                const value = iti.getNumber();
     299                                if (value) {
     300                                        return value;
     301                                }
     302                        }
     303
     304                        return getRawInputValue();
     305                };
     306
     307                const resetPhoneInput = () => {
     308                        if (iti && typeof iti.setNumber === 'function') {
     309                                iti.setNumber('');
     310                                if (defaultCountry || fallbackCountry) {
     311                                        try {
     312                                                iti.setCountry(defaultCountry || fallbackCountry);
     313                                        } catch (error) {
     314                                                // Ignore errors when resetting the default country.
     315                                        }
     316                                }
     317                        } else if (phoneInput) {
     318                                phoneInput.value = '';
     319                        }
     320
     321                        lastCountry = defaultCountry || fallbackCountry;
     322                };
    131323
    132324                const setMessage = (text, variant = 'info') => {
     
    269461                        }
    270462
    271                         const phone = formatPhone(phoneInput?.value || '');
    272                         if (!phone) {
     463                        const rawDigits = getRawInputValue().replace(/[^0-9]/g, '');
     464                        if (!rawDigits) {
    273465                                setMessage('<?php echo esc_js( __( 'Please enter your phone number.', 'login-me-now' ) ); ?>', 'error');
     466                                phoneInput?.focus();
     467                                return;
     468                        }
     469
     470                        const selectedCountry = getSelectedCountry();
     471                        const selectedIso = selectedCountry?.iso2 || (allowedCountries.length ? allowedCountries[0] : defaultCountry || fallbackCountry || '');
     472                        const countryIso = selectedIso ? selectedIso.toUpperCase() : '';
     473                        const dialCode = selectedCountry?.dialCode ? String(selectedCountry.dialCode) : '';
     474
     475                        if (allowedCountries.length && (!countryIso || !allowedCountries.includes(countryIso.toLowerCase()))) {
     476                                setMessage('<?php echo esc_js( __( 'This country is not available for phone verification.', 'login-me-now' ) ); ?>', 'error');
     477                                return;
     478                        }
     479
     480                        const phone = getFormattedNumber();
     481
     482                        if (iti && typeof iti.isValidNumber === 'function' && !iti.isValidNumber()) {
     483                                setMessage('<?php echo esc_js( __( 'Please enter a valid phone number for the selected country.', 'login-me-now' ) ); ?>', 'error');
    274484                                phoneInput?.focus();
    275485                                return;
     
    288498                                        body: JSON.stringify({
    289499                                                phone,
     500                                                country: countryIso,
     501                                                dialCode,
    290502                                                nonce,
    291503                                                recaptchaToken,
     
    307519                                                cooldown = Number(payload.data.cooldown) || 60;
    308520                                                lastPhone = phone;
     521                                                lastCountry = (selectedIso || defaultCountry || fallbackCountry).toLowerCase();
    309522
    310523                                                setMessage(payload.data.message, 'success');
    311524                                                showStep('verify');
    312525                                                if (selectedPhoneLabel) {
    313                                                         selectedPhoneLabel.textContent = phone;
     526                                                        selectedPhoneLabel.textContent = getDisplayNumber();
    314527                                                }
    315528                                                resetDigits();
     
    397610                        }
    398611
    399                         phoneInput.value = lastPhone;
     612                        if (iti && typeof iti.setNumber === 'function') {
     613                                try {
     614                                        if (lastCountry || fallbackCountry) {
     615                                                iti.setCountry(lastCountry || fallbackCountry);
     616                                        }
     617                                        iti.setNumber(lastPhone);
     618                                } catch (error) {
     619                                        // Ignore formatting errors when reusing the last phone number.
     620                                        if (phoneInput) {
     621                                                phoneInput.value = lastPhone;
     622                                        }
     623                                }
     624                        } else if (phoneInput) {
     625                                phoneInput.value = lastPhone;
     626                        }
    400627                        showStep('request');
    401628                        handleSend();
     
    412639                const resetForm = () => {
    413640                        showStep('request');
    414                         phoneInput.value = '';
     641                        resetPhoneInput();
    415642                        setMessage('');
    416643                        sessionToken = '';
    417644                        cooldown = 60;
    418645                        lastPhone = '';
     646                        lastCountry = defaultCountry || fallbackCountry;
    419647                        clearCountdown();
    420648                        remaining = 0;
     
    422650                        updateTimerLabel();
    423651                        resetDigits();
    424                 };
    425 
    426                 triggerButton?.addEventListener('click', (event) => {
    427                         event.preventDefault();
    428                         openModal();
    429                 });
     652                        if (selectedPhoneLabel) {
     653                                selectedPhoneLabel.textContent = '';
     654                        }
     655                };
     656
     657                        triggerButton?.addEventListener('click', (event) => {
     658                                event.preventDefault();
     659                                openModal();
     660                        });
    430661
    431662                closeButtons?.forEach((button) => {
     
    475706                });
    476707
    477                 document.addEventListener('keydown', (event) => {
    478                         if (event.key === 'Escape' && modal?.classList.contains('is-active')) {
    479                                 closeModal();
    480                         }
    481                 });
     708                        document.addEventListener('keydown', (event) => {
     709                                if (event.key === 'Escape' && modal?.classList.contains('is-active')) {
     710                                        closeModal();
     711                                }
     712                        });
     713                };
     714
     715                if (document.readyState === 'loading') {
     716                        document.addEventListener('DOMContentLoaded', initPhoneOtp);
     717                } else {
     718                        initPhoneOtp();
     719                }
    482720        })();
    483721</script>
  • login-me-now/tags/1.11/config.php

    r3384718 r3391214  
    22
    33return [
    4     'version'         => '1.10',
     4    'version'         => '1.11',
    55    'min_php'         => '7.4',
    66    'db_version'      => '1.0.0',
  • login-me-now/tags/1.11/login-me-now.php

    r3384718 r3391214  
    55 * Author: Pluginly
    66 * Author URI: https://loginmenow.com/
    7  * Version: 1.10
     7 * Version: 1.11
    88 * Tested up to: 6.8.3
    99 * Requires PHP: 7.4
  • login-me-now/tags/1.11/public/css/main.css

    r3384718 r3391214  
    390390
    391391.lmn_phone_otp_label {
    392     display: flex;
     392    display: flex !important;
    393393    flex-direction: column;
    394394    gap: 6px;
     
    423423
    424424.lmn_phone_otp_code {
    425     display: grid;
    426     grid-template-columns: repeat(6, 1fr);
    427     gap: 10px;
     425    display: flex;
     426    justify-content: center;
     427    gap: 12px;
     428    flex-wrap: wrap;
     429    margin: 20px auto;
     430    max-width: 360px;
    428431}
    429432
    430433.lmn_phone_otp_digit {
    431     text-align: center;
     434    display: flex;
     435    align-items: center;
     436    justify-content: center;
    432437    font-size: 20px;
    433     padding: 12px 0;
    434438    border: 1px solid #cbd5f5;
    435439    border-radius: 10px;
    436440    background: #f8fafc;
    437441    transition: border-color 0.2s ease, background-color 0.2s ease;
     442    width: 45px;
     443    height: 45px;
     444    flex: 0 0 32px;
     445    text-align: center;
     446    padding: 0;
    438447}
    439448
     
    520529    .lmn_phone_otp_code {
    521530        gap: 8px;
     531        margin: 16px auto;
    522532    }
    523533
    524534    .lmn_phone_otp_digit {
    525535        padding: 10px 0;
     536        height: 52px;
     537        flex-basis: 48px;
    526538    }
    527539}
  • login-me-now/tags/1.11/readme.txt

    r3384718 r3391214  
    55Tested up to: 6.8.3
    66Requires PHP: 7.4
    7 Stable tag: 1.10
     7Stable tag: 1.11
    88License: GPLv3 or Any Later Version
    99
     
    4747- Send one-time passcodes via Firebase or Twilio Verify
    4848- Built-in rate limiting, resend timer, and reCAPTCHA support
     49- Country flags and country restriction
    4950- Store, normalize, and verify phone numbers from user profiles
    5051- Drop a shortcode or toggle the native login option to expose the modal anywhere
     
    151152
    152153== Changelog ==
     154
     155= 1.11 – Nov 06, 2025 =
     156* New: Added a new setting to allow users to choose whether to show the country flag in phone numbers.
     157* New: Added country restriction and default country settings
     158* Fix: OTP Digits for WooCommerce Account Login
    153159
    154160= 1.10 – Oct 26, 2025 =
  • login-me-now/tags/1.11/vendor/composer/installed.php

    r3384718 r3391214  
    22    'root' => array(
    33        'name' => 'pluginly/login-me-now',
    4         'pretty_version' => 'v1.10',
    5         'version' => '1.10.0.0',
    6         'reference' => '1d9eb7759e730f586bf667607bb15c65ab68efd6',
     4        'pretty_version' => 'v1.11',
     5        'version' => '1.11.0.0',
     6        'reference' => '7db00a09f43000f61d2cd85b89eb413b0b25d44e',
    77        'type' => 'library',
    88        'install_path' => __DIR__ . '/../../',
     
    120120        ),
    121121        'pluginly/login-me-now' => array(
    122             'pretty_version' => 'v1.10',
    123             'version' => '1.10.0.0',
    124             'reference' => '1d9eb7759e730f586bf667607bb15c65ab68efd6',
     122            'pretty_version' => 'v1.11',
     123            'version' => '1.11.0.0',
     124            'reference' => '7db00a09f43000f61d2cd85b89eb413b0b25d44e',
    125125            'type' => 'library',
    126126            'install_path' => __DIR__ . '/../../',
  • login-me-now/trunk/app/Controllers/PhoneOTPController.php

    r3384718 r3391214  
    44
    55use LoginMeNow\Repositories\PhoneOTPRepository;
     6use LoginMeNow\Repositories\SettingsRepository;
     7use LoginMeNow\User\PhoneMeta;
    68use WP_REST_Request;
    79use WP_REST_Response;
     
    2224
    2325                $phone           = (string) $request->get_param( 'phone' );
     26                $country         = strtoupper( (string) $request->get_param( 'country' ) );
     27                $dial_code_param = (string) $request->get_param( 'dialCode' );
    2428                $recaptcha_token = (string) $request->get_param( 'recaptchaToken' );
    25                 $result          = $this->repo->send_otp( $phone, $recaptcha_token );
     29                $normalized      = PhoneMeta::normalize( $phone );
    2630
    27         return $this->respond( $result );
    28     }
     31                if ( empty( $normalized ) ) {
     32                        return $this->error( __( 'Invalid phone number.', 'login-me-now' ), 400 );
     33                }
     34
     35                $allowed = SettingsRepository::get( 'phone_otp_allowed_countries', [] );
     36                $allowed = array_values( array_unique( array_filter( array_map( 'strtoupper', is_array( $allowed ) ? $allowed : (array) $allowed ) ) ) );
     37
     38                if ( ! empty( $allowed ) && ( empty( $country ) || ! in_array( $country, $allowed, true ) ) ) {
     39                        return $this->error( __( 'Phone verification is not available for the selected country.', 'login-me-now' ), 400 );
     40                }
     41
     42                $dial_code = preg_replace( '/[^0-9]/', '', $dial_code_param );
     43                if ( ! empty( $dial_code ) ) {
     44                        $expected_prefix = '+' . ltrim( $dial_code, '+' );
     45                        if ( 0 !== strpos( $normalized, $expected_prefix ) ) {
     46                                return $this->error( __( 'The phone number does not match the selected country code.', 'login-me-now' ), 400 );
     47                        }
     48                }
     49
     50                $result = $this->repo->send_otp( $normalized, $recaptcha_token );
     51
     52                return $this->respond( $result );
     53        }
    2954
    3055    public function verify_phone_otp( WP_REST_Request $request ): WP_REST_Response {
  • login-me-now/trunk/app/Frontend/Enqueuer.php

    r3147204 r3391214  
    1313class Enqueuer extends EnqueuerBase {
    1414
    15     public function wp_enqueue_scripts(): void {
    16         $this->register_script( 'login-me-now-google-api', '//apis.google.com/js/api:client.js' );
    17         $this->register_script( 'login-me-now-social-login-main', LOGIN_ME_NOW_PUBLIC . 'js/main.js', ['login-me-now-google-api'] );
     15        public function wp_enqueue_scripts(): void {
     16                $handles = $this->register_phone_input_assets();
    1817
    19         $this->register_style( 'login-me-now-social-login-main', LOGIN_ME_NOW_PUBLIC . 'css/main.css' );
    20         $this->local_script();
    21     }
     18                $this->register_script( 'login-me-now-google-api', '//apis.google.com/js/api:client.js' );
     19                $this->register_script(
     20                        'login-me-now-social-login-main',
     21                        LOGIN_ME_NOW_PUBLIC . 'js/main.js',
     22                        ['login-me-now-google-api', $handles['utils']]
     23                );
    2224
    23     public function login_enqueue_scripts(): void {
    24         $this->register_script( 'login-me-now-google-api', '//apis.google.com/js/api:client.js' );
    25         $this->register_script( 'login-me-now-social-login-main', LOGIN_ME_NOW_PUBLIC . 'js/main.js', ['login-me-now-google-api'] );
     25                $this->register_style(
     26                        'login-me-now-social-login-main',
     27                        LOGIN_ME_NOW_PUBLIC . 'css/main.css',
     28                        [$handles['style']]
     29                );
     30                $this->local_script();
     31        }
    2632
    27         $this->register_style( 'login-me-now-social-login-main', LOGIN_ME_NOW_PUBLIC . 'css/main.css' );
    28         $this->local_script();
    29     }
     33        public function login_enqueue_scripts(): void {
     34                $handles = $this->register_phone_input_assets();
     35
     36                $this->register_script( 'login-me-now-google-api', '//apis.google.com/js/api:client.js' );
     37                $this->register_script(
     38                        'login-me-now-social-login-main',
     39                        LOGIN_ME_NOW_PUBLIC . 'js/main.js',
     40                        ['login-me-now-google-api', $handles['utils']]
     41                );
     42
     43                $this->register_style(
     44                        'login-me-now-social-login-main',
     45                        LOGIN_ME_NOW_PUBLIC . 'css/main.css',
     46                        [$handles['style']]
     47                );
     48                $this->local_script();
     49        }
    3050
    3151    public function local_script() {
     
    4565        }
    4666
    47         wp_localize_script( 'login-me-now-social-login-main', 'login_me_now_social_login_main_obj', $data );
    48     }
     67                wp_localize_script( 'login-me-now-social-login-main', 'login_me_now_social_login_main_obj', $data );
     68        }
     69
     70        private function register_phone_input_assets(): array {
     71                $version   = apply_filters( 'login_me_now_phone_otp_phone_input_version', '18.2.1' );
     72                $base_url  = apply_filters(
     73                        'login_me_now_phone_otp_phone_input_base_url',
     74                        sprintf( 'https://cdn.jsdelivr.net/npm/intl-tel-input@%s/build/', $version )
     75                );
     76                $base_url  = trailingslashit( $base_url );
     77                $style     = 'login-me-now-intl-tel-input-style';
     78                $script    = 'login-me-now-intl-tel-input';
     79                $utils     = 'login-me-now-intl-tel-input-utils';
     80
     81                $utils_src = apply_filters( 'login_me_now_phone_otp_utils_url', $base_url . 'js/utils.js' );
     82
     83                $this->register_style( $style, $base_url . 'css/intlTelInput.css' );
     84                $this->register_script( $script, $base_url . 'js/intlTelInput.min.js' );
     85                $this->register_script( $utils, $utils_src, [$script] );
     86
     87                return [
     88                        'style' => $style,
     89                        'script' => $script,
     90                        'utils'  => $utils,
     91                ];
     92        }
    4993}
  • login-me-now/trunk/app/Logins/PhoneOTPLogin/Settings.php

    r3384718 r3391214  
    215215
    216216                $fields[] = [
     217                        'title'         => __( 'Default Country', 'login-me-now' ),
     218                        'description'   => __( 'Choose the country that should be preselected when the phone field loads.', 'login-me-now' ),
     219                        'id'            => 'phone_otp_default_country',
     220                        'type'          => 'select',
     221                        'options'       => $this->get_country_options(),
     222                        'tab'           => 'phone-otp',
     223                        'previous_data' => SettingsRepository::get( 'phone_otp_default_country', 'US' ),
     224                        'if_has'        => ['phone_otp_enabled'],
     225                ];
     226
     227                $fields[] = [
     228                        'title'         => __( 'Allowed Countries', 'login-me-now' ),
     229                        'description'   => __( 'Limit the dropdown to specific countries. Leave empty to allow all countries.', 'login-me-now' ),
     230                        'id'            => 'phone_otp_allowed_countries',
     231                        'type'          => 'multi-select',
     232                        'options'       => $this->get_country_options(),
     233                        'tab'           => 'phone-otp',
     234                        'previous_data' => array_values( array_filter( array_map( 'strtoupper', (array) SettingsRepository::get( 'phone_otp_allowed_countries', [] ) ) ) ),
     235                        'if_has'        => ['phone_otp_enabled'],
     236                ];
     237
     238                $fields[] = [
     239                        'type'   => 'separator',
     240                        'tab'    => 'phone-otp',
     241                        'if_has' => ['phone_otp_enabled'],
     242                ];
     243
     244                $fields[] = [
    217245                        'title'         => __( 'Button Label', 'login-me-now' ),
    218246                        'description'   => __( 'Text displayed on the Phone OTP button.', 'login-me-now' ),
     
    249277                return $fields;
    250278        }
     279
     280        private function get_country_options(): array {
     281                static $options = null;
     282
     283                if ( null === $options ) {
     284                        $options = include __DIR__ . '/countries.php';
     285
     286                        $options = array_map(
     287                                static function ( array $option ): array {
     288                                        $option['value'] = strtoupper( $option['value'] );
     289                                        $option['label'] = __( $option['label'], 'login-me-now' );
     290
     291                                        return $option;
     292                                },
     293                                $options
     294                        );
     295                }
     296
     297                return apply_filters( 'login_me_now_phone_otp_country_options', $options );
     298        }
    251299}
  • login-me-now/trunk/app/Logins/PhoneOTPLogin/Views/Button.php

    r3384718 r3391214  
    1717$verify_endpoint = get_rest_url( null, 'login-me-now/verify-phone-otp' );
    1818$nonce         = wp_create_nonce( 'lmn-phone-otp-nonce' );
     19$default_country_setting = SettingsRepository::get( 'phone_otp_default_country', 'US' );
     20$default_country        = strtolower( is_string( $default_country_setting ) ? $default_country_setting : 'US' );
     21
     22$allowed_countries_setting = SettingsRepository::get( 'phone_otp_allowed_countries', [] );
     23$allowed_countries         = array_values(
     24        array_unique(
     25                array_filter(
     26                        array_map(
     27                                static function ( $value ): string {
     28                                        return strtolower( (string) $value );
     29                                },
     30                                is_array( $allowed_countries_setting ) ? $allowed_countries_setting : (array) $allowed_countries_setting
     31                        )
     32                )
     33        )
     34);
     35
     36if ( '' === $default_country ) {
     37        $default_country = 'us';
     38}
     39
     40if ( ! empty( $allowed_countries ) && ! in_array( $default_country, $allowed_countries, true ) ) {
     41        $default_country = $allowed_countries[0];
     42}
     43
     44$allowed_countries = apply_filters( 'login_me_now_phone_otp_allowed_countries', $allowed_countries );
     45$allowed_countries = array_values(
     46        array_unique(
     47                array_filter(
     48                        array_map(
     49                                static function ( $value ): string {
     50                                        return strtolower( (string) $value );
     51                                },
     52                                (array) $allowed_countries
     53                        )
     54                )
     55        )
     56);
     57$phone_input_version = apply_filters( 'login_me_now_phone_otp_phone_input_version', '18.2.1' );
     58$phone_input_base_url = trailingslashit(
     59        apply_filters(
     60                'login_me_now_phone_otp_phone_input_base_url',
     61                sprintf( 'https://cdn.jsdelivr.net/npm/intl-tel-input@%s/build/', $phone_input_version )
     62        )
     63);
     64$phone_input_utils_url = apply_filters( 'login_me_now_phone_otp_utils_url', $phone_input_base_url . 'js/utils.js' );
    1965?>
    2066
     
    2571        data-lmn-phone-otp-provider="<?php echo esc_attr( $provider ); ?>"
    2672        data-lmn-phone-otp-recaptcha-key="<?php echo esc_attr( $recaptcha_key ); ?>"
     73        data-lmn-phone-otp-default-country="<?php echo esc_attr( $default_country ); ?>"
     74        data-lmn-phone-otp-utils-url="<?php echo esc_url( $phone_input_utils_url ); ?>"
     75        data-lmn-phone-otp-allowed-countries='<?php echo esc_attr( wp_json_encode( array_values( $allowed_countries ) ) ); ?>'
    2776        <?php if ( ! empty( $help_url ) ) : ?>data-lmn-phone-otp-help-url="<?php echo esc_url( $help_url ); ?>"<?php endif; ?>
    2877>
     
    4998                        <div class="lmn_phone_otp_step is-active" data-lmn-phone-otp-step="request">
    5099                                <label class="lmn_phone_otp_label" for="lmn_phone_number">
    51                                         <span><?php esc_html_e( 'Phone number', 'login-me-now' ); ?></span>
    52                                         <input type="tel" name="lmn_phone_number" id="lmn_phone_number" autocomplete="tel" placeholder="<?php esc_attr_e( '+1 234 567 8900', 'login-me-now' ); ?>" aria-required="true">
     100                                        <p><?php esc_html_e( 'Phone number', 'login-me-now' ); ?></p>
     101                                        <input type="tel" name="lmn_phone_number" id="lmn_phone_number" autocomplete="tel" placeholder="<?php esc_attr_e( '234 567 8900', 'login-me-now' ); ?>" aria-required="true">
    53102                                </label>
    54103                                <button type="button" class="lmn_btn lmn_phone_otp_primary" data-lmn-phone-otp-send>
     
    90139<script>
    91140        (function () {
    92                 const container = document.querySelector('[data-lmn-phone-otp]');
    93                 if (!container) {
    94                         return;
    95                 }
    96 
    97                 const triggerButton = container.querySelector('.lmn_phone_otp_trigger');
    98                 const modal = container.querySelector('.lmn_phone_otp_modal');
    99                 const overlay = container.querySelector('[data-lmn-phone-otp-overlay]');
    100                 const closeButtons = container.querySelectorAll('[data-lmn-phone-otp-close]');
    101                 const messageBox = container.querySelector('[data-lmn-phone-otp-message]');
    102                 const phoneInput = container.querySelector('#lmn_phone_number');
    103                 const sendButton = container.querySelector('[data-lmn-phone-otp-send]');
    104                 const verifyButton = container.querySelector('[data-lmn-phone-otp-verify]');
    105                 const resendButton = container.querySelector('[data-lmn-phone-otp-resend]');
    106                 const timerLabel = container.querySelector('[data-lmn-phone-otp-timer]');
    107                 const selectedPhoneLabel = container.querySelector('[data-lmn-phone-otp-selected]');
    108                 const helpButton = container.querySelector('[data-lmn-phone-otp-help]');
    109                 const steps = {
    110                         request: container.querySelector('[data-lmn-phone-otp-step="request"]'),
    111                         verify: container.querySelector('[data-lmn-phone-otp-step="verify"]')
    112                 };
    113                 const digitInputs = Array.from(container.querySelectorAll('[data-lmn-phone-otp-digit]'));
     141                const initPhoneOtp = () => {
     142                        const container = document.querySelector('[data-lmn-phone-otp]');
     143                        if (!container) {
     144                                return;
     145                        }
     146
     147                        const triggerButton = container.querySelector('.lmn_phone_otp_trigger');
     148                        const modal = container.querySelector('.lmn_phone_otp_modal');
     149                        const overlay = container.querySelector('[data-lmn-phone-otp-overlay]');
     150                        const closeButtons = container.querySelectorAll('[data-lmn-phone-otp-close]');
     151                        const messageBox = container.querySelector('[data-lmn-phone-otp-message]');
     152                        const phoneInput = container.querySelector('#lmn_phone_number');
     153                        const sendButton = container.querySelector('[data-lmn-phone-otp-send]');
     154                        const verifyButton = container.querySelector('[data-lmn-phone-otp-verify]');
     155                        const resendButton = container.querySelector('[data-lmn-phone-otp-resend]');
     156                        const timerLabel = container.querySelector('[data-lmn-phone-otp-timer]');
     157                        const selectedPhoneLabel = container.querySelector('[data-lmn-phone-otp-selected]');
     158                        const helpButton = container.querySelector('[data-lmn-phone-otp-help]');
     159                        const steps = {
     160                                request: container.querySelector('[data-lmn-phone-otp-step="request"]'),
     161                                verify: container.querySelector('[data-lmn-phone-otp-step="verify"]')
     162                        };
     163                        const digitInputs = Array.from(container.querySelectorAll('[data-lmn-phone-otp-digit]'));
    114164
    115165                const endpoints = {
     
    121171                const provider = container.dataset.lmnPhoneOtpProvider || 'firebase';
    122172                const recaptchaKey = container.dataset.lmnPhoneOtpRecaptchaKey || '';
     173                const defaultCountry = (container.dataset.lmnPhoneOtpDefaultCountry || 'us').toLowerCase();
     174                const utilsUrl = container.dataset.lmnPhoneOtpUtilsUrl || '';
     175                const allowedCountries = (() => {
     176                        const raw = container.dataset.lmnPhoneOtpAllowedCountries || '[]';
     177                        try {
     178                                const parsed = JSON.parse(raw);
     179                                if (Array.isArray(parsed)) {
     180                                        return parsed
     181                                                .map((value) => (typeof value === 'string' ? value.toLowerCase() : ''))
     182                                                .filter((value, index, self) => value && self.indexOf(value) === index);
     183                                }
     184                        } catch (error) {
     185                                // Ignore malformed configuration and fall back to all countries.
     186                        }
     187
     188                        return [];
     189                })();
     190                const fallbackCountry = allowedCountries.length ? allowedCountries[0] : defaultCountry || 'us';
    123191
    124192                let countdownId = null;
     
    127195                let cooldown = 60;
    128196                let lastPhone = '';
    129 
    130                 const formatPhone = (value) => value.replace(/\s+/g, '');
     197                        let lastCountry = defaultCountry || fallbackCountry;
     198                        let iti = null;
     199                        let intlInitAttempts = 0;
     200
     201                        const maxIntlInitAttempts = 40;
     202
     203                        const getSelectedCountry = () => {
     204                                if (iti && typeof iti.getSelectedCountryData === 'function') {
     205                                        const data = iti.getSelectedCountryData();
     206                                        if (data && typeof data === 'object') {
     207                                                return data;
     208                                        }
     209                                }
     210
     211                                return null;
     212                        };
     213
     214                        if (utilsUrl && window.intlTelInputGlobals && typeof window.intlTelInputGlobals.loadUtils === 'function') {
     215                                window.intlTelInputGlobals.loadUtils(utilsUrl);
     216                        }
     217
     218                        const configureIntlTelInput = () => {
     219                                if (!phoneInput || typeof window.intlTelInput !== 'function' || iti) {
     220                                        return Boolean(iti);
     221                                }
     222
     223                                try {
     224                                        const config = {
     225                                                initialCountry: defaultCountry || 'us',
     226                                                separateDialCode: true,
     227                                                nationalMode: false,
     228                                                autoPlaceholder: 'polite',
     229                                        };
     230
     231                                        if (allowedCountries.length) {
     232                                                config.onlyCountries = allowedCountries;
     233                                        }
     234
     235                                        iti = window.intlTelInput(phoneInput, config);
     236
     237                                        const selected = getSelectedCountry();
     238                                        if (selected?.iso2) {
     239                                                lastCountry = selected.iso2.toLowerCase();
     240                                        }
     241
     242                                        phoneInput.addEventListener('countrychange', () => {
     243                                                const country = getSelectedCountry();
     244                                                if (country?.iso2) {
     245                                                        lastCountry = country.iso2.toLowerCase();
     246                                                }
     247                                        });
     248
     249                                        return Boolean(iti);
     250                                } catch (error) {
     251                                        iti = null;
     252                                        return false;
     253                                }
     254                        };
     255
     256                        const ensureIntlTelInput = () => {
     257                                if (configureIntlTelInput()) {
     258                                        return;
     259                                }
     260
     261                                if (intlInitAttempts >= maxIntlInitAttempts) {
     262                                        return;
     263                                }
     264
     265                                intlInitAttempts += 1;
     266                                window.setTimeout(ensureIntlTelInput, 150);
     267                        };
     268
     269                        ensureIntlTelInput();
     270                        const handleWindowLoad = () => {
     271                                window.removeEventListener('load', handleWindowLoad);
     272                                ensureIntlTelInput();
     273                        };
     274                        window.addEventListener('load', handleWindowLoad);
     275
     276                        const getRawInputValue = () => (phoneInput?.value || '').trim();
     277
     278                const getFormattedNumber = () => {
     279                        if (iti && typeof iti.getNumber === 'function') {
     280                                const value = iti.getNumber();
     281                                if (value) {
     282                                        return value;
     283                                }
     284                        }
     285
     286                        return getRawInputValue().replace(/\s+/g, '');
     287                };
     288
     289                const getDisplayNumber = () => {
     290                        if (iti && typeof iti.getNumber === 'function') {
     291                                if (window.intlTelInputUtils && window.intlTelInputUtils.numberFormat && typeof window.intlTelInputUtils.numberFormat.INTERNATIONAL !== 'undefined') {
     292                                        const formatted = iti.getNumber(window.intlTelInputUtils.numberFormat.INTERNATIONAL);
     293                                        if (formatted) {
     294                                                return formatted;
     295                                        }
     296                                }
     297
     298                                const value = iti.getNumber();
     299                                if (value) {
     300                                        return value;
     301                                }
     302                        }
     303
     304                        return getRawInputValue();
     305                };
     306
     307                const resetPhoneInput = () => {
     308                        if (iti && typeof iti.setNumber === 'function') {
     309                                iti.setNumber('');
     310                                if (defaultCountry || fallbackCountry) {
     311                                        try {
     312                                                iti.setCountry(defaultCountry || fallbackCountry);
     313                                        } catch (error) {
     314                                                // Ignore errors when resetting the default country.
     315                                        }
     316                                }
     317                        } else if (phoneInput) {
     318                                phoneInput.value = '';
     319                        }
     320
     321                        lastCountry = defaultCountry || fallbackCountry;
     322                };
    131323
    132324                const setMessage = (text, variant = 'info') => {
     
    269461                        }
    270462
    271                         const phone = formatPhone(phoneInput?.value || '');
    272                         if (!phone) {
     463                        const rawDigits = getRawInputValue().replace(/[^0-9]/g, '');
     464                        if (!rawDigits) {
    273465                                setMessage('<?php echo esc_js( __( 'Please enter your phone number.', 'login-me-now' ) ); ?>', 'error');
     466                                phoneInput?.focus();
     467                                return;
     468                        }
     469
     470                        const selectedCountry = getSelectedCountry();
     471                        const selectedIso = selectedCountry?.iso2 || (allowedCountries.length ? allowedCountries[0] : defaultCountry || fallbackCountry || '');
     472                        const countryIso = selectedIso ? selectedIso.toUpperCase() : '';
     473                        const dialCode = selectedCountry?.dialCode ? String(selectedCountry.dialCode) : '';
     474
     475                        if (allowedCountries.length && (!countryIso || !allowedCountries.includes(countryIso.toLowerCase()))) {
     476                                setMessage('<?php echo esc_js( __( 'This country is not available for phone verification.', 'login-me-now' ) ); ?>', 'error');
     477                                return;
     478                        }
     479
     480                        const phone = getFormattedNumber();
     481
     482                        if (iti && typeof iti.isValidNumber === 'function' && !iti.isValidNumber()) {
     483                                setMessage('<?php echo esc_js( __( 'Please enter a valid phone number for the selected country.', 'login-me-now' ) ); ?>', 'error');
    274484                                phoneInput?.focus();
    275485                                return;
     
    288498                                        body: JSON.stringify({
    289499                                                phone,
     500                                                country: countryIso,
     501                                                dialCode,
    290502                                                nonce,
    291503                                                recaptchaToken,
     
    307519                                                cooldown = Number(payload.data.cooldown) || 60;
    308520                                                lastPhone = phone;
     521                                                lastCountry = (selectedIso || defaultCountry || fallbackCountry).toLowerCase();
    309522
    310523                                                setMessage(payload.data.message, 'success');
    311524                                                showStep('verify');
    312525                                                if (selectedPhoneLabel) {
    313                                                         selectedPhoneLabel.textContent = phone;
     526                                                        selectedPhoneLabel.textContent = getDisplayNumber();
    314527                                                }
    315528                                                resetDigits();
     
    397610                        }
    398611
    399                         phoneInput.value = lastPhone;
     612                        if (iti && typeof iti.setNumber === 'function') {
     613                                try {
     614                                        if (lastCountry || fallbackCountry) {
     615                                                iti.setCountry(lastCountry || fallbackCountry);
     616                                        }
     617                                        iti.setNumber(lastPhone);
     618                                } catch (error) {
     619                                        // Ignore formatting errors when reusing the last phone number.
     620                                        if (phoneInput) {
     621                                                phoneInput.value = lastPhone;
     622                                        }
     623                                }
     624                        } else if (phoneInput) {
     625                                phoneInput.value = lastPhone;
     626                        }
    400627                        showStep('request');
    401628                        handleSend();
     
    412639                const resetForm = () => {
    413640                        showStep('request');
    414                         phoneInput.value = '';
     641                        resetPhoneInput();
    415642                        setMessage('');
    416643                        sessionToken = '';
    417644                        cooldown = 60;
    418645                        lastPhone = '';
     646                        lastCountry = defaultCountry || fallbackCountry;
    419647                        clearCountdown();
    420648                        remaining = 0;
     
    422650                        updateTimerLabel();
    423651                        resetDigits();
    424                 };
    425 
    426                 triggerButton?.addEventListener('click', (event) => {
    427                         event.preventDefault();
    428                         openModal();
    429                 });
     652                        if (selectedPhoneLabel) {
     653                                selectedPhoneLabel.textContent = '';
     654                        }
     655                };
     656
     657                        triggerButton?.addEventListener('click', (event) => {
     658                                event.preventDefault();
     659                                openModal();
     660                        });
    430661
    431662                closeButtons?.forEach((button) => {
     
    475706                });
    476707
    477                 document.addEventListener('keydown', (event) => {
    478                         if (event.key === 'Escape' && modal?.classList.contains('is-active')) {
    479                                 closeModal();
    480                         }
    481                 });
     708                        document.addEventListener('keydown', (event) => {
     709                                if (event.key === 'Escape' && modal?.classList.contains('is-active')) {
     710                                        closeModal();
     711                                }
     712                        });
     713                };
     714
     715                if (document.readyState === 'loading') {
     716                        document.addEventListener('DOMContentLoaded', initPhoneOtp);
     717                } else {
     718                        initPhoneOtp();
     719                }
    482720        })();
    483721</script>
  • login-me-now/trunk/config.php

    r3384718 r3391214  
    22
    33return [
    4     'version'         => '1.10',
     4    'version'         => '1.11',
    55    'min_php'         => '7.4',
    66    'db_version'      => '1.0.0',
  • login-me-now/trunk/login-me-now.php

    r3384718 r3391214  
    55 * Author: Pluginly
    66 * Author URI: https://loginmenow.com/
    7  * Version: 1.10
     7 * Version: 1.11
    88 * Tested up to: 6.8.3
    99 * Requires PHP: 7.4
  • login-me-now/trunk/public/css/main.css

    r3384718 r3391214  
    390390
    391391.lmn_phone_otp_label {
    392     display: flex;
     392    display: flex !important;
    393393    flex-direction: column;
    394394    gap: 6px;
     
    423423
    424424.lmn_phone_otp_code {
    425     display: grid;
    426     grid-template-columns: repeat(6, 1fr);
    427     gap: 10px;
     425    display: flex;
     426    justify-content: center;
     427    gap: 12px;
     428    flex-wrap: wrap;
     429    margin: 20px auto;
     430    max-width: 360px;
    428431}
    429432
    430433.lmn_phone_otp_digit {
    431     text-align: center;
     434    display: flex;
     435    align-items: center;
     436    justify-content: center;
    432437    font-size: 20px;
    433     padding: 12px 0;
    434438    border: 1px solid #cbd5f5;
    435439    border-radius: 10px;
    436440    background: #f8fafc;
    437441    transition: border-color 0.2s ease, background-color 0.2s ease;
     442    width: 45px;
     443    height: 45px;
     444    flex: 0 0 32px;
     445    text-align: center;
     446    padding: 0;
    438447}
    439448
     
    520529    .lmn_phone_otp_code {
    521530        gap: 8px;
     531        margin: 16px auto;
    522532    }
    523533
    524534    .lmn_phone_otp_digit {
    525535        padding: 10px 0;
     536        height: 52px;
     537        flex-basis: 48px;
    526538    }
    527539}
  • login-me-now/trunk/readme.txt

    r3384718 r3391214  
    55Tested up to: 6.8.3
    66Requires PHP: 7.4
    7 Stable tag: 1.10
     7Stable tag: 1.11
    88License: GPLv3 or Any Later Version
    99
     
    4747- Send one-time passcodes via Firebase or Twilio Verify
    4848- Built-in rate limiting, resend timer, and reCAPTCHA support
     49- Country flags and country restriction
    4950- Store, normalize, and verify phone numbers from user profiles
    5051- Drop a shortcode or toggle the native login option to expose the modal anywhere
     
    151152
    152153== Changelog ==
     154
     155= 1.11 – Nov 06, 2025 =
     156* New: Added a new setting to allow users to choose whether to show the country flag in phone numbers.
     157* New: Added country restriction and default country settings
     158* Fix: OTP Digits for WooCommerce Account Login
    153159
    154160= 1.10 – Oct 26, 2025 =
  • login-me-now/trunk/vendor/composer/installed.php

    r3384718 r3391214  
    22    'root' => array(
    33        'name' => 'pluginly/login-me-now',
    4         'pretty_version' => 'v1.10',
    5         'version' => '1.10.0.0',
    6         'reference' => '1d9eb7759e730f586bf667607bb15c65ab68efd6',
     4        'pretty_version' => 'v1.11',
     5        'version' => '1.11.0.0',
     6        'reference' => '7db00a09f43000f61d2cd85b89eb413b0b25d44e',
    77        'type' => 'library',
    88        'install_path' => __DIR__ . '/../../',
     
    120120        ),
    121121        'pluginly/login-me-now' => array(
    122             'pretty_version' => 'v1.10',
    123             'version' => '1.10.0.0',
    124             'reference' => '1d9eb7759e730f586bf667607bb15c65ab68efd6',
     122            'pretty_version' => 'v1.11',
     123            'version' => '1.11.0.0',
     124            'reference' => '7db00a09f43000f61d2cd85b89eb413b0b25d44e',
    125125            'type' => 'library',
    126126            'install_path' => __DIR__ . '/../../',
Note: See TracChangeset for help on using the changeset viewer.