Changeset 3427655
- Timestamp:
- 12/26/2025 11:24:15 AM (3 months ago)
- Location:
- otppal/trunk
- Files:
-
- 2 added
- 7 edited
-
assets/js/integrations/otppal-login.js (modified) (2 diffs)
-
assets/js/otppal-lightbox.js (modified) (5 diffs)
-
data (added)
-
data/country-dial-codes.php (added)
-
includes/class-otppal-api.php (modified) (1 diff)
-
includes/class-otppal-user-lookup.php (modified) (6 diffs)
-
otppal.php (modified) (1 diff)
-
public/class-otppal-public.php (modified) (4 diffs)
-
readme.txt (modified) (7 diffs)
Legend:
- Unmodified
- Added
- Removed
-
otppal/trunk/assets/js/integrations/otppal-login.js
r3427485 r3427655 136 136 const redirectTo = currentRedirectUrl || (typeof otppalLogin !== 'undefined' ? otppalLogin.redirectTo : ''); 137 137 138 // Get country code from login lightbox if available 139 let countryCode = null; 140 if (loginLightbox && loginLightbox.countryCode) { 141 countryCode = loginLightbox.countryCode; 142 } 143 138 144 $.ajax({ 139 145 url: apiUrl, … … 146 152 hash: hash, 147 153 otp: otpCode, 148 redirect_to: redirectTo 154 redirect_to: redirectTo, 155 country_code: countryCode 149 156 }), 150 157 success: function(response) { -
otppal/trunk/assets/js/otppal-lightbox.js
r3427485 r3427655 130 130 this.otpHash = null; 131 131 this.phoneNumber = ''; 132 this.countryCode = null; 132 133 this.isLoadingOTP = false; 133 134 this.$modal = null; … … 547 548 } 548 549 550 // Get country code from phone input 551 let countryCode = null; 552 if (this.phoneInput && this.phoneInput.countrySelect) { 553 countryCode = this.phoneInput.countrySelect.find(':selected').val(); 554 } 555 549 556 // Use local WordPress REST API endpoint (proxies to server) 550 557 const apiUrl = (typeof otppalConfig !== 'undefined' && otppalConfig.restUrl) … … 559 566 }, 560 567 data: JSON.stringify({ 561 phone 568 phone: phone, 569 country_code: countryCode 562 570 }), 563 571 success: function(response) { … … 566 574 } 567 575 568 if (response.success && response.hash) { 569 self.otpHash = response.hash; 576 // Check for hash in response (handle different response formats) 577 const hash = response.hash || (response.data && response.data.hash); 578 const isSuccess = response.success !== false && hash; // Treat as success if hash exists and success is not explicitly false 579 580 if (hash) { 581 self.otpHash = hash; 570 582 self.phoneNumber = phone; 583 self.countryCode = countryCode; 571 584 // Move to OTP state (whether from loading or phone state) 572 585 self.setState('otp'); … … 579 592 } 580 593 } 581 self.setError(response.message || __('Failed to send OTP. Please try again.', 'otppal')); 594 // Extract error message from various possible response formats 595 const errorMsg = response.message 596 || (response.data && response.data.message) 597 || (response.data && response.data.data && response.data.data.message) 598 || __('Failed to send OTP. Please try again.', 'otppal'); 599 self.setError(errorMsg); 582 600 } 583 601 }, -
otppal/trunk/includes/class-otppal-api.php
r3427485 r3427655 30 30 public function send_otp($phone) 31 31 { 32 return new WP_Error('otppal_not_configured', $this->api_key);33 32 if (empty($this->api_key)) { 34 33 return new WP_Error('otppal_not_configured', 'OtpPal API key must be configured.'); -
otppal/trunk/includes/class-otppal-user-lookup.php
r3423725 r3427655 13 13 { 14 14 /** 15 * Cached country dial codes array 16 * Loaded from data/country-dial-codes.php file 17 */ 18 private static $country_dial_codes = null; 19 20 /** 15 21 * Normalize phone number for comparison 16 22 * Removes all non-digit characters … … 26 32 27 33 /** 34 * Get dial code for a country code 35 * Loads country codes from the dedicated file 36 * 37 * @param string $country_code ISO country code (e.g., 'IL', 'US', 'il', 'us') 38 * @return string|null Dial code (e.g., '972', '1') or null if not found 39 */ 40 private static function get_dial_code($country_code) 41 { 42 if (empty($country_code)) { 43 return null; 44 } 45 46 // Load country codes file if not already loaded 47 if (self::$country_dial_codes === null) { 48 $codes_file = OTPPAL_PATH . 'data/country-dial-codes.php'; 49 if (file_exists($codes_file)) { 50 self::$country_dial_codes = require $codes_file; 51 } else { 52 self::$country_dial_codes = array(); 53 } 54 } 55 56 // Convert to lowercase for lookup (file uses lowercase keys) 57 $country_code_lower = strtolower($country_code); 58 59 if (isset(self::$country_dial_codes[$country_code_lower])) { 60 return self::$country_dial_codes[$country_code_lower]; 61 } 62 63 return null; 64 } 65 66 /** 28 67 * Find user by phone number 29 68 * Searches in WooCommerce billing_phone and custom otppal_phone meta 30 69 * 31 70 * @param string $phone Phone number (will be normalized) 71 * @param string|null $country_code ISO country code (e.g., 'IL', 'US') for better format matching 32 72 * @return int|null User ID if found, null otherwise 33 73 */ 34 public static function find_user_by_phone($phone )74 public static function find_user_by_phone($phone, $country_code = null) 35 75 { 36 76 if (empty($phone)) { … … 46 86 // First, try WooCommerce billing_phone if WooCommerce is active 47 87 if (class_exists('WooCommerce') || function_exists('wc_get_customer')) { 48 $user_id = self::find_user_by_meta('billing_phone', $normalized_phone );88 $user_id = self::find_user_by_meta('billing_phone', $normalized_phone, $country_code); 49 89 if ($user_id) { 50 90 return $user_id; 51 91 } 92 93 // Also check shipping_phone (WooCommerce stores phone in both locations) 94 $user_id = self::find_user_by_meta('shipping_phone', $normalized_phone, $country_code); 95 if ($user_id) { 96 return $user_id; 97 } 52 98 } 53 99 54 100 // Fallback to custom otppal_phone meta 55 $user_id = self::find_user_by_meta('otppal_phone', $normalized_phone );101 $user_id = self::find_user_by_meta('otppal_phone', $normalized_phone, $country_code); 56 102 if ($user_id) { 57 103 return $user_id; … … 68 114 * @param string $meta_key Meta key to search 69 115 * @param string $phone Phone number (already normalized) 116 * @param string|null $country_code ISO country code for generating correct format variations 70 117 * @return int|null User ID if found, null otherwise 71 118 */ 72 private static function find_user_by_meta($meta_key, $normalized_phone )119 private static function find_user_by_meta($meta_key, $normalized_phone, $country_code = null) 73 120 { 74 121 global $wpdb; … … 87 134 } 88 135 89 // Generate alternative formats to try 90 // If phone starts with country code (e.g., 972), also try without it 91 // If phone doesn't start with 0, try with leading 0 92 $formats_to_try = array($normalized_phone); 93 94 // If international format (starts with country code like 972), try without country code 95 if (strlen($normalized_phone) > 9 && (substr($normalized_phone, 0, 3) === '972' || substr($normalized_phone, 0, 2) === '1')) { 96 // Remove country code and try 97 if (substr($normalized_phone, 0, 3) === '972') { 98 $formats_to_try[] = substr($normalized_phone, 3); // Remove 972 99 $formats_to_try[] = '0' . substr($normalized_phone, 3); // Add leading 0 100 } elseif (substr($normalized_phone, 0, 2) === '1') { 101 $formats_to_try[] = substr($normalized_phone, 1); // Remove 1 102 } 103 } else { 104 // If national format (starts with 0), try with country code 105 if (substr($normalized_phone, 0, 1) === '0') { 106 $formats_to_try[] = substr($normalized_phone, 1); // Remove leading 0 107 $formats_to_try[] = '972' . substr($normalized_phone, 1); // Add country code (Israel) 108 } else { 109 // No leading 0, try adding it 110 $formats_to_try[] = '0' . $normalized_phone; 111 $formats_to_try[] = '972' . $normalized_phone; // Add country code 112 } 113 } 136 // Generate format variations for the search phone 137 $search_formats = self::generate_phone_formats($normalized_phone, $country_code); 114 138 115 139 // Compare normalized phone numbers 116 140 foreach ($results as $row) { 117 141 $stored_phone = self::normalize_phone($row->meta_value); 118 119 // Try exact match first 120 if (in_array($stored_phone, $formats_to_try, true)) { 121 return (int) $row->user_id; 122 } 123 124 // Also try comparing stored phone in different formats 125 $stored_formats = array($stored_phone); 126 if (strlen($stored_phone) > 9 && substr($stored_phone, 0, 3) === '972') { 127 $stored_formats[] = substr($stored_phone, 3); 128 $stored_formats[] = '0' . substr($stored_phone, 3); 129 } elseif (substr($stored_phone, 0, 1) === '0') { 130 $stored_formats[] = substr($stored_phone, 1); 131 $stored_formats[] = '972' . substr($stored_phone, 1); 132 } 133 142 143 // Generate format variations for the stored phone 144 $stored_formats = self::generate_phone_formats($stored_phone, $country_code); 145 134 146 // Check if any format matches 135 foreach ($ formats_to_try as $format) {136 if (in_array($ format, $stored_formats, true)) {147 foreach ($search_formats as $search_format) { 148 if (in_array($search_format, $stored_formats, true)) { 137 149 return (int) $row->user_id; 138 150 } … … 141 153 142 154 return null; 155 } 156 157 /** 158 * Generate all possible phone number format variations for comparison 159 * Handles international (with country code), national (with leading 0), and local formats 160 * 161 * @param string $phone Normalized phone (digits only) 162 * @param string|null $country_code ISO country code (e.g., 'IL', 'US') 163 * @return array Array of all possible format variations 164 */ 165 private static function generate_phone_formats($phone, $country_code = null) 166 { 167 $formats = array($phone); // Always include original 168 169 $dial_code = null; 170 if ($country_code) { 171 $dial_code = self::get_dial_code($country_code); 172 } 173 174 // If country code provided and we have dial code 175 if ($dial_code) { 176 $dial_code_digits = $dial_code; 177 $dial_code_length = strlen($dial_code_digits); 178 179 // Check if phone starts with dial code 180 if (substr($phone, 0, $dial_code_length) === $dial_code_digits) { 181 // Phone has dial code - generate formats without it 182 $without_dial = substr($phone, $dial_code_length); 183 $formats[] = $without_dial; 184 $formats[] = '0' . $without_dial; // Add leading 0 185 } else { 186 // Phone doesn't have dial code - generate formats with it 187 $formats[] = $dial_code_digits . $phone; // Add dial code 188 189 // If phone starts with 0, also try without 0 and with dial code 190 if (substr($phone, 0, 1) === '0') { 191 $without_zero = substr($phone, 1); 192 $formats[] = $without_zero; 193 $formats[] = $dial_code_digits . $without_zero; 194 } else { 195 // No leading 0, try adding it 196 $formats[] = '0' . $phone; 197 } 198 } 199 } else { 200 // No country code provided - fall back to common patterns 201 // Try to detect if phone starts with common dial codes 202 $common_dial_codes = array('972', '1', '44', '33', '49', '39', '34', '7', '81', '86', '91'); 203 204 foreach ($common_dial_codes as $code) { 205 $code_length = strlen($code); 206 if (substr($phone, 0, $code_length) === $code) { 207 // Phone starts with this dial code 208 $without_code = substr($phone, $code_length); 209 $formats[] = $without_code; 210 $formats[] = '0' . $without_code; 211 break; 212 } 213 } 214 215 // If phone starts with 0, generate formats without it 216 if (substr($phone, 0, 1) === '0') { 217 $without_zero = substr($phone, 1); 218 $formats[] = $without_zero; 219 220 // Try adding common dial codes 221 foreach ($common_dial_codes as $code) { 222 $formats[] = $code . $without_zero; 223 } 224 } else { 225 // No leading 0, try adding it 226 $formats[] = '0' . $phone; 227 228 // Try adding common dial codes 229 foreach ($common_dial_codes as $code) { 230 $formats[] = $code . $phone; 231 } 232 } 233 } 234 235 // Remove duplicates and return 236 return array_unique($formats); 143 237 } 144 238 -
otppal/trunk/otppal.php
r3427485 r3427655 21 21 // Define plugin constants 22 22 if (!defined('OTPPAL_VERSION')) { 23 define('OTPPAL_VERSION', '2.3. 8');23 define('OTPPAL_VERSION', '2.3.9'); 24 24 } 25 25 if (!defined('OTPPAL_PATH')) { -
otppal/trunk/public/class-otppal-public.php
r3423725 r3427655 240 240 241 241 $phone = $request->get_param('phone'); 242 $country_code = $request->get_param('country_code'); 243 242 244 if (empty($phone)) { 243 245 return new WP_Error( … … 254 256 // If OTP login is enabled, check user existence before sending OTP 255 257 if (get_option('otppal_enable_otp_login', '0') === '1') { 256 // Check if user exists with this phone number 257 $user_id = OtpPal_User_Lookup::find_user_by_phone($phone );258 // Check if user exists with this phone number, passing country code for better matching 259 $user_id = OtpPal_User_Lookup::find_user_by_phone($phone, $country_code); 258 260 259 261 if (!$user_id) { … … 457 459 $hash = $request->get_param('hash'); 458 460 $otp = $request->get_param('otp'); 461 $country_code = $request->get_param('country_code'); 459 462 460 463 // Validate required parameters … … 487 490 } 488 491 489 // Find user by phone number 490 $user_id = OtpPal_User_Lookup::find_user_by_phone($phone );492 // Find user by phone number, passing country code for better matching 493 $user_id = OtpPal_User_Lookup::find_user_by_phone($phone, $country_code); 491 494 492 495 if (!$user_id) { -
otppal/trunk/readme.txt
r3427485 r3427655 6 6 Tested up to: 6.9 7 7 Requires PHP: 7.4 8 Stable tag: 2.3. 88 Stable tag: 2.3.9 9 9 License: GPL2 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html 11 11 12 Client-side plugin for OtpPal Server – Send and verify OTP messages via WhatsApp.12 Client-side plugin for OtpPal – Send and verify OTPs via WhatsApp. 13 13 14 14 == Description == 15 15 16 OtpPal is a client-side WordPress plugin that connects to your OtpPal Server installationto send and verify OTP (One-Time Password) messages via WhatsApp. This plugin enables secure phone number verification for your WordPress forms, helping prevent spam and ensuring legitimate user submissions.16 OtpPal is a client-side WordPress plugin that connects to otppal.com to send and verify OTP (One-Time Password) messages via WhatsApp. This plugin enables secure phone number verification for your WordPress forms, helping prevent spam and ensuring legitimate user submissions. 17 17 18 18 **Key Features:** … … 23 23 * **Easy Configuration**: Simple setup through WordPress admin panel 24 24 * **Secure Verification**: Server-side validation ensures secure OTP verification 25 * **Rate Limiting**: Built-in protection against abuse ( handled by OtpPal Server)25 * **Rate Limiting**: Built-in protection against abuse (OtpPal.com) 26 26 * **Customizable UI**: Clean, responsive lightbox interface for OTP input 27 27 … … 37 37 1. Upload the plugin files to `/wp-content/plugins/otppal` directory, or install through the WordPress plugins screen directly 38 38 2. Activate the plugin through the 'Plugins' menu in WordPress 39 3. Ensure you have an active OtpPal Server installation 40 4. Go to OtpPal > Settings in WordPress admin to configure the plugin 41 5. Enter your OtpPal Server URL and API key 42 6. Configure phone format preferences (international or national) 39 3. Go to OtpPal > Settings in WordPress admin to configure the plugin 40 4. Enter your API key (get it at https://otppal.com) 41 5. Configure phone format preferences (international or national) 43 42 44 43 == Configuration == 45 44 46 45 1. Navigate to **OtpPal > Settings** in your WordPress admin panel 47 2. Enter your **OtpPal Server URL** (e.g., https://otppal.com or your server URL) 48 3. Enter your **API key** from the OtpPal Server 49 4. Choose your preferred **Phone Format**: 46 2. Enter your OtpPal **API key** 47 3. Choose your preferred **Phone Format**: 50 48 * International: Users enter phone numbers with country code (e.g., +1234567890) 51 49 * National: Users enter phone numbers without country code (e.g., 1234567890) 52 5. If using National format, select a **Default Country** for phone number validation53 6. Click **Save Settings**50 4. If using National format, select a **Default Country** for phone number validation 51 5. Click **Save Settings** 54 52 55 53 == Usage == … … 81 79 **Note:** `[otppal_verify]` and `[otppal_phone]` cannot be used together in the same form. 82 80 83 **WPForms, Gravity Forms, Fluent Forms, Ninja Forms, Formidable Forms, SureForms :**81 **WPForms, Gravity Forms, Fluent Forms, Ninja Forms, Formidable Forms, SureForms, Elementor Pro:** 84 82 85 83 For these builders, OtpPal uses CSS classes on existing fields instead of custom shortcodes: … … 110 108 == Frequently Asked Questions == 111 109 112 = Do I need an OtpPal Server installation? =113 114 Yes , this plugin requires a separate OtpPal Server installation. This plugin is the client-side component that communicates with your server.110 = Does this plugin enable OTP login for my users? 111 112 Yes! with a fully customizable login button. Just enable it in the settings, and use the shortcode to embed it anywhere on your site. 115 113 116 114 = How do I get an API key? = 117 115 118 You can obtain an API key from your OtpPal Server installation. Check your server's documentation for API key generation.116 You can obtain an API key from OtpPal at https://otppal.com. 119 117 120 118 = Does this work with other form plugins? = 121 119 122 Currently, OtpPal supports Contact Form 7. Support for other form plugins may be added in future versions.120 OtpPal supports many different integrations including Contact Form 7, Elementor Pro, WPForms, Gravity Forms, Fluent Forms, Ninja Forms, Formidable Forms, and SureForms. See otppal.com for more information. 123 121 124 122 = What phone formats are supported? = 125 123 126 The plugin supports both international format (with country code) and national format (without country code). You can configure this in the plugin settings. 124 The plugin supports both international format (with country code) and national format (without country code). You can configure this in the plugin settings. When using international format, the plugin automatically detects the user's country based on their IP address for a better user experience. 127 125 128 126 = Is my data secure? = 129 127 130 Yes. The plugin uses secure REST API communication with your OtpPal Server. API keys are stored securely in WordPress options and never exposed to the frontend.128 Yes. The plugin uses secure REST API communication with the OtpPal Servers. Phone numbers aren't saved anywhere. 131 129 132 130 = Can I customize the OTP verification interface? = … … 145 143 146 144 == Changelog == 145 = 2.3.9 = 146 * Enhanced phone number lookup to support both billing and shipping phone fields 147 * Improved OTP lightbox state transitions after errors 148 * Fixed compatibility issues with WooCommerce admin classes 149 150 = 2.3.8 = 151 * Stability improvements 152 153 = 2.3.7 = 154 * Stability improvements 155 156 = 2.3.6 = 157 * Country autodetect on international format 158 159 = 2.3.5 = 160 * Security and UI enhancements 161 162 = 2.3.4 = 163 * Security enhancements 164 165 = 2.3.3 = 166 * Security enhancements 167 168 = 2.3.2 = 169 * Javascript improvements 170 171 = 2.3.1 = 172 * Stability improvements 173 174 = 2.3.0 = 175 * Rebuild user lookup 176 177 = 2.2.0 = 178 * Rebuild plugin to server authentication 179 180 = 2.1.0 = 181 * Add customization options 182 183 = 2.0.0 = 184 * Improve security 185 * Add OTP login 186 187 = 1.0.3 = 188 * Add form plugin integrations 189 190 = 1.0.2 = 191 * Add form plugin integrations 192 193 = 1.0.1 = 194 * Stability improvements 147 195 148 196 = 1.0.0 = … … 152 200 * OTP verification via WhatsApp 153 201 * Admin settings page 154 * REST API integration with OtpPal Server202 * REST API integration with the OtpPal server 155 203 156 204 == Upgrade Notice == 205 = 2.3.9 = 206 Enhanced phone number matching and improved error handling in OTP verification flow. 207 208 = 2.3.8 = 209 Rebuild plugin with security enhancements and new json api url structure 157 210 158 211 = 1.0.0 = 159 Initial release of OtpPal client plugin. Requires OtpPal Server installation.212 Initial release of OtpPal client plugin. 160 213 161 214 == Support == 162 215 163 For support, please visit the plugin's support forum or contact the plugin author. For issues related to OtpPal Server, please refer to your server's documentation.216 For support, please visit the plugin's support forum or contact the plugin author.
Note: See TracChangeset
for help on using the changeset viewer.