diff --git a/composer.json b/composer.json
index 0afbfb4319..9e8986d1f7 100644
--- a/composer.json
+++ b/composer.json
@@ -7,7 +7,7 @@
"doctrine/cache": "~1.5",
"guzzlehttp/guzzle": "^5.3",
"guzzlehttp/ringphp": "^1.1",
- "platformsh/console-form": ">=0.0.25 <2.0",
+ "platformsh/console-form": ">=0.0.26 <2.0",
"platformsh/client": ">=0.57.0 <2.0",
"symfony/console": "^3.0 >=3.2",
"symfony/yaml": "^3.0 || ^2.6",
@@ -20,7 +20,8 @@
"symfony/dependency-injection": "^3.1",
"symfony/config": "^3.1",
"paragonie/random_compat": "^2.0",
- "ext-json": "*"
+ "ext-json": "*",
+ "commerceguys/addressing": "^1.0"
},
"suggest": {
"drush/drush": "For Drupal projects"
diff --git a/composer.lock b/composer.lock
index 282fe1a0a0..2ebd59f724 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": "f7a7490bf13334a9b50e223fc80b021f",
+ "content-hash": "e057f995dbe2fa6633ef0a0f74e37b92",
"packages": [
{
"name": "cocur/slugify",
@@ -74,6 +74,69 @@
},
"time": "2017-03-23T21:52:55+00:00"
},
+ {
+ "name": "commerceguys/addressing",
+ "version": "v1.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/commerceguys/addressing.git",
+ "reference": "8aac9af11d38c1abe04a5e4a252d5a6740b2b69a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/commerceguys/addressing/zipball/8aac9af11d38c1abe04a5e4a252d5a6740b2b69a",
+ "reference": "8aac9af11d38c1abe04a5e4a252d5a6740b2b69a",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/collections": "~1.0",
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "mikey179/vfsstream": "1.*",
+ "phpunit/phpunit": "~4.0",
+ "squizlabs/php_codesniffer": "2.*",
+ "symfony/validator": ">=3.2"
+ },
+ "suggest": {
+ "symfony/validator": "to validate addresses"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "CommerceGuys\\Addressing\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bojan Zivanovic"
+ },
+ {
+ "name": "Damien Tournoud"
+ }
+ ],
+ "description": "Addressing library powered by CLDR and Google's address data.",
+ "keywords": [
+ "address",
+ "internationalization",
+ "localization",
+ "postal"
+ ],
+ "support": {
+ "issues": "https://github.com/commerceguys/addressing/issues",
+ "source": "https://github.com/commerceguys/addressing/tree/v1.0.6"
+ },
+ "time": "2019-10-21T14:31:17+00:00"
+ },
{
"name": "composer/ca-bundle",
"version": "1.3.1",
@@ -224,6 +287,75 @@
},
"time": "2017-07-22T12:49:21+00:00"
},
+ {
+ "name": "doctrine/collections",
+ "version": "v1.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/collections.git",
+ "reference": "6c1e4eef75f310ea1b3e30945e9f06e652128b8a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/collections/zipball/6c1e4eef75f310ea1b3e30945e9f06e652128b8a",
+ "reference": "6c1e4eef75f310ea1b3e30945e9f06e652128b8a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Doctrine\\Common\\Collections\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "Collections Abstraction library",
+ "homepage": "http://www.doctrine-project.org",
+ "keywords": [
+ "array",
+ "collections",
+ "iterator"
+ ],
+ "support": {
+ "source": "https://github.com/doctrine/collections/tree/v1.3.0"
+ },
+ "time": "2015-04-14T22:21:58+00:00"
+ },
{
"name": "firebase/php-jwt",
"version": "v2.2.0",
@@ -778,16 +910,16 @@
},
{
"name": "platformsh/console-form",
- "version": "v0.0.25",
+ "version": "v0.0.26",
"source": {
"type": "git",
"url": "https://github.com/platformsh/console-form.git",
- "reference": "9b1a93e5e27aa1c1614ac14102a55162f69732ae"
+ "reference": "39aa4f31c70db66710baeb3fd1123c1e809ac953"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/platformsh/console-form/zipball/9b1a93e5e27aa1c1614ac14102a55162f69732ae",
- "reference": "9b1a93e5e27aa1c1614ac14102a55162f69732ae",
+ "url": "https://api.github.com/repos/platformsh/console-form/zipball/39aa4f31c70db66710baeb3fd1123c1e809ac953",
+ "reference": "39aa4f31c70db66710baeb3fd1123c1e809ac953",
"shasum": ""
},
"require": {
@@ -815,9 +947,9 @@
"description": "A lightweight Symfony Console form system.",
"support": {
"issues": "https://github.com/platformsh/console-form/issues",
- "source": "https://github.com/platformsh/console-form/tree/v0.0.25"
+ "source": "https://github.com/platformsh/console-form/tree/v0.0.26"
},
- "time": "2021-06-16T16:02:08+00:00"
+ "time": "2022-01-14T15:24:02+00:00"
},
{
"name": "psr/container",
diff --git a/src/Command/Organization/Billing/OrganizationAddressCommand.php b/src/Command/Organization/Billing/OrganizationAddressCommand.php
index ca7f80647f..4603d9bfb2 100644
--- a/src/Command/Organization/Billing/OrganizationAddressCommand.php
+++ b/src/Command/Organization/Billing/OrganizationAddressCommand.php
@@ -8,9 +8,11 @@
use Platformsh\Cli\Service\Table;
use Platformsh\Client\Model\Organization\Address;
use Platformsh\Client\Model\Organization\Organization;
+use Platformsh\ConsoleForm\Form;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class OrganizationAddressCommand extends OrganizationCommandBase
@@ -23,13 +25,19 @@ protected function configure()
->addOrganizationOptions()
->addArgument('property', InputArgument::OPTIONAL, 'The name of a property to view or change')
->addArgument('value', InputArgument::OPTIONAL, 'A new value for the property')
- ->addArgument('properties', InputArgument::IS_ARRAY|InputArgument::OPTIONAL, 'Additional property/value pairs');
+ ->addArgument('properties', InputArgument::IS_ARRAY|InputArgument::OPTIONAL, 'Additional property/value pairs')
+ ->addOption('form', null, InputOption::VALUE_NONE, 'Display a form for updating the address interactively');
PropertyFormatter::configureInput($this->getDefinition());
Table::configureInput($this->getDefinition());
}
protected function execute(InputInterface $input, OutputInterface $output)
{
+ if ($input->getOption('form') && !$input->isInteractive()) {
+ $this->stdErr->writeln('The --form option cannot be used non-interactively.');
+ return 1;
+ }
+
$property = $input->getArgument('property');
$updates = $this->parseUpdates($input);
@@ -37,11 +45,28 @@ protected function execute(InputInterface $input, OutputInterface $output)
$org = $this->validateOrganizationInput($input, 'orders');
$address = $org->getAddress();
+ if ($input->getOption('form')) {
+ $form = Form::fromArray($this->getAddressFormFields());
+ foreach ($address->getProperties() as $key => $value) {
+ if ($value !== '' && ($field = $form->getField($key))) {
+ $field->set('default', $value);
+ }
+ }
+ foreach ($updates as $key => $value) {
+ if ($field = $form->getField($key)) {
+ $field->set('default', $value);
+ }
+ }
+ /** @var \Platformsh\Cli\Service\QuestionHelper $questionHelper */
+ $questionHelper = $this->getService('question_helper');
+ $updates = $form->resolveOptions($input, $output, $questionHelper);
+ }
+
/** @var PropertyFormatter $formatter */
$formatter = $this->getService('property_formatter');
$result = 0;
- if ($property !== null) {
+ if ($property !== null || !empty($updates)) {
if (empty($updates)) {
$formatter->displayData($output, $address->getProperties(), $property);
return $result;
@@ -76,6 +101,7 @@ protected function display(Address $address, Organization $org, InputInterface $
$table->renderSimple($values, $headings);
if (!$table->formatIsMachineReadable()) {
+ $this->stdErr->writeln('');
$this->stdErr->writeln(\sprintf('To view the billing profile, run: %s', $this->otherCommandExample($input, 'org:billing:profile')));
$this->stdErr->writeln(\sprintf('To view organization details, run: %s', $this->otherCommandExample($input, 'org:info')));
}
diff --git a/src/Command/Organization/OrganizationCommandBase.php b/src/Command/Organization/OrganizationCommandBase.php
index 6d21d790ee..ff303a22e2 100644
--- a/src/Command/Organization/OrganizationCommandBase.php
+++ b/src/Command/Organization/OrganizationCommandBase.php
@@ -2,8 +2,16 @@
namespace Platformsh\Cli\Command\Organization;
+use CommerceGuys\Addressing\AddressFormat\AddressField;
+use CommerceGuys\Addressing\AddressFormat\AddressFormatRepository;
+use CommerceGuys\Addressing\AddressFormat\AdministrativeAreaType;
+use CommerceGuys\Addressing\AddressFormat\LocalityType;
+use CommerceGuys\Addressing\AddressFormat\PostalCodeType;
+use CommerceGuys\Addressing\Country\CountryRepository;
use Platformsh\Cli\Command\CommandBase;
use Platformsh\Client\Model\Organization\Member;
+use Platformsh\ConsoleForm\Field\Field;
+use Platformsh\ConsoleForm\Field\OptionsField;
use Symfony\Component\Console\Input\InputInterface;
class OrganizationCommandBase extends CommandBase
@@ -52,4 +60,114 @@ protected function otherCommandExample(InputInterface $input, $commandName, $oth
}
return \implode(' ', $args);
}
+
+ /**
+ * Returns a list of interactive console form fields for an address.
+ *
+ * They can dynamically change name, validation or 'required' status, depending on the address country.
+ *
+ * @return Field[]
+ */
+ protected function getAddressFormFields()
+ {
+ $countryRepository = new CountryRepository();
+ $addressFormatRepository = new AddressFormatRepository();
+ $countryList = $countryRepository->getList();
+ $fields = [
+ 'country' => new OptionsField('Country', [
+ 'asChoice' => false,
+ 'includeAsOption' => false,
+ 'options' => $countryList,
+ 'normalizer' => function ($value) use ($countryList) {
+ if (isset($countryList[$value])) {
+ return $value;
+ }
+ return \array_search($value, $countryList, true);
+ },
+ ]),
+ ];
+
+ $possibleFields = [
+ 'premise' => [AddressField::ADDRESS_LINE1, 'Address line 1 ("premise")'],
+ 'thoroughfare' => [AddressField::ADDRESS_LINE2, 'Address line 2 ("thoroughfare")'],
+ 'locality' => [AddressField::LOCALITY, 'City or town ("locality")'],
+ 'dependent_locality' => [AddressField::DEPENDENT_LOCALITY, 'Dependent locality'],
+ 'administrative_area' => [AddressField::ADMINISTRATIVE_AREA, 'State/county ("administrative area")'],
+ 'postal_code' => [AddressField::POSTAL_CODE, 'Postal code'],
+ ];
+ foreach ($possibleFields as $key => $info) {
+ list($addressFieldName, $name) = $info;
+ $fields[$key] = new Field($name, [
+ 'includeAsOption' => false,
+ ]);
+ $field = &$fields[$key];
+ $field->set('conditions', [
+ 'country' => function ($country) use ($addressFieldName, $addressFormatRepository, $field, $key) {
+ $format = $addressFormatRepository->get($country);
+ if (!$format || !\in_array($addressFieldName, $format->getUsedFields())) {
+ return false;
+ }
+ $field->set('required', \in_array($addressFieldName, $format->getRequiredFields(), true));
+ if ($addressFieldName === AddressField::LOCALITY) {
+ if ($localityType = $format->getLocalityType()) {
+ switch ($localityType) {
+ case LocalityType::CITY:
+ $field->set('name', 'City');
+ break;
+ case LocalityType::DISTRICT:
+ case LocalityType::SUBURB:
+ $field->set('name', 'District or suburb');
+ break;
+ case LocalityType::POST_TOWN:
+ $field->set('name', 'City or town');
+ break;
+ }
+ }
+ }
+ if ($addressFieldName === AddressField::ADMINISTRATIVE_AREA) {
+ $map = [
+ AdministrativeAreaType::AREA => 'Area',
+ AdministrativeAreaType::COUNTY => 'County',
+ AdministrativeAreaType::DEPARTMENT => 'Department',
+ AdministrativeAreaType::DISTRICT => 'District',
+ AdministrativeAreaType::DO_SI => 'Do/Si',
+ AdministrativeAreaType::EMIRATE => 'Emirate',
+ AdministrativeAreaType::ISLAND => 'Island',
+ AdministrativeAreaType::OBLAST => 'Oblast',
+ AdministrativeAreaType::PARISH => 'Parish',
+ AdministrativeAreaType::PREFECTURE => 'Prefecture',
+ AdministrativeAreaType::PROVINCE => 'Province',
+ AdministrativeAreaType::STATE => 'State',
+ ];
+ if (isset($map[$format->getAdministrativeAreaType()])) {
+ $field->set('name', $map[$format->getAdministrativeAreaType()]);
+ }
+ }
+ if ($addressFieldName === AddressField::POSTAL_CODE) {
+ if ($postalCodeType = $format->getPostalCodeType()) {
+ switch ($postalCodeType) {
+ case PostalCodeType::PIN:
+ $field->set('name', 'Pin code');
+ break;
+ case PostalCodeType::EIR:
+ $field->set('name', 'Eircode');
+ break;
+ case PostalCodeType::ZIP:
+ $field->set('name', 'Zip code');
+ break;
+ case PostalCodeType::POSTAL:
+ $field->set('name', 'Postal code');
+ break;
+ }
+ }
+ $field->set('validator', function ($value) use ($format) {
+ return \preg_match('/' . $format->getPostalCodePattern() . '/i', $value) === 1;
+ });
+ }
+ return true;
+ },
+ ]);
+ }
+ return $fields;
+ }
}
diff --git a/src/Command/Organization/OrganizationCreateCommand.php b/src/Command/Organization/OrganizationCreateCommand.php
index cb779f48e1..0ca71d36b7 100644
--- a/src/Command/Organization/OrganizationCreateCommand.php
+++ b/src/Command/Organization/OrganizationCreateCommand.php
@@ -106,6 +106,9 @@ protected function execute(InputInterface $input, OutputInterface $output)
$this->runOtherCommand('organization:info', ['--org' => $organization->name], $this->stdErr);
+ $this->stdErr->writeln('');
+ $this->stdErr->writeln(\sprintf('To view or update the organization\'s billing address, run: %s org:billing:address --org %s', $this->config()->get('application.executable'), $organization->name));
+
return 0;
}
}