diff --git a/CHANGELOG b/CHANGELOG index 7d7315f9d4f..70f5189191b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ UNRELEASED CHANGES: * Add ability to archive a contact * Add right-click support on contact list +* Standardize phonenumber format while importing vCard * Set currency and timezone for new users * Fix settings' sidebar links and change security icon * Fix CSV import diff --git a/app/Helpers/LocaleHelper.php b/app/Helpers/LocaleHelper.php index b02b0cebb35..ad6aa9f55b0 100644 --- a/app/Helpers/LocaleHelper.php +++ b/app/Helpers/LocaleHelper.php @@ -4,6 +4,7 @@ use Matriphe\ISO639\ISO639; use Illuminate\Support\Facades\Auth; +use libphonenumber\PhoneNumberFormat; class LocaleHelper { @@ -104,4 +105,32 @@ public static function getLocaleAlpha($locale) return $lang; } + + /** + * Format phone number by country. + * + * @param string $tel + * @param $iso + * @param int $format + * + * @return null | string + */ + public static function formatTelephoneNumberByISO(string $tel, $iso, int $format = PhoneNumberFormat::INTERNATIONAL) + { + if (empty($iso)) { + return $tel; + } + + try { + $phoneUtil = \libphonenumber\PhoneNumberUtil::getInstance(); + + $phoneInstance = $phoneUtil->parse($tel, strtoupper($iso)); + + $tel = $phoneUtil->format($phoneInstance, $format); + } catch (\libphonenumber\NumberParseException $e) { + // Do nothing if the number cannot be parsed successfully + } + + return $tel; + } } diff --git a/app/Helpers/VCardHelper.php b/app/Helpers/VCardHelper.php index 989eae4761b..8eab259c9f9 100644 --- a/app/Helpers/VCardHelper.php +++ b/app/Helpers/VCardHelper.php @@ -116,4 +116,22 @@ public static function addAddressToVCard(Contact $contact, VCard $vCard) return $vCard; } + + /** + * Get country model object from given VCard file. + * + * @param \Sabre\VObject\Component\VCard $VCard + * + * @return null | string + */ + public static function getCountryISOFromSabreVCard(\Sabre\VObject\Component\VCard $VCard) + { + $VCardAddress = $VCard->ADR; + + if (empty($VCardAddress)) { + return; + } + + return CountriesHelper::find($VCardAddress->getParts()[6]); + } } diff --git a/app/Models/Account/ImportJob.php b/app/Models/Account/ImportJob.php index 74887cf3a96..4037382b7bf 100644 --- a/app/Models/Account/ImportJob.php +++ b/app/Models/Account/ImportJob.php @@ -5,6 +5,8 @@ use Exception; use App\Models\User\User; use Sabre\VObject\Reader; +use App\Helpers\VCardHelper; +use App\Helpers\LocaleHelper; use App\Models\Contact\Gender; use App\Models\Contact\Address; use App\Models\Contact\Contact; @@ -537,6 +539,12 @@ public function importTel(Contact $contact): void } foreach ($this->currentEntry->TEL as $tel) { + $tel = (string) $this->currentEntry->TEL; + + $countryISO = VCardHelper::getCountryISOFromSabreVCard($this->currentEntry); + + $tel = LocaleHelper::formatTelephoneNumberByISO($tel, $countryISO); + ContactField::firstOrCreate([ 'account_id' => $contact->account_id, 'contact_id' => $contact->id, diff --git a/app/Traits/VCardImporter.php b/app/Traits/VCardImporter.php index 7fb51c14546..b5474b1d5ba 100644 --- a/app/Traits/VCardImporter.php +++ b/app/Traits/VCardImporter.php @@ -3,6 +3,8 @@ namespace App\Traits; use Sabre\VObject\Reader; +use App\Helpers\VCardHelper; +use App\Helpers\LocaleHelper; use App\Models\Contact\Gender; use App\Models\Contact\Address; use App\Models\Contact\Contact; @@ -126,11 +128,16 @@ private function vCardToContact($vcard, $account_id, $gender_id) } if (! is_null($this->formatValue($vcard->TEL))) { + $tel = (string) $vcard->TEL; + + $countryISO = VCardHelper::getCountryISOFromSabreVCard($vcard); + $tel = LocaleHelper::formatTelephoneNumberByISO($tel, $countryISO); + // Saves the phone number $contactField = new ContactField; $contactField->contact_id = $contact->id; $contactField->account_id = $contact->account_id; - $contactField->data = $this->formatValue($vcard->TEL); + $contactField->data = $this->formatValue($tel); $contactField->contact_field_type_id = $this->contactFieldPhoneId(); $contactField->save(); } diff --git a/composer.json b/composer.json index 91d4598030f..2d1e4c7c0b9 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "doctrine/dbal": "^2.5", "erusev/parsedown": "~1.7", "fideloper/proxy": "^4.0", + "giggsey/libphonenumber-for-php": "^8.9", "guzzlehttp/guzzle": "^6.2", "intervention/image": "^2.3", "ircop/antiflood": "^0.1.4", diff --git a/composer.lock b/composer.lock index 39a7b6da32d..9239b9402b0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "94804e5b8c3726b5119aa7c6b4d02dc9", + "content-hash": "c1ea9284fe06c55e1c67234cfe57083e", "packages": [ { "name": "aws/aws-sdk-php", - "version": "3.69.2", + "version": "3.69.16", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "65c88e074dffe4cb25b840c154b2f39564b39287" + "reference": "eda89d207e40f3abb0b5e2fb1fabcebe668116fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/65c88e074dffe4cb25b840c154b2f39564b39287", - "reference": "65c88e074dffe4cb25b840c154b2f39564b39287", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/eda89d207e40f3abb0b5e2fb1fabcebe668116fb", + "reference": "eda89d207e40f3abb0b5e2fb1fabcebe668116fb", "shasum": "" }, "require": { @@ -25,7 +25,7 @@ "ext-pcre": "*", "ext-simplexml": "*", "ext-spl": "*", - "guzzlehttp/guzzle": "^5.3.1|^6.2.1", + "guzzlehttp/guzzle": "^5.3.3|^6.2.1", "guzzlehttp/promises": "~1.0", "guzzlehttp/psr7": "^1.4.1", "mtdowling/jmespath.php": "~2.2", @@ -87,7 +87,7 @@ "s3", "sdk" ], - "time": "2018-10-08T20:05:34+00:00" + "time": "2018-10-26T20:05:18+00:00" }, { "name": "bacon/bacon-qr-code", @@ -291,16 +291,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "46afded9720f40b9dc63542af4e3e43a1177acb0" + "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/46afded9720f40b9dc63542af4e3e43a1177acb0", - "reference": "46afded9720f40b9dc63542af4e3e43a1177acb0", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/8afa52cd417f4ec417b4bfe86b68106538a87660", + "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660", "shasum": "" }, "require": { @@ -343,7 +343,7 @@ "ssl", "tls" ], - "time": "2018-08-08T08:57:40+00:00" + "time": "2018-10-18T06:09:13+00:00" }, { "name": "creativeorange/gravatar", @@ -1211,6 +1211,123 @@ ], "time": "2018-01-18T21:30:24+00:00" }, + { + "name": "giggsey/libphonenumber-for-php", + "version": "8.9.16", + "source": { + "type": "git", + "url": "https://github.com/giggsey/libphonenumber-for-php.git", + "reference": "04425318e27b3a41ef9910fc0a9e43c9d3c3e725" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/04425318e27b3a41ef9910fc0a9e43c9d3c3e725", + "reference": "04425318e27b3a41ef9910fc0a9e43c9d3c3e725", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "giggsey/locale": "^1.2", + "php": ">=5.3.2" + }, + "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", + "phpunit/phpunit": "^4.8.36|^5.0", + "symfony/console": "^2.8|^3.0" + }, + "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" + ], + "time": "2018-10-18T08:12:16+00:00" + }, + { + "name": "giggsey/locale", + "version": "1.6", + "source": { + "type": "git", + "url": "https://github.com/giggsey/Locale.git", + "reference": "da6845720b5d104d319d7e84576f54e44dd9e4f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/giggsey/Locale/zipball/da6845720b5d104d319d7e84576f54e44dd9e4f5", + "reference": "da6845720b5d104d319d7e84576f54e44dd9e4f5", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "pear/pear-core-minimal": "^1.9", + "pear/pear_exception": "^1.0", + "pear/versioncontrol_git": "^0.5", + "phing/phing": "~2.7", + "phpunit/phpunit": "^4.8|^5.0", + "satooshi/php-coveralls": "^1.0", + "symfony/console": "^2.8|^3.0|^4.0", + "symfony/filesystem": "^2.8|^3.0|^4.0", + "symfony/finder": "^2.8|^3.0|^4.0", + "symfony/process": "^2.8|^3.0|^4.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": "http://giggsey.com/" + } + ], + "description": "Locale functions required by libphonenumber-for-php", + "time": "2018-10-18T07:17:52+00:00" + }, { "name": "graham-campbell/manager", "version": "v4.1.0", @@ -3518,16 +3635,16 @@ }, { "name": "ok/ipstack-client", - "version": "1.2", + "version": "1.3", "source": { "type": "git", "url": "https://github.com/GitHubHubus/ipstack-client.git", - "reference": "0c0f3927a503cb01515b8b2dd6bc353493ab7076" + "reference": "6891193164428dbb50bcfe7a64ede9960bfed839" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GitHubHubus/ipstack-client/zipball/0c0f3927a503cb01515b8b2dd6bc353493ab7076", - "reference": "0c0f3927a503cb01515b8b2dd6bc353493ab7076", + "url": "https://api.github.com/repos/GitHubHubus/ipstack-client/zipball/6891193164428dbb50bcfe7a64ede9960bfed839", + "reference": "6891193164428dbb50bcfe7a64ede9960bfed839", "shasum": "" }, "require": { @@ -3559,7 +3676,7 @@ "ipstack", "php" ], - "time": "2018-07-05T07:24:24+00:00" + "time": "2018-10-26T13:11:34+00:00" }, { "name": "opis/closure", @@ -5196,16 +5313,16 @@ }, { "name": "sabre/xml", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/sabre-io/xml.git", - "reference": "22e96661458c55a2e532885922bf4a232186f6a1" + "reference": "c5f510f8d0fa8135de9495145e2758f56037aa1e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sabre-io/xml/zipball/22e96661458c55a2e532885922bf4a232186f6a1", - "reference": "22e96661458c55a2e532885922bf4a232186f6a1", + "url": "https://api.github.com/repos/sabre-io/xml/zipball/c5f510f8d0fa8135de9495145e2758f56037aa1e", + "reference": "c5f510f8d0fa8135de9495145e2758f56037aa1e", "shasum": "" }, "require": { @@ -5217,8 +5334,7 @@ "sabre/uri": ">=1.0,<3.0.0" }, "require-dev": { - "phpunit/phpunit": "*", - "sabre/cs": "~1.0.0" + "phpunit/phpunit": "*" }, "type": "library", "autoload": { @@ -5255,7 +5371,7 @@ "dom", "xml" ], - "time": "2018-02-08T11:00:49+00:00" + "time": "2018-10-09T11:41:10+00:00" }, { "name": "sentry/sentry", @@ -5323,16 +5439,16 @@ }, { "name": "sentry/sentry-laravel", - "version": "0.10.0", + "version": "0.10.1", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-laravel.git", - "reference": "bca732f2947adc9f41811bde25c54f2fd0a7e700" + "reference": "bad3ebf478143a14297f6a25c3e94b3ab5c26378" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/bca732f2947adc9f41811bde25c54f2fd0a7e700", - "reference": "bca732f2947adc9f41811bde25c54f2fd0a7e700", + "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/bad3ebf478143a14297f6a25c3e94b3ab5c26378", + "reference": "bad3ebf478143a14297f6a25c3e94b3ab5c26378", "shasum": "" }, "require": { @@ -5383,7 +5499,7 @@ "logging", "sentry" ], - "time": "2018-09-04T13:48:59+00:00" + "time": "2018-10-26T13:09:52+00:00" }, { "name": "stevebauman/location", @@ -8766,6 +8882,17 @@ { "name": "roave/security-advisories", "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/Roave/SecurityAdvisories.git", + "reference": "0d96c6cd7cee8572be836fca71f9f01c8b1c0cb2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/0d96c6cd7cee8572be836fca71f9f01c8b1c0cb2", + "reference": "0d96c6cd7cee8572be836fca71f9f01c8b1c0cb2", + "shasum": "" + }, "conflict": { "3f/pygmentize": "<1.2", "adodb/adodb-php": "<5.20.12", diff --git a/tests/Unit/Helpers/LocaleHelperTest.php b/tests/Unit/Helpers/LocaleHelperTest.php index d2959fa3280..7d09038b89c 100644 --- a/tests/Unit/Helpers/LocaleHelperTest.php +++ b/tests/Unit/Helpers/LocaleHelperTest.php @@ -61,4 +61,14 @@ public function test_get_direction_hebrew() LocaleHelper::getDirection() ); } + + public function test_format_telephone_by_iso() + { + $tel = LocaleHelper::formatTelephoneNumberByISO('202-555-0191', 'gb'); + + $this->assertEquals( + '+44 20 2555 0191', + $tel + ); + } } diff --git a/tests/Unit/Helpers/VCardHelperTest.php b/tests/Unit/Helpers/VCardHelperTest.php index 79933bd2fac..b9efd1b5b84 100644 --- a/tests/Unit/Helpers/VCardHelperTest.php +++ b/tests/Unit/Helpers/VCardHelperTest.php @@ -196,4 +196,19 @@ public function test_it_prepares_an_complete_vcard() $vCard->getProperties() ); } + + public function test_it_get_country_by_sabre_vcard() + { + $vcard = new \Sabre\VObject\Component\VCard([ + 'TEL' => '202-555-0191', + 'ADR' => ['', '', '17 Shakespeare Ave.', 'Southampton', '', 'SO17 2HB', 'United Kingdom'], + ]); + + $iso = VCardHelper::getCountryISOFromSabreVCard($vcard); + + $this->assertEquals( + 'GB', + $iso + ); + } } diff --git a/tests/Unit/Models/ImportJobTest.php b/tests/Unit/Models/ImportJobTest.php index f5d5ac420ba..7c97497d48e 100644 --- a/tests/Unit/Models/ImportJobTest.php +++ b/tests/Unit/Models/ImportJobTest.php @@ -642,6 +642,31 @@ public function test_it_imports_phone() ]); } + public function test_it_imports_phone_by_international_format() + { + $importJob = $this->createImportJob(); + $vcard = new VCard([ + 'TEL' => '202-555-0191', + 'ADR' => ['', '', '17 Shakespeare Ave.', 'Southampton', '', 'SO17 2HB', 'United Kingdom'], + ]); + + $importJob->currentEntry = $vcard; + $contact = factory(Contact::class)->create([ + 'account_id' => $importJob->account->id, + ]); + $contactFieldType = factory(ContactFieldType::class)->create([ + 'account_id' => $importJob->account->id, + 'type' => 'phone', + ]); + $importJob->importTel($contact); + + $this->assertDatabaseHas('contact_fields', [ + 'account_id' => $importJob->account_id, + 'contact_id' => $contact->id, + 'data' => '+44 20 2555 0191', + ]); + } + private function createImportJob() { $account = factory(Account::class)->create([]);