From d5a56eb0b876a58e6b68611aa16cb0dbc0567dc1 Mon Sep 17 00:00:00 2001 From: anonymoususer72041 <247563575+anonymoususer72041@users.noreply.github.com> Date: Tue, 16 Dec 2025 12:15:00 +0100 Subject: [PATCH 1/3] Add backend support for configurable default phone country code and E.164 extraction --- db/cats_schema.sql | 7 +- lib/Session.php | 28 ++++++ lib/Site.php | 22 +++++ lib/StringUtility.php | 175 +++++++++++++++++++++++++++++++------ modules/install/Schema.php | 6 ++ 5 files changed, 209 insertions(+), 29 deletions(-) diff --git a/db/cats_schema.sql b/db/cats_schema.sql index 37c551a95..09d98616a 100755 --- a/db/cats_schema.sql +++ b/db/cats_schema.sql @@ -859,7 +859,7 @@ insert into `module_schema`(`module_schema_id`,`name`,`version`) values (9,'ext insert into `module_schema`(`module_schema_id`,`name`,`version`) values (10,'graphs',0); insert into `module_schema`(`module_schema_id`,`name`,`version`) values (11,'home',0); insert into `module_schema`(`module_schema_id`,`name`,`version`) values (12,'import',0); -insert into `module_schema`(`module_schema_id`,`name`,`version`) values (13,'install',363); +insert into `module_schema`(`module_schema_id`,`name`,`version`) values (13,'install',365); insert into `module_schema`(`module_schema_id`,`name`,`version`) values (14,'joborders',0); insert into `module_schema`(`module_schema_id`,`name`,`version`) values (15,'lists',0); insert into `module_schema`(`module_schema_id`,`name`,`version`) values (16,'login',0); @@ -999,6 +999,7 @@ CREATE TABLE `site` ( `time_zone` int(5) DEFAULT '0', `time_format_24` int(1) DEFAULT '0', `date_format_ddmmyy` int(1) DEFAULT '0', + `default_phone_country_code` varchar(8) COLLATE utf8_unicode_ci NOT NULL DEFAULT '+1', `is_hr_mode` int(1) DEFAULT '0', `file_size_kb` int(11) DEFAULT '0', `page_views` bigint(20) DEFAULT '0', @@ -1014,8 +1015,8 @@ CREATE TABLE `site` ( /*Data for the table `site` */ -insert into `site`(`site_id`,`name`,`is_demo`,`user_licenses`,`entered_by`,`date_created`,`unix_name`,`company_id`,`is_free`,`account_active`,`account_deleted`,`reason_disabled`,`time_zone`,`time_format_24`,`date_format_ddmmyy`,`is_hr_mode`,`file_size_kb`,`page_views`,`page_view_days`,`last_viewed_day`,`first_time_setup`,`localization_configured`,`agreed_to_license`,`limit_warning`) values (1,'testdomain.com',0,0,0,'2005-06-01 00:00:00',NULL,NULL,0,1,0,NULL,2,0,1,0,0,574,1,'2009-11-19',0,0,1,0); -insert into `site`(`site_id`,`name`,`is_demo`,`user_licenses`,`entered_by`,`date_created`,`unix_name`,`company_id`,`is_free`,`account_active`,`account_deleted`,`reason_disabled`,`time_zone`,`time_format_24`,`date_format_ddmmyy`,`is_hr_mode`,`file_size_kb`,`page_views`,`page_view_days`,`last_viewed_day`,`first_time_setup`,`localization_configured`,`agreed_to_license`,`limit_warning`) values (180,'CATS_ADMIN',0,0,0,'2005-06-01 00:00:00','catsadmin',NULL,0,1,0,NULL,2,0,1,0,0,0,0,NULL,0,0,0,0); +insert into `site`(`site_id`,`name`,`is_demo`,`user_licenses`,`entered_by`,`date_created`,`unix_name`,`company_id`,`is_free`,`account_active`,`account_deleted`,`reason_disabled`,`time_zone`,`time_format_24`,`date_format_ddmmyy`,`default_phone_country_code`,`is_hr_mode`,`file_size_kb`,`page_views`,`page_view_days`,`last_viewed_day`,`first_time_setup`,`localization_configured`,`agreed_to_license`,`limit_warning`) values (1,'testdomain.com',0,0,0,'2005-06-01 00:00:00',NULL,NULL,0,1,0,NULL,2,0,1,'+1',0,0,574,1,'2009-11-19',0,0,1,0); +insert into `site`(`site_id`,`name`,`is_demo`,`user_licenses`,`entered_by`,`date_created`,`unix_name`,`company_id`,`is_free`,`account_active`,`account_deleted`,`reason_disabled`,`time_zone`,`time_format_24`,`date_format_ddmmyy`,`default_phone_country_code`,`is_hr_mode`,`file_size_kb`,`page_views`,`page_view_days`,`last_viewed_day`,`first_time_setup`,`localization_configured`,`agreed_to_license`,`limit_warning`) values (180,'CATS_ADMIN',0,0,0,'2005-06-01 00:00:00','catsadmin',NULL,0,1,0,NULL,2,0,1,'+1',0,0,0,0,NULL,0,0,0,0); /*Table structure for table `sph_counter` */ diff --git a/lib/Session.php b/lib/Session.php index 96ab845c7..b56c89564 100755 --- a/lib/Session.php +++ b/lib/Session.php @@ -72,6 +72,7 @@ class CATSSession private $_storedBuild = -1; private $_timeZoneOffset = 0; private $_timeZone = 0; + private $_defaultPhoneCountryCode = '+1'; private $_dateDMY = false; private $_pipelineEntriesPerPage = 15; private $_storedData = array(); @@ -533,6 +534,29 @@ public function getTimeZone() return $this->_timeZone; } + /** + * Returns the default phone country calling code (E.164) for the + * current site. The value is stored in the "site" table. + * + * @return string + */ + public function getDefaultPhoneCountryCode() + { + return $this->_defaultPhoneCountryCode; + } + + /** + * Sets the default phone country calling code for the current site + * on the session object. This does not write to the database. + * + * @param string $countryCode + * @return void + */ + public function setDefaultPhoneCountryCode($countryCode) + { + $this->_defaultPhoneCountryCode = $countryCode; + } + // FIXME: Document me! public function getUserCategories() { @@ -678,6 +702,7 @@ public function processLogin($username, $password, $addToHistory = true) site.account_active AS accountActive, site.account_deleted AS accountDeleted, site.time_zone AS timeZone, + site.default_phone_country_code AS defaultPhoneCountryCode, site.date_format_ddmmyy AS dateFormatDMY, site.is_free AS isFree, site.is_hr_mode AS isHrMode, @@ -810,6 +835,7 @@ public function processLogin($username, $password, $addToHistory = true) $this->_userAgent = $userAgent; $this->_timeZoneOffset = $rs['timeZone'] - OFFSET_GMT; $this->_timeZone = $rs['timeZone']; + $this->_defaultPhoneCountryCode = $rs['defaultPhoneCountryCode']; $this->_dateDMY = ($rs['dateFormatDMY'] == 0 ? false : true); $this->_canSeeEEOInfo = ($rs['canSeeEEOInfo'] == 0 ? false : true); $this->_pipelineEntriesPerPage = $rs['pipelineEntriesPerPage']; @@ -958,6 +984,7 @@ public function transparentLogin($toSiteID, $asUserID, $asSiteID) site.account_active AS accountActive, site.account_deleted AS accountDeleted, site.time_zone AS timeZone, + site.default_phone_country_code AS defaultPhoneCountryCode, site.date_format_ddmmyy AS dateFormatDMY, site.is_free AS isFree, site.is_hr_mode AS isHrMode @@ -992,6 +1019,7 @@ public function transparentLogin($toSiteID, $asUserID, $asSiteID) $this->_accountDeleted = ($rs['accountDeleted'] == 0 ? false : true); $this->_email = $rs['email']; $this->_timeZone = $rs['timeZone']; + $this->_defaultPhoneCountryCode = $rs['defaultPhoneCountryCode']; $this->_dateDMY = ($rs['dateFormatDMY'] == 0 ? false : true); $this->_isFirstTimeSetup = true; $this->_isAgreedToLicense = true; diff --git a/lib/Site.php b/lib/Site.php index 3b212401d..8fb8d5202 100755 --- a/lib/Site.php +++ b/lib/Site.php @@ -95,6 +95,28 @@ public function setLocalization($timeZone, $isDMY) return (boolean) $this->_db->query($sql); } + /** + * Sets the default phone country calling code for the current site. + * + * @param string $countryCode E.164 country calling code (for example "+49"). + * @return boolean True if successful; false otherwise. + */ + public function setDefaultPhoneCountryCode($countryCode) + { + $sql = sprintf( + "UPDATE + site + SET + default_phone_country_code = %s + WHERE + site_id = %s", + $this->_db->makeQueryString($countryCode), + $this->_siteID + ); + + return (boolean) $this->_db->query($sql); + } + /** * Get site information by unix name. * diff --git a/lib/StringUtility.php b/lib/StringUtility.php index 459b1f1ec..9799129f7 100755 --- a/lib/StringUtility.php +++ b/lib/StringUtility.php @@ -119,44 +119,167 @@ public static function containsPhoneNumber($string) return false; } + /** + * Returns the first phone number that could be extracted from a string. + * + * Uses the new E.164-based implementation. The previous NANP-specific + * implementation is kept below as commented legacy code to support + * future refactoring. + * + * @param string $string String to test. + * @return string Phone number or '' if not found. + */ + public static function extractPhoneNumber($string) + { + /* + * Legacy NANP-based implementation kept for reference during + * refactoring. + * + * if (preg_match('/' + * . self::matchPHECountryCode . self::matchPHSeparator . self::matchPHEAreaCode + * . self::matchPHSeparator . self::matchPHEExchange . self::matchPHSeparator + * . self::matchPHENumber . self::matchPHSeparator . self::matchPHEExtension + * . '/i', $string, $matches)) + * { + * // Do not format international phone numbers. + * if (!empty($matches['countryCode']) && ($matches['countryCode'] != '1')) + * { + * return $string; + * } + * + * $formattedPhoneNumber = sprintf( + * '%s-%s-%s', + * $matches['areaCode'], + * $matches['exchange'], + * $matches['number'] + * ); + * + * if (isset($matches['extension']) && !empty($matches['extension'])) + * { + * $formattedPhoneNumber .= ' x ' . $matches['extension']; + * } + * + * return $formattedPhoneNumber; + * } + * + * return ''; + */ + + return self::extractPhoneNumberE164($string); + } + /** - * Returns the first phone number that could be extracted from a string. + * Returns the first phone number that could be extracted from a string + * and normalizes it to E.164 where possible. * - * @param string to test - * @return string phone number or '' if not found + * @param string $string String to test. + * @return string Normalized E.164 phone number or '' if not found. */ - public static function extractPhoneNumber($string) + private static function extractPhoneNumberE164($string) { - if (preg_match('/' - . self::matchPHECountryCode . self::matchPHSeparator . self::matchPHEAreaCode - . self::matchPHSeparator . self::matchPHEExchange . self::matchPHSeparator - . self::matchPHENumber . self::matchPHSeparator . self::matchPHEExtension - . '/i', $string, $matches)) + /* 1) Trim leading / trailing whitespace. */ + $string = trim((string) $string); + + /* 2) Replace leading "00" with "+" (0049... -> +49...). */ + if (strpos($string, '00') === 0) { - //print_r($matches); + $string = '+' . substr($string, 2); + } - /* Don't format international phone numbers. */ - if (!empty($matches['countryCode']) && ($matches['countryCode'] != '1')) - { - return $string; - } + /* + * 3) Remove all characters except digits and '+'. + * This strips spaces, '-', '/', '()', '.', etc. + */ + $string = preg_replace('/[^0-9+]/', '', $string); + + /* + * 4) If there are no digits (and no '+' sign) left after normalization, + * we treat this as "no phone number found". + */ + if ($string === '') + { + return ''; + } - $formattedPhoneNumber = sprintf( - "%s-%s-%s", - $matches['areaCode'], - $matches['exchange'], - $matches['number'] - ); + /* + * 5) If the value already starts with '+', assume that the caller + * provided a full international number in E.164 format. + */ + if ($string[0] === '+') + { + return $string; + } + + /* + * 6) Local number: + * - determine the default country calling code + * - strip leading trunk zeros + * - build the E.164 representation + */ + $countryCode = self::getDefaultPhoneCountryCode(); - if (isset($matches['extension']) && !empty($matches['extension'])) + if ($countryCode === '') + { + /* + * No configuration available; we cannot safely build an + * E.164 number. Returning an empty string makes this + * failure explicit. + */ + return ''; + } + + $nationalNumber = ltrim($string, '0'); + + if ($nationalNumber === '') + { + /* Input consisted only of zeros – treat as invalid. */ + return ''; + } + + return $countryCode . $nationalNumber; + } + + /** + * Returns the default phone country calling code (E.164) for the + * current site. + * + * The value is obtained from the active session if available. If no + * usable configuration can be read, a hard-coded fallback is returned + * to keep the behaviour predictable. + * + * @return string E.164 country calling code (for example "+49"). + */ + private static function getDefaultPhoneCountryCode() + { + /* + * Try to read the default phone country code from the current + * session. This requires the Session class to expose a + * getDefaultPhoneCountryCode() method which returns the value + * stored in the "site" table. + */ + if (isset($_SESSION['CATS']) + && method_exists($_SESSION['CATS'], 'getDefaultPhoneCountryCode')) + { + $code = trim((string) $_SESSION['CATS']->getDefaultPhoneCountryCode()); + + /* + * Basic sanity check: the value must start with '+' and + * contain at least one digit. More strict validation is + * performed when the value is saved in the administration UI. + */ + if ($code !== '' && preg_match('/^\+[0-9]+$/', $code)) { - $formattedPhoneNumber .= ' x ' . $matches['extension']; + return $code; } - - return $formattedPhoneNumber; } - return ''; + /* + * Fallback: if no usable configuration is available we return the + * original NANP-style default of "+1". This keeps behaviour + * predictable for existing installations that have not yet + * configured a different default. + */ + return '+1'; } /** diff --git a/modules/install/Schema.php b/modules/install/Schema.php index 495405504..dcd0915ce 100755 --- a/modules/install/Schema.php +++ b/modules/install/Schema.php @@ -1328,6 +1328,12 @@ public static function get() '364' => ' UPDATE user SET password = md5(password) WHERE can_change_password=1; ', + '365' => ' + ALTER IGNORE TABLE `site` + ADD COLUMN `default_phone_country_code` varchar(8) + COLLATE utf8_unicode_ci NOT NULL DEFAULT \'+1\' + AFTER `date_format_ddmmyy`; + ', ); } From 7c5dba60677d74e4ddea57a455741bb9365c7815 Mon Sep 17 00:00:00 2001 From: anonymoususer72041 <247563575+anonymoususer72041@users.noreply.github.com> Date: Tue, 16 Dec 2025 12:15:00 +0100 Subject: [PATCH 2/3] Add installer field for default phone country calling code --- installwizard.php | 10 ++++++++++ modules/install/ajax/ui.php | 40 ++++++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/installwizard.php b/installwizard.php index 911d5fd39..6f82a1d90 100755 --- a/installwizard.php +++ b/installwizard.php @@ -468,6 +468,16 @@ + + Please enter your default phone country calling code. + + + + + + + +

diff --git a/modules/install/ajax/ui.php b/modules/install/ajax/ui.php index 53db04e79..0ba4307a9 100755 --- a/modules/install/ajax/ui.php +++ b/modules/install/ajax/ui.php @@ -493,7 +493,9 @@ { $onClick .= htmlspecialchars($index) . ',\' + encodeURIComponent(getCheckedValue(document.getElementsByName(\'' . htmlspecialchars($index) . '\'))) + \','; } - $onClick .= '&timeZone=\' + encodeURIComponent(document.getElementById(\'timeZone\').value) + \'&dateFormat=\' + encodeURIComponent(document.getElementById(\'dateFormat\').value) + \'\');'; + $onClick .= '&timeZone=\' + encodeURIComponent(document.getElementById(\'timeZone\').value)'; + $onClick .= '&dateFormat=\' + encodeURIComponent(document.getElementById(\'dateFormat\').value)'; + $onClick .= '&defaultPhoneCountryCodeDigits=\' + encodeURIComponent(document.getElementById(\'defaultPhoneCountryCodeDigits\').value) + '\');'; echo '