mirror of
https://github.com/espocrm/espocrm.git
synced 2026-06-28 06:56:05 +00:00
internation phone numbers
This commit is contained in:
@@ -29,25 +29,27 @@
|
||||
|
||||
namespace Espo\Classes\FieldValidators;
|
||||
|
||||
use Brick\PhoneNumber\PhoneNumber;
|
||||
use Brick\PhoneNumber\PhoneNumberParseException;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\ORM\Defs;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class PhoneType
|
||||
{
|
||||
private Metadata $metadata;
|
||||
|
||||
private Defs $defs;
|
||||
|
||||
private const DEFAULT_MAX_LENGTH = 36;
|
||||
|
||||
public function __construct(Metadata $metadata, Defs $defs)
|
||||
{
|
||||
$this->metadata = $metadata;
|
||||
$this->defs = $defs;
|
||||
}
|
||||
public function __construct(
|
||||
private Metadata $metadata,
|
||||
private Defs $defs,
|
||||
private Config $config
|
||||
) {}
|
||||
|
||||
public function checkRequired(Entity $entity, string $field): bool
|
||||
{
|
||||
@@ -193,7 +195,22 @@ class PhoneType
|
||||
|
||||
$preparedPattern = '/^' . $pattern . '$/';
|
||||
|
||||
return (bool) preg_match($preparedPattern, $number);
|
||||
if (!preg_match($preparedPattern, $number)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->config->get('phoneNumberInternational')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
$numberObj = PhoneNumber::parse($number);
|
||||
}
|
||||
catch (PhoneNumberParseException) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $numberObj->isPossibleNumber();
|
||||
}
|
||||
|
||||
protected function isNotEmpty(Entity $entity, string $field): bool
|
||||
|
||||
@@ -279,6 +279,8 @@ return [
|
||||
'oidcScopes' => ['profile', 'email', 'phone'],
|
||||
'listViewSettingsDisabled' => false,
|
||||
'cleanupDeletedRecords' => true,
|
||||
'phoneNumberNumericSearch' => false,
|
||||
'phoneNumberNumericSearch' => true,
|
||||
'phoneNumberInternational' => true,
|
||||
'phoneNumberPreferredCountryList' => ['us', 'de'],
|
||||
'isInstalled' => false,
|
||||
];
|
||||
|
||||
@@ -305,6 +305,10 @@
|
||||
"fieldNotMatchingPattern$uriOptionalProtocol": "{field} must be a valid URL",
|
||||
"fieldInvalid": "{field} is invalid",
|
||||
"fieldIsRequired": "{field} is required",
|
||||
"fieldPhoneInvalid": "{field} is invalid",
|
||||
"fieldPhoneInvalidCode": "Invalid country code",
|
||||
"fieldPhoneTooShort": "{field} is too short",
|
||||
"fieldPhoneTooLong": "{field} is too long",
|
||||
"fieldPhoneInvalidCharacters": "Only digits, latin letters and characters `-+_@:#().` are allowed",
|
||||
"fieldShouldBeEmail": "{field} should be a valid email",
|
||||
"fieldShouldBeFloat": "{field} should be a valid float",
|
||||
|
||||
@@ -71,6 +71,9 @@
|
||||
"confirmRemoveImportLog" : "This will remove the import log. All imported records will be kept. You won't be able to revert import results. Are you sure?",
|
||||
"removeImportLog": "This will remove the import log. All imported records will be kept. Use it if you are sure that import is fine."
|
||||
},
|
||||
"params": {
|
||||
"phoneNumberCountry": "Telephone country code"
|
||||
},
|
||||
"fields": {
|
||||
"file": "File",
|
||||
"entityType": "Entity Type",
|
||||
|
||||
@@ -110,6 +110,8 @@
|
||||
"adminNotificationsNewExtensionVersion": "Show notification when new versions of extensions are available",
|
||||
"textFilterUseContainsForVarchar": "Use 'contains' operator when filtering varchar fields",
|
||||
"phoneNumberNumericSearch": "Numeric phone number search",
|
||||
"phoneNumberInternational": "International phone numbers",
|
||||
"phoneNumberPreferredCountryList": "Preferred telephone country codes",
|
||||
"authTokenPreventConcurrent": "Only one auth token per user",
|
||||
"scopeColorsDisabled": "Disable scope colors",
|
||||
"tabColorsDisabled": "Disable tab colors",
|
||||
@@ -282,6 +284,7 @@
|
||||
"Misc": "Misc",
|
||||
"SMTP": "SMTP",
|
||||
"General": "General",
|
||||
"Phone Numbers": "Phone Numbers",
|
||||
"Navbar": "Navbar",
|
||||
"Dashboard": "Dashboard",
|
||||
"Configuration": "Configuration",
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
{
|
||||
"label": "Search",
|
||||
"rows": [
|
||||
[{"name": "textFilterUseContainsForVarchar"}, {"name": "globalSearchEntityList"}],
|
||||
[{"name": "phoneNumberNumericSearch"}, false]
|
||||
[{"name": "textFilterUseContainsForVarchar"}, {"name": "globalSearchEntityList"}]
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -36,6 +35,13 @@
|
||||
[{"name": "addressStateList"}, false]
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Phone Numbers",
|
||||
"rows": [
|
||||
[{"name": "phoneNumberInternational"}, {"name": "phoneNumberPreferredCountryList"}],
|
||||
[{"name": "phoneNumberNumericSearch"}, false]
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Activities",
|
||||
"rows": [
|
||||
|
||||
@@ -78,6 +78,18 @@
|
||||
"exportsAs": "Selectize"
|
||||
},
|
||||
"autonumeric": {},
|
||||
"intl-tel-input": {
|
||||
"exportsTo": "window",
|
||||
"exportsAs": "intlTelInput"
|
||||
},
|
||||
"intl-tel-input-utils": {
|
||||
"exportsTo": "window",
|
||||
"exportsAs": "intlTelInputUtils"
|
||||
},
|
||||
"intl-tel-input-globals": {
|
||||
"exportsTo": "window",
|
||||
"exportsAs": "intlTelInputGlobals"
|
||||
},
|
||||
"cronstrue": {
|
||||
"path": "client/lib/cronstrue-i18n.js",
|
||||
"devPath": "client/lib/original/cronstrue-i18n.js",
|
||||
|
||||
@@ -691,6 +691,13 @@
|
||||
"phoneNumberNumericSearch": {
|
||||
"type": "bool"
|
||||
},
|
||||
"phoneNumberInternational": {
|
||||
"type": "bool"
|
||||
},
|
||||
"phoneNumberPreferredCountryList": {
|
||||
"type": "multiEnum",
|
||||
"view": "views/settings/fields/phone-number-preferred-country-list"
|
||||
},
|
||||
"scopeColorsDisabled": {
|
||||
"type": "bool"
|
||||
},
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
|
||||
namespace Espo\Tools\Import;
|
||||
|
||||
use Brick\PhoneNumber\PhoneNumber;
|
||||
use Brick\PhoneNumber\PhoneNumberParseException;
|
||||
use Espo\Core\FieldValidation\Exceptions\ValidationError;
|
||||
use Espo\Core\Job\JobSchedulerFactory;
|
||||
use Espo\Entities\Attachment;
|
||||
@@ -770,7 +772,7 @@ class Import
|
||||
}
|
||||
|
||||
$o = (object) [
|
||||
'phoneNumber' => $value,
|
||||
'phoneNumber' => $this->formatPhoneNumber($value, $params),
|
||||
'primary' => true,
|
||||
];
|
||||
|
||||
@@ -867,7 +869,7 @@ class Import
|
||||
}
|
||||
|
||||
$o = (object) [
|
||||
'phoneNumber' => $value,
|
||||
'phoneNumber' => $this->formatPhoneNumber($value, $params),
|
||||
'type' => $type,
|
||||
'primary' => $isPrimary,
|
||||
];
|
||||
@@ -917,7 +919,6 @@ class Import
|
||||
{
|
||||
$params = $this->params;
|
||||
|
||||
/** @noinspection PhpRedundantVariableDocTypeInspection */
|
||||
/** @var non-empty-string $decimalMark */
|
||||
$decimalMark = $params->getDecimalMark() ?? self::DEFAULT_DECIMAL_MARK;
|
||||
|
||||
@@ -1255,4 +1256,26 @@ class Import
|
||||
|
||||
$errorIndex++;
|
||||
}
|
||||
|
||||
private function formatPhoneNumber(string $value, Params $params): string
|
||||
{
|
||||
if (str_starts_with($value, '+')) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (!$params->getPhoneNumberCountry()) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$code = strtoupper($params->getPhoneNumberCountry());
|
||||
|
||||
try {
|
||||
$number = PhoneNumber::parse($value, $code);
|
||||
|
||||
return (string) $number;
|
||||
}
|
||||
catch (PhoneNumberParseException) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ class Params
|
||||
private ?string $currency = null;
|
||||
private ?string $timezone = null;
|
||||
private ?string $decimalMark = null;
|
||||
private ?string $phoneNumberCountry = null;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
@@ -88,6 +89,11 @@ class Params
|
||||
return $this->personNameFormat;
|
||||
}
|
||||
|
||||
public function getPhoneNumberCountry(): ?string
|
||||
{
|
||||
return $this->phoneNumberCountry;
|
||||
}
|
||||
|
||||
public function isIdleMode(): bool
|
||||
{
|
||||
return $this->idleMode;
|
||||
@@ -193,6 +199,14 @@ class Params
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withPhoneNumberCountry(?string $phoneNumberCountry): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->phoneNumberCountry = $phoneNumberCountry;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withIdleMode(bool $idleMode = true): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
@@ -339,6 +353,7 @@ class Params
|
||||
->withIdleMode($raw->idleMode ?? false)
|
||||
->withManualMode($raw->manualMode ?? false)
|
||||
->withPersonNameFormat($raw->personNameFormat ?? null)
|
||||
->withPhoneNumberCountry($raw->phoneNumberCountry ?? null)
|
||||
->withSilentMode($raw->silentMode ?? false)
|
||||
->withSkipDuplicateChecking($raw->skipDuplicateChecking ?? false)
|
||||
->withStartFromLastIndex($raw->startFromLastIndex ?? false)
|
||||
@@ -368,6 +383,7 @@ class Params
|
||||
'skipDuplicateChecking' => $this->skipDuplicateChecking,
|
||||
'startFromLastIndex' => $this->startFromLastIndex,
|
||||
'textQualifier' => $this->textQualifier,
|
||||
'phoneNumberCountry' => $this->phoneNumberCountry,
|
||||
'timeFormat' => $this->timeFormat,
|
||||
'timezone' => $this->timezone,
|
||||
'updateBy' => $this->updateBy,
|
||||
|
||||
@@ -109,6 +109,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4 form-group cell">
|
||||
<label class="control-label">{{translate 'phoneNumberCountry' category='params' scope='Import'}}</label>
|
||||
<div data-name="phoneNumberCountry" class="field">
|
||||
{{{phoneNumberCountryField}}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
@@ -28,6 +28,11 @@
|
||||
|
||||
import VarcharFieldView from 'views/fields/varchar';
|
||||
import Select from 'ui/select';
|
||||
import intlTelInput from 'intl-tel-input';
|
||||
// noinspection NpmUsedModulesInstalled
|
||||
import intlTelInputUtils from 'intl-tel-input-utils';
|
||||
// noinspection NpmUsedModulesInstalled
|
||||
import intlTelInputGlobals from 'intl-tel-input-globals';
|
||||
|
||||
class PhoneFieldView extends VarcharFieldView {
|
||||
|
||||
@@ -176,20 +181,47 @@ class PhoneFieldView extends VarcharFieldView {
|
||||
.replace('{field}', this.getLabelText());
|
||||
const number = row.phoneNumber;
|
||||
|
||||
const n = (i + 1).toString();
|
||||
const selector = `div.phone-number-block:nth-child(${n}) input.phone-number`;
|
||||
|
||||
if (!regExp.test(number)) {
|
||||
notValid = true;
|
||||
|
||||
const msg = this.translate('fieldPhoneInvalidCharacters', 'messages')
|
||||
.replace('{field}', this.getLabelText());
|
||||
|
||||
this.showValidationMessage(msg, 'div.phone-number-block:nth-child(' + (i + 1)
|
||||
.toString() + ') input.phone-number');
|
||||
this.showValidationMessage(msg, selector);
|
||||
}
|
||||
|
||||
if (this.useInternational) {
|
||||
const element = this.$el.find(selector).get(0);
|
||||
|
||||
if (element) {
|
||||
const obj = this.intlTelInputMap.get(element);
|
||||
|
||||
if (obj && !obj.isPossibleNumber()) {
|
||||
notValid = true;
|
||||
|
||||
const code = obj.getValidationError();
|
||||
|
||||
const key = [
|
||||
'fieldPhoneInvalid',
|
||||
'fieldPhoneInvalidCode',
|
||||
'fieldPhoneTooShort',
|
||||
'fieldPhoneTooLong',
|
||||
][code || 0] || 'fieldPhoneInvalid';
|
||||
|
||||
const msg = this.translate(key, 'messages')
|
||||
.replace('{field}', this.getLabelText());
|
||||
|
||||
this.showValidationMessage(msg, selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const numberClean = String(number).replace(/[\s+]/g, '');
|
||||
|
||||
if (~numberList.indexOf(numberClean)) {
|
||||
|
||||
this.showValidationMessage(msg, 'div.phone-number-block:nth-child(' + (i + 1)
|
||||
.toString() + ') input.phone-number');
|
||||
|
||||
@@ -239,13 +271,17 @@ class PhoneFieldView extends VarcharFieldView {
|
||||
if (phoneNumberData) {
|
||||
phoneNumberData = Espo.Utils.cloneDeep(phoneNumberData);
|
||||
|
||||
phoneNumberData.forEach((item) => {
|
||||
phoneNumberData.forEach(item => {
|
||||
const number = item.phoneNumber || '';
|
||||
|
||||
item.erased = number.indexOf(this.erasedPlaceholder) === 0;
|
||||
|
||||
if (!item.erased) {
|
||||
item.valueForLink = number.replace(/ /g, '');
|
||||
|
||||
if (this.isReadMode()) {
|
||||
item.phoneNumber = this.formatNumber(item.phoneNumber);
|
||||
}
|
||||
}
|
||||
|
||||
item.lineThrough = item.optOut || item.invalid || this.model.get('doNotCall');
|
||||
@@ -253,13 +289,16 @@ class PhoneFieldView extends VarcharFieldView {
|
||||
}
|
||||
|
||||
if ((!phoneNumberData || phoneNumberData.length === 0) && this.model.get(this.name)) {
|
||||
|
||||
const o = {
|
||||
phoneNumber: number,
|
||||
phoneNumber: this.formatNumber(number),
|
||||
primary: true,
|
||||
valueForLink: number.replace(/ /g, ''),
|
||||
};
|
||||
|
||||
if (this.isReadMode()) {
|
||||
o.phoneNumber = this.formatNumber(o.phoneNumber);
|
||||
}
|
||||
|
||||
if (this.mode === 'edit' && this.model.isNew()) {
|
||||
o.type = this.defaultType;
|
||||
}
|
||||
@@ -267,6 +306,8 @@ class PhoneFieldView extends VarcharFieldView {
|
||||
phoneNumberData = [o];
|
||||
}
|
||||
|
||||
|
||||
|
||||
const data = {
|
||||
...super.data(),
|
||||
phoneNumberData: phoneNumberData,
|
||||
@@ -287,6 +328,7 @@ class PhoneFieldView extends VarcharFieldView {
|
||||
}
|
||||
|
||||
data.valueIsSet = this.model.has(this.name);
|
||||
data.value = this.formatNumber(data.value);
|
||||
}
|
||||
|
||||
data.itemMaxLength = this.itemMaxLength;
|
||||
@@ -314,6 +356,19 @@ class PhoneFieldView extends VarcharFieldView {
|
||||
this.trigger('change');
|
||||
}
|
||||
|
||||
formatNumber(value) {
|
||||
if (!value || value === '' || !this.useInternational) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// noinspection JSUnresolvedReference
|
||||
return intlTelInputUtils.formatNumber(
|
||||
value,
|
||||
null,
|
||||
intlTelInputUtils.numberFormat.INTERNATIONAL
|
||||
);
|
||||
}
|
||||
|
||||
addPhoneNumber() {
|
||||
const data = Espo.Utils.cloneDeep(this.fetchPhoneNumberData());
|
||||
|
||||
@@ -346,6 +401,25 @@ class PhoneFieldView extends VarcharFieldView {
|
||||
}
|
||||
}
|
||||
|
||||
afterRenderEdit() {
|
||||
super.afterRenderEdit();
|
||||
|
||||
if (this.useInternational) {
|
||||
const inputElements = this.element.querySelectorAll('input.phone-number');
|
||||
|
||||
inputElements.forEach(inputElement => {
|
||||
const obj = intlTelInput(inputElement, {
|
||||
separateDialCode: true,
|
||||
showFlags: false,
|
||||
preferredCountries: this.preferredCountryList,
|
||||
localizedCountries: this._codeNames,
|
||||
});
|
||||
|
||||
this.intlTelInputMap.set(inputElement, obj);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
afterRenderSearch() {
|
||||
super.afterRenderSearch();
|
||||
|
||||
@@ -424,6 +498,17 @@ class PhoneFieldView extends VarcharFieldView {
|
||||
this.isInvalidFieldName = this.name + 'IsInvalid';
|
||||
|
||||
this.phoneNumberOptedOutByDefault = this.getConfig().get('phoneNumberIsOptedOutByDefault');
|
||||
this.useInternational = this.getConfig().get('phoneNumberInternational') || false;
|
||||
this.preferredCountryList = this.getConfig().get('phoneNumberPreferredCountryList') || [];
|
||||
|
||||
if (this.useInternational && !this.isListMode() && !this.isSearchMode()) {
|
||||
this._codeNames = intlTelInputGlobals.getCountryData()
|
||||
.reduce((map, item) => {
|
||||
map[item.iso2] = item.iso2.toUpperCase();
|
||||
|
||||
return map;
|
||||
}, {});
|
||||
}
|
||||
|
||||
if (this.model.has('doNotCall')) {
|
||||
this.listenTo(this.model, 'change:doNotCall', (model, value, o) => {
|
||||
@@ -443,6 +528,16 @@ class PhoneFieldView extends VarcharFieldView {
|
||||
|
||||
this.itemMaxLength = this.getMetadata()
|
||||
.get(['entityDefs', 'PhoneNumber', 'fields', 'name', 'maxLength']);
|
||||
|
||||
this.intlTelInputMap = new Map();
|
||||
|
||||
this.once('remove', () => {
|
||||
for (const obj of this.intlTelInputMap.values()) {
|
||||
obj.destroy();
|
||||
}
|
||||
|
||||
this.intlTelInputMap.clear();
|
||||
})
|
||||
}
|
||||
|
||||
fetchPhoneNumberData() {
|
||||
@@ -455,7 +550,18 @@ class PhoneFieldView extends VarcharFieldView {
|
||||
const row = {};
|
||||
const $d = $(d);
|
||||
|
||||
row.phoneNumber = $d.find('input.phone-number').val().trim();
|
||||
/** @type {HTMLInputElement} */
|
||||
const inputElement = $d.find('input.phone-number').get(0);
|
||||
|
||||
if (!inputElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
row.phoneNumber = inputElement.value.trim();
|
||||
|
||||
if (this.intlTelInputMap.has(inputElement)) {
|
||||
row.phoneNumber = this.intlTelInputMap.get(inputElement).getNumber();
|
||||
}
|
||||
|
||||
if (row.phoneNumber === '') {
|
||||
return;
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
|
||||
import View from 'view';
|
||||
import Model from 'model';
|
||||
// noinspection NpmUsedModulesInstalled
|
||||
import intlTelInputGlobals from 'intl-tel-input-globals';
|
||||
|
||||
class Step1ImportView extends View {
|
||||
|
||||
@@ -100,6 +102,7 @@ class Step1ImportView extends View {
|
||||
'idleMode',
|
||||
'skipDuplicateChecking',
|
||||
'manualMode',
|
||||
'phoneNumberCountry',
|
||||
];
|
||||
|
||||
this.paramList.forEach(item => {
|
||||
@@ -355,6 +358,22 @@ class Step1ImportView extends View {
|
||||
tooltipText: this.translate('manualMode', 'tooltips', 'Import'),
|
||||
});
|
||||
|
||||
this.createView('phoneNumberCountryField', 'views/fields/enum', {
|
||||
selector: '.field[data-name="phoneNumberCountry"]',
|
||||
model: this.model,
|
||||
name: 'phoneNumberCountry',
|
||||
mode: 'edit',
|
||||
params: {
|
||||
options: ['', ...intlTelInputGlobals.getCountryData().map(item => item.iso2)],
|
||||
},
|
||||
translatedOptions: intlTelInputGlobals.getCountryData()
|
||||
.reduce((map, item) => {
|
||||
map[item.iso2] = `${item.iso2.toUpperCase()} +${item.dialCode}`;
|
||||
|
||||
return map;
|
||||
}, {})
|
||||
});
|
||||
|
||||
this.listenTo(this.model, 'change', (m, o) => {
|
||||
if (!o.ui) {
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
import MultiEnumFieldView from 'views/fields/multi-enum';
|
||||
// noinspection NpmUsedModulesInstalled
|
||||
import intlTelInputGlobals from 'intl-tel-input-globals';
|
||||
|
||||
class SettingsPhoneNumberPreferredCountryListFieldView extends MultiEnumFieldView {
|
||||
|
||||
setupOptions() {
|
||||
try {
|
||||
const dataList = intlTelInputGlobals.getCountryData();
|
||||
|
||||
this.params.options = dataList
|
||||
.map(item => item.iso2);
|
||||
|
||||
this.translatedOptions = dataList.reduce((map, item) => {
|
||||
map[item.iso2] = `${item.iso2.toUpperCase()} +${item.dialCode}`;
|
||||
|
||||
return map;
|
||||
}, {});
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export default SettingsPhoneNumberPreferredCountryListFieldView;
|
||||
@@ -47,7 +47,8 @@
|
||||
"johngrogg/ics-parser": "^3.0",
|
||||
"phpseclib/phpseclib": "^3.0",
|
||||
"openspout/openspout": "^4.9",
|
||||
"dompdf/dompdf": "^2.0"
|
||||
"dompdf/dompdf": "^2.0",
|
||||
"brick/phonenumber": "^0.5.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9",
|
||||
|
||||
181
composer.lock
generated
181
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "2b80267e733b9b4153f66fc0cfce5315",
|
||||
"content-hash": "d00cfc7a66d5f0fc733141d48f9eca16",
|
||||
"packages": [
|
||||
{
|
||||
"name": "async-aws/core",
|
||||
@@ -133,6 +133,59 @@
|
||||
],
|
||||
"time": "2021-02-01T21:10:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "brick/phonenumber",
|
||||
"version": "0.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/brick/phonenumber.git",
|
||||
"reference": "6286a218375a46a6f4d0fd361323f22313882814"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/brick/phonenumber/zipball/6286a218375a46a6f4d0fd361323f22313882814",
|
||||
"reference": "6286a218375a46a6f4d0fd361323f22313882814",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"giggsey/libphonenumber-for-php": "^7.0 || ^8.0",
|
||||
"php": "^7.4 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-coveralls/php-coveralls": "^2.0",
|
||||
"phpunit/phpunit": "^9.0",
|
||||
"vimeo/psalm": "5.7.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Brick\\PhoneNumber\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Phone number library",
|
||||
"keywords": [
|
||||
"brick",
|
||||
"phone",
|
||||
"phone number",
|
||||
"phonenumber"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/brick/phonenumber/issues",
|
||||
"source": "https://github.com/brick/phonenumber/tree/0.5.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/BenMorel",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2023-02-23T21:41:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "cboden/ratchet",
|
||||
"version": "v0.4.4",
|
||||
@@ -901,6 +954,132 @@
|
||||
},
|
||||
"time": "2020-11-24T22:02:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "giggsey/libphonenumber-for-php",
|
||||
"version": "8.13.24",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/giggsey/libphonenumber-for-php.git",
|
||||
"reference": "746ca6a565b9d4167c94c80824f43fa6fb463fd1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/746ca6a565b9d4167c94c80824f43fa6fb463fd1",
|
||||
"reference": "746ca6a565b9d4167c94c80824f43fa6fb463fd1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"giggsey/locale": "^1.7|^2.0",
|
||||
"php": ">=5.3.2",
|
||||
"symfony/polyfill-mbstring": "^1.17"
|
||||
},
|
||||
"require-dev": {
|
||||
"pear/pear-core-minimal": "^1.9",
|
||||
"pear/pear_exception": "^1.0",
|
||||
"pear/versioncontrol_git": "^0.5",
|
||||
"phing/phing": "^2.7",
|
||||
"php-coveralls/php-coveralls": "^1.0|^2.0",
|
||||
"symfony/console": "^2.8|^3.0|^v4.4|^v5.2",
|
||||
"symfony/phpunit-bridge": "^4.2 || ^5"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "8.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"libphonenumber\\": "src/"
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/src/data/",
|
||||
"/src/carrier/data/",
|
||||
"/src/geocoding/data/",
|
||||
"/src/timezone/data/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Joshua Gigg",
|
||||
"email": "giggsey@gmail.com",
|
||||
"homepage": "https://giggsey.com/"
|
||||
}
|
||||
],
|
||||
"description": "PHP Port of Google's libphonenumber",
|
||||
"homepage": "https://github.com/giggsey/libphonenumber-for-php",
|
||||
"keywords": [
|
||||
"geocoding",
|
||||
"geolocation",
|
||||
"libphonenumber",
|
||||
"mobile",
|
||||
"phonenumber",
|
||||
"validation"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/giggsey/libphonenumber-for-php/issues",
|
||||
"source": "https://github.com/giggsey/libphonenumber-for-php"
|
||||
},
|
||||
"time": "2023-10-31T08:12:54+00:00"
|
||||
},
|
||||
{
|
||||
"name": "giggsey/locale",
|
||||
"version": "2.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/giggsey/Locale.git",
|
||||
"reference": "e6d4540109a01dd2bc7334cdc842d6a6a67cf239"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/giggsey/Locale/zipball/e6d4540109a01dd2bc7334cdc842d6a6a67cf239",
|
||||
"reference": "e6d4540109a01dd2bc7334cdc842d6a6a67cf239",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-json": "*",
|
||||
"pear/pear-core-minimal": "^1.9",
|
||||
"pear/pear_exception": "^1.0",
|
||||
"pear/versioncontrol_git": "^0.5",
|
||||
"phing/phing": "^2.7",
|
||||
"php-coveralls/php-coveralls": "^2.0",
|
||||
"phpunit/phpunit": "^8.5|^9.5",
|
||||
"symfony/console": "^5.0|^6.0",
|
||||
"symfony/filesystem": "^5.0|^6.0",
|
||||
"symfony/finder": "^5.0|^6.0",
|
||||
"symfony/process": "^5.0|^6.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Giggsey\\Locale\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Joshua Gigg",
|
||||
"email": "giggsey@gmail.com",
|
||||
"homepage": "https://giggsey.com/"
|
||||
}
|
||||
],
|
||||
"description": "Locale functions required by libphonenumber-for-php",
|
||||
"support": {
|
||||
"issues": "https://github.com/giggsey/Locale/issues",
|
||||
"source": "https://github.com/giggsey/Locale/tree/2.5"
|
||||
},
|
||||
"time": "2023-11-01T17:19:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/psr7",
|
||||
"version": "1.9.1",
|
||||
|
||||
@@ -43,7 +43,8 @@ button.btn.active:focus-visible,
|
||||
input[type=radio]:focus-visible,
|
||||
input[type=checkbox]:focus-visible,
|
||||
input[type=file]:focus-visible,
|
||||
label.attach-file-label:focus-visible {
|
||||
label.attach-file-label:focus-visible,
|
||||
.iti__selected-flag:focus-visible {
|
||||
outline: 1px solid var(--input-border-focus-rgba);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
@@ -3536,6 +3537,88 @@ body > .autocomplete-suggestions.text-search-suggestions {
|
||||
}
|
||||
}
|
||||
|
||||
.input-group-item > .iti {
|
||||
top: 3px;
|
||||
margin-left: -1px;
|
||||
width: ~"calc(100% + 1px)";
|
||||
|
||||
.iti__flag-container {
|
||||
user-select: none;
|
||||
|
||||
&,
|
||||
&:hover {
|
||||
.iti__selected-flag {
|
||||
background-color: transparent;
|
||||
|
||||
&[aria-expanded="true"] {
|
||||
background-color: var(--dropdown-link-hover-bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.iti__country-list {
|
||||
z-index: 5;
|
||||
max-height: 300px;
|
||||
background-color: var(--dropdown-bg);
|
||||
border: var(--dropdown-border) solid var(--dropdown-border-width);
|
||||
border-radius: var(--dropdown-border-radius);
|
||||
box-shadow: var(--dropdown-box-shadow);
|
||||
|
||||
.iti__dial-code {
|
||||
color: var(--text-muted-color);
|
||||
}
|
||||
|
||||
.iti__country {
|
||||
color: var(--dropdown-link-color);
|
||||
padding: 4px 10px;
|
||||
|
||||
&.iti__highlight {
|
||||
background-color: var(--dropdown-link-hover-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.iti__divider {
|
||||
border-bottom-color: var(--dropdown-divider-bg);
|
||||
}
|
||||
|
||||
padding: 5px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.iti__selected-flag {
|
||||
z-index: 4;
|
||||
padding: 0 8px 0 8px;
|
||||
color: var(--gray-soft);
|
||||
|
||||
> .iti__arrow {
|
||||
visibility: hidden;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: " ";
|
||||
color: var(--text-muted-color);
|
||||
//display: inline-block;
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
top: 15px;
|
||||
right: 8px;
|
||||
margin-left: 2px;
|
||||
vertical-align: middle;
|
||||
border-top: 4px dashed;
|
||||
border-right: 4px solid transparent;
|
||||
border-left: 4px solid transparent;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
> input {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@import "misc/kanban.less";
|
||||
|
||||
@import "elements/placement.less";
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
@import "misc/colorpicker/colorpicker.less";
|
||||
@import "misc/fontawesome/fontawesome.less";
|
||||
@import "misc/gridstack/gridstack.less";
|
||||
@import "misc/intl-tel-input/intlTelInput.less";
|
||||
|
||||
@import "fonts.less";
|
||||
|
||||
1278
frontend/less/espo/misc/intl-tel-input/intlTelInput.less
Normal file
1278
frontend/less/espo/misc/intl-tel-input/intlTelInput.less
Normal file
File diff suppressed because it is too large
Load Diff
@@ -114,6 +114,17 @@
|
||||
"bundle": true,
|
||||
"amdId": "autonumeric"
|
||||
},
|
||||
{
|
||||
"src": "node_modules/intl-tel-input/build/js/intlTelInput.js",
|
||||
"bundle": true,
|
||||
"amdId": "intl-tel-input",
|
||||
"suppressAmd": true
|
||||
},
|
||||
{
|
||||
"src": "node_modules/intl-tel-input/build/js/utils.js",
|
||||
"bundle": true,
|
||||
"amdId": "intl-tel-input-utils"
|
||||
},
|
||||
{
|
||||
"src": "node_modules/summernote/dist/summernote.js",
|
||||
"amdId": "summernote",
|
||||
|
||||
11
package-lock.json
generated
11
package-lock.json
generated
@@ -31,6 +31,7 @@
|
||||
"getobject": ">=1.0.0",
|
||||
"gridstack": "github:yurikuzn/gridstack.js#5.1.1.e3",
|
||||
"handlebars": "^4.7.7",
|
||||
"intl-tel-input": "^18.2.1",
|
||||
"jquery": "^3.7.0",
|
||||
"jquery-textcomplete": "^1.8.5",
|
||||
"jquery-ui-espo": "github:yurikuzn/jquery-ui-espo#0.2.2",
|
||||
@@ -4725,6 +4726,11 @@
|
||||
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz",
|
||||
"integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ="
|
||||
},
|
||||
"node_modules/intl-tel-input": {
|
||||
"version": "18.2.1",
|
||||
"resolved": "https://registry.npmjs.org/intl-tel-input/-/intl-tel-input-18.2.1.tgz",
|
||||
"integrity": "sha512-wOm0/61kTtpYjOOW1bhHzC4G8Om+atTxHmg31FS0KD0LQ8k8BpgO925npyi4jlT/EK4+joABABZzz0/XeSgupQ=="
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
@@ -10688,6 +10694,11 @@
|
||||
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz",
|
||||
"integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ="
|
||||
},
|
||||
"intl-tel-input": {
|
||||
"version": "18.2.1",
|
||||
"resolved": "https://registry.npmjs.org/intl-tel-input/-/intl-tel-input-18.2.1.tgz",
|
||||
"integrity": "sha512-wOm0/61kTtpYjOOW1bhHzC4G8Om+atTxHmg31FS0KD0LQ8k8BpgO925npyi4jlT/EK4+joABABZzz0/XeSgupQ=="
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
"getobject": ">=1.0.0",
|
||||
"gridstack": "github:yurikuzn/gridstack.js#5.1.1.e3",
|
||||
"handlebars": "^4.7.7",
|
||||
"intl-tel-input": "^18.2.1",
|
||||
"jquery": "^3.7.0",
|
||||
"jquery-textcomplete": "^1.8.5",
|
||||
"jquery-ui-espo": "github:yurikuzn/jquery-ui-espo#0.2.2",
|
||||
|
||||
10
upgrades/8.1/data.json
Normal file
10
upgrades/8.1/data.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"manifest": {
|
||||
"delete": [
|
||||
"client/lib/original/espo.js",
|
||||
"client/lib/espo.min.js",
|
||||
"client/lib/espo.min.js.map",
|
||||
"client/cfg/pre-load.json"
|
||||
]
|
||||
}
|
||||
}
|
||||
48
upgrades/8.1/scripts/AfterUpgrade.php
Normal file
48
upgrades/8.1/scripts/AfterUpgrade.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2022 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
use Espo\Core\Container;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Config\ConfigWriter;
|
||||
|
||||
class AfterUpgrade
|
||||
{
|
||||
public function run(Container $container): void
|
||||
{
|
||||
$configWriter = $container->getByClass(InjectableFactory::class)
|
||||
->create(ConfigWriter::class);
|
||||
|
||||
$configWriter->setMultiple([
|
||||
'phoneNumberNumericSearch' => false,
|
||||
'phoneNumberInternational' => false,
|
||||
]);
|
||||
|
||||
$configWriter->save();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user