Changeset 3391214
- Timestamp:
- 11/06/2025 02:47:08 PM (5 months ago)
- Location:
- login-me-now
- Files:
-
- 2 added
- 18 edited
- 1 copied
-
tags/1.11 (copied) (copied from login-me-now/trunk)
-
tags/1.11/app/Controllers/PhoneOTPController.php (modified) (2 diffs)
-
tags/1.11/app/Frontend/Enqueuer.php (modified) (2 diffs)
-
tags/1.11/app/Logins/PhoneOTPLogin/Settings.php (modified) (2 diffs)
-
tags/1.11/app/Logins/PhoneOTPLogin/Views/Button.php (modified) (13 diffs)
-
tags/1.11/app/Logins/PhoneOTPLogin/countries.php (added)
-
tags/1.11/config.php (modified) (1 diff)
-
tags/1.11/login-me-now.php (modified) (1 diff)
-
tags/1.11/public/css/main.css (modified) (3 diffs)
-
tags/1.11/readme.txt (modified) (3 diffs)
-
tags/1.11/vendor/composer/installed.php (modified) (2 diffs)
-
trunk/app/Controllers/PhoneOTPController.php (modified) (2 diffs)
-
trunk/app/Frontend/Enqueuer.php (modified) (2 diffs)
-
trunk/app/Logins/PhoneOTPLogin/Settings.php (modified) (2 diffs)
-
trunk/app/Logins/PhoneOTPLogin/Views/Button.php (modified) (13 diffs)
-
trunk/app/Logins/PhoneOTPLogin/countries.php (added)
-
trunk/config.php (modified) (1 diff)
-
trunk/login-me-now.php (modified) (1 diff)
-
trunk/public/css/main.css (modified) (3 diffs)
-
trunk/readme.txt (modified) (3 diffs)
-
trunk/vendor/composer/installed.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
login-me-now/tags/1.11/app/Controllers/PhoneOTPController.php
r3384718 r3391214 4 4 5 5 use LoginMeNow\Repositories\PhoneOTPRepository; 6 use LoginMeNow\Repositories\SettingsRepository; 7 use LoginMeNow\User\PhoneMeta; 6 8 use WP_REST_Request; 7 9 use WP_REST_Response; … … 22 24 23 25 $phone = (string) $request->get_param( 'phone' ); 26 $country = strtoupper( (string) $request->get_param( 'country' ) ); 27 $dial_code_param = (string) $request->get_param( 'dialCode' ); 24 28 $recaptcha_token = (string) $request->get_param( 'recaptchaToken' ); 25 $ result = $this->repo->send_otp( $phone, $recaptcha_token);29 $normalized = PhoneMeta::normalize( $phone ); 26 30 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 } 29 54 30 55 public function verify_phone_otp( WP_REST_Request $request ): WP_REST_Response { -
login-me-now/tags/1.11/app/Frontend/Enqueuer.php
r3147204 r3391214 13 13 class Enqueuer extends EnqueuerBase { 14 14 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(); 18 17 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 ); 22 24 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 } 26 32 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 } 30 50 31 51 public function local_script() { … … 45 65 } 46 66 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 } 49 93 } -
login-me-now/tags/1.11/app/Logins/PhoneOTPLogin/Settings.php
r3384718 r3391214 215 215 216 216 $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[] = [ 217 245 'title' => __( 'Button Label', 'login-me-now' ), 218 246 'description' => __( 'Text displayed on the Phone OTP button.', 'login-me-now' ), … … 249 277 return $fields; 250 278 } 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 } 251 299 } -
login-me-now/tags/1.11/app/Logins/PhoneOTPLogin/Views/Button.php
r3384718 r3391214 17 17 $verify_endpoint = get_rest_url( null, 'login-me-now/verify-phone-otp' ); 18 18 $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 36 if ( '' === $default_country ) { 37 $default_country = 'us'; 38 } 39 40 if ( ! 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' ); 19 65 ?> 20 66 … … 25 71 data-lmn-phone-otp-provider="<?php echo esc_attr( $provider ); ?>" 26 72 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 ) ) ); ?>' 27 76 <?php if ( ! empty( $help_url ) ) : ?>data-lmn-phone-otp-help-url="<?php echo esc_url( $help_url ); ?>"<?php endif; ?> 28 77 > … … 49 98 <div class="lmn_phone_otp_step is-active" data-lmn-phone-otp-step="request"> 50 99 <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( ' +1234 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"> 53 102 </label> 54 103 <button type="button" class="lmn_btn lmn_phone_otp_primary" data-lmn-phone-otp-send> … … 90 139 <script> 91 140 (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]')); 114 164 115 165 const endpoints = { … … 121 171 const provider = container.dataset.lmnPhoneOtpProvider || 'firebase'; 122 172 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'; 123 191 124 192 let countdownId = null; … … 127 195 let cooldown = 60; 128 196 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 }; 131 323 132 324 const setMessage = (text, variant = 'info') => { … … 269 461 } 270 462 271 const phone = formatPhone(phoneInput?.value ||'');272 if (! phone) {463 const rawDigits = getRawInputValue().replace(/[^0-9]/g, ''); 464 if (!rawDigits) { 273 465 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'); 274 484 phoneInput?.focus(); 275 485 return; … … 288 498 body: JSON.stringify({ 289 499 phone, 500 country: countryIso, 501 dialCode, 290 502 nonce, 291 503 recaptchaToken, … … 307 519 cooldown = Number(payload.data.cooldown) || 60; 308 520 lastPhone = phone; 521 lastCountry = (selectedIso || defaultCountry || fallbackCountry).toLowerCase(); 309 522 310 523 setMessage(payload.data.message, 'success'); 311 524 showStep('verify'); 312 525 if (selectedPhoneLabel) { 313 selectedPhoneLabel.textContent = phone;526 selectedPhoneLabel.textContent = getDisplayNumber(); 314 527 } 315 528 resetDigits(); … … 397 610 } 398 611 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 } 400 627 showStep('request'); 401 628 handleSend(); … … 412 639 const resetForm = () => { 413 640 showStep('request'); 414 phoneInput.value = '';641 resetPhoneInput(); 415 642 setMessage(''); 416 643 sessionToken = ''; 417 644 cooldown = 60; 418 645 lastPhone = ''; 646 lastCountry = defaultCountry || fallbackCountry; 419 647 clearCountdown(); 420 648 remaining = 0; … … 422 650 updateTimerLabel(); 423 651 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 }); 430 661 431 662 closeButtons?.forEach((button) => { … … 475 706 }); 476 707 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 } 482 720 })(); 483 721 </script> -
login-me-now/tags/1.11/config.php
r3384718 r3391214 2 2 3 3 return [ 4 'version' => '1.1 0',4 'version' => '1.11', 5 5 'min_php' => '7.4', 6 6 'db_version' => '1.0.0', -
login-me-now/tags/1.11/login-me-now.php
r3384718 r3391214 5 5 * Author: Pluginly 6 6 * Author URI: https://loginmenow.com/ 7 * Version: 1.1 07 * Version: 1.11 8 8 * Tested up to: 6.8.3 9 9 * Requires PHP: 7.4 -
login-me-now/tags/1.11/public/css/main.css
r3384718 r3391214 390 390 391 391 .lmn_phone_otp_label { 392 display: flex ;392 display: flex !important; 393 393 flex-direction: column; 394 394 gap: 6px; … … 423 423 424 424 .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; 428 431 } 429 432 430 433 .lmn_phone_otp_digit { 431 text-align: center; 434 display: flex; 435 align-items: center; 436 justify-content: center; 432 437 font-size: 20px; 433 padding: 12px 0;434 438 border: 1px solid #cbd5f5; 435 439 border-radius: 10px; 436 440 background: #f8fafc; 437 441 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; 438 447 } 439 448 … … 520 529 .lmn_phone_otp_code { 521 530 gap: 8px; 531 margin: 16px auto; 522 532 } 523 533 524 534 .lmn_phone_otp_digit { 525 535 padding: 10px 0; 536 height: 52px; 537 flex-basis: 48px; 526 538 } 527 539 } -
login-me-now/tags/1.11/readme.txt
r3384718 r3391214 5 5 Tested up to: 6.8.3 6 6 Requires PHP: 7.4 7 Stable tag: 1.1 07 Stable tag: 1.11 8 8 License: GPLv3 or Any Later Version 9 9 … … 47 47 - Send one-time passcodes via Firebase or Twilio Verify 48 48 - Built-in rate limiting, resend timer, and reCAPTCHA support 49 - Country flags and country restriction 49 50 - Store, normalize, and verify phone numbers from user profiles 50 51 - Drop a shortcode or toggle the native login option to expose the modal anywhere … … 151 152 152 153 == 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 153 159 154 160 = 1.10 – Oct 26, 2025 = -
login-me-now/tags/1.11/vendor/composer/installed.php
r3384718 r3391214 2 2 'root' => array( 3 3 'name' => 'pluginly/login-me-now', 4 'pretty_version' => 'v1.1 0',5 'version' => '1.1 0.0.0',6 'reference' => ' 1d9eb7759e730f586bf667607bb15c65ab68efd6',4 'pretty_version' => 'v1.11', 5 'version' => '1.11.0.0', 6 'reference' => '7db00a09f43000f61d2cd85b89eb413b0b25d44e', 7 7 'type' => 'library', 8 8 'install_path' => __DIR__ . '/../../', … … 120 120 ), 121 121 'pluginly/login-me-now' => array( 122 'pretty_version' => 'v1.1 0',123 'version' => '1.1 0.0.0',124 'reference' => ' 1d9eb7759e730f586bf667607bb15c65ab68efd6',122 'pretty_version' => 'v1.11', 123 'version' => '1.11.0.0', 124 'reference' => '7db00a09f43000f61d2cd85b89eb413b0b25d44e', 125 125 'type' => 'library', 126 126 'install_path' => __DIR__ . '/../../', -
login-me-now/trunk/app/Controllers/PhoneOTPController.php
r3384718 r3391214 4 4 5 5 use LoginMeNow\Repositories\PhoneOTPRepository; 6 use LoginMeNow\Repositories\SettingsRepository; 7 use LoginMeNow\User\PhoneMeta; 6 8 use WP_REST_Request; 7 9 use WP_REST_Response; … … 22 24 23 25 $phone = (string) $request->get_param( 'phone' ); 26 $country = strtoupper( (string) $request->get_param( 'country' ) ); 27 $dial_code_param = (string) $request->get_param( 'dialCode' ); 24 28 $recaptcha_token = (string) $request->get_param( 'recaptchaToken' ); 25 $ result = $this->repo->send_otp( $phone, $recaptcha_token);29 $normalized = PhoneMeta::normalize( $phone ); 26 30 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 } 29 54 30 55 public function verify_phone_otp( WP_REST_Request $request ): WP_REST_Response { -
login-me-now/trunk/app/Frontend/Enqueuer.php
r3147204 r3391214 13 13 class Enqueuer extends EnqueuerBase { 14 14 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(); 18 17 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 ); 22 24 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 } 26 32 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 } 30 50 31 51 public function local_script() { … … 45 65 } 46 66 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 } 49 93 } -
login-me-now/trunk/app/Logins/PhoneOTPLogin/Settings.php
r3384718 r3391214 215 215 216 216 $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[] = [ 217 245 'title' => __( 'Button Label', 'login-me-now' ), 218 246 'description' => __( 'Text displayed on the Phone OTP button.', 'login-me-now' ), … … 249 277 return $fields; 250 278 } 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 } 251 299 } -
login-me-now/trunk/app/Logins/PhoneOTPLogin/Views/Button.php
r3384718 r3391214 17 17 $verify_endpoint = get_rest_url( null, 'login-me-now/verify-phone-otp' ); 18 18 $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 36 if ( '' === $default_country ) { 37 $default_country = 'us'; 38 } 39 40 if ( ! 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' ); 19 65 ?> 20 66 … … 25 71 data-lmn-phone-otp-provider="<?php echo esc_attr( $provider ); ?>" 26 72 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 ) ) ); ?>' 27 76 <?php if ( ! empty( $help_url ) ) : ?>data-lmn-phone-otp-help-url="<?php echo esc_url( $help_url ); ?>"<?php endif; ?> 28 77 > … … 49 98 <div class="lmn_phone_otp_step is-active" data-lmn-phone-otp-step="request"> 50 99 <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( ' +1234 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"> 53 102 </label> 54 103 <button type="button" class="lmn_btn lmn_phone_otp_primary" data-lmn-phone-otp-send> … … 90 139 <script> 91 140 (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]')); 114 164 115 165 const endpoints = { … … 121 171 const provider = container.dataset.lmnPhoneOtpProvider || 'firebase'; 122 172 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'; 123 191 124 192 let countdownId = null; … … 127 195 let cooldown = 60; 128 196 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 }; 131 323 132 324 const setMessage = (text, variant = 'info') => { … … 269 461 } 270 462 271 const phone = formatPhone(phoneInput?.value ||'');272 if (! phone) {463 const rawDigits = getRawInputValue().replace(/[^0-9]/g, ''); 464 if (!rawDigits) { 273 465 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'); 274 484 phoneInput?.focus(); 275 485 return; … … 288 498 body: JSON.stringify({ 289 499 phone, 500 country: countryIso, 501 dialCode, 290 502 nonce, 291 503 recaptchaToken, … … 307 519 cooldown = Number(payload.data.cooldown) || 60; 308 520 lastPhone = phone; 521 lastCountry = (selectedIso || defaultCountry || fallbackCountry).toLowerCase(); 309 522 310 523 setMessage(payload.data.message, 'success'); 311 524 showStep('verify'); 312 525 if (selectedPhoneLabel) { 313 selectedPhoneLabel.textContent = phone;526 selectedPhoneLabel.textContent = getDisplayNumber(); 314 527 } 315 528 resetDigits(); … … 397 610 } 398 611 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 } 400 627 showStep('request'); 401 628 handleSend(); … … 412 639 const resetForm = () => { 413 640 showStep('request'); 414 phoneInput.value = '';641 resetPhoneInput(); 415 642 setMessage(''); 416 643 sessionToken = ''; 417 644 cooldown = 60; 418 645 lastPhone = ''; 646 lastCountry = defaultCountry || fallbackCountry; 419 647 clearCountdown(); 420 648 remaining = 0; … … 422 650 updateTimerLabel(); 423 651 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 }); 430 661 431 662 closeButtons?.forEach((button) => { … … 475 706 }); 476 707 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 } 482 720 })(); 483 721 </script> -
login-me-now/trunk/config.php
r3384718 r3391214 2 2 3 3 return [ 4 'version' => '1.1 0',4 'version' => '1.11', 5 5 'min_php' => '7.4', 6 6 'db_version' => '1.0.0', -
login-me-now/trunk/login-me-now.php
r3384718 r3391214 5 5 * Author: Pluginly 6 6 * Author URI: https://loginmenow.com/ 7 * Version: 1.1 07 * Version: 1.11 8 8 * Tested up to: 6.8.3 9 9 * Requires PHP: 7.4 -
login-me-now/trunk/public/css/main.css
r3384718 r3391214 390 390 391 391 .lmn_phone_otp_label { 392 display: flex ;392 display: flex !important; 393 393 flex-direction: column; 394 394 gap: 6px; … … 423 423 424 424 .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; 428 431 } 429 432 430 433 .lmn_phone_otp_digit { 431 text-align: center; 434 display: flex; 435 align-items: center; 436 justify-content: center; 432 437 font-size: 20px; 433 padding: 12px 0;434 438 border: 1px solid #cbd5f5; 435 439 border-radius: 10px; 436 440 background: #f8fafc; 437 441 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; 438 447 } 439 448 … … 520 529 .lmn_phone_otp_code { 521 530 gap: 8px; 531 margin: 16px auto; 522 532 } 523 533 524 534 .lmn_phone_otp_digit { 525 535 padding: 10px 0; 536 height: 52px; 537 flex-basis: 48px; 526 538 } 527 539 } -
login-me-now/trunk/readme.txt
r3384718 r3391214 5 5 Tested up to: 6.8.3 6 6 Requires PHP: 7.4 7 Stable tag: 1.1 07 Stable tag: 1.11 8 8 License: GPLv3 or Any Later Version 9 9 … … 47 47 - Send one-time passcodes via Firebase or Twilio Verify 48 48 - Built-in rate limiting, resend timer, and reCAPTCHA support 49 - Country flags and country restriction 49 50 - Store, normalize, and verify phone numbers from user profiles 50 51 - Drop a shortcode or toggle the native login option to expose the modal anywhere … … 151 152 152 153 == 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 153 159 154 160 = 1.10 – Oct 26, 2025 = -
login-me-now/trunk/vendor/composer/installed.php
r3384718 r3391214 2 2 'root' => array( 3 3 'name' => 'pluginly/login-me-now', 4 'pretty_version' => 'v1.1 0',5 'version' => '1.1 0.0.0',6 'reference' => ' 1d9eb7759e730f586bf667607bb15c65ab68efd6',4 'pretty_version' => 'v1.11', 5 'version' => '1.11.0.0', 6 'reference' => '7db00a09f43000f61d2cd85b89eb413b0b25d44e', 7 7 'type' => 'library', 8 8 'install_path' => __DIR__ . '/../../', … … 120 120 ), 121 121 'pluginly/login-me-now' => array( 122 'pretty_version' => 'v1.1 0',123 'version' => '1.1 0.0.0',124 'reference' => ' 1d9eb7759e730f586bf667607bb15c65ab68efd6',122 'pretty_version' => 'v1.11', 123 'version' => '1.11.0.0', 124 'reference' => '7db00a09f43000f61d2cd85b89eb413b0b25d44e', 125 125 'type' => 'library', 126 126 'install_path' => __DIR__ . '/../../',
Note: See TracChangeset
for help on using the changeset viewer.