diff --git a/README.md b/README.md index 1124346..45d59a8 100644 --- a/README.md +++ b/README.md @@ -160,11 +160,20 @@ $transfer = $reg->transfer($domain, 'authcode', [$contact]); ``` +### Update Auto-Renew +```php +use Utopia\Domains\Registrar\UpdateDetails; + +$details = new UpdateDetails(autoRenew: true); +$reg->updateDomain($domain, $details); +``` + ## Library Registrar API * **available(string $domain): bool** - Checks to see if a domain is available for registration. * **purchase(string $domain, array|Contact $contacts, int $periodYears = 1, array $nameservers = []): Registration** - Purchase a domain name and returns a Registration object. * **suggest(array $query, array $tlds = [], int|null $limit = null, int|null $priceMax = null, int|null $priceMin = null): array** - Suggest or search for domain names. * **getDomain(string $domain): Domain** - Get domain details and returns a Domain object. +* **updateDomain(string $domain, UpdateDetails $details): bool** - Update domain details such as auto-renew. * **renew(string $domain, int $periodYears): Renewal** - Renewal a domain name and returns a Renewal object. * **transfer(string $domain, string $authCode, array|Contact $contacts, int $periodYears = 1, array $nameservers = []): Registration** - Transfer a domain name and returns a Registration object. * **getAuthCode(string $domain): string** - Retrieve the authorization code for a domain. diff --git a/src/Domains/Registrar/Adapter/Mock.php b/src/Domains/Registrar/Adapter/Mock.php index acdb543..6dae736 100644 --- a/src/Domains/Registrar/Adapter/Mock.php +++ b/src/Domains/Registrar/Adapter/Mock.php @@ -337,7 +337,6 @@ public function renew(string $domain, int $periodYears): Renewal * @param UpdateDetails $details * @return bool * @throws DomainsException - * @throws InvalidContactException */ public function updateDomain(string $domain, UpdateDetails $details): bool { @@ -345,12 +344,8 @@ public function updateDomain(string $domain, UpdateDetails $details): bool throw new DomainsException("Domain {$domain} not found in mock registry", self::RESPONSE_CODE_NOT_FOUND); } - // Extract details from UpdateDetails object - $detailsArray = $details->toArray(); - - // Validate contacts if present - if (isset($detailsArray['contacts']) && $detailsArray['contacts']) { - $this->validateContacts($detailsArray['contacts']); + if ($details->autoRenew === null) { + throw new DomainsException('Details must include autoRenew', 400); } return true; diff --git a/src/Domains/Registrar/Adapter/Mock/UpdateDetails.php b/src/Domains/Registrar/Adapter/Mock/UpdateDetails.php deleted file mode 100644 index 671c09a..0000000 --- a/src/Domains/Registrar/Adapter/Mock/UpdateDetails.php +++ /dev/null @@ -1,34 +0,0 @@ -|null $details Domain details to update (e.g., autoRenew, locked) - * @param array|Contact|null $contacts Contacts to update - */ - public function __construct( - public ?array $details = null, - public array|Contact|null $contacts = null, - ) { - } - - public function toArray(): array - { - $result = []; - - if ($this->details !== null) { - $result = array_merge($result, $this->details); - } - - if ($this->contacts !== null) { - $result['contacts'] = $this->contacts; - } - - return $result; - } -} diff --git a/src/Domains/Registrar/Adapter/NameCom.php b/src/Domains/Registrar/Adapter/NameCom.php index 39fd603..530ee63 100644 --- a/src/Domains/Registrar/Adapter/NameCom.php +++ b/src/Domains/Registrar/Adapter/NameCom.php @@ -420,10 +420,8 @@ public function getDomain(string $domain): Domain * * Example request: * - * $details = new NameComUpdateDetails( - * autorenewEnabled: true, - * privacyEnabled: true, - * locked: false + * $details = new UpdateDetails( + * autoRenew: true * ); * $reg->updateDomain('example.com', $details); * @@ -437,10 +435,14 @@ public function getDomain(string $domain): Domain public function updateDomain(string $domain, UpdateDetails $details): bool { try { - $data = $details->toArray(); + $data = []; + if ($details->autoRenew !== null) { + $data['autorenewEnabled'] = $details->autoRenew; + } + if (empty($data)) { throw new DomainsException( - 'Details must contain at least one of: autorenewEnabled, privacyEnabled, locked', + 'Details must include autoRenew', 400 ); } diff --git a/src/Domains/Registrar/Adapter/NameCom/UpdateDetails.php b/src/Domains/Registrar/Adapter/NameCom/UpdateDetails.php deleted file mode 100644 index 5033922..0000000 --- a/src/Domains/Registrar/Adapter/NameCom/UpdateDetails.php +++ /dev/null @@ -1,39 +0,0 @@ -autorenewEnabled !== null) { - $result['autorenewEnabled'] = $this->autorenewEnabled; - } - - if ($this->privacyEnabled !== null) { - $result['privacyEnabled'] = $this->privacyEnabled; - } - - if ($this->locked !== null) { - $result['locked'] = $this->locked; - } - - return $result; - } -} diff --git a/src/Domains/Registrar/Adapter/OpenSRS.php b/src/Domains/Registrar/Adapter/OpenSRS.php index e5cdc72..1fcd6fd 100644 --- a/src/Domains/Registrar/Adapter/OpenSRS.php +++ b/src/Domains/Registrar/Adapter/OpenSRS.php @@ -16,7 +16,7 @@ use Utopia\Domains\Registrar\TransferStatus; use Utopia\Domains\Registrar\Domain; use Utopia\Domains\Registrar\TransferStatusEnum; -use Utopia\Domains\Registrar\UpdateDetails as UpdateDetails; +use Utopia\Domains\Registrar\UpdateDetails; use Utopia\Domains\Registrar; class OpenSRS extends Adapter @@ -578,23 +578,10 @@ public function getDomain(string $domain): Domain /** * Update the domain information * - * Example request 1: + * Example request: * - * $details = new OpenSRSUpdateDetails( - * data: 'contact_info', - * contacts: [ - * 'owner' => new Contact(...), - * 'admin' => new Contact(...), - * ] - * ); - * $reg->updateDomain('example.com', $details); - * - * - * Example request 2: - * - * $details = new OpenSRSUpdateDetails( - * data: 'ca_whois_display_setting', - * display: 'FULL' + * $details = new UpdateDetails( + * autoRenew: true * ); * $reg->updateDomain('example.com', $details); * @@ -605,11 +592,16 @@ public function getDomain(string $domain): Domain */ public function updateDomain(string $domain, UpdateDetails $details): bool { - if (!$details instanceof OpenSRS\UpdateDetails) { - throw new Exception("Invalid details type: expected OpenSRS\\UpdateDetails"); + if ($details->autoRenew === null) { + throw new DomainsException('Details must include autoRenew', 400); } - $attributes = $details->toArray(); + $attributes = [ + 'data' => 'expire_action', + 'affect_domains' => 0, + 'auto_renew' => $details->autoRenew ? 1 : 0, + 'let_expire' => $details->autoRenew ? 0 : 1, + ]; $message = [ 'object' => 'DOMAIN', @@ -618,14 +610,6 @@ public function updateDomain(string $domain, UpdateDetails $details): bool 'attributes' => $attributes, ]; - if ($details->contacts !== null) { - if ($details->data !== 'contact_info') { - throw new Exception("Invalid data: data must be 'contact_info' in order to update contacts"); - } - $contacts = $this->sanitizeContacts($details->contacts); - $message['attributes']['contact_set'] = $contacts; - } - $xpath = implode('/', [ '//body', 'data_block', @@ -642,6 +626,13 @@ public function updateDomain(string $domain, UpdateDetails $details): bool $result = $this->send($message); $result = $this->sanitizeResponse($result); $elements = $result->xpath($xpath); + if (empty($elements)) { + $elements = $result->xpath('//body/data_block/dt_assoc/item[@key="is_success"]'); + } + + if (empty($elements)) { + throw new DomainsException('Failed to update domain: invalid response from OpenSRS', 500); + } return (string) $elements[0] === '1'; } diff --git a/src/Domains/Registrar/Adapter/OpenSRS/UpdateDetails.php b/src/Domains/Registrar/Adapter/OpenSRS/UpdateDetails.php deleted file mode 100644 index 57a10e2..0000000 --- a/src/Domains/Registrar/Adapter/OpenSRS/UpdateDetails.php +++ /dev/null @@ -1,41 +0,0 @@ -|null $contacts Associative array of contacts by type (owner, admin, tech, billing) - * @param string|null $display Display setting for CA domains (e.g., 'FULL', 'PRIVATE') - * @param array $additionalData Additional data for specific update types - */ - public function __construct( - public string $data, - public ?array $contacts = null, - public ?string $display = null, - public array $additionalData = [], - ) { - } - - public function toArray(): array - { - $result = [ - 'data' => $this->data, - ]; - - if ($this->display !== null) { - $result['display'] = $this->display; - } - - // Merge any additional data - if (!empty($this->additionalData)) { - $result = array_merge($result, $this->additionalData); - } - - return $result; - } -} diff --git a/src/Domains/Registrar/UpdateDetails.php b/src/Domains/Registrar/UpdateDetails.php index 5cabd32..eecdd07 100644 --- a/src/Domains/Registrar/UpdateDetails.php +++ b/src/Domains/Registrar/UpdateDetails.php @@ -2,12 +2,13 @@ namespace Utopia\Domains\Registrar; -abstract class UpdateDetails +final class UpdateDetails { /** - * Convert details to array format - * - * @return array + * @param bool|null $autoRenew Enable or disable automatic renewal */ - abstract public function toArray(): array; + public function __construct( + public ?bool $autoRenew = null, + ) { + } } diff --git a/tests/Registrar/Base.php b/tests/Registrar/Base.php index bfc1052..4fc066e 100644 --- a/tests/Registrar/Base.php +++ b/tests/Registrar/Base.php @@ -38,11 +38,10 @@ abstract protected function getExpectedAdapterName(): string; /** * Get an UpdateDetails instance for testing * - * @param array $details Domain details to update - * @param array|Contact|null $contacts Contacts to update + * @param bool|null $autoRenew Enable or disable automatic renewal * @return UpdateDetails */ - abstract protected function getUpdateDetails(array $details = [], array|Contact|null $contacts = null): UpdateDetails; + abstract protected function getUpdateDetails(?bool $autoRenew = null): UpdateDetails; /** * Get purchase contact info @@ -270,13 +269,7 @@ public function testUpdateDomain(): void $result = $this->getRegistrar()->updateDomain( $testDomain, - $this->getUpdateDetails( - [ - 'autorenew' => true, - 'data' => 'contact_info', - ], - $this->getPurchaseContact('2') - ) + $this->getUpdateDetails(true) ); $this->assertTrue($result); diff --git a/tests/Registrar/MockTest.php b/tests/Registrar/MockTest.php index 5539eaa..342c508 100644 --- a/tests/Registrar/MockTest.php +++ b/tests/Registrar/MockTest.php @@ -10,7 +10,7 @@ use Utopia\Domains\Registrar\Exception\DomainTakenException; use Utopia\Domains\Registrar\Exception\InvalidContactException; use Utopia\Domains\Registrar\Adapter\Mock; -use Utopia\Domains\Registrar\Adapter\Mock\UpdateDetails; +use Utopia\Domains\Registrar\UpdateDetails; class MockTest extends Base { @@ -64,9 +64,9 @@ protected function getDefaultNameservers(): array ]; } - protected function getUpdateDetails(array $details = [], array|Contact|null $contacts = null): UpdateDetails + protected function getUpdateDetails(?bool $autoRenew = null): UpdateDetails { - return new UpdateDetails($details, $contacts); + return new UpdateDetails($autoRenew); } // Mock-specific tests @@ -132,35 +132,6 @@ public function testTransferWithInvalidContact(): void $this->registrar->transfer('transfer.com', 'auth-code', [$invalidContact]); } - public function testUpdateDomainWithInvalidContact(): void - { - $domain = 'testdomain.com'; - $this->registrar->purchase($domain, $this->getPurchaseContact(), 1); - - $this->expectException(InvalidContactException::class); - $this->expectExceptionMessage('missing required field'); - - $invalidContact = new Contact( - '', // Empty firstname - 'Doe', - '+1.5551234567', - 'john.doe@example.com', - '123 Main St', - 'Suite 100', - '', - 'San Francisco', - 'CA', - 'US', - '94105', - 'Test Inc' - ); - - $this->registrar->updateDomain( - $domain, - new UpdateDetails(['data' => 'contact_info'], [$invalidContact]) - ); - } - public function testCheckTransferStatusWithRequestAddress(): void { $domain = 'example.com'; diff --git a/tests/Registrar/NameComTest.php b/tests/Registrar/NameComTest.php index 79bca59..b38a33e 100644 --- a/tests/Registrar/NameComTest.php +++ b/tests/Registrar/NameComTest.php @@ -8,8 +8,7 @@ use Utopia\Domains\Registrar; use Utopia\Domains\Registrar\Exception\AuthException; use Utopia\Domains\Registrar\Adapter\NameCom; -use Utopia\Domains\Registrar\Adapter\NameCom\UpdateDetails; -use Utopia\Domains\Registrar\Contact; +use Utopia\Domains\Registrar\UpdateDetails; class NameComTest extends Base { @@ -83,12 +82,9 @@ protected function getDefaultNameservers(): array ]; } - protected function getUpdateDetails(array $details = [], array|Contact|null $contacts = null): UpdateDetails + protected function getUpdateDetails(?bool $autoRenew = null): UpdateDetails { - $autorenewEnabled = $details['autorenew'] ?? null; - $privacyEnabled = $details['privacy'] ?? null; - $locked = $details['locked'] ?? null; - return new UpdateDetails($autorenewEnabled, $privacyEnabled, $locked); + return new UpdateDetails($autoRenew); } protected function getPricingTestDomain(): string diff --git a/tests/Registrar/OpenSRSTest.php b/tests/Registrar/OpenSRSTest.php index d7417ed..480e9bd 100644 --- a/tests/Registrar/OpenSRSTest.php +++ b/tests/Registrar/OpenSRSTest.php @@ -9,8 +9,7 @@ use Utopia\Domains\Registrar\Exception\AuthException; use Utopia\Domains\Registrar\Exception\DomainNotTransferableException; use Utopia\Domains\Registrar\Adapter\OpenSRS; -use Utopia\Domains\Registrar\Adapter\OpenSRS\UpdateDetails; -use Utopia\Domains\Registrar\Contact; +use Utopia\Domains\Registrar\UpdateDetails; class OpenSRSTest extends Base { @@ -86,10 +85,9 @@ protected function getDefaultNameservers(): array ]; } - protected function getUpdateDetails(array $details = [], array|Contact|null $contacts = null): UpdateDetails + protected function getUpdateDetails(?bool $autoRenew = null): UpdateDetails { - $data = $details['data'] ?? 'contact_info'; - return new UpdateDetails($data, $contacts); + return new UpdateDetails($autoRenew); } // OpenSRS-specific tests