user = OPENPROVIDER['username']; $this->pass = OPENPROVIDER['password']; $this->ip = OPENPROVIDER['ip']; $this->cache = new Cache($this->cacheDir); } public function deleteCache(string $name): void { $this->cache->murder($name); } /** * トークンの受け取り。 * このライブリリーを使ったら、一回「login()」を実行する事が必須となります。 * * @return Result 結果 */ public function login(): Result { if (!OPENPROVIDER_ENABLED) return Result::Error('エラー:OpenProviderは無効です。'); $cacheName = 'login'; $cache = $this->cache->get($cacheName); // 以前は$lastAuthが0のまま比較していたので、キャッシュのトークンが使われず毎回パスワードを送っていた $this->lastAuth = (int)($cache['last_auth'] ?? 0); if (isset($cache['data']['token']) && time() < ($this->lastAuth + $this->tokenDuration)) { $this->token = $cache['data']['token']; $this->resellerId = (int)($cache['data']['reseller_id'] ?? 0); return Result::Success('', $cache); } $payload = [ 'username' => $this->user, 'password' => $this->pass, 'ip' => $this->ip ?: '0.0.0.0', ]; $curl = $this->setupCurl('/auth/login', 'POST', $payload); $res = $curl->execute(); if (!$res->isSuccess) { $err = "CURLの実行に失敗: ".$curl->getError(); assert_unless_success($res, $err); return Result::Error($err); } $body = $curl->getResponseBody(); if ($curl->getResponseCode() != 200) { $err = self::errorDesc($body, $curl->getResponseCode()); assert($curl->getResponseCode() == 200, $err); return Result::Error($err); } assert_not_null($body, "返事ボディーは空です。"); $res = json_decode($body, true); if (is_array($res) && isset($res['data']['token'])) { $this->token = $res['data']['token']; // 以前はリセラーIDを記録していなかった(getResellerId()が常に0) $this->resellerId = (int)($res['data']['reseller_id'] ?? 0); $this->lastAuth = time(); $res['last_auth'] = $this->lastAuth; $this->cache->set($cacheName, $res); return Result::Success('', $res); } return Result::Error('ログインに失敗。'); } // ドメイン //// ドメイン /** * ドメイン一覧。 * * @param array $query 検索クエリー(limit、offset、order_by.*、status、domain_name_pattern、with_*等) * @return Result 結果。dataはドメインの配列(results) */ public function listDomains(array $query = []): Result { $res = $this->get('ドメイン一覧の受け取り', '/domains', $query, 'listdomains'); return $res->isSuccess ? Result::Success('', $res->data['data']['results'] ?? []) : $res; } /** * ドメイン名の登録。 * ハンドルを省略するとconfig.phpのOPENPROVIDERの「owner_handle」、ネームサーバーを省略するとOpenProviderのネームサーバー * * @param string $name 登録したいドメイン名(例:"076studio.jp") * @param array $info owner_handle、admin_handle、tech_handle、billing_handle、reseller_handle、 * name_servers(名前の配列、又は['name' => …, 'ip' => …]の配列)、autorenew、 * 他にAPIの項目(ns_group、additional_data、is_private_whois_enabled等)もそのまま送る * @param int $period 年間(デフォルト=1年) * @return Result 結果。data['data']:'activation_date'(リアルタイムで成功したのみ), 'auth_code'(TLDが対応している場合のみ), 'expiration_date', 'id', 'renewal_date', 'status'(ACT又はREQ) */ public function createDomain(string $name, array $info, int $period = 1): Result { if (!OPENPROVIDER_ENABLED) return Result::Error('エラー:OpenProviderは無効です。'); $domain = self::splitDomain($name); if ($domain === null) return Result::Error("不正なドメイン形: {$name}"); foreach (['owner_handle', 'admin_handle', 'tech_handle', 'billing_handle', 'reseller_handle'] as $h) { if (($info[$h] ?? '') === '') $info[$h] = OPENPROVIDER['owner_handle']; } // 以前はns_group等の他の項目を送らなかった if (empty($info['name_servers']) && empty($info['ns_group'])) $info['name_servers'] = ['ns1.openprovider.nl', 'ns2.openprovider.be', 'ns3.openprovider.eu']; if (isset($info['name_servers'])) $info['name_servers'] = array_map(fn($n) => is_array($n) ? $n : ['name' => $n], $info['name_servers']); if (($info['autorenew'] ?? '') === '') $info['autorenew'] = 'default'; $payload = ['domain' => $domain, 'period' => $period] + $info; return $this->send('ドメインの登録', 'POST', '/domains', $payload, [], ['listdomains', 'domain-']); } /** * ドメインを登録可能かどうかの確認。 * * @param array $domains ドメイン名リスト。例:['076.moe', '076.co.jp', '076.com'] * @param bool $with_price 値段を表示するかどうか。デフォルトは「false」 * @return Result 結果。dataは配列(results):'domain'と'status'はいつでもあり、'reason', 'premium', 'is_premium'が多分あり、そして'price'が「with_price」が「true」の場合のみ。 */ public function checkDomainAvailable(array $domains, bool $with_price = false): Result { if (!OPENPROVIDER_ENABLED) return Result::Error('エラー:OpenProviderは無効です。'); $domainList = []; foreach ($domains as $d) { // 「076.co.jp」の様なTLDもあるので、最初の「.」で分ける $domain = self::splitDomain((string)$d); if ($domain === null) return Result::Error("不正なドメイン形: {$d}"); $domainList[] = $domain; } $res = $this->send('ドメインの確認', 'POST', '/domains/check', ['domains' => $domainList, 'with_price' => $with_price]); return $res->isSuccess ? Result::Success('', $res->data['data']['results'] ?? []) : $res; } /** * ドメイン名を勧められる。 * * @param array $payload name(必須)、tlds、language、limit、sensitive、provider * @return Result 結果。data['data']['results']:勧められたドメイン */ public function suggestDomainname(array $payload = []): Result { return $this->send('ドメイン名の提案', 'POST', '/domains/suggest-name', $payload); } /** * ドメイン名を交換する(所有者の変更)。 * * @param array $payload domain(name、extension)、owner_handle、auth_code等 * @return Result 結果。data['data']:creation_date、expiration_date、renewal_date、status */ public function tradeDomainname(array $payload = []): Result { return $this->send('ドメインの交換', 'POST', '/domains/trade', $payload, [], ['listdomains', 'domain-']); } /** * ドメイン名を移転する。 * * @param array $payload domain(name、extension)、auth_code、owner_handle等 * @return Result 結果。data['data']:id、status、auth_code、expiration_date等 */ public function transferDomainname(array $payload = []): Result { return $this->send('ドメインの移転', 'POST', '/domains/transfer', $payload, [], ['listdomains', 'domain-']); } /** * ドメイン名の受け取り * * @param int $id ドメインID * @param array $query 検索クエリー(with_history、with_additional_data等) * @return Result 結果。data['data']:ドメイン */ public function getDomainname(int $id, array $query = []): Result { return $this->get('ドメインの受け取り', "/domains/{$id}", $query, "domain-{$id}"); } /** * ドメイン名の更新 * * @param int $id ドメインID * @param array $payload 更新する項目(name_servers、autorenew、is_locked等) * @return Result 結果。data['data']:id、status */ public function updateDomainname(int $id, array $payload = []): Result { return $this->send('ドメイン名の更新', 'PUT', "/domains/{$id}", $payload, [], ['listdomains', "domain-{$id}"]); } /** * ドメイン名の削除 * * @param int $id ドメインID * @param array $query type(例:「transfer」)、skip_soft_quarantine、force_delete等 * @return Result 結果。data['data']:status */ public function deleteDomainname(int $id, array $query = []): Result { // 以前は検索の条件を本文で送っていた(DELETEはクエリー) return $this->send('ドメイン名の削除', 'DELETE', "/domains/{$id}", null, $query, ['listdomains', "domain-{$id}"]); } /** * ドメイン名の最後行動の再度実行 * * @param int $id ドメインID * @param array $payload auth_code等 * @return Result 結果 */ public function retryLastDomainnameOperation(int $id, array $payload = []): Result { return $this->send('ドメインの最後行動の再度実行', 'POST', "/domains/{$id}/last-operation/restart", ['id' => $id] + $payload, [], ['listdomains', "domain-{$id}"]); } /** * ドメイン名の有効期限の更新 * * @param int $id ドメインID * @param array $payload period(年)等 * @return Result 結果。data['data']:status */ public function renewDomainname(int $id, array $payload = []): Result { return $this->send('ドメインの有効期限の更新', 'POST', "/domains/{$id}/renew", ['id' => $id] + $payload, [], ['listdomains', "domain-{$id}"]); } /** * 削除済みドメイン名の復活 * * @param int $id ドメインID * @param array $payload フォームデータ * @return Result 結果。data['data']:status */ public function restoreDomainname(int $id, array $payload = []): Result { return $this->send('削除済みドメイン名の復活', 'POST', "/domains/{$id}/restore", ['id' => $id] + $payload, [], ['listdomains', "domain-{$id}"]); } /** * ドメイン名の移動の承諾 * * @param int $id ドメインID * @param array $payload approve(1=承諾、0=拒否)、auth_code、registrar_tag等 * @return Result 結果 */ public function approveDomainnameTransfer(int $id, array $payload = []): Result { return $this->send('ドメイン名の移動の承諾', 'POST', "/domains/{$id}/transfer/approve", ['id' => $id] + $payload, [], ['listdomains', "domain-{$id}"]); } /** * ドメイン名のFOA1(移転の確認メール)の送信 * * @param int $id ドメインID * @param array $payload フォームデータ * @return Result 結果 */ public function sendDomainnameFoa1(int $id, array $payload = []): Result { return $this->send('ドメイン名のFOA1の送信', 'POST', "/domains/{$id}/transfer/send-foa1", ['id' => $id] + $payload); } //// 追加データ /** * 追加データ(TLDで必要な項目)の受け取り * * @param array $query domain.name、domain.extension、operation * @return Result 結果。data['data']:項目の配列 */ public function getDomainAdditionalData(array $query): Result { return $this->get('ドメインの追加データの受け取り', '/domains/additional-data', $query, 'domainadditionaldata-'.self::cacheKey($query)); } //// 顧客様の追加データ /** * 顧客様の追加データ(TLDで顧客様に必要な項目)の受け取り * * @param array $query domain.name、domain.extension * @return Result 結果。data['data']:項目の配列 */ public function getDomainCustomerAdditionalData(array $query): Result { // 以前はドメインの追加データと同じキャッシュ名で、違う結果を返す事があった return $this->get('ドメインの顧客様の追加データの受け取り', '/domains/additional-data/customers', $query, 'domaincustomerdata-'.self::cacheKey($query)); } //// ドメイン値段 /** * ドメインの値段 * * @param array $query domain.name、domain.extension、operation(create、transfer、renew等)、period * @return Result 結果。data['data']:price、is_premium等 */ public function getDomainPrices(array $query): Result { return $this->get('ドメインの値段の受け取り', '/domains/prices', $query, 'domainprices-'.self::cacheKey($query)); } //// 認証コード /** * 認証コードの受け取り(キャッシュしない) * * @param int $id ドメイン名ID * @param array $query auth_code_type、sending_type等 * @return Result 結果。data['data']:auth_code、type、success */ public function getAuthCode(int $id, array $query = []): Result { return $this->get('認証コードの受け取り', "/domains/{$id}/authcode", $query); } /** * 認証コードの再設定 * * @param int $id ドメイン名ID * @param array $payload auth_code_type、sending_type等 * @return Result 結果。data['data']:auth_code等 */ public function resetAuthCode(int $id, array $payload = []): Result { return $this->send('認証コードの再設定', 'POST', "/domains/{$id}/authcode/reset", ['id' => $id] + $payload, [], ["domain-{$id}"]); } //// TLD /** * TLD一覧 * * @param array $query 検索クエリー(省略すると最初の10件) * @return Result 結果。data['data']:results、total */ public function listTlds(array $query = []): Result { if (empty($query)) $query = ['limit' => 10, 'offset' => 0, 'order' => 'ASC']; return $this->get('TLD一覧の受け取り', '/tlds', $query, 'listtlds'); } /** * TLDの表示 * * @param string $tld TLD * @param array $query 検索クエリー(with_price、with_description等) * @return Result 結果。data['data']:TLD */ public function getTld(string $tld, array $query = []): Result { if (empty($query)) $query = ['limit' => 10, 'offset' => 0]; return $this->get('TLDの受け取り', '/tlds/'.self::seg($tld), $query, 'gettld-'.self::cacheKey($tld)); } // 請求 //// 請求書 /** * 請求書一覧 * * @param array $query 検索クエリー(省略すると最初の10件) * @return Result 結果。data['data']:results、total */ public function listInvoices(array $query = []): Result { if (empty($query)) $query = ['limit' => 10, 'offset' => 0]; return $this->get('請求書一覧の受け取り', '/invoices', $query, 'listinvoices'); } //// 支払 /** * 支払一覧 * * @param array $query 検索クエリー(省略すると最初の10件) * @return Result 結果。data['data']:results、total */ public function listPayments(array $query = []): Result { if (empty($query)) $query = ['limit' => 10, 'offset' => 0]; return $this->get('支払一覧の受け取り', '/payments', $query, 'listpayments'); } /** * 推奨のチャージ額(キャッシュしない) * * @return Result 結果。data['data']:suggested_top_up_amount */ public function getSuggestedTopUpAmount(): Result { return $this->get('推奨のチャージ額の受け取り', '/payments/suggested-top-up-amount'); } //// トランザクション /** * トランザクション一覧 * * @param array $query 検索クエリー(省略すると最初の10件) * @return Result 結果。data['data']:results、total */ public function listTransactions(array $query = []): Result { if (empty($query)) $query = ['limit' => 10, 'offset' => 0]; return $this->get('トランザクション一覧の受け取り', '/transactions', $query, 'listtransactions'); } // DNS //// ドメイントークン /** * ドメイントークンの作成(他のDNSプロバイダーでドメインの所有を確認する為) * * @param array $payload domain、zone_provider * @return Result 結果。data['data']:token、url */ public function createDomainToken(array $payload = []): Result { // 以前は成功・失敗のメッセージがDNSテンプレートの物だった return $this->send('ドメイントークンの作成', 'POST', '/dns/domain-token', $payload); } //// ネームサーバー /** * ネームサーバー一覧 * * @param array $query 検索クエリー(省略すると名前順の最初の25件) * @return Result 結果。data['data']:results、total */ public function listNameservers(array $query = []): Result { // 「order_by.name」はこのAPIに無い(order、order_by) if (empty($query)) $query = ['limit' => 25, 'offset' => 0, 'order_by' => 'name', 'order' => 'asc']; return $this->get('ネームサーバー一覧の受け取り', '/dns/nameservers', $query, 'dnsns-list'); } /** * ネームサーバーの作成 * * @param array $payload name、ip、ip6 * @return Result 結果。data['data']:name、ip、ip6 */ public function createNameserver(array $payload = []): Result { return $this->send('ネームサーバーの追加', 'POST', '/dns/nameservers', $payload, [], ['dnsns-']); } /** * ネームサーバーの表示 * * @param string $name ネームサーバー名(例:ns1.example.com) * @return Result 結果。data['data']:name、ip、ip6 */ public function getNameserver(string $name): Result { // 以前は一覧のクエリー(limit等)を付けていた return $this->get('ネームサーバーの受け取り', '/dns/nameservers/'.self::seg($name), [], 'dnsns-'.self::cacheKey($name)); } /** * ネームサーバーの更新 * * @param string $name ネームサーバー名 * @param array $payload ip、ip6 * @return Result 結果 */ public function updateNameserver(string $name, array $payload = []): Result { return $this->send('ネームサーバーの更新', 'PUT', '/dns/nameservers/'.self::seg($name), ['name' => $name] + $payload, [], ['dnsns-']); } /** * ネームサーバーの削除 * * @param string $name ネームサーバー名 * @return Result 結果 */ public function deleteNameserver(string $name): Result { return $this->send('ネームサーバーの削除', 'DELETE', '/dns/nameservers/'.self::seg($name), null, [], ['dnsns-']); } //// NSグループ /** * NSグループ一覧 * * @param array $query 検索クエリー(省略すると名前順の最初の25件) * @return Result 結果。data['data']:results、total */ public function listNsGroups(array $query = []): Result { // 以前は「order_by.name」(このAPIでは「order_by.ns_group」) if (empty($query)) $query = ['limit' => 25, 'offset' => 0, 'order_by.ns_group' => 'asc']; return $this->get('NSグループ一覧の受け取り', '/dns/nameservers/groups', $query, 'dnsnsgroup-list'); } /** * NSグループの作成 * * @param array $payload ns_group、name_servers(['name' => …, 'ip' => …]の配列) * @return Result 結果 */ public function createNsGroup(array $payload = []): Result { return $this->send('NSグループの追加', 'POST', '/dns/nameservers/groups', $payload, [], ['dnsnsgroup-']); } /** * NSグループの表示 * * @param string $nsGroup NSグループ名 * @param array $query id * @return Result 結果。data['data']:ns_group、name_servers等 */ public function getNsGroup(string $nsGroup, array $query = []): Result { return $this->get('NSグループの受け取り', '/dns/nameservers/groups/'.self::seg($nsGroup), $query, 'dnsnsgroup-'.self::cacheKey($nsGroup)); } /** * NSグループの更新 * * @param string $nsGroup NSグループ名 * @param array $payload name_servers * @return Result 結果 */ public function updateNsGroup(string $nsGroup, array $payload = []): Result { return $this->send('NSグループの更新', 'PUT', '/dns/nameservers/groups/'.self::seg($nsGroup), ['ns_group' => $nsGroup] + $payload, [], ['dnsnsgroup-']); } /** * NSグループの削除 * * @param string $nsGroup NSグループ名 * @return Result 結果 */ public function deleteNsGroup(string $nsGroup): Result { return $this->send('NSグループの削除', 'DELETE', '/dns/nameservers/groups/'.self::seg($nsGroup), null, [], ['dnsnsgroup-']); } //// テンプレート /** * DNSテンプレート一覧 * * @param array $query 検索クエリー(with_records、name_pattern等) * @return Result 結果。data['data']:results、total */ public function listDnsTemplate(array $query = []): Result { return $this->get('DNSテンプレート一覧の受け取り', '/dns/templates', $query, 'dnstemplate-list'); } /** * DNSテンプレートの作成 * * @param array $payload name、records、is_spamexperts_enabled * @return Result 結果。data['data']:id */ public function createDnsTemplate(array $payload = []): Result { return $this->send('DNSテンプレートの追加', 'POST', '/dns/templates', $payload, [], ['dnstemplate-']); } /** * DNSテンプレートの表示 * * @param int $id DNSテンプレートID * @return Result 結果。data['data']:id、name、records等 */ public function getDnsTemplate(int $id): Result { return $this->get('DNSテンプレートの受け取り', "/dns/templates/{$id}", [], "dnstemplate-{$id}"); } /** * DNSテンプレートの削除 * * @param int $id DNSテンプレートID * @return Result 結果 */ public function deleteDnsTemplate(int $id): Result { return $this->send('DNSテンプレートの削除', 'DELETE', "/dns/templates/{$id}", null, [], ['dnstemplate-']); } //// DNSゾーン /** * DNSゾーン一覧 * * @param array $query 検索クエリー(省略すると名前順の最初の25件) * @return Result 結果。data['data']:results、total */ public function listDnsZones(array $query = []): Result { if (empty($query)) $query = ['limit' => 25, 'offset' => 0, 'order_by.name' => 'asc']; return $this->get('DNSゾーン一覧の受け取り', '/dns/zones', $query, 'dnszone-list'); } /** * DNSゾーンの作成 * * @param array $payload domain(name、extension)、type(master又はslave)、records、template_name等 * @return Result 結果 */ public function createDnsZone(array $payload = []): Result { return $this->send('DNSゾーンの追加', 'POST', '/dns/zones', $payload, [], ['dnszone-']); } /** * DNSゾーンの表示 * * @param string $domain ドメイン名 * @param array $query with_records、with_history、with_dnskey、provider * @return Result 結果。data['data']:ゾーン */ public function getDnsZone(string $domain, array $query = []): Result { // 以前は一覧のクエリー(limit、order_by.name等)を付けていた return $this->get('DNSゾーンの受け取り', '/dns/zones/'.self::seg($domain), $query, 'dnszone-'.self::cacheKey($domain)); } /** * DNSゾーンの更新 * * @param string $domain ドメイン名 * @param array $payload records(add、remove、replace、update)等 * @return Result 結果 */ public function updateDnsZone(string $domain, array $payload = []): Result { return $this->send('DNSゾーンの更新', 'PUT', '/dns/zones/'.self::seg($domain), ['name' => $domain] + $payload, [], ['dnszone-', 'dnsrecords-'.self::cacheKey($domain)]); } /** * DNSゾーンの削除 * * @param string $domain ドメイン名 * @param array $query id、provider等 * @return Result 結果 */ public function deleteDnsZone(string $domain, array $query = []): Result { return $this->send('DNSゾーンの削除', 'DELETE', '/dns/zones/'.self::seg($domain), null, $query, ['dnszone-', 'dnsrecords-'.self::cacheKey($domain)]); } //// ゾーンレコード /** * DNSレコード一覧 * * @param string $domain ドメイン名 * @param array $query 検索クエリー(type、limit、order_by.*等) * @return Result 結果。data['data']:results、total */ public function listZoneRecords(string $domain, array $query = []): Result { return $this->get('DNSレコード一覧の受け取り', '/dns/zones/'.self::seg($domain).'/records', $query, 'dnsrecords-'.self::cacheKey($domain)); } // EasyDMARC /** * ドメインのEasyDMARCの表示 * * @param string $domain ドメイン名(例:076.moe) * @return Result 結果。data['data']:id、status、record_host、record_value等 */ public function getEasydmarc(string $domain): Result { $d = self::splitDomain($domain); if ($d === null) return Result::Error("不正なドメイン形: {$domain}"); // 以前は「/dns/easydmarcs」でクエリーも「name」「extension」だった return $this->get('EasyDMARCの受け取り', '/easydmarcs', ['domain.name' => $d['name'], 'domain.extension' => $d['extension']], 'easydmarc-'.self::cacheKey($domain)); } /** * EasyDMARCの注文 * * @param string $domain ドメイン名 * @param string $ownerHandle 所有者のハンドル(省略するとconfig.phpのOPENPROVIDERの「owner_handle」) * @return Result 結果。data['data']:注文 */ public function createEasydmarc(string $domain, string $ownerHandle = ''): Result { $d = self::splitDomain($domain); if ($d === null) return Result::Error("不正なドメイン形: {$domain}"); $payload = ['domain' => $d, 'owner_handle' => $ownerHandle !== '' ? $ownerHandle : (string)(OPENPROVIDER['owner_handle'] ?? '')]; return $this->send('EasyDMARCの注文', 'POST', '/easydmarcs', $payload, [], ['easydmarc-']); } /** * EasyDMARC一覧 * * @param array $query 検索クエリー(status、domain.name等)。「domain」(例:076.moe)も使える * @return Result 結果。data['data']:results、total */ public function listEasydmarcs(array $query = []): Result { if (isset($query['domain'])) { $d = self::splitDomain((string)$query['domain']); if ($d === null) return Result::Error("不正なドメイン形: {$query['domain']}"); unset($query['domain']); $query += ['domain.name' => $d['name'], 'domain.extension' => $d['extension']]; } if (empty($query)) $query = ['limit' => 100, 'offset' => 0, 'order_by.id' => 'asc']; return $this->get('EasyDMARC一覧の受け取り', '/easydmarcs/list', $query, 'easydmarc-list'); } /** * EasyDMARCの削除 * * @param int $orderId 注文ID * @return Result 結果。data['data']:status */ public function deleteEasydmarc(int $orderId): Result { // 以前は未定義の$domainを使っていた return $this->send('EasyDMARCの削除', 'DELETE', "/easydmarcs/{$orderId}", null, [], ['easydmarc-']); } /** * 失敗したEasyDMARCの注文の再試行 * * @param int $orderId 注文ID * @return Result 結果。data['data']:注文 */ public function retryEasydmarc(int $orderId): Result { return $this->send('EasyDMARCの注文の再試行', 'POST', "/easydmarcs/{$orderId}/retry", ['id' => $orderId], [], ['easydmarc-']); } /** * EasyDMARCの管理画面にログインするURL(使い捨てなのでキャッシュしない) * * @param int $orderId 注文ID * @param string $type 画面の種類(省略可) * @return Result 結果。data['data']:url */ public function getEasydmarcSso(int $orderId, string $type = ''): Result { return $this->get('EasyDMARCのSSOの受け取り', "/easydmarcs/{$orderId}/sso", $type !== '' ? ['type' => $type] : []); } // メールテンプレート //// メール /** * メールテンプレート一覧 * * @param array $query 検索クエリー(id、name、group、is_system等) * @return Result 結果。data['data']:results、total */ public function listEmails(array $query = []): Result { return $this->get('メールテンプレート一覧の受け取り', '/emails', $query, 'email-list'); } /** * メールテンプレートの作成 * * @param array $payload name、group、fields、locale、tags、is_active、is_default * @return Result 結果。data['data']:id */ public function createEmail(array $payload = []): Result { return $this->send('メールテンプレートの追加', 'POST', '/emails', $payload, [], ['email-']); } /** * メールテンプレートの更新 * * @param int $id メールテンプレートID * @param array $payload 更新する項目 * @return Result 結果 */ public function updateEmail(int $id, array $payload = []): Result { return $this->send('メールテンプレートの更新', 'PUT', "/emails/{$id}", ['id' => $id] + $payload, [], ['email-']); } /** * メールテンプレートの削除 * * @param int $id メールテンプレートID * @return Result 結果 */ public function deleteEmail(int $id): Result { return $this->send('メールテンプレートの削除', 'DELETE', "/emails/{$id}", null, [], ['email-']); } // ライセンス /** * ライセンス一覧(値段付き) * * @param array $query 検索クエリー(product、status、key_number等) * @return Result 結果。data['data']:results、total */ public function listLicenses(array $query = []): Result { return $this->get('ライセンス一覧の受け取り', '/licenses', $query, 'license-list'); } /** * ライセンスのハードウェアIDの再設定 * * @param string $product 商品(例:plesk、virtuozzo) * @param int $keyId キーID * @return Result 結果 */ public function resetLicenseHwid(string $product, int $keyId): Result { return $this->send('ハードウェアIDの再設定', 'POST', '/licenses/hwids/reset/'.self::seg($product)."/{$keyId}", ['key_id' => $keyId, 'product' => $product], [], ['license-']); } /** * ライセンスの商品一覧 * * @param array $query 検索クエリー(product等) * @return Result 結果。data['data']:results、total */ public function listLicenseItems(array $query = []): Result { return $this->get('ライセンスの商品一覧の受け取り', '/licenses/items', $query, 'license-items'); } //// Plesk /** * Pleskのライセンス一覧 * * @param array $query 検索クエリー * @return Result 結果。data['data']:results、total */ public function listPleskLicenses(array $query = []): Result { return $this->get('Pleskのライセンス一覧の受け取り', '/licenses/plesk', $query, 'license-plesk'); } /** * Pleskのライセンスの注文 * * @param array $payload items、period、title、ip_address_binding等 * @return Result 結果。data['data']:key_id、attached_keys */ public function createPleskLicense(array $payload): Result { return $this->send('Pleskのライセンスの注文', 'POST', '/licenses/plesk', $payload, [], ['license-']); } /** * Pleskのライセンスキー(キャッシュしない) * * @param int $keyId キーID * @return Result 結果。data['data']:key */ public function getPleskKey(int $keyId): Result { return $this->get('Pleskのライセンスキーの受け取り', "/licenses/plesk/key/{$keyId}"); } /** * Pleskのライセンスの表示 * * @param int $keyId キーID * @return Result 結果。data['data']:ライセンス */ public function getPleskLicense(int $keyId): Result { return $this->get('Pleskのライセンスの受け取り', "/licenses/plesk/{$keyId}", [], "license-plesk-{$keyId}"); } /** * Pleskのライセンスの更新 * * @param int $keyId キーID * @param array $payload 更新する項目(items、title、suspended等) * @return Result 結果 */ public function updatePleskLicense(int $keyId, array $payload = []): Result { return $this->send('Pleskのライセンスの更新', 'PUT', "/licenses/plesk/{$keyId}", ['key_id' => $keyId] + $payload, [], ['license-']); } /** * Pleskのライセンスの削除 * * @param int $keyId キーID * @return Result 結果 */ public function deletePleskLicense(int $keyId): Result { return $this->send('Pleskのライセンスの削除', 'DELETE', "/licenses/plesk/{$keyId}", null, [], ['license-']); } //// Virtuozzo /** * Virtuozzoのライセンス一覧 * * @param array $query 検索クエリー * @return Result 結果。data['data']:results、total */ public function listVirtuozzoLicenses(array $query = []): Result { return $this->get('Virtuozzoのライセンス一覧の受け取り', '/licenses/virtuozzo', $query, 'license-virtuozzo'); } /** * Virtuozzoのライセンスの注文 * * @param array $payload items、period、pcs_capacity、title等 * @return Result 結果。data['data']:key_id、attached_keys */ public function createVirtuozzoLicense(array $payload): Result { return $this->send('Virtuozzoのライセンスの注文', 'POST', '/licenses/virtuozzo', $payload, [], ['license-']); } /** * Virtuozzoのライセンスの表示 * * @param int $keyId キーID * @return Result 結果。data['data']:ライセンス */ public function getVirtuozzoLicense(int $keyId): Result { return $this->get('Virtuozzoのライセンスの受け取り', "/licenses/virtuozzo/{$keyId}", [], "license-virtuozzo-{$keyId}"); } /** * Virtuozzoのライセンスの更新 * * @param int $keyId キーID * @param array $payload 更新する項目 * @return Result 結果 */ public function updateVirtuozzoLicense(int $keyId, array $payload = []): Result { return $this->send('Virtuozzoのライセンスの更新', 'PUT', "/licenses/virtuozzo/{$keyId}", ['key_id' => $keyId] + $payload, [], ['license-']); } /** * Virtuozzoのライセンスの削除 * * @param int $keyId キーID * @return Result 結果 */ public function deleteVirtuozzoLicense(int $keyId): Result { return $this->send('Virtuozzoのライセンスの削除', 'DELETE', "/licenses/virtuozzo/{$keyId}", null, [], ['license-']); } /** * Virtuozzoのライセンスキー(キャッシュしない) * * @param int $keyId キーID * @return Result 結果。data['data']:key */ public function getVirtuozzoKey(int $keyId): Result { return $this->get('Virtuozzoのライセンスキーの受け取り', "/licenses/virtuozzo/{$keyId}/key"); } // リセラー・顧客様 //// 連絡先 /** * 連絡先の一覧 * * @param array $query 検索クエリー * @return Result 結果。data['data']:results、total */ public function listContacts(array $query = []): Result { return $this->get('連絡先一覧の受け取り', '/contacts', $query, 'contact-list'); } /** * 連絡先の作成 * * @param array $payload name、email、phone、address、role、username、password等 * @return Result 結果。data['data']:id */ public function createContact(array $payload): Result { return $this->send('連絡先の追加', 'POST', '/contacts', $payload, [], ['contact-']); } /** * 連絡先の表示 * * @param int $id 連絡先ID * @param bool $withAdditionalData 追加データも受け取る * @return Result 結果。data['data']:連絡先 */ public function getContact(int $id, bool $withAdditionalData = false): Result { return $this->get('連絡先の受け取り', "/contacts/{$id}", ['with_additional_data' => $withAdditionalData], "contact-{$id}"); } /** * 連絡先の更新 * * @param int $id 連絡先ID * @param array $payload 更新する項目 * @return Result 結果 */ public function updateContact(int $id, array $payload = []): Result { return $this->send('連絡先の更新', 'PUT', "/contacts/{$id}", ['id' => $id] + $payload, [], ['contact-']); } /** * 連絡先の削除 * * @param int $id 連絡先ID * @return Result 結果 */ public function deleteContact(int $id): Result { return $this->send('連絡先の削除', 'DELETE', "/contacts/{$id}", null, [], ['contact-']); } /** * 連絡先の通知の設定(ログイン中の連絡先) * * @return Result 結果。data['data']:operational_notification、marketing_promotion等 */ public function getContactNotificationSetting(): Result { return $this->get('通知の設定の受け取り', '/contact/notification-setting', [], 'contact-notification'); } /** * 連絡先の通知の設定の更新 * * @param array $payload educational_content、marketing_promotion、operational_notification、platform_update_news(真偽値) * @return Result 結果 */ public function updateContactNotificationSetting(array $payload): Result { return $this->send('通知の設定の更新', 'POST', '/contact/notification-setting', $payload, [], ['contact-notification']); } //// 顧客様 /** * 顧客様一覧 * * @param array $query 検索クエリー * @return Result 結果。data['data']:results、total */ public function listCustomers(array $query = []): Result { return $this->get('顧客様一覧の受け取り', '/customers', $query, 'customer-list'); } /** * 顧客様の作成 * * @param array $payload name、email、phone、address、company_name等 * @return Result 結果。data['data']:handle */ public function createCustomer(array $payload): Result { return $this->send('顧客様の追加', 'POST', '/customers', $payload, [], ['customer-']); } /** * 顧客様の表示 * * @param string $handle 顧客様のハンドル * @param bool $withAdditionalData 追加データも受け取る * @return Result 結果。data['data']:顧客様 */ public function getCustomer(string $handle, bool $withAdditionalData = false): Result { return $this->get('顧客様の受け取り', '/customers/'.self::seg($handle), ['with_additional_data' => $withAdditionalData], 'customer-'.self::cacheKey($handle)); } /** * 顧客様の更新 * * @param string $handle 顧客様のハンドル * @param array $payload 更新する項目 * @return Result 結果 */ public function updateCustomer(string $handle, array $payload = []): Result { return $this->send('顧客様の更新', 'PUT', '/customers/'.self::seg($handle), ['handle' => $handle] + $payload, [], ['customer-']); } /** * 顧客様の削除 * * @param string $handle 顧客様のハンドル * @return Result 結果 */ public function deleteCustomer(string $handle): Result { return $this->send('顧客様の削除', 'DELETE', '/customers/'.self::seg($handle), null, [], ['customer-']); } //// メール認証 /** * ドメインのメール認証一覧 * * @param array $query 検索クエリー(domain、email、handle、status等) * @return Result 結果。data['data']:results、total */ public function listEmailVerifications(array $query = []): Result { return $this->get('メール認証一覧の受け取り', '/customers/verifications/emails/domains', $query, 'verification-list'); } /** * メール認証の再送信 * * @param string $email メールアドレス * @param string $language 言語(省略可) * @param string $tag タグ(省略可) * @return Result 結果 */ public function restartEmailVerifications(string $email, string $language = '', string $tag = ''): Result { // 以前は空の項目も送っていた $payload = array_filter(['email' => $email, 'language' => $language, 'tag' => $tag], fn($v) => $v !== ''); return $this->send('メール認証の再送信', 'POST', '/customers/verifications/emails/restart', $payload, [], ['verification-']); } /** * メール認証の開始 * * @param string $email メールアドレス * @param string $handle 顧客様のハンドル(省略可) * @param string $language 言語(省略可) * @param string $tag タグ(省略可) * @return Result 結果。data['data']:id */ public function startEmailVerifications(string $email, string $handle = '', string $language = '', string $tag = ''): Result { $payload = array_filter(['email' => $email, 'handle' => $handle, 'language' => $language, 'tag' => $tag], fn($v) => $v !== ''); return $this->send('メール認証の開始', 'POST', '/customers/verifications/emails/start', $payload, [], ['verification-']); } //// リセラー /** * リセラー(自分)の表示 * * @param array $query with_statistics、with_settings、with_contacts、with_additional_data * @return Result 結果。data['data']:リセラー(balance等) */ public function getReseller(array $query = []): Result { return $this->get('リセラーの受け取り', '/resellers', $query, 'reseller'); } /** * リセラーの更新 * * @param int $id リセラーID(getResellerId()) * @param array $payload 更新する項目 * @return Result 結果 */ public function updateReseller(int $id, array $payload = []): Result { return $this->send('リセラーの更新', 'PUT', "/resellers/{$id}", ['id' => $id] + $payload, [], ['reseller']); } //// 設定 /** * リセラーの設定 * * @return Result 結果。data['data']:設定 */ public function listSettings(): Result { return $this->get('設定の受け取り', '/resellers/settings', [], 'reseller-settings'); } //// 統計 /** * リセラーの統計 * * @return Result 結果。data['data']:domain、dns、ssl等の統計 */ public function listStatistics(): Result { return $this->get('統計の受け取り', '/resellers/statistics', [], 'reseller-statistics'); } //// タグ /** * タグ一覧 * * @param array $query key、value * @return Result 結果。data['data']:results、total */ public function listTags(array $query = []): Result { return $this->get('タグ一覧の受け取り', '/tags', $query, 'tag-list'); } /** * タグの作成 * * @param array $payload key、value * @return Result 結果 */ public function createTag(array $payload = []): Result { return $this->send('タグの追加', 'POST', '/tags', $payload, [], ['tag-']); } /** * タグの削除 * * @param array $query key、value(削除するタグ) * @return Result 結果 */ public function deleteTag(array $query = []): Result { // 以前は未定義の$idで「/tags/{id}」に送っていた(APIは「/tags?key=…&value=…」) return $this->send('タグの削除', 'DELETE', '/tags', null, $query, ['tag-']); } // SpamExpert /** * SpamExpertの管理画面にログインするURL(使い捨てなのでキャッシュしない) * * @param array $payload domain_or_email、bundle * @return Result 結果。data['data']:url */ public function generateLoginUrlSpamExpert(array $payload = []): Result { // 以前はURLが「/spam-expert-generate-login-url」で、メッセージがDNSテンプレートの物だった return $this->send('SpamExpertのログインURLの作成', 'POST', '/spam-expert/generate-login-url', $payload); } //// SEDomain /** * SpamExpertにドメインを追加する * * @param array $payload domain_name、destinations、aliases、products、bundle * @return Result 結果 */ public function createSeDomain(array $payload): Result { return $this->send('SpamExpertのドメインの追加', 'POST', '/spam-expert/domains', $payload, [], ['spamexpert-']); } /** * SpamExpertのドメインの表示 * * @param string $domain ドメイン名 * @param array $query with_records、bundle * @return Result 結果。data['data']:ドメイン */ public function getSeDomain(string $domain, array $query = []): Result { return $this->get('SpamExpertのドメインの受け取り', '/spam-expert/domains/'.self::seg($domain), $query, 'spamexpert-'.self::cacheKey($domain)); } /** * SpamExpertのドメインの更新 * * @param string $domain ドメイン名 * @param array $payload 更新する項目 * @return Result 結果 */ public function updateSeDomain(string $domain, array $payload = []): Result { return $this->send('SpamExpertのドメインの更新', 'PUT', '/spam-expert/domains/'.self::seg($domain), ['domain_name' => $domain] + $payload, [], ['spamexpert-']); } /** * SpamExpertのドメインの削除 * * @param string $domain ドメイン名 * @param array $query bundle * @return Result 結果 */ public function deleteSeDomain(string $domain, array $query = []): Result { return $this->send('SpamExpertのドメインの削除', 'DELETE', '/spam-expert/domains/'.self::seg($domain), null, $query, ['spamexpert-']); } // SSL証明書 //// 確認担当者メール /** * 確認担当者のメールアドレス一覧(ドメインの所有を確認するメールの宛先) * * @param array $query product_id、domain * @return Result 結果。data['data']['results']:メールアドレス */ public function listApproverEmails(array $query = []): Result { return $this->get('確認担当者メール一覧の受け取り', '/ssl/approver-emails', $query, 'ssl-approver-'.self::cacheKey($query)); } //// CSR /** * CSRと秘密鍵の作成 * * @param array $payload common_name、country、organization、email、bits、subject_alternative_name等 * @return Result 結果。data['data']:csr、key等 */ public function createCsr(array $payload): Result { // 以前はメッセージが確認担当者メールの物だった return $this->send('CSRの作成', 'POST', '/ssl/csr', $payload); } /** * CSRの解析 * * @param string $csr CSR(PEM) * @return Result 結果。data['data']:subject、subject_alternative_name等 */ public function decodeCsr(string $csr): Result { return $this->send('CSRの解析', 'POST', '/ssl/csr/decode', ['csr' => $csr]); } //// 注文 /** * SSL証明書の注文一覧 * * @param array $query 検索クエリー(status、common_name_pattern等) * @return Result 結果。data['data']:results、total */ public function listSslOrders(array $query = []): Result { return $this->get('SSL証明書の注文一覧の受け取り', '/ssl/orders', $query, 'ssl-order-list'); } /** * SSL証明書の注文 * * @param array $payload product_id、period、csr、host_names、approver_email、organization_handle、technical_handle等 * @return Result 結果。data['data']:id */ public function createSslOrder(array $payload): Result { return $this->send('SSL証明書の注文', 'POST', '/ssl/orders', $payload, [], ['ssl-order-']); } /** * SSL証明書の注文の表示 * * @param int $id 注文ID * @return Result 結果。data['data']:注文(certificate、status等) */ public function getSslOrder(int $id): Result { return $this->get('SSL証明書の注文の受け取り', "/ssl/orders/{$id}", [], "ssl-order-{$id}"); } /** * SSL証明書の注文の更新 * * @param int $id 注文ID * @param array $payload 更新する項目 * @return Result 結果。data['data']:id */ public function updateSslOrder(int $id, array $payload = []): Result { return $this->send('SSL証明書の注文の更新', 'PUT', "/ssl/orders/{$id}", ['id' => $id] + $payload, [], ['ssl-order-']); } /** * SSL証明書の注文の取消 * * @param int $id 注文ID * @return Result 結果。data['data']:id */ public function cancelSslOrder(int $id): Result { return $this->send('SSL証明書の注文の取消', 'POST', "/ssl/orders/{$id}/cancel", ['id' => $id], [], ['ssl-order-']); } /** * SSL証明書の再発行 * * @param int $id 注文ID * @param array $payload csr、host_names、approver_email等 * @return Result 結果。data['data']:id */ public function reissueSslOrder(int $id, array $payload = []): Result { return $this->send('SSL証明書の再発行', 'POST', "/ssl/orders/{$id}/reissue", ['id' => $id] + $payload, [], ['ssl-order-']); } /** * SSL証明書の更新(期間の延長) * * @param int $id 注文ID * @param array $payload enable_dns_automation * @return Result 結果。data['data']:id */ public function renewSslOrder(int $id, array $payload = []): Result { return $this->send('SSL証明書の更新', 'POST', "/ssl/orders/{$id}/renew", ['id' => $id] + $payload, [], ['ssl-order-']); } //// 注文確認担当者メール /** * 注文の確認担当者のメールアドレスの変更 * * @param int $id 注文ID * @param string $approverEmail メールアドレス * @return Result 結果。data['data']:id */ public function updateSslApproverEmail(int $id, string $approverEmail): Result { return $this->send('確認担当者メールの変更', 'PUT', "/ssl/orders/{$id}/approver-email", ['id' => $id, 'approver_email' => $approverEmail], [], ["ssl-order-{$id}"]); } /** * 確認担当者メールの再送信 * * @param int $id 注文ID * @return Result 結果。data['data']:id */ public function resendSslApproverEmail(int $id): Result { return $this->send('確認担当者メールの再送信', 'POST', "/ssl/orders/{$id}/approver-email/resend", ['id' => $id]); } //// OTPトークン /** * SSL証明書の管理画面に入るOTPトークンの作成 * * @param int $id 注文ID * @return Result 結果。data['data']:token、uri、expire_at */ public function createSslOtpToken(int $id): Result { return $this->send('OTPトークンの作成', 'POST', "/ssl/orders/{$id}/otp-tokens", ['id' => $id]); } //// 商品 /** * SSL証明書の商品一覧 * * @param array $query 検索クエリー(with_price、with_description等) * @return Result 結果。data['data']:results、total */ public function listSslProducts(array $query = []): Result { return $this->get('SSL証明書の商品一覧の受け取り', '/ssl/products', $query, 'ssl-product-list'); } /** * SSL証明書の商品の表示 * * @param int $id 商品ID * @return Result 結果。data['data']:商品 */ public function getSslProduct(int $id): Result { // 以前は未定義の$uriを使っていた return $this->get('SSL証明書の商品の受け取り', "/ssl/products/{$id}", [], "ssl-product-{$id}"); } //////// /** * トークンの受け取り。 * * @return string トークン */ public function getToken(): string { if (!OPENPROVIDER_ENABLED) return ''; return $this->token; } /** * リセラーIDの受け取り(login()の後)。 * * @return int リセラーID */ public function getResellerId(): int { if (!OPENPROVIDER_ENABLED) return 0; return $this->resellerId; } ////////// protected function setupCurl(string $uri, string $resType = 'GET', array $payload = []): Curl { $curl = new Curl("{$this->BASEURL}{$uri}"); $curl->setMethod($resType); if (!empty($payload)) $curl->setPostRaw(json_encode($payload)); if ($this->token !== '') $curl->setBearerAuth($this->token); $curl->addHeader('Content-Type', 'application/json'); $curl->addHeader('Accept', 'application/json'); return $curl; } private function curlResult(Curl &$curl): Result { $res = $curl->execute(); if (!$res->isSuccess) { $err = "CURL実行に失敗: {$curl->getError()}"; assert_unless_success($res, $err); return Result::Error($err); } $body = $curl->getResponseBody(); if ($curl->getResponseCode() != 200) { $err = self::errorDesc($body, $curl->getResponseCode()); assert($curl->getResponseCode() == 200, $err); return Result::Error($err); } assert_not_null($body, "返事ボディーは空です。"); $data = json_decode($body, true); if (!is_array($data)) return Result::Error('レスポンスのJSONの解析に失敗。'); return Result::Success('', $data); } /** * GETのAPIを呼ぶ(キャッシュ名があれば、同じクエリーの結果を30分キャッシュする) * * @param string $what 操作の説明(メッセージ用) * @param string $uri パス(/v1betaの後) * @param array $query クエリー * @param string|null $cacheName キャッシュ名(nullならキャッシュしない) * @return Result data:レスポンス(code、data等) */ private function get(string $what, string $uri, array $query = [], ?string $cacheName = null): Result { if (!OPENPROVIDER_ENABLED) return Result::Error('エラー:OpenProviderは無効です。'); $query = self::normalizeQuery($query); if ($cacheName !== null) { $cache = $this->cache->get($cacheName); if ($this->cache->resurrect($cache, $query)) { unset($cache['query'], $cache['lifespan']); return Result::Success('', $cache); } } $res = $this->call($what, 'GET', $uri, $query); if ($res->isSuccess && $cacheName !== null) $this->cache->set($cacheName, $res->data + ['query' => $query]); return $res; } /** * POST・PUT・DELETEのAPIを呼び、成功したら関係するキャッシュを消す * * @param string $what 操作の説明(メッセージ用) * @param string $method POST、PUT又はDELETE * @param string $uri パス(/v1betaの後) * @param array|null $payload 本文(JSON) * @param array $query クエリー * @param array $forget 消すキャッシュの名前の始まり * @return Result data:レスポンス(code、data等) */ private function send(string $what, string $method, string $uri, ?array $payload = null, array $query = [], array $forget = []): Result { if (!OPENPROVIDER_ENABLED) return Result::Error('エラー:OpenProviderは無効です。'); $res = $this->call($what, $method, $uri, self::normalizeQuery($query), $payload); if ($res->isSuccess) foreach ($forget as $prefix) $this->cache->murderPrefix($prefix); return $res; } /** * APIを呼ぶ。HTTPのエラー、「code」が0以外、「success」がfalseの場合は失敗 */ private function call(string $what, string $method, string $uri, array $query, ?array $payload = null): Result { $qs = self::buildQuery($query); $curl = $this->setupCurl($uri.($qs !== '' ? "?{$qs}" : ''), $method, $payload ?? []); // 本文が空のPOST(再試行等)でも「{}」を送る(JSONを期待するAPIがある) if ($payload !== null && empty($payload)) $curl->setPostRaw('{}'); $res = $this->curlResult($curl); if (!$res->isSuccess) return Result::Error("{$what}に失敗: {$res->message}"); if ((int)($res->data['code'] ?? 0) !== 0) { return Result::Error("{$what}に失敗: ".((string)($res->data['desc'] ?? '') ?: "code {$res->data['code']}")); } if (($res->data['data']['success'] ?? true) === false) return Result::Error("{$what}に失敗。"); return Result::Success("{$what}に成功。", $res->data); } /** * クエリーの真偽値を「true」「false」にする */ private static function normalizeQuery(array $query): array { foreach ($query as $k => $v) { if (is_bool($v)) $query[$k] = $v ? 'true' : 'false'; else if (is_array($v)) $query[$k] = array_map(fn($x) => is_bool($x) ? ($x ? 'true' : 'false') : $x, $v); } return $query; } /** * クエリー文字列。配列は同じキーを繰り返す(status=ACT&status=REQ。APIの「multi」形式) */ private static function buildQuery(array $query): string { $parts = []; foreach ($query as $k => $v) { foreach (is_array($v) ? $v : [$v] as $x) { if ($x === null) continue; $parts[] = rawurlencode((string)$k).'='.rawurlencode((string)$x); } } return implode('&', $parts); } /** * パスの一部(ドメイン名・ハンドル等)を安全にする */ private static function seg(string $s): string { return rawurlencode($s); } /** * キャッシュ名に使える文字列(ドメイン名等はそのまま、他は短いハッシュ) */ private static function cacheKey(mixed $v): string { if (is_string($v) && preg_match('/^[A-Za-z0-9._-]{1,100}$/', $v) && !str_contains($v, '..')) return $v; return substr(sha1(json_encode($v)), 0, 16); } /** * 「076.co.jp」を['name' => '076', 'extension' => 'co.jp']にする(最初の「.」で分ける) */ private static function splitDomain(string $domain): ?array { $parts = explode('.', $domain, 2); if (count($parts) !== 2 || $parts[0] === '' || $parts[1] === '') return null; return ['name' => $parts[0], 'extension' => $parts[1]]; } /** * エラーレスポンスの説明(JSONでない場合もある) */ private static function errorDesc(string $body, int $code): string { $err = json_decode($body, true); $desc = is_array($err) ? (string)($err['desc'] ?? '') : ''; return $desc !== '' ? $desc : "HTTP {$code}"; } }