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/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/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`;
+ ',
);
}
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 '