diff --git a/application/Espo/Classes/FieldValidators/PhoneType.php b/application/Espo/Classes/FieldValidators/PhoneType.php index 493ef38d3b..76d831c849 100644 --- a/application/Espo/Classes/FieldValidators/PhoneType.php +++ b/application/Espo/Classes/FieldValidators/PhoneType.php @@ -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 diff --git a/application/Espo/Resources/defaults/config.php b/application/Espo/Resources/defaults/config.php index 19e11c93d5..cd4d41399d 100644 --- a/application/Espo/Resources/defaults/config.php +++ b/application/Espo/Resources/defaults/config.php @@ -279,6 +279,8 @@ return [ 'oidcScopes' => ['profile', 'email', 'phone'], 'listViewSettingsDisabled' => false, 'cleanupDeletedRecords' => true, - 'phoneNumberNumericSearch' => false, + 'phoneNumberNumericSearch' => true, + 'phoneNumberInternational' => true, + 'phoneNumberPreferredCountryList' => ['us', 'de'], 'isInstalled' => false, ]; diff --git a/application/Espo/Resources/i18n/en_US/Global.json b/application/Espo/Resources/i18n/en_US/Global.json index 35988853ac..5986c3b5a9 100644 --- a/application/Espo/Resources/i18n/en_US/Global.json +++ b/application/Espo/Resources/i18n/en_US/Global.json @@ -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", diff --git a/application/Espo/Resources/i18n/en_US/Import.json b/application/Espo/Resources/i18n/en_US/Import.json index 96c5fc0c90..75a56003c9 100644 --- a/application/Espo/Resources/i18n/en_US/Import.json +++ b/application/Espo/Resources/i18n/en_US/Import.json @@ -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", diff --git a/application/Espo/Resources/i18n/en_US/Settings.json b/application/Espo/Resources/i18n/en_US/Settings.json index da7f21e63b..f41d66d4ea 100644 --- a/application/Espo/Resources/i18n/en_US/Settings.json +++ b/application/Espo/Resources/i18n/en_US/Settings.json @@ -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", diff --git a/application/Espo/Resources/layouts/Settings/settings.json b/application/Espo/Resources/layouts/Settings/settings.json index e8188f5972..53d6e4151d 100644 --- a/application/Espo/Resources/layouts/Settings/settings.json +++ b/application/Espo/Resources/layouts/Settings/settings.json @@ -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": [ diff --git a/application/Espo/Resources/metadata/app/jsLibs.json b/application/Espo/Resources/metadata/app/jsLibs.json index 3cfdf198d1..9a2fb82ceb 100644 --- a/application/Espo/Resources/metadata/app/jsLibs.json +++ b/application/Espo/Resources/metadata/app/jsLibs.json @@ -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", diff --git a/application/Espo/Resources/metadata/entityDefs/Settings.json b/application/Espo/Resources/metadata/entityDefs/Settings.json index 4371445207..a091576418 100644 --- a/application/Espo/Resources/metadata/entityDefs/Settings.json +++ b/application/Espo/Resources/metadata/entityDefs/Settings.json @@ -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" }, diff --git a/application/Espo/Tools/Import/Import.php b/application/Espo/Tools/Import/Import.php index bfd9e86b8f..362840fe01 100644 --- a/application/Espo/Tools/Import/Import.php +++ b/application/Espo/Tools/Import/Import.php @@ -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; + } + } } diff --git a/application/Espo/Tools/Import/Params.php b/application/Espo/Tools/Import/Params.php index c11f4a0b8a..1a7c549461 100644 --- a/application/Espo/Tools/Import/Params.php +++ b/application/Espo/Tools/Import/Params.php @@ -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, diff --git a/client/res/templates/import/step-1.tpl b/client/res/templates/import/step-1.tpl index bb02dae757..4c784a361c 100644 --- a/client/res/templates/import/step-1.tpl +++ b/client/res/templates/import/step-1.tpl @@ -109,6 +109,10 @@
+ +
+ {{{phoneNumberCountryField}}} +
diff --git a/client/src/views/fields/phone.js b/client/src/views/fields/phone.js index 695bed8196..1e60c3399a 100644 --- a/client/src/views/fields/phone.js +++ b/client/src/views/fields/phone.js @@ -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; diff --git a/client/src/views/import/step1.js b/client/src/views/import/step1.js index 4e57d28a7f..21f93de7c3 100644 --- a/client/src/views/import/step1.js +++ b/client/src/views/import/step1.js @@ -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; diff --git a/client/src/views/settings/fields/phone-number-preferred-country-list.js b/client/src/views/settings/fields/phone-number-preferred-country-list.js new file mode 100644 index 0000000000..ab0a6cdf32 --- /dev/null +++ b/client/src/views/settings/fields/phone-number-preferred-country-list.js @@ -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; diff --git a/composer.json b/composer.json index 2deb83b455..458703346d 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/composer.lock b/composer.lock index 4de61d6e37..484d2907c2 100644 --- a/composer.lock +++ b/composer.lock @@ -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", diff --git a/frontend/less/espo/custom.less b/frontend/less/espo/custom.less index 6ded648fe3..06fe79023c 100644 --- a/frontend/less/espo/custom.less +++ b/frontend/less/espo/custom.less @@ -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"; diff --git a/frontend/less/espo/misc.less b/frontend/less/espo/misc.less index b9502923a3..cac2695edb 100644 --- a/frontend/less/espo/misc.less +++ b/frontend/less/espo/misc.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"; diff --git a/frontend/less/espo/misc/intl-tel-input/intlTelInput.less b/frontend/less/espo/misc/intl-tel-input/intlTelInput.less new file mode 100644 index 0000000000..47e76aefd3 --- /dev/null +++ b/frontend/less/espo/misc/intl-tel-input/intlTelInput.less @@ -0,0 +1,1278 @@ +.iti { + position: relative; + display: inline-block; +} +.iti * { + box-sizing: border-box; +} +.iti__hide { + display: none; +} +.iti__v-hide { + visibility: hidden; +} +.iti input, +.iti input[type=text], +.iti input[type=tel] { + position: relative; + z-index: 0; + margin-top: 0 !important; + margin-bottom: 0 !important; + padding-right: 36px; + margin-right: 0; +} +.iti__flag-container { + position: absolute; + top: 0; + bottom: 0; + right: 0; + padding: 1px; +} +.iti__selected-flag { + z-index: 1; + position: relative; + display: flex; + align-items: center; + height: 100%; + padding: 0 6px 0 8px; +} +.iti__arrow { + margin-left: 6px; + width: 0; + height: 0; + border-left: 3px solid transparent; + border-right: 3px solid transparent; + border-top: 4px solid #555; +} +[dir=rtl] .iti__arrow { + margin-right: 6px; + margin-left: 0; +} +.iti__arrow--up { + border-top: none; + border-bottom: 4px solid #555; +} +.iti__country-list { + position: absolute; + z-index: 2; + list-style: none; + padding: 0; + margin: 0 0 0 -1px; + box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.2); + background-color: white; + border: 1px solid #ccc; + white-space: nowrap; + max-height: 200px; + overflow-y: scroll; + -webkit-overflow-scrolling: touch; +} +.iti__country-list--dropup { + bottom: 100%; + margin-bottom: -1px; +} +@media (max-width: 500px) { + .iti__country-list { + white-space: normal; + } +} +.iti__flag-box { + display: inline-block; + width: 20px; +} +.iti__divider { + padding-bottom: 5px; + margin-bottom: 5px; + border-bottom: 1px solid #ccc; +} +.iti__country { + display: flex; + align-items: center; + padding: 5px 10px; + outline: none; +} +.iti__dial-code { + color: #999; +} +.iti__country.iti__highlight { + background-color: rgba(0, 0, 0, 0.05); +} +.iti__flag-box, .iti__country-name { + margin-right: 6px; +} +[dir=rtl] .iti__flag-box, [dir=rtl] .iti__country-name { + margin-right: 0; + margin-left: 6px; +} +.iti--allow-dropdown input, +.iti--allow-dropdown input[type=text], +.iti--allow-dropdown input[type=tel], .iti--separate-dial-code input, +.iti--separate-dial-code input[type=text], +.iti--separate-dial-code input[type=tel] { + padding-right: 6px; + padding-left: 52px; + margin-left: 0; +} +[dir=rtl] .iti--allow-dropdown input, +[dir=rtl] .iti--allow-dropdown input[type=text], +[dir=rtl] .iti--allow-dropdown input[type=tel], [dir=rtl] .iti--separate-dial-code input, +[dir=rtl] .iti--separate-dial-code input[type=text], +[dir=rtl] .iti--separate-dial-code input[type=tel] { + padding-right: 52px; + padding-left: 6px; + margin-right: 0; +} +.iti--allow-dropdown .iti__flag-container, .iti--separate-dial-code .iti__flag-container { + right: auto; + left: 0; +} +[dir=rtl] .iti--allow-dropdown .iti__flag-container, [dir=rtl] .iti--separate-dial-code .iti__flag-container { + right: 0; + left: auto; +} +.iti--allow-dropdown .iti__flag-container:hover { + cursor: pointer; +} +.iti--allow-dropdown .iti__flag-container:hover .iti__selected-flag { + background-color: rgba(0, 0, 0, 0.05); +} +.iti--allow-dropdown input[disabled] + .iti__flag-container:hover, +.iti--allow-dropdown input[readonly] + .iti__flag-container:hover { + cursor: default; +} +.iti--allow-dropdown input[disabled] + .iti__flag-container:hover .iti__selected-flag, +.iti--allow-dropdown input[readonly] + .iti__flag-container:hover .iti__selected-flag { + background-color: transparent; +} +.iti--separate-dial-code .iti__selected-flag { + background-color: rgba(0, 0, 0, 0.05); +} +.iti--separate-dial-code.iti--show-flags .iti__selected-dial-code { + margin-left: 6px; +} +[dir=rtl] .iti--separate-dial-code.iti--show-flags .iti__selected-dial-code { + margin-left: 0; + margin-right: 6px; +} +.iti--container { + position: absolute; + top: -1000px; + left: -1000px; + z-index: 1060; + padding: 1px; +} +.iti--container:hover { + cursor: pointer; +} + +.iti-mobile .iti--container { + top: 30px; + bottom: 30px; + left: 30px; + right: 30px; + position: fixed; +} +.iti-mobile .iti__country-list { + max-height: 100%; + width: 100%; +} +.iti-mobile .iti__country { + padding: 10px 10px; + line-height: 1.5em; +} + +.iti__flag { + width: 20px; +} +.iti__flag.iti__be { + width: 18px; +} +.iti__flag.iti__ch { + width: 15px; +} +.iti__flag.iti__mc { + width: 19px; +} +.iti__flag.iti__ne { + width: 18px; +} +.iti__flag.iti__np { + width: 13px; +} +.iti__flag.iti__va { + width: 15px; +} +@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { + .iti__flag { + background-size: 5762px 15px; + } +} +.iti__flag.iti__ac { + height: 10px; + background-position: 0px 0px; +} +.iti__flag.iti__ad { + height: 14px; + background-position: -22px 0px; +} +.iti__flag.iti__ae { + height: 10px; + background-position: -44px 0px; +} +.iti__flag.iti__af { + height: 14px; + background-position: -66px 0px; +} +.iti__flag.iti__ag { + height: 14px; + background-position: -88px 0px; +} +.iti__flag.iti__ai { + height: 10px; + background-position: -110px 0px; +} +.iti__flag.iti__al { + height: 15px; + background-position: -132px 0px; +} +.iti__flag.iti__am { + height: 10px; + background-position: -154px 0px; +} +.iti__flag.iti__ao { + height: 14px; + background-position: -176px 0px; +} +.iti__flag.iti__aq { + height: 14px; + background-position: -198px 0px; +} +.iti__flag.iti__ar { + height: 13px; + background-position: -220px 0px; +} +.iti__flag.iti__as { + height: 10px; + background-position: -242px 0px; +} +.iti__flag.iti__at { + height: 14px; + background-position: -264px 0px; +} +.iti__flag.iti__au { + height: 10px; + background-position: -286px 0px; +} +.iti__flag.iti__aw { + height: 14px; + background-position: -308px 0px; +} +.iti__flag.iti__ax { + height: 13px; + background-position: -330px 0px; +} +.iti__flag.iti__az { + height: 10px; + background-position: -352px 0px; +} +.iti__flag.iti__ba { + height: 10px; + background-position: -374px 0px; +} +.iti__flag.iti__bb { + height: 14px; + background-position: -396px 0px; +} +.iti__flag.iti__bd { + height: 12px; + background-position: -418px 0px; +} +.iti__flag.iti__be { + height: 15px; + background-position: -440px 0px; +} +.iti__flag.iti__bf { + height: 14px; + background-position: -460px 0px; +} +.iti__flag.iti__bg { + height: 12px; + background-position: -482px 0px; +} +.iti__flag.iti__bh { + height: 12px; + background-position: -504px 0px; +} +.iti__flag.iti__bi { + height: 12px; + background-position: -526px 0px; +} +.iti__flag.iti__bj { + height: 14px; + background-position: -548px 0px; +} +.iti__flag.iti__bl { + height: 14px; + background-position: -570px 0px; +} +.iti__flag.iti__bm { + height: 10px; + background-position: -592px 0px; +} +.iti__flag.iti__bn { + height: 10px; + background-position: -614px 0px; +} +.iti__flag.iti__bo { + height: 14px; + background-position: -636px 0px; +} +.iti__flag.iti__bq { + height: 14px; + background-position: -658px 0px; +} +.iti__flag.iti__br { + height: 14px; + background-position: -680px 0px; +} +.iti__flag.iti__bs { + height: 10px; + background-position: -702px 0px; +} +.iti__flag.iti__bt { + height: 14px; + background-position: -724px 0px; +} +.iti__flag.iti__bv { + height: 15px; + background-position: -746px 0px; +} +.iti__flag.iti__bw { + height: 14px; + background-position: -768px 0px; +} +.iti__flag.iti__by { + height: 10px; + background-position: -790px 0px; +} +.iti__flag.iti__bz { + height: 12px; + background-position: -812px 0px; +} +.iti__flag.iti__ca { + height: 10px; + background-position: -834px 0px; +} +.iti__flag.iti__cc { + height: 10px; + background-position: -856px 0px; +} +.iti__flag.iti__cd { + height: 15px; + background-position: -878px 0px; +} +.iti__flag.iti__cf { + height: 14px; + background-position: -900px 0px; +} +.iti__flag.iti__cg { + height: 14px; + background-position: -922px 0px; +} +.iti__flag.iti__ch { + height: 15px; + background-position: -944px 0px; +} +.iti__flag.iti__ci { + height: 14px; + background-position: -961px 0px; +} +.iti__flag.iti__ck { + height: 10px; + background-position: -983px 0px; +} +.iti__flag.iti__cl { + height: 14px; + background-position: -1005px 0px; +} +.iti__flag.iti__cm { + height: 14px; + background-position: -1027px 0px; +} +.iti__flag.iti__cn { + height: 14px; + background-position: -1049px 0px; +} +.iti__flag.iti__co { + height: 14px; + background-position: -1071px 0px; +} +.iti__flag.iti__cp { + height: 14px; + background-position: -1093px 0px; +} +.iti__flag.iti__cq { + height: 12px; + background-position: -1115px 0px; +} +.iti__flag.iti__cr { + height: 12px; + background-position: -1137px 0px; +} +.iti__flag.iti__cu { + height: 10px; + background-position: -1159px 0px; +} +.iti__flag.iti__cv { + height: 12px; + background-position: -1181px 0px; +} +.iti__flag.iti__cw { + height: 14px; + background-position: -1203px 0px; +} +.iti__flag.iti__cx { + height: 10px; + background-position: -1225px 0px; +} +.iti__flag.iti__cy { + height: 14px; + background-position: -1247px 0px; +} +.iti__flag.iti__cz { + height: 14px; + background-position: -1269px 0px; +} +.iti__flag.iti__de { + height: 12px; + background-position: -1291px 0px; +} +.iti__flag.iti__dg { + height: 10px; + background-position: -1313px 0px; +} +.iti__flag.iti__dj { + height: 14px; + background-position: -1335px 0px; +} +.iti__flag.iti__dk { + height: 15px; + background-position: -1357px 0px; +} +.iti__flag.iti__dm { + height: 10px; + background-position: -1379px 0px; +} +.iti__flag.iti__do { + height: 14px; + background-position: -1401px 0px; +} +.iti__flag.iti__dz { + height: 14px; + background-position: -1423px 0px; +} +.iti__flag.iti__ea { + height: 14px; + background-position: -1445px 0px; +} +.iti__flag.iti__ec { + height: 14px; + background-position: -1467px 0px; +} +.iti__flag.iti__ee { + height: 13px; + background-position: -1489px 0px; +} +.iti__flag.iti__eg { + height: 14px; + background-position: -1511px 0px; +} +.iti__flag.iti__eh { + height: 10px; + background-position: -1533px 0px; +} +.iti__flag.iti__er { + height: 10px; + background-position: -1555px 0px; +} +.iti__flag.iti__es { + height: 14px; + background-position: -1577px 0px; +} +.iti__flag.iti__et { + height: 10px; + background-position: -1599px 0px; +} +.iti__flag.iti__eu { + height: 14px; + background-position: -1621px 0px; +} +.iti__flag.iti__ez { + height: 14px; + background-position: -1643px 0px; +} +.iti__flag.iti__fi { + height: 12px; + background-position: -1665px 0px; +} +.iti__flag.iti__fj { + height: 10px; + background-position: -1687px 0px; +} +.iti__flag.iti__fk { + height: 10px; + background-position: -1709px 0px; +} +.iti__flag.iti__fm { + height: 11px; + background-position: -1731px 0px; +} +.iti__flag.iti__fo { + height: 15px; + background-position: -1753px 0px; +} +.iti__flag.iti__fr { + height: 14px; + background-position: -1775px 0px; +} +.iti__flag.iti__fx { + height: 14px; + background-position: -1797px 0px; +} +.iti__flag.iti__ga { + height: 15px; + background-position: -1819px 0px; +} +.iti__flag.iti__gb { + height: 10px; + background-position: -1841px 0px; +} +.iti__flag.iti__gd { + height: 12px; + background-position: -1863px 0px; +} +.iti__flag.iti__ge { + height: 14px; + background-position: -1885px 0px; +} +.iti__flag.iti__gf { + height: 14px; + background-position: -1907px 0px; +} +.iti__flag.iti__gg { + height: 14px; + background-position: -1929px 0px; +} +.iti__flag.iti__gh { + height: 14px; + background-position: -1951px 0px; +} +.iti__flag.iti__gi { + height: 10px; + background-position: -1973px 0px; +} +.iti__flag.iti__gl { + height: 14px; + background-position: -1995px 0px; +} +.iti__flag.iti__gm { + height: 14px; + background-position: -2017px 0px; +} +.iti__flag.iti__gn { + height: 14px; + background-position: -2039px 0px; +} +.iti__flag.iti__gp { + height: 14px; + background-position: -2061px 0px; +} +.iti__flag.iti__gq { + height: 14px; + background-position: -2083px 0px; +} +.iti__flag.iti__gr { + height: 14px; + background-position: -2105px 0px; +} +.iti__flag.iti__gs { + height: 10px; + background-position: -2127px 0px; +} +.iti__flag.iti__gt { + height: 13px; + background-position: -2149px 0px; +} +.iti__flag.iti__gu { + height: 11px; + background-position: -2171px 0px; +} +.iti__flag.iti__gw { + height: 10px; + background-position: -2193px 0px; +} +.iti__flag.iti__gy { + height: 12px; + background-position: -2215px 0px; +} +.iti__flag.iti__hk { + height: 14px; + background-position: -2237px 0px; +} +.iti__flag.iti__hm { + height: 10px; + background-position: -2259px 0px; +} +.iti__flag.iti__hn { + height: 10px; + background-position: -2281px 0px; +} +.iti__flag.iti__hr { + height: 10px; + background-position: -2303px 0px; +} +.iti__flag.iti__ht { + height: 12px; + background-position: -2325px 0px; +} +.iti__flag.iti__hu { + height: 10px; + background-position: -2347px 0px; +} +.iti__flag.iti__ic { + height: 14px; + background-position: -2369px 0px; +} +.iti__flag.iti__id { + height: 14px; + background-position: -2391px 0px; +} +.iti__flag.iti__ie { + height: 10px; + background-position: -2413px 0px; +} +.iti__flag.iti__il { + height: 15px; + background-position: -2435px 0px; +} +.iti__flag.iti__im { + height: 10px; + background-position: -2457px 0px; +} +.iti__flag.iti__in { + height: 14px; + background-position: -2479px 0px; +} +.iti__flag.iti__io { + height: 10px; + background-position: -2501px 0px; +} +.iti__flag.iti__iq { + height: 14px; + background-position: -2523px 0px; +} +.iti__flag.iti__ir { + height: 12px; + background-position: -2545px 0px; +} +.iti__flag.iti__is { + height: 15px; + background-position: -2567px 0px; +} +.iti__flag.iti__it { + height: 14px; + background-position: -2589px 0px; +} +.iti__flag.iti__je { + height: 12px; + background-position: -2611px 0px; +} +.iti__flag.iti__jm { + height: 10px; + background-position: -2633px 0px; +} +.iti__flag.iti__jo { + height: 10px; + background-position: -2655px 0px; +} +.iti__flag.iti__jp { + height: 14px; + background-position: -2677px 0px; +} +.iti__flag.iti__ke { + height: 14px; + background-position: -2699px 0px; +} +.iti__flag.iti__kg { + height: 12px; + background-position: -2721px 0px; +} +.iti__flag.iti__kh { + height: 13px; + background-position: -2743px 0px; +} +.iti__flag.iti__ki { + height: 10px; + background-position: -2765px 0px; +} +.iti__flag.iti__km { + height: 12px; + background-position: -2787px 0px; +} +.iti__flag.iti__kn { + height: 14px; + background-position: -2809px 0px; +} +.iti__flag.iti__kp { + height: 10px; + background-position: -2831px 0px; +} +.iti__flag.iti__kr { + height: 14px; + background-position: -2853px 0px; +} +.iti__flag.iti__kw { + height: 10px; + background-position: -2875px 0px; +} +.iti__flag.iti__ky { + height: 10px; + background-position: -2897px 0px; +} +.iti__flag.iti__kz { + height: 10px; + background-position: -2919px 0px; +} +.iti__flag.iti__la { + height: 14px; + background-position: -2941px 0px; +} +.iti__flag.iti__lb { + height: 14px; + background-position: -2963px 0px; +} +.iti__flag.iti__lc { + height: 10px; + background-position: -2985px 0px; +} +.iti__flag.iti__li { + height: 12px; + background-position: -3007px 0px; +} +.iti__flag.iti__lk { + height: 10px; + background-position: -3029px 0px; +} +.iti__flag.iti__lr { + height: 11px; + background-position: -3051px 0px; +} +.iti__flag.iti__ls { + height: 14px; + background-position: -3073px 0px; +} +.iti__flag.iti__lt { + height: 12px; + background-position: -3095px 0px; +} +.iti__flag.iti__lu { + height: 12px; + background-position: -3117px 0px; +} +.iti__flag.iti__lv { + height: 10px; + background-position: -3139px 0px; +} +.iti__flag.iti__ly { + height: 10px; + background-position: -3161px 0px; +} +.iti__flag.iti__ma { + height: 14px; + background-position: -3183px 0px; +} +.iti__flag.iti__mc { + height: 15px; + background-position: -3205px 0px; +} +.iti__flag.iti__md { + height: 10px; + background-position: -3226px 0px; +} +.iti__flag.iti__me { + height: 10px; + background-position: -3248px 0px; +} +.iti__flag.iti__mf { + height: 14px; + background-position: -3270px 0px; +} +.iti__flag.iti__mg { + height: 14px; + background-position: -3292px 0px; +} +.iti__flag.iti__mh { + height: 11px; + background-position: -3314px 0px; +} +.iti__flag.iti__mk { + height: 10px; + background-position: -3336px 0px; +} +.iti__flag.iti__ml { + height: 14px; + background-position: -3358px 0px; +} +.iti__flag.iti__mm { + height: 14px; + background-position: -3380px 0px; +} +.iti__flag.iti__mn { + height: 10px; + background-position: -3402px 0px; +} +.iti__flag.iti__mo { + height: 14px; + background-position: -3424px 0px; +} +.iti__flag.iti__mp { + height: 10px; + background-position: -3446px 0px; +} +.iti__flag.iti__mq { + height: 14px; + background-position: -3468px 0px; +} +.iti__flag.iti__mr { + height: 14px; + background-position: -3490px 0px; +} +.iti__flag.iti__ms { + height: 10px; + background-position: -3512px 0px; +} +.iti__flag.iti__mt { + height: 14px; + background-position: -3534px 0px; +} +.iti__flag.iti__mu { + height: 14px; + background-position: -3556px 0px; +} +.iti__flag.iti__mv { + height: 14px; + background-position: -3578px 0px; +} +.iti__flag.iti__mw { + height: 14px; + background-position: -3600px 0px; +} +.iti__flag.iti__mx { + height: 12px; + background-position: -3622px 0px; +} +.iti__flag.iti__my { + height: 10px; + background-position: -3644px 0px; +} +.iti__flag.iti__mz { + height: 14px; + background-position: -3666px 0px; +} +.iti__flag.iti__na { + height: 14px; + background-position: -3688px 0px; +} +.iti__flag.iti__nc { + height: 10px; + background-position: -3710px 0px; +} +.iti__flag.iti__ne { + height: 15px; + background-position: -3732px 0px; +} +.iti__flag.iti__nf { + height: 10px; + background-position: -3752px 0px; +} +.iti__flag.iti__ng { + height: 10px; + background-position: -3774px 0px; +} +.iti__flag.iti__ni { + height: 12px; + background-position: -3796px 0px; +} +.iti__flag.iti__nl { + height: 14px; + background-position: -3818px 0px; +} +.iti__flag.iti__no { + height: 15px; + background-position: -3840px 0px; +} +.iti__flag.iti__np { + height: 15px; + background-position: -3862px 0px; +} +.iti__flag.iti__nr { + height: 10px; + background-position: -3877px 0px; +} +.iti__flag.iti__nu { + height: 10px; + background-position: -3899px 0px; +} +.iti__flag.iti__nz { + height: 10px; + background-position: -3921px 0px; +} +.iti__flag.iti__om { + height: 10px; + background-position: -3943px 0px; +} +.iti__flag.iti__pa { + height: 14px; + background-position: -3965px 0px; +} +.iti__flag.iti__pe { + height: 14px; + background-position: -3987px 0px; +} +.iti__flag.iti__pf { + height: 14px; + background-position: -4009px 0px; +} +.iti__flag.iti__pg { + height: 15px; + background-position: -4031px 0px; +} +.iti__flag.iti__ph { + height: 10px; + background-position: -4053px 0px; +} +.iti__flag.iti__pk { + height: 14px; + background-position: -4075px 0px; +} +.iti__flag.iti__pl { + height: 13px; + background-position: -4097px 0px; +} +.iti__flag.iti__pm { + height: 14px; + background-position: -4119px 0px; +} +.iti__flag.iti__pn { + height: 10px; + background-position: -4141px 0px; +} +.iti__flag.iti__pr { + height: 14px; + background-position: -4163px 0px; +} +.iti__flag.iti__ps { + height: 10px; + background-position: -4185px 0px; +} +.iti__flag.iti__pt { + height: 14px; + background-position: -4207px 0px; +} +.iti__flag.iti__pw { + height: 13px; + background-position: -4229px 0px; +} +.iti__flag.iti__py { + height: 11px; + background-position: -4251px 0px; +} +.iti__flag.iti__qa { + height: 8px; + background-position: -4273px 0px; +} +.iti__flag.iti__re { + height: 14px; + background-position: -4295px 0px; +} +.iti__flag.iti__ro { + height: 14px; + background-position: -4317px 0px; +} +.iti__flag.iti__rs { + height: 14px; + background-position: -4339px 0px; +} +.iti__flag.iti__ru { + height: 14px; + background-position: -4361px 0px; +} +.iti__flag.iti__rw { + height: 14px; + background-position: -4383px 0px; +} +.iti__flag.iti__sa { + height: 14px; + background-position: -4405px 0px; +} +.iti__flag.iti__sb { + height: 10px; + background-position: -4427px 0px; +} +.iti__flag.iti__sc { + height: 10px; + background-position: -4449px 0px; +} +.iti__flag.iti__sd { + height: 10px; + background-position: -4471px 0px; +} +.iti__flag.iti__se { + height: 13px; + background-position: -4493px 0px; +} +.iti__flag.iti__sg { + height: 14px; + background-position: -4515px 0px; +} +.iti__flag.iti__sh { + height: 10px; + background-position: -4537px 0px; +} +.iti__flag.iti__si { + height: 10px; + background-position: -4559px 0px; +} +.iti__flag.iti__sj { + height: 15px; + background-position: -4581px 0px; +} +.iti__flag.iti__sk { + height: 14px; + background-position: -4603px 0px; +} +.iti__flag.iti__sl { + height: 14px; + background-position: -4625px 0px; +} +.iti__flag.iti__sm { + height: 15px; + background-position: -4647px 0px; +} +.iti__flag.iti__sn { + height: 14px; + background-position: -4669px 0px; +} +.iti__flag.iti__so { + height: 14px; + background-position: -4691px 0px; +} +.iti__flag.iti__sr { + height: 14px; + background-position: -4713px 0px; +} +.iti__flag.iti__ss { + height: 10px; + background-position: -4735px 0px; +} +.iti__flag.iti__st { + height: 10px; + background-position: -4757px 0px; +} +.iti__flag.iti__su { + height: 10px; + background-position: -4779px 0px; +} +.iti__flag.iti__sv { + height: 12px; + background-position: -4801px 0px; +} +.iti__flag.iti__sx { + height: 14px; + background-position: -4823px 0px; +} +.iti__flag.iti__sy { + height: 14px; + background-position: -4845px 0px; +} +.iti__flag.iti__sz { + height: 14px; + background-position: -4867px 0px; +} +.iti__flag.iti__ta { + height: 10px; + background-position: -4889px 0px; +} +.iti__flag.iti__tc { + height: 10px; + background-position: -4911px 0px; +} +.iti__flag.iti__td { + height: 14px; + background-position: -4933px 0px; +} +.iti__flag.iti__tf { + height: 14px; + background-position: -4955px 0px; +} +.iti__flag.iti__tg { + height: 13px; + background-position: -4977px 0px; +} +.iti__flag.iti__th { + height: 14px; + background-position: -4999px 0px; +} +.iti__flag.iti__tj { + height: 10px; + background-position: -5021px 0px; +} +.iti__flag.iti__tk { + height: 10px; + background-position: -5043px 0px; +} +.iti__flag.iti__tl { + height: 10px; + background-position: -5065px 0px; +} +.iti__flag.iti__tm { + height: 14px; + background-position: -5087px 0px; +} +.iti__flag.iti__tn { + height: 14px; + background-position: -5109px 0px; +} +.iti__flag.iti__to { + height: 10px; + background-position: -5131px 0px; +} +.iti__flag.iti__tr { + height: 14px; + background-position: -5153px 0px; +} +.iti__flag.iti__tt { + height: 12px; + background-position: -5175px 0px; +} +.iti__flag.iti__tv { + height: 10px; + background-position: -5197px 0px; +} +.iti__flag.iti__tw { + height: 14px; + background-position: -5219px 0px; +} +.iti__flag.iti__tz { + height: 14px; + background-position: -5241px 0px; +} +.iti__flag.iti__ua { + height: 14px; + background-position: -5263px 0px; +} +.iti__flag.iti__ug { + height: 14px; + background-position: -5285px 0px; +} +.iti__flag.iti__uk { + height: 10px; + background-position: -5307px 0px; +} +.iti__flag.iti__um { + height: 11px; + background-position: -5329px 0px; +} +.iti__flag.iti__un { + height: 14px; + background-position: -5351px 0px; +} +.iti__flag.iti__us { + height: 11px; + background-position: -5373px 0px; +} +.iti__flag.iti__uy { + height: 14px; + background-position: -5395px 0px; +} +.iti__flag.iti__uz { + height: 10px; + background-position: -5417px 0px; +} +.iti__flag.iti__va { + height: 15px; + background-position: -5439px 0px; +} +.iti__flag.iti__vc { + height: 14px; + background-position: -5456px 0px; +} +.iti__flag.iti__ve { + height: 14px; + background-position: -5478px 0px; +} +.iti__flag.iti__vg { + height: 10px; + background-position: -5500px 0px; +} +.iti__flag.iti__vi { + height: 14px; + background-position: -5522px 0px; +} +.iti__flag.iti__vn { + height: 14px; + background-position: -5544px 0px; +} +.iti__flag.iti__vu { + height: 12px; + background-position: -5566px 0px; +} +.iti__flag.iti__wf { + height: 14px; + background-position: -5588px 0px; +} +.iti__flag.iti__ws { + height: 10px; + background-position: -5610px 0px; +} +.iti__flag.iti__xk { + height: 15px; + background-position: -5632px 0px; +} +.iti__flag.iti__ye { + height: 14px; + background-position: -5654px 0px; +} +.iti__flag.iti__yt { + height: 14px; + background-position: -5676px 0px; +} +.iti__flag.iti__za { + height: 14px; + background-position: -5698px 0px; +} +.iti__flag.iti__zm { + height: 14px; + background-position: -5720px 0px; +} +.iti__flag.iti__zw { + height: 10px; + background-position: -5742px 0px; +} + +.iti__flag { + height: 15px; + box-shadow: 0px 0px 1px 0px #888; + background-image: url("../img/flags.png?1"); + background-repeat: no-repeat; + background-color: #dbdbdb; + background-position: 20px 0; +} +@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { + .iti__flag { + background-image: url("../img/flags@2x.png?1"); + } +} + +.iti__flag.iti__np { + background-color: transparent; +} \ No newline at end of file diff --git a/frontend/libs.json b/frontend/libs.json index f59eab6be0..6f36fa85df 100644 --- a/frontend/libs.json +++ b/frontend/libs.json @@ -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", diff --git a/package-lock.json b/package-lock.json index 25348a4989..a534bf06ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index d929e4a2c7..79793ebcaf 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/upgrades/8.1/data.json b/upgrades/8.1/data.json new file mode 100644 index 0000000000..60ec91eb56 --- /dev/null +++ b/upgrades/8.1/data.json @@ -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" + ] + } +} diff --git a/upgrades/8.1/scripts/AfterUpgrade.php b/upgrades/8.1/scripts/AfterUpgrade.php new file mode 100644 index 0000000000..215b868c6c --- /dev/null +++ b/upgrades/8.1/scripts/AfterUpgrade.php @@ -0,0 +1,48 @@ +getByClass(InjectableFactory::class) + ->create(ConfigWriter::class); + + $configWriter->setMultiple([ + 'phoneNumberNumericSearch' => false, + 'phoneNumberInternational' => false, + ]); + + $configWriter->save(); + } +}