Просмотр файла libarea-0.9/vendor/utopia-php/domains/src/Domains/Registrar/OpenSRS.php

Размер файла: 17.79Kb
<?php

namespace Utopia\Domains\Registrar;

use Exception;
use Utopia\Domains\Contact;

class OpenSRS extends Adapter
{
    protected array $defaultNameservers;

    protected array $user;

    /**
     * __construct
     * Instantiate a new adapter.
     *
     * @param  string  $apiKey
     * @param  string  $apiSecret
     * @param  string  $username
     * @param  string  $password
     * @param  array  $defaultNameservers
     * @param  bool  $production
     * @return void
     */
    public function __construct(string $apiKey, string $apiSecret, string $username, string $password, array $defaultNameservers, bool $production = false)
    {
        $this->endpoint =
          $production === false
          ? 'https://horizon.opensrs.net:55443'
          : 'https://rr-n1-tor.opensrs.net:55443';

        $this->apiKey = $apiKey;
        $this->apiSecret = $apiSecret;
        $this->defaultNameservers = $defaultNameservers;

        $this->user = [
            'username' => $username,
            'password' => $password,
        ];

        $this->headers = [
            'Content-Type:text/xml',
            'X-Username:'.$this->apiSecret,
        ];
    }

    public function send(array $params = []): array|string
    {
        $object = $params['object'];
        $action = $params['action'];
        $domain = $params['domain'] ?? null;
        $attributes = $params['attributes'];

        $xml = $this->buildEnvelop($object, $action, $attributes, $domain);

        $headers = array_merge($this->headers, [
            'X-Signature:'.md5(md5($xml.$this->apiKey).$this->apiKey),
        ]);

        $ch = curl_init($this->endpoint);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);

        return curl_exec($ch);
    }

    public function available(string $domain): bool
    {
        $result = $this->send([
            'object' => 'DOMAIN',
            'action' => 'LOOKUP',
            'attributes' => [
                'domain' => $domain,
            ],
        ]);


        $result = $this->sanitizeResponse($result);
        $elements = $result->xpath('//body/data_block/dt_assoc/item[@key="response_code"]');

        return (string) $elements[0] === '210';
    }

    private function sanitizeResponse(string $response)
    {
        $result = simplexml_load_string($response);
        $elements = $result->xpath('//body/data_block/dt_assoc/item[@key="response_code"]');
        $code = (int) "{$elements[0]}";

        if ($code > 299) {
            $elements = $result->xpath('//body/data_block/dt_assoc/item[@key="response_text"]');
            $text = "{$elements[0]}";

            throw new Exception($text, $code);
        }

        return $result;
    }

    public function updateNameservers(string $domain, array $nameservers): array
    {
        $message = [
            'object' => 'DOMAIN',
            'action' => 'advanced_update_nameservers',
            'domain' => $domain,
            'attributes' => [
                'add_ns' => $nameservers,
                'op_type' => 'add_remove',
            ],
        ];

        $result = $this->send($message);
        $result = $this->sanitizeResponse($result);

        $elements = $result->xpath('//body/data_block/dt_assoc/item[@key="is_success"]');
        $successful = "{$elements[0]}" === '1' ? true : false;

        $elements = $result->xpath('//body/data_block/dt_assoc/item[@key="response_text"]');
        $text = "{$elements[0]}";

        $elements = $result->xpath('//body/data_block/dt_assoc/item[@key="response_code"]');
        $code = "{$elements[0]}";

        return [
            'code' => $code,
            'text' => $text,
            'successful' => $successful,
        ];
    }

    private function register(string $domain, string $regType, array $user, array $contacts, array $nameservers = []): string
    {
        $hasNameservers = empty($nameservers) ? 0 : 1;

        $message = [
            'object' => 'DOMAIN',
            'action' => 'SW_REGISTER',
            'attributes' => [
                'domain' => $domain,
                'period' => 1,
                'contact_set' => $contacts,
                'custom_tech_contact' => 0,
                'custom_nameservers' => $hasNameservers,
                'reg_username' => $user['username'],
                'reg_password' => $user['password'],
                'reg_type' => $regType,
                'handle' => 'process',
                'f_whois_privacy' => 1,
                'auto_renew' => 0,
            ],
        ];

        if ($hasNameservers) {
            $message['attributes']['nameserver_list'] = $nameservers;
        }

        $result = $this->send($message);

        return $result;
    }

    public function purchase(string $domain, array|Contact $contacts, array $nameservers = []): array
    {
        $contacts = is_array($contacts) ? $contacts : [$contacts];

        $nameservers =
          empty($nameservers)
          ? $this->defaultNameservers
          : $nameservers;

        $contacts = $this->sanitizeContacts($contacts);

        $regType = 'new';

        $result = $this->register($domain, $regType, $this->user, $contacts, $nameservers);

        $result = $this->response($result);

        return $result;
    }

    public function transfer(string $domain, array|Contact $contacts, array $nameservers = []): array
    {
        $contacts = is_array($contacts) ? $contacts : [$contacts];

        $nameservers =
          empty($nameservers)
          ? $this->defaultNameservers
          : $nameservers;

        $contacts = $this->sanitizeContacts($contacts);

        $regType = 'transfer';

        $result = $this->register($domain, $regType, $this->user, $contacts, $nameservers);
        $result = $this->response($result);

        return $result;
    }

    public function cancelPurchase(): bool
    {
        $timestamp = date('Y-m-d\TH:i:s.000');
        $timestamp = strtotime($timestamp);

        $message = [
            'object' => 'ORDER',
            'action' => 'cancel_pending_orders',
            'attributes' => [
                'to_date' => $timestamp,
                'status' => [
                    'declined',
                    'pending',
                ],
            ],
        ];

        $result = $this->send($message);
        $result = $this->sanitizeResponse($result);

        $elements = $result->xpath('//body/data_block/dt_assoc/item[@key="is_success"]');
        $successful = "{$elements[0]}" === '1' ? true : false;

        return $successful;
    }

    public function suggest(array|string $query, array $tlds = [], $minLength = 1, $maxLength = 100): array
    {
        $query = is_array($query) ? $query : [$query];

        $message = [
            'object' => 'DOMAIN',
            'action' => 'name_suggest',
            'attributes' => [
                'services' => [
                    'suggestion',
                ],
                'searchstring' => implode(' ', $query),
                'tlds' => $tlds,
            ],
        ];

        $xpath = implode('/', [
            '//body',
            'data_block',
            'dt_assoc',
            'item[@key="attributes"]',
            'dt_assoc',
            'item[@key="suggestion"]',
            'dt_assoc',
            'item[@key="items"]',
            'dt_array',
            'item',
        ]);

        $result = $this->send($message);
        $result = $this->sanitizeResponse($result);
        $elements = $result->xpath($xpath);

        $items = [];

        foreach ($elements as $element) {
            $item = $element->xpath('dt_assoc/item');
            $domain = (string) $item[0];
            $available = (string) $item[1] === 'available';

            $items[$domain] = $available;
        }

        return $items;
    }

    public function tlds(): array
    {
        // OpenSRS offers no endpoint for this
        return [];
    }

    public function getDomain(string $domain): array
    {
        $message = [
            'object' => 'domain',
            'action' => 'get',
            'attributes' => [
                'domain' => $domain,
                'type' => 'all_info',
                'clean_ca_subset' => 1,
            ],
        ];

        $xpath = implode('/', [
            '//body',
            'data_block',
            'dt_assoc',
            'item[@key="attributes"]',
            'dt_assoc',
            'item',
        ]);

        $result = $this->send($message);
        $result = $this->sanitizeResponse($result);
        $elements = $result->xpath($xpath);

        $results = [];

        foreach ($elements as $element) {
            $key = "{$element['key']}";
            $value = "{$element}";

            $results[$key] = $value;
        }

        return $results;
    }

    public function updateDomain(string $domain, array $contacts, array $details): bool
    {
        $contacts = $this->sanitizeContacts($contacts);

        $message = [
            'object' => 'domain',
            'action' => 'modify',
            'attributes' => [
                'domain' => $domain,
                'affect_domains' => 0,
                'data' => $details['data'],
                'contact_set' => $contacts,
            ],
        ];

        $xpath = implode('/', [
            '//body',
            'data_block',
            'dt_assoc',
            'item[@key="attributes"]',
            'dt_assoc',
            'item[@key="details"]',
            'dt_assoc',
            'item[@key="'.$domain.'"]',
            'dt_assoc',
            'item[@key="is_success"]',
        ]);

        $result = $this->send($message);
        $result = $this->sanitizeResponse($result);
        $elements = $result->xpath($xpath);

        return (string) $elements[0] === '1';
    }

    public function renew(string $domain, int $years): array
    {
        $message = [
            'object' => 'domain',
            'action' => 'renew',
            'attributes' => [
                'domain' => $domain,
                'auto_renew' => 0,
                'currentexpirationyear' => '2022',
                'domain' => $domain,
                'period' => $years,
                'handle' => 'process',
            ],
        ];

        $xpath = implode('/', [
            '//body',
            'data_block',
            'dt_assoc',
            'item[@key="attributes"]',
            'dt_assoc',
            'item',
        ]);

        $result = $this->send($message);
        $result = simplexml_load_string($result);
        $elements = $result->xpath($xpath);

        $results = [];

        foreach ($elements as $item) {
            $key = "{$item['key']}";

            if ($key === 'registration expiration date') {
                $result['new_expiration'] = "{$item}";

                continue;
            }

            $value = "{$item}";

            $results[$key] = $value;
        }

        return $results;
    }

    private function response(string $xml): array
    {
        $doc = $this->sanitizeResponse($xml);
        $elements = $doc->xpath('//data_block/dt_assoc/item[@key="response_code"]');
        $responseCode = "{$elements[0]}";

        $elements = $doc->xpath('//data_block/dt_assoc/item[@key="attributes"]/dt_assoc/item[@key="id"]');
        $responseId = count($elements) > 0 ? "{$elements[0]}" : '';

        $elements = $doc->xpath('//data_block/dt_assoc/item[@key="attributes"]/dt_assoc/item[@key="domain_id"]');
        $responseDomainId = count($elements) > 0 ? "{$elements[0]}" : '';

        $elements = $doc->xpath('//data_block/dt_assoc/item[@key="is_success"]');
        $responseSuccessful = "{$elements[0]}" === '1' ? true : false;

        return [
            'code' => $responseCode,
            'id' => $responseId,
            'domainId' => $responseDomainId,
            'successful' => $responseSuccessful,
        ];
    }

    private function createArray(string $key, array $ary): string
    {
        $result = [
            '<item key="'.$key.'">',
            '<dt_array>',
        ];

        foreach ($ary as $key => $value) {
            $result[] = $this->createEnvelopItem($key, $value);
        }

        $result[] = '</dt_array>';
        $result[] = '</item>';

        return implode(PHP_EOL, $result);
    }

    private function createEnvelopItem(string $key, string|int|array $value): string
    {
        if (is_array($value)) {
            return $this->createArray($key, $value);
        }

        return "<item key='{$key}'>{$value}</item>";
    }

    private function validateContact(array $contact): array
    {
        $required = [
            'firstname',
            'lastname',
            'email',
            'phone',
            'address1',
            'city',
            'state',
            'postalcode',
            'country',
            'owner',
            'org',
        ];

        $filter = [
            'firstname' => 'first_name',
            'lastname' => 'last_name',
            'org' => 'org_name',
            'postalcode' => 'postal_code',
        ];

        $result = [];

        foreach ($required as $key) {
            $key = strtolower($key);

            if (! isset($contact[$key])) {
                throw new Exception("Contact is missing required field: {$key}");
            }

            $filtered_key = $filter[$key] ?? $key;

            $result[$filtered_key] = $contact[$key];
        }

        return $result;
    }

    private function createContact(string $type, array $contact): string
    {
        $contact = $this->validateContact($contact);
        $result = [
            "<item key='{$type}'>",
            '<dt_assoc>',
        ];

        foreach ($contact as $key => $value) {
            $result[] = $this->createEnvelopItem($key, $value);
        }

        $result[] = '</dt_assoc>';
        $result[] = '</item>';

        $xml = implode(PHP_EOL, $result);

        return $xml;
    }

    private function createContactSet(array $contacts): string
    {
        $result = [
            '<item key="contact_set">',
            '<dt_assoc>',
        ];

        foreach ($contacts as $type => $contact) {
            $result[] = $this->createContact($type, $contact);
        }

        $result[] = '</dt_assoc>';
        $result[] = '</item>';

        return implode(PHP_EOL, $result);
    }

    private function createNameserver(string $name, int $sortOrder): string
    {
        return implode(PHP_EOL, [
            '<dt_assoc>',
            $this->createEnvelopItem('name', $name),
            $this->createEnvelopItem('sortorder', $sortOrder),
            '</dt_assoc>',
        ]);
    }

    private function createNameserverList(array $nameservers): string
    {
        $result = [
            '<item key="nameserver_list">',
            '<dt_array>',
        ];

        for ($index = 0; $index < count($nameservers); $index++) {
            $result[] = $this->createNameserver($nameservers[$index], $index);
        }

        $result[] = '</dt_array>';
        $result[] = '</item>';

        return implode(PHP_EOL, $result);
    }

    private function createNamespaceAssign(array $nameservers): string
    {
        $result = [
            '<item key="add_ns">',
            '<dt_array>',
        ];

        for ($index = 0; $index < count($nameservers); $index++) {
            $result[] = $this->createEnvelopItem($index, $nameservers[$index]);
        }

        $result[] = '</dt_array>';
        $result[] = '</item>';

        return implode(PHP_EOL, $result);
    }

    private function buildEnvelop(string $object, string $action, array $attributes, string $domain = null): string
    {
        $result = [
            '<?xml version="1.0" encoding="UTF-8" standalone="no"?>',
            "<!DOCTYPE OPS_envelope SYSTEM 'ops.dtd'>",
            '<OPS_envelope>',
            '<header>',
            '<version>0.9</version>',
            '</header>',
            '<body>',
            '<data_block>',
            '<dt_assoc>',
            $this->createEnvelopItem('protocol', 'XCP'),
            $this->createEnvelopItem('object', $object),
            $this->createEnvelopItem('action', $action),
            (
                is_null($domain)
                ? ''
                : $this->createEnvelopItem('domain', $domain)
            ),
            '<item key="attributes">',
            '<dt_assoc>',
        ];

        foreach ($attributes as $key => $value) {
            switch($key) {
                case 'contact_set':
                    $result[] = $this->createContactSet($value);
                    break;
                case 'nameserver_list':
                    $result[] = $this->createNameserverList($value);
                    break;
                case 'assign_ns':
                case 'add_ns':
                case 'remove_ns':
                    $result[] = $this->createNamespaceAssign($value);
                    break;
                default:
                    $result[] =
                      is_array($value)
                      ? $this->createArray($key, $value)
                      : $this->createEnvelopItem($key, $value);
            }
        }

        $closing = [
            '</dt_assoc>',
            '</item>',
            '</dt_assoc>',
            '</data_block>',
            '</body>',
            '</OPS_envelope>',
        ];

        foreach ($closing as $line) {
            $result[] = $line;
        }

        $xml = implode(PHP_EOL, $result);

        return $xml;
    }

    private function sanitizeContacts(array $contacts): array
    {
        if (count(array_keys($contacts)) == 1) {
            return [
                'owner' => $contacts[0]->toArray(),
                'admin' => $contacts[0]->toArray(),
                'tech' => $contacts[0]->toArray(),
                'billing' => $contacts[0]->toArray(),
            ];
        }

        $result = [];
        foreach ($contacts as $key => $val) {
            $result[$key] = $val->toArray();
        }

        return $result;
    }
}