1const baseCountry = IntlPhoneNumberFormatData.base_country; 2const fields = IntlPhoneNumberFormatData.fields; 3const shippingCountries = IntlPhoneNumberFormatData.shipping_countries; 4const allowedCountries = IntlPhoneNumberFormatData.allowed_countries; 5const allCountries = IntlPhoneNumberFormatData.all_countries; 6const validationMessages = IntlPhoneNumberFormatData.validations; 7const patterns = IntlPhoneNumberFormatData.patterns; 8const lookup = IntlPhoneNumberFormatData.lookup; 9const inputHelperClass = IntlPhoneNumberFormatData.input_helper_class; 10const apiUrl = "https://ipapi.co/json"; 11 12jQuery(document).ready(function ($) { 13 getFields().forEach((entry) => { 14 let field = entry.id; 15 let required = entry.required; 16 let countries = entry.countries; 17 buildIntlPhoneNumberField(field, required, countries); 18 }); 19 20 validateForms(); 21}); 22 23function validateForms() { 24 checkoutPage(); 25 otherForms(); 26} 27 28function checkoutPage() { 29 let checkoutForm = jQuery("form.checkout"); 30 31 if (checkoutForm.length) { 32 checkoutForm.on("checkout_place_order", function (event) { 33 let isValid = true; 34 35 getFields().forEach((entry) => { 36 let field = entry.id; 37 let required = entry.required; 38 let type = entry.type;
39 let isShippingField = type === "shipping"; 40 41 if (getInput(field) && required) { 42 let isValidField = true; 43 44 if (isShippingField) { 45 let shipToDifferentAddress = document.querySelector( 46 "#ship-to-different-address-checkbox" 47 ); 48 if ( 49 shipToDifferentAddress && 50 shipToDifferentAddress.checked && 51 !validatePhoneNumber(field) 52 ) { 53 isValidField = false; 54 } 55 } else if (!validatePhoneNumber(field)) { 56 isValidField = false; 57 } 58 59 if (!isValidField) { 60 isValid = false; 61 } 62 } 63 }); 64 65 if (!isValid) { 66 event.preventDefault(); // Prevent form submission if any field is invalid 67 return false; 68 } 69 }); 70 } 71 72 return true; 73} 74 75function otherForms() { 76 getFields().forEach((entry) => { 77 let field = entry.id; 78 let required = entry.required; 79 let form = getFormByField(field); 80 if (form && required) { 81 form.addEventListener("submit", function (event) { 82 let isValid = validatePhoneNumber(field); 83 84 if (!isValid) { 85 event.preventDefault(); 86 return false; 87 } 88 return isValid; 89 }); 90 } 91 }); 92 93 return true; 94} 95 96function getFormByField(field) { 97 let input = getInput(field); 98 if (!input) { 99 return null; 100 } 101 102 let form = input; 103 while (form && form.tagName !== "FORM") { 104 form = form.parentElement; 105 } 106 107 return form; 108} 109 110function buildIntlPhoneNumberField(field, required, countries) { 111 let inputID = "#" + field; 112 let input = document.querySelector(inputID); 113 if (input) { 114 let configs = { 115 hiddenInput: field, 116 separateDialCode: true, 117 nationalMode: true, 118 }; 119 120 let preferredCountries = []; 121 let defaultCountry = baseCountry; 122 let onlyCountries = getCountriesBy(countries); 123 124 if (!onlyCountries.includes(defaultCountry) && onlyCountries.length > 0) { 125 defaultCountry = onlyCountries[0] ?? ""; 126 } else if ( 127 !onlyCountries.includes(defaultCountry) && 128 onlyCountries.length == 0 129 ) { 130 defaultCountry = defaultCountry; 131 } else { 132 preferredCountries = [defaultCountry]; 133 } 134 135 let hasMultipleCountry = onlyCountries.length > 1; 136 137 if (lookup.active && hasMultipleCountry) { 138 let lockupCountry = getCountryFromLocalStorage(); 139 140 if (lockupCountry) { 141 if (onlyCountries.includes(lockupCountry)) { 142 if (lockupCountry != defaultCountry) { 143 preferredCountries.unshift(lockupCountry); 144 } 145 } 146 147 if (!onlyCountries.includes(lockupCountry)) { 148 configs.initialCountry = defaultCountry; 149 } 150 } else { 151 let lockupCountryCallback = (callback) => { 152 fetch(apiUrl) 153 .then((res) => res.json()) 154 .then((data) => { 155 let country; 156 if ( 157 data?.country_code && 158 onlyCountries.includes(data.country_code) 159 ) { 160 country = data.country_code; 161 setCountryLocalStorage(country); 162 } else { 163 country = defaultCountry; 164 } 165 callback(country); 166 }) 167 .catch(() => callback(defaultCountry)); 168 }; 169 170 configs.geoIpLookup = lockupCountryCallback; 171 configs.initialCountry = "auto"; 172 } 173 } else { 174 configs.initialCountry = defaultCountry; 175 } 176 177 configs.onlyCountries = onlyCountries; 178 configs.allowDropdown = hasMultipleCountry; 179 configs.preferredCountries = preferredCountries; 180 181 let iti = window.intlTelInput(input, configs); 182 183 let filterInput = (event) => { 184 let input = event.target; 185 let cleanedValue = input.value.replace(/[^\d\s-]/g, ""); 186 cleanedValue = cleanedValue.trim(); 187 188 if (cleanedValue.length > 15) { 189 cleanedValue = cleanedValue.substring(0, 15); 190 } 191 192 input.value = cleanedValue; 193 }; 194 195 let handleChange = () => { 196 if (required) { 197 validateError(iti, input); 198 } 199 }; 200 201 input.addEventListener("input", filterInput); 202 input.addEventListener("change", handleChange); 203 input.addEventListener("keyup", handleChange); 204 input.addEventListener("countrychange", handleChange); 205 input.addEventListener("blur", handleChange); 206 input.addEventListener("input", handleChange); 207 } 208} 209 210function validatePhoneNumber(field) { 211 let phoneInput = getInput(field); 212 213 if (!phoneInput) { 214 return false;
215 } 216 217 let iti = getFieldIntlInstance(field); 218 if (!iti) { 219 return false; 220 } 221 222 let hasError = false; 223 let hasErrorMessage = validateError(iti, phoneInput); 224 let validPhoneNumber = iti.isValidNumber(); 225 226 if (hasErrorMessage) { 227 hasError = true; 228 } 229 230 if (!validPhoneNumber) { 231 hasError = true; 232 } 233 234 if (hasError) { 235 phoneInput.scrollIntoView({ behavior: "smooth" }); 236 return false; 237 } 238 239 return true; 240} 241 242function getFieldIntlInstance(field) { 243 return window.intlTelInputGlobals.getInstance(getInput(field)); 244} 245 246function getInput(field) { 247 let inputID = "#" + field; 248 return document.querySelector(inputID); 249} 250 251function validateError(iti, input) { 252 let hasError = false; 253 let text; 254 255 if (input.value) { 256 text = iti.isValidNumber() 257 ? "" 258 : validationMessages.invalid_phone_number_format; 259 260 let fullNumber = iti.getNumber(); 261 262 Object.entries(patterns).forEach((patternEntry) => { 263 let [prefix, regexString] = patternEntry; 264 if (fullNumber.startsWith(prefix)) { 265 let regex = new RegExp(regexString); 266 if (!regex.test(fullNumber)) { 267 text = validationMessages.invalid_phone_number_format; 268 return; 269 } 270 } 271 }); 272 } else { 273 text = validationMessages.required_phone_number; 274 } 275 276 hasError = text !== ""; 277 278 let classArray = ["validate-required", "form-row"]; 279 let closestAncestor = null; 280 281 for (let className of classArray) { 282 closestAncestor = findClosestAncestor(input, className); 283 if (closestAncestor) { 284 break; 285 } 286 } 287 288 if (closestAncestor) { 289 closestAncestor.classList.remove( 290 "woocommerce-invalid", 291 "woocommerce-validated" 292 ); 293 if (text !== "") { 294 closestAncestor.classList.add("woocommerce-invalid"); 295 } else { 296 closestAncestor.classList.add("woocommerce-validated"); 297 } 298 } 299 300 let existingHelpText = input.parentElement.nextElementSibling; 301 if (existingHelpText) { 302 existingHelpText.textContent = text; 303 if (text) { 304 existingHelpText.style.display = "block"; 305 } else { 306 existingHelpText.style.display = "none"; 307 } 308 } else { 309 let errorMessage = document.createElement("span"); 310 errorMessage.className = inputHelperClass; 311 errorMessage.textContent = text; 312 if (text) { 313 errorMessage.style.display = "block"; 314 } else { 315 errorMessage.style.display = "none"; 316 } 317 input.parentElement.insertAdjacentElement("afterend", errorMessage); 318 } 319 320 return hasError; 321} 322 323function findClosestAncestor(element, className) { 324 let parent = element.parentElement; 325 while (parent) { 326 if (parent.classList.contains(className)) { 327 return parent; 328 } 329 parent = parent.parentElement; 330 } 331 return null; 332} 333 334function getFields() { 335 return fields.length 336 ? fields 337 .map((field) => ({ 338 ...field, 339 element: document.getElementById(field.id), 340 })) 341 .filter((field) => field.element !== null) 342 .sort((a, b) => { 343 return compareElementPositions(a.element, b.element); 344 }) 345 : []; 346} 347 348function compareElementPositions(elementA, elementB) { 349 if (elementA === elementB) { 350 return 0; 351 } 352 if ( 353 elementA.compareDocumentPosition(elementB) & 354 Node.DOCUMENT_POSITION_PRECEDING 355 ) { 356 return -1; 357 } 358 return 1; 359} 360 361function getCountriesBy(countriesType) { 362 let onlyCountries = []; 363 if (countriesType === "billing") { 364 onlyCountries = allowedCountries; 365 } else if (countriesType === "shipping") { 366 onlyCountries = shippingCountries; 367 } else if (countriesType === "all") { 368 onlyCountries = allCountries; 369 } 370 return onlyCountries; 371} 372 373function setCountryLocalStorage(countryCode) { 374 let expiration = new Date().getTime() + 60 * 60 * 1000 * lookup.ttl; 375 let data = { countryCode, expiration }; 376 localStorage.setItem( 377 "intl_phone_number_lcokup_country", 378 JSON.stringify(data) 379 ); 380} 381 382function getCountryFromLocalStorage() { 383 let storedData = localStorage.getItem("intl_phone_number_lcokup_country"); 384 if (storedData) { 385 let data = JSON.parse(storedData); 386 if (data.expiration && new Date().getTime() < data.expiration) { 387 return data.countryCode || ""; 388 } 389 } 390 return ""; 391}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.