*/ const DIAL_CODES = array( // Middle East & North Africa 'AE' => '971', 'SA' => '966', 'EG' => '20', 'KW' => '965', 'QA' => '974', 'BH' => '973', 'OM' => '968', 'JO' => '962', 'IQ' => '964', 'LB' => '961', 'YE' => '967', 'SY' => '963', 'PS' => '970', 'IL' => '972', 'IR' => '98', 'MA' => '212', 'DZ' => '213', 'TN' => '216', 'LY' => '218', 'SD' => '249', 'MR' => '222', // Europe 'GB' => '44', 'DE' => '49', 'FR' => '33', 'IT' => '39', 'ES' => '34', 'NL' => '31', 'BE' => '32', 'CH' => '41', 'AT' => '43', 'SE' => '46', 'NO' => '47', 'DK' => '45', 'FI' => '358', 'PT' => '351', 'IE' => '353', 'PL' => '48', 'CZ' => '420', 'SK' => '421', 'HU' => '36', 'RO' => '40', 'BG' => '359', 'GR' => '30', 'HR' => '385', 'RS' => '381', 'SI' => '386', 'UA' => '380', 'RU' => '7', 'BY' => '375', 'LT' => '370', 'LV' => '371', 'EE' => '372', 'CY' => '357', 'MT' => '356', 'LU' => '352', 'IS' => '354', 'AL' => '355', 'MK' => '389', 'BA' => '387', 'MD' => '373', 'GE' => '995', 'AM' => '374', 'AZ' => '994', // Americas 'US' => '1', 'CA' => '1', 'MX' => '52', 'BR' => '55', 'AR' => '54', 'CO' => '57', 'CL' => '56', 'PE' => '51', 'VE' => '58', 'EC' => '593', 'UY' => '598', 'PY' => '595', 'BO' => '591', 'CR' => '506', 'PA' => '507', 'DO' => '1', 'GT' => '502', 'SV' => '503', 'HN' => '504', 'NI' => '505', 'JM' => '1', 'TT' => '1', // Asia-Pacific 'IN' => '91', 'PK' => '92', 'BD' => '880', 'LK' => '94', 'NP' => '977', 'AF' => '93', 'CN' => '86', 'HK' => '852', 'MO' => '853', 'TW' => '886', 'JP' => '81', 'KR' => '82', 'SG' => '65', 'MY' => '60', 'TH' => '66', 'ID' => '62', 'PH' => '63', 'VN' => '84', 'MM' => '95', 'KH' => '855', 'LA' => '856', 'AU' => '61', 'NZ' => '64', 'KZ' => '7', 'UZ' => '998', 'TM' => '993', 'KG' => '996', 'TJ' => '992', 'MN' => '976', 'BN' => '673', 'MV' => '960', 'TR' => '90', // Sub-Saharan Africa 'NG' => '234', 'ZA' => '27', 'KE' => '254', 'GH' => '233', 'ET' => '251', 'TZ' => '255', 'UG' => '256', 'RW' => '250', 'ZM' => '260', 'ZW' => '263', 'MZ' => '258', 'AO' => '244', 'CM' => '237', 'CI' => '225', 'SN' => '221', 'ML' => '223', 'BF' => '226', 'NE' => '227', 'TD' => '235', 'SO' => '252', 'DJ' => '253', 'ER' => '291', 'GM' => '220', 'GN' => '224', 'LR' => '231', 'SL' => '232', 'TG' => '228', 'BJ' => '229', 'GA' => '241', 'CG' => '242', 'CD' => '243', 'MG' => '261', 'MU' => '230', 'NA' => '264', 'BW' => '267', 'MW' => '265', 'LS' => '266', 'SZ' => '268', ); /** * Normalize a raw phone string to E.164 format (with leading +). * * Returns null when the number is invalid. * * @param string $phone Raw phone number from any source. * @param string $country_code ISO-2 country code (default: AE). * * @return string|null E.164 string or null. */ /** * Country to assume for a phone typed without a country code. * * 6rsh serves stores worldwide, so never default to one country: the * customer's own billing country wins, then the country saved at setup, * then the store's WooCommerce base country. Empty = don't guess (only * numbers already in international format are accepted). * * @param string $billing_country The customer's billing country, if known. * * @return string ISO-3166 alpha-2 or ''. */ public static function country_for( string $billing_country = '' ): string { $billing_country = strtoupper( trim( $billing_country ) ); if ( '' !== $billing_country && isset( self::DIAL_CODES[ $billing_country ] ) ) { return $billing_country; } $saved = strtoupper( (string) get_option( 'sixrsh_country_code', '' ) ); if ( '' !== $saved ) { return $saved; } if ( function_exists( 'WC' ) && WC() && WC()->countries ) { return strtoupper( (string) WC()->countries->get_base_country() ); } return ''; } public static function normalize( string $phone, string $country_code = '' ): ?string { try { // Strip spaces, dashes, parentheses, dots but keep leading + and digits. $clean = preg_replace( '/[^\d+]/', '', $phone ); if ( ! $clean ) { return null; } // Unknown store country → never guess a dial code; only numbers already // in an unambiguous international format are accepted below. $dial = self::DIAL_CODES[ strtoupper( $country_code ) ] ?? null; // Already in +E.164 format. if ( str_starts_with( $clean, '+' ) ) { $digits = substr( $clean, 1 ); if ( ! self::valid_length( $digits ) ) { return null; } return '+' . $digits; } // 00-prefixed international format. if ( str_starts_with( $clean, '00' ) ) { $digits = substr( $clean, 2 ); if ( ! self::valid_length( $digits ) ) { return null; } return '+' . $digits; } // From here on a local→international conversion needs a known dial code. if ( null === $dial ) { // Best effort: a long bare number may already embed its country code. if ( ! str_starts_with( $clean, '0' ) && strlen( $clean ) >= 10 && self::valid_length( $clean ) ) { return '+' . $clean; } return null; } // Local format with leading 0. if ( str_starts_with( $clean, '0' ) ) { $local = substr( $clean, 1 ); $digits = $dial . $local; if ( ! self::valid_length( $digits ) ) { return null; } return '+' . $digits; } // Bare number: prepend country dial code if it looks too short. if ( strlen( $clean ) < 10 ) { $digits = $dial . $clean; if ( ! self::valid_length( $digits ) ) { return null; } return '+' . $digits; } // Long enough to already have country code embedded or be a local long form. if ( self::valid_length( $clean ) ) { return '+' . $clean; } // Try prepending dial code. $digits = $dial . $clean; if ( self::valid_length( $digits ) ) { return '+' . $digits; } return null; } catch ( \Throwable $e ) { error_log( '[6rsh WhatsApp] Phone::normalize exception: ' . $e->getMessage() ); return null; } } /** * Strip all non-digit characters for use in wa.me URLs. * * @param string $phone E.164 or any phone string. * * @return string Digits only, no +. */ public static function to_wa_me( string $phone ): string { return preg_replace( '/\D/', '', $phone ); } /** * Validate that the digit string has a plausible E.164 length (7-15 digits). * * @param string $digits Pure digit string (no +). * * @return bool */ private static function valid_length( string $digits ): bool { $len = strlen( $digits ); return $len >= 7 && $len <= 15; } }