master
ブランチ master
LittleBeast / src / Std / Lib / Openprovider.php
61.5 KB、1625行 生 Blame 履歴
1<?php
2/*************************************************************
3# 076 License
4
5Copyright (c) テクニカル諏訪子
6
7Permission is hereby granted to any person obtaining a copy of the software
8Little Beast (the "Software") to use, modify, merge, copy, publish, distribute,
9sublicense, and/or sell copies of the Software, subject to the following conditions:
10
11 1. **Origin Attribution**:
12 - You must not misrepresent the origin of the Software; you must not claim
13 you created the original Software.
14 - If the Software is used in a product, you must either:
15 a. Provide clear attribution in the product's documentation, user interface,
16 or other visible areas, **OR**
17 b. Pay the original developers a fee they specify in writing.
18 2. **Usage Restriction**:
19 - The Software, or any derivative works, dependencies, or libraries
20 incorporating it, must not be used for censorship or to suppress freedom of
21 speech, expression, or creativity. Prohibited uses include, but are not
22 limited to:
23 - Censorship of so-called "hate speech", visuals, non-mainstream opinions,
24 ideas, or objective reality.
25 - Tools or systems designed to restrict access to information or
26 artistic works.
27 3. **Notice Preservation**:
28 - This license and the above copyright notice must remain intact in all copies
29 of the source code.
30
31THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
32INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
33PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
34HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
35CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
36OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37 */
38namespace Std\Lib;
39
40use Std\Lib\Cache;
41use Std\Lib\Curl;
42use Result;
43
44/**
45 * OpenProviderのREST API(v1beta)
46 *
47 * 使い方:最初にlogin()を1回実行し、その後で各メソッドを呼ぶ。
48 * 結果のdataは、特に書いていなければAPIのレスポンス全体(code、data、desc等)。
49 * 一覧と表示は30分キャッシュし(Cache)、追加・更新・削除をすると関係するキャッシュを消す。
50 *
51 * APIの仕様:https://docs.openprovider.com/doc/all
52 */
53class Openprovider {
54 protected string $cacheDir = 'openprovider';
55 protected int $tokenDuration = 172800; // 48時間
56
57 private Cache $cache;
58 private string $user = '';
59 private string $pass = '';
60 private string $token = '';
61 private string $ip = '';
62 private int $resellerId = 0;
63 private int $lastAuth = 0;
64 // private string $BASEURL = 'https://api.openprovider.eu/v1beta';
65 protected string $BASEURL = DEBUG_MODE ? 'http://api.sandbox.openprovider.nl:8480/v1beta'
66 : 'https://api.openprovider.eu/v1beta';
67
68 public function __construct() {
69 if (!OPENPROVIDER_ENABLED) return;
70 $this->user = OPENPROVIDER['username'];
71 $this->pass = OPENPROVIDER['password'];
72 $this->ip = OPENPROVIDER['ip'];
73 $this->cache = new Cache($this->cacheDir);
74 }
75
76 public function deleteCache(string $name): void {
77 $this->cache->murder($name);
78 }
79
80 /**
81 * トークンの受け取り。
82 * このライブリリーを使ったら、一回「login()」を実行する事が必須となります。
83 *
84 * @return Result 結果
85 */
86 public function login(): Result {
87 if (!OPENPROVIDER_ENABLED) return Result::Error('エラー:OpenProviderは無効です。');
88 $cacheName = 'login';
89 $cache = $this->cache->get($cacheName);
90
91 // 以前は$lastAuthが0のまま比較していたので、キャッシュのトークンが使われず毎回パスワードを送っていた
92 $this->lastAuth = (int)($cache['last_auth'] ?? 0);
93 if (isset($cache['data']['token']) && time() < ($this->lastAuth + $this->tokenDuration)) {
94 $this->token = $cache['data']['token'];
95 $this->resellerId = (int)($cache['data']['reseller_id'] ?? 0);
96 return Result::Success('', $cache);
97 }
98
99 $payload = [
100 'username' => $this->user,
101 'password' => $this->pass,
102 'ip' => $this->ip ?: '0.0.0.0',
103 ];
104
105 $curl = $this->setupCurl('/auth/login', 'POST', $payload);
106
107 $res = $curl->execute();
108 if (!$res->isSuccess) {
109 $err = "CURLの実行に失敗: ".$curl->getError();
110 assert_unless_success($res, $err);
111 return Result::Error($err);
112 }
113
114 $body = $curl->getResponseBody();
115 if ($curl->getResponseCode() != 200) {
116 $err = self::errorDesc($body, $curl->getResponseCode());
117 assert($curl->getResponseCode() == 200, $err);
118 return Result::Error($err);
119 }
120 assert_not_null($body, "返事ボディーは空です。");
121
122 $res = json_decode($body, true);
123 if (is_array($res) && isset($res['data']['token'])) {
124 $this->token = $res['data']['token'];
125 // 以前はリセラーIDを記録していなかった(getResellerId()が常に0)
126 $this->resellerId = (int)($res['data']['reseller_id'] ?? 0);
127 $this->lastAuth = time();
128 $res['last_auth'] = $this->lastAuth;
129 $this->cache->set($cacheName, $res);
130
131 return Result::Success('', $res);
132 }
133
134 return Result::Error('ログインに失敗。');
135 }
136
137 // ドメイン
138
139 //// ドメイン
140
141 /**
142 * ドメイン一覧。
143 *
144 * @param array $query 検索クエリー(limit、offset、order_by.*、status、domain_name_pattern、with_*等)
145 * @return Result 結果。dataはドメインの配列(results)
146 */
147 public function listDomains(array $query = []): Result {
148 $res = $this->get('ドメイン一覧の受け取り', '/domains', $query, 'listdomains');
149 return $res->isSuccess ? Result::Success('', $res->data['data']['results'] ?? []) : $res;
150 }
151
152 /**
153 * ドメイン名の登録。
154 * ハンドルを省略するとconfig.phpのOPENPROVIDERの「owner_handle」、ネームサーバーを省略するとOpenProviderのネームサーバー
155 *
156 * @param string $name 登録したいドメイン名(例:"076studio.jp")
157 * @param array $info owner_handle、admin_handle、tech_handle、billing_handle、reseller_handle、
158 * name_servers(名前の配列、又は['name' => …, 'ip' => …]の配列)、autorenew、
159 * 他にAPIの項目(ns_group、additional_data、is_private_whois_enabled等)もそのまま送る
160 * @param int $period 年間(デフォルト=1年)
161 * @return Result 結果。data['data']:'activation_date'(リアルタイムで成功したのみ), 'auth_code'(TLDが対応している場合のみ), 'expiration_date', 'id', 'renewal_date', 'status'(ACT又はREQ)
162 */
163 public function createDomain(string $name, array $info, int $period = 1): Result {
164 if (!OPENPROVIDER_ENABLED) return Result::Error('エラー:OpenProviderは無効です。');
165 $domain = self::splitDomain($name);
166 if ($domain === null) return Result::Error("不正なドメイン形: {$name}");
167
168 foreach (['owner_handle', 'admin_handle', 'tech_handle', 'billing_handle', 'reseller_handle'] as $h) {
169 if (($info[$h] ?? '') === '') $info[$h] = OPENPROVIDER['owner_handle'];
170 }
171 // 以前はns_group等の他の項目を送らなかった
172 if (empty($info['name_servers']) && empty($info['ns_group'])) $info['name_servers'] = ['ns1.openprovider.nl', 'ns2.openprovider.be', 'ns3.openprovider.eu'];
173 if (isset($info['name_servers'])) $info['name_servers'] = array_map(fn($n) => is_array($n) ? $n : ['name' => $n], $info['name_servers']);
174 if (($info['autorenew'] ?? '') === '') $info['autorenew'] = 'default';
175
176 $payload = ['domain' => $domain, 'period' => $period] + $info;
177 return $this->send('ドメインの登録', 'POST', '/domains', $payload, [], ['listdomains', 'domain-']);
178 }
179
180 /**
181 * ドメインを登録可能かどうかの確認。
182 *
183 * @param array $domains ドメイン名リスト。例:['076.moe', '076.co.jp', '076.com']
184 * @param bool $with_price 値段を表示するかどうか。デフォルトは「false」
185 * @return Result 結果。dataは配列(results):'domain'と'status'はいつでもあり、'reason', 'premium', 'is_premium'が多分あり、そして'price'が「with_price」が「true」の場合のみ。
186 */
187 public function checkDomainAvailable(array $domains, bool $with_price = false): Result {
188 if (!OPENPROVIDER_ENABLED) return Result::Error('エラー:OpenProviderは無効です。');
189 $domainList = [];
190 foreach ($domains as $d) {
191 // 「076.co.jp」の様なTLDもあるので、最初の「.」で分ける
192 $domain = self::splitDomain((string)$d);
193 if ($domain === null) return Result::Error("不正なドメイン形: {$d}");
194 $domainList[] = $domain;
195 }
196
197 $res = $this->send('ドメインの確認', 'POST', '/domains/check', ['domains' => $domainList, 'with_price' => $with_price]);
198 return $res->isSuccess ? Result::Success('', $res->data['data']['results'] ?? []) : $res;
199 }
200
201 /**
202 * ドメイン名を勧められる。
203 *
204 * @param array $payload name(必須)、tlds、language、limit、sensitive、provider
205 * @return Result 結果。data['data']['results']:勧められたドメイン
206 */
207 public function suggestDomainname(array $payload = []): Result {
208 return $this->send('ドメイン名の提案', 'POST', '/domains/suggest-name', $payload);
209 }
210
211 /**
212 * ドメイン名を交換する(所有者の変更)。
213 *
214 * @param array $payload domain(name、extension)、owner_handle、auth_code等
215 * @return Result 結果。data['data']:creation_date、expiration_date、renewal_date、status
216 */
217 public function tradeDomainname(array $payload = []): Result {
218 return $this->send('ドメインの交換', 'POST', '/domains/trade', $payload, [], ['listdomains', 'domain-']);
219 }
220
221 /**
222 * ドメイン名を移転する。
223 *
224 * @param array $payload domain(name、extension)、auth_code、owner_handle等
225 * @return Result 結果。data['data']:id、status、auth_code、expiration_date等
226 */
227 public function transferDomainname(array $payload = []): Result {
228 return $this->send('ドメインの移転', 'POST', '/domains/transfer', $payload, [], ['listdomains', 'domain-']);
229 }
230
231 /**
232 * ドメイン名の受け取り
233 *
234 * @param int $id ドメインID
235 * @param array $query 検索クエリー(with_history、with_additional_data等)
236 * @return Result 結果。data['data']:ドメイン
237 */
238 public function getDomainname(int $id, array $query = []): Result {
239 return $this->get('ドメインの受け取り', "/domains/{$id}", $query, "domain-{$id}");
240 }
241
242 /**
243 * ドメイン名の更新
244 *
245 * @param int $id ドメインID
246 * @param array $payload 更新する項目(name_servers、autorenew、is_locked等)
247 * @return Result 結果。data['data']:id、status
248 */
249 public function updateDomainname(int $id, array $payload = []): Result {
250 return $this->send('ドメイン名の更新', 'PUT', "/domains/{$id}", $payload, [], ['listdomains', "domain-{$id}"]);
251 }
252
253 /**
254 * ドメイン名の削除
255 *
256 * @param int $id ドメインID
257 * @param array $query type(例:「transfer」)、skip_soft_quarantine、force_delete等
258 * @return Result 結果。data['data']:status
259 */
260 public function deleteDomainname(int $id, array $query = []): Result {
261 // 以前は検索の条件を本文で送っていた(DELETEはクエリー)
262 return $this->send('ドメイン名の削除', 'DELETE', "/domains/{$id}", null, $query, ['listdomains', "domain-{$id}"]);
263 }
264
265 /**
266 * ドメイン名の最後行動の再度実行
267 *
268 * @param int $id ドメインID
269 * @param array $payload auth_code等
270 * @return Result 結果
271 */
272 public function retryLastDomainnameOperation(int $id, array $payload = []): Result {
273 return $this->send('ドメインの最後行動の再度実行', 'POST', "/domains/{$id}/last-operation/restart", ['id' => $id] + $payload, [], ['listdomains', "domain-{$id}"]);
274 }
275
276 /**
277 * ドメイン名の有効期限の更新
278 *
279 * @param int $id ドメインID
280 * @param array $payload period(年)等
281 * @return Result 結果。data['data']:status
282 */
283 public function renewDomainname(int $id, array $payload = []): Result {
284 return $this->send('ドメインの有効期限の更新', 'POST', "/domains/{$id}/renew", ['id' => $id] + $payload, [], ['listdomains', "domain-{$id}"]);
285 }
286
287 /**
288 * 削除済みドメイン名の復活
289 *
290 * @param int $id ドメインID
291 * @param array $payload フォームデータ
292 * @return Result 結果。data['data']:status
293 */
294 public function restoreDomainname(int $id, array $payload = []): Result {
295 return $this->send('削除済みドメイン名の復活', 'POST', "/domains/{$id}/restore", ['id' => $id] + $payload, [], ['listdomains', "domain-{$id}"]);
296 }
297
298 /**
299 * ドメイン名の移動の承諾
300 *
301 * @param int $id ドメインID
302 * @param array $payload approve(1=承諾、0=拒否)、auth_code、registrar_tag等
303 * @return Result 結果
304 */
305 public function approveDomainnameTransfer(int $id, array $payload = []): Result {
306 return $this->send('ドメイン名の移動の承諾', 'POST', "/domains/{$id}/transfer/approve", ['id' => $id] + $payload, [], ['listdomains', "domain-{$id}"]);
307 }
308
309 /**
310 * ドメイン名のFOA1(移転の確認メール)の送信
311 *
312 * @param int $id ドメインID
313 * @param array $payload フォームデータ
314 * @return Result 結果
315 */
316 public function sendDomainnameFoa1(int $id, array $payload = []): Result {
317 return $this->send('ドメイン名のFOA1の送信', 'POST', "/domains/{$id}/transfer/send-foa1", ['id' => $id] + $payload);
318 }
319
320 //// 追加データ
321
322 /**
323 * 追加データ(TLDで必要な項目)の受け取り
324 *
325 * @param array $query domain.name、domain.extension、operation
326 * @return Result 結果。data['data']:項目の配列
327 */
328 public function getDomainAdditionalData(array $query): Result {
329 return $this->get('ドメインの追加データの受け取り', '/domains/additional-data', $query, 'domainadditionaldata-'.self::cacheKey($query));
330 }
331
332 //// 顧客様の追加データ
333
334 /**
335 * 顧客様の追加データ(TLDで顧客様に必要な項目)の受け取り
336 *
337 * @param array $query domain.name、domain.extension
338 * @return Result 結果。data['data']:項目の配列
339 */
340 public function getDomainCustomerAdditionalData(array $query): Result {
341 // 以前はドメインの追加データと同じキャッシュ名で、違う結果を返す事があった
342 return $this->get('ドメインの顧客様の追加データの受け取り', '/domains/additional-data/customers', $query, 'domaincustomerdata-'.self::cacheKey($query));
343 }
344
345 //// ドメイン値段
346
347 /**
348 * ドメインの値段
349 *
350 * @param array $query domain.name、domain.extension、operation(create、transfer、renew等)、period
351 * @return Result 結果。data['data']:price、is_premium等
352 */
353 public function getDomainPrices(array $query): Result {
354 return $this->get('ドメインの値段の受け取り', '/domains/prices', $query, 'domainprices-'.self::cacheKey($query));
355 }
356
357 //// 認証コード
358
359 /**
360 * 認証コードの受け取り(キャッシュしない)
361 *
362 * @param int $id ドメイン名ID
363 * @param array $query auth_code_type、sending_type等
364 * @return Result 結果。data['data']:auth_code、type、success
365 */
366 public function getAuthCode(int $id, array $query = []): Result {
367 return $this->get('認証コードの受け取り', "/domains/{$id}/authcode", $query);
368 }
369
370 /**
371 * 認証コードの再設定
372 *
373 * @param int $id ドメイン名ID
374 * @param array $payload auth_code_type、sending_type等
375 * @return Result 結果。data['data']:auth_code等
376 */
377 public function resetAuthCode(int $id, array $payload = []): Result {
378 return $this->send('認証コードの再設定', 'POST', "/domains/{$id}/authcode/reset", ['id' => $id] + $payload, [], ["domain-{$id}"]);
379 }
380
381 //// TLD
382
383 /**
384 * TLD一覧
385 *
386 * @param array $query 検索クエリー(省略すると最初の10件)
387 * @return Result 結果。data['data']:results、total
388 */
389 public function listTlds(array $query = []): Result {
390 if (empty($query)) $query = ['limit' => 10, 'offset' => 0, 'order' => 'ASC'];
391 return $this->get('TLD一覧の受け取り', '/tlds', $query, 'listtlds');
392 }
393
394 /**
395 * TLDの表示
396 *
397 * @param string $tld TLD
398 * @param array $query 検索クエリー(with_price、with_description等)
399 * @return Result 結果。data['data']:TLD
400 */
401 public function getTld(string $tld, array $query = []): Result {
402 if (empty($query)) $query = ['limit' => 10, 'offset' => 0];
403 return $this->get('TLDの受け取り', '/tlds/'.self::seg($tld), $query, 'gettld-'.self::cacheKey($tld));
404 }
405
406 // 請求
407 //// 請求書
408
409 /**
410 * 請求書一覧
411 *
412 * @param array $query 検索クエリー(省略すると最初の10件)
413 * @return Result 結果。data['data']:results、total
414 */
415 public function listInvoices(array $query = []): Result {
416 if (empty($query)) $query = ['limit' => 10, 'offset' => 0];
417 return $this->get('請求書一覧の受け取り', '/invoices', $query, 'listinvoices');
418 }
419
420 //// 支払
421
422 /**
423 * 支払一覧
424 *
425 * @param array $query 検索クエリー(省略すると最初の10件)
426 * @return Result 結果。data['data']:results、total
427 */
428 public function listPayments(array $query = []): Result {
429 if (empty($query)) $query = ['limit' => 10, 'offset' => 0];
430 return $this->get('支払一覧の受け取り', '/payments', $query, 'listpayments');
431 }
432
433 /**
434 * 推奨のチャージ額(キャッシュしない)
435 *
436 * @return Result 結果。data['data']:suggested_top_up_amount
437 */
438 public function getSuggestedTopUpAmount(): Result {
439 return $this->get('推奨のチャージ額の受け取り', '/payments/suggested-top-up-amount');
440 }
441
442 //// トランザクション
443
444 /**
445 * トランザクション一覧
446 *
447 * @param array $query 検索クエリー(省略すると最初の10件)
448 * @return Result 結果。data['data']:results、total
449 */
450 public function listTransactions(array $query = []): Result {
451 if (empty($query)) $query = ['limit' => 10, 'offset' => 0];
452 return $this->get('トランザクション一覧の受け取り', '/transactions', $query, 'listtransactions');
453 }
454
455 // DNS
456 //// ドメイントークン
457
458 /**
459 * ドメイントークンの作成(他のDNSプロバイダーでドメインの所有を確認する為)
460 *
461 * @param array $payload domain、zone_provider
462 * @return Result 結果。data['data']:token、url
463 */
464 public function createDomainToken(array $payload = []): Result {
465 // 以前は成功・失敗のメッセージがDNSテンプレートの物だった
466 return $this->send('ドメイントークンの作成', 'POST', '/dns/domain-token', $payload);
467 }
468
469 //// ネームサーバー
470
471 /**
472 * ネームサーバー一覧
473 *
474 * @param array $query 検索クエリー(省略すると名前順の最初の25件)
475 * @return Result 結果。data['data']:results、total
476 */
477 public function listNameservers(array $query = []): Result {
478 // 「order_by.name」はこのAPIに無い(order、order_by)
479 if (empty($query)) $query = ['limit' => 25, 'offset' => 0, 'order_by' => 'name', 'order' => 'asc'];
480 return $this->get('ネームサーバー一覧の受け取り', '/dns/nameservers', $query, 'dnsns-list');
481 }
482
483 /**
484 * ネームサーバーの作成
485 *
486 * @param array $payload name、ip、ip6
487 * @return Result 結果。data['data']:name、ip、ip6
488 */
489 public function createNameserver(array $payload = []): Result {
490 return $this->send('ネームサーバーの追加', 'POST', '/dns/nameservers', $payload, [], ['dnsns-']);
491 }
492
493 /**
494 * ネームサーバーの表示
495 *
496 * @param string $name ネームサーバー名(例:ns1.example.com)
497 * @return Result 結果。data['data']:name、ip、ip6
498 */
499 public function getNameserver(string $name): Result {
500 // 以前は一覧のクエリー(limit等)を付けていた
501 return $this->get('ネームサーバーの受け取り', '/dns/nameservers/'.self::seg($name), [], 'dnsns-'.self::cacheKey($name));
502 }
503
504 /**
505 * ネームサーバーの更新
506 *
507 * @param string $name ネームサーバー名
508 * @param array $payload ip、ip6
509 * @return Result 結果
510 */
511 public function updateNameserver(string $name, array $payload = []): Result {
512 return $this->send('ネームサーバーの更新', 'PUT', '/dns/nameservers/'.self::seg($name), ['name' => $name] + $payload, [], ['dnsns-']);
513 }
514
515 /**
516 * ネームサーバーの削除
517 *
518 * @param string $name ネームサーバー名
519 * @return Result 結果
520 */
521 public function deleteNameserver(string $name): Result {
522 return $this->send('ネームサーバーの削除', 'DELETE', '/dns/nameservers/'.self::seg($name), null, [], ['dnsns-']);
523 }
524
525 //// NSグループ
526
527 /**
528 * NSグループ一覧
529 *
530 * @param array $query 検索クエリー(省略すると名前順の最初の25件)
531 * @return Result 結果。data['data']:results、total
532 */
533 public function listNsGroups(array $query = []): Result {
534 // 以前は「order_by.name」(このAPIでは「order_by.ns_group」)
535 if (empty($query)) $query = ['limit' => 25, 'offset' => 0, 'order_by.ns_group' => 'asc'];
536 return $this->get('NSグループ一覧の受け取り', '/dns/nameservers/groups', $query, 'dnsnsgroup-list');
537 }
538
539 /**
540 * NSグループの作成
541 *
542 * @param array $payload ns_group、name_servers(['name' => …, 'ip' => …]の配列)
543 * @return Result 結果
544 */
545 public function createNsGroup(array $payload = []): Result {
546 return $this->send('NSグループの追加', 'POST', '/dns/nameservers/groups', $payload, [], ['dnsnsgroup-']);
547 }
548
549 /**
550 * NSグループの表示
551 *
552 * @param string $nsGroup NSグループ名
553 * @param array $query id
554 * @return Result 結果。data['data']:ns_group、name_servers等
555 */
556 public function getNsGroup(string $nsGroup, array $query = []): Result {
557 return $this->get('NSグループの受け取り', '/dns/nameservers/groups/'.self::seg($nsGroup), $query, 'dnsnsgroup-'.self::cacheKey($nsGroup));
558 }
559
560 /**
561 * NSグループの更新
562 *
563 * @param string $nsGroup NSグループ名
564 * @param array $payload name_servers
565 * @return Result 結果
566 */
567 public function updateNsGroup(string $nsGroup, array $payload = []): Result {
568 return $this->send('NSグループの更新', 'PUT', '/dns/nameservers/groups/'.self::seg($nsGroup), ['ns_group' => $nsGroup] + $payload, [], ['dnsnsgroup-']);
569 }
570
571 /**
572 * NSグループの削除
573 *
574 * @param string $nsGroup NSグループ名
575 * @return Result 結果
576 */
577 public function deleteNsGroup(string $nsGroup): Result {
578 return $this->send('NSグループの削除', 'DELETE', '/dns/nameservers/groups/'.self::seg($nsGroup), null, [], ['dnsnsgroup-']);
579 }
580
581 //// テンプレート
582
583 /**
584 * DNSテンプレート一覧
585 *
586 * @param array $query 検索クエリー(with_records、name_pattern等)
587 * @return Result 結果。data['data']:results、total
588 */
589 public function listDnsTemplate(array $query = []): Result {
590 return $this->get('DNSテンプレート一覧の受け取り', '/dns/templates', $query, 'dnstemplate-list');
591 }
592
593 /**
594 * DNSテンプレートの作成
595 *
596 * @param array $payload name、records、is_spamexperts_enabled
597 * @return Result 結果。data['data']:id
598 */
599 public function createDnsTemplate(array $payload = []): Result {
600 return $this->send('DNSテンプレートの追加', 'POST', '/dns/templates', $payload, [], ['dnstemplate-']);
601 }
602
603 /**
604 * DNSテンプレートの表示
605 *
606 * @param int $id DNSテンプレートID
607 * @return Result 結果。data['data']:id、name、records等
608 */
609 public function getDnsTemplate(int $id): Result {
610 return $this->get('DNSテンプレートの受け取り', "/dns/templates/{$id}", [], "dnstemplate-{$id}");
611 }
612
613 /**
614 * DNSテンプレートの削除
615 *
616 * @param int $id DNSテンプレートID
617 * @return Result 結果
618 */
619 public function deleteDnsTemplate(int $id): Result {
620 return $this->send('DNSテンプレートの削除', 'DELETE', "/dns/templates/{$id}", null, [], ['dnstemplate-']);
621 }
622
623 //// DNSゾーン
624
625 /**
626 * DNSゾーン一覧
627 *
628 * @param array $query 検索クエリー(省略すると名前順の最初の25件)
629 * @return Result 結果。data['data']:results、total
630 */
631 public function listDnsZones(array $query = []): Result {
632 if (empty($query)) $query = ['limit' => 25, 'offset' => 0, 'order_by.name' => 'asc'];
633 return $this->get('DNSゾーン一覧の受け取り', '/dns/zones', $query, 'dnszone-list');
634 }
635
636 /**
637 * DNSゾーンの作成
638 *
639 * @param array $payload domain(name、extension)、type(master又はslave)、records、template_name等
640 * @return Result 結果
641 */
642 public function createDnsZone(array $payload = []): Result {
643 return $this->send('DNSゾーンの追加', 'POST', '/dns/zones', $payload, [], ['dnszone-']);
644 }
645
646 /**
647 * DNSゾーンの表示
648 *
649 * @param string $domain ドメイン名
650 * @param array $query with_records、with_history、with_dnskey、provider
651 * @return Result 結果。data['data']:ゾーン
652 */
653 public function getDnsZone(string $domain, array $query = []): Result {
654 // 以前は一覧のクエリー(limit、order_by.name等)を付けていた
655 return $this->get('DNSゾーンの受け取り', '/dns/zones/'.self::seg($domain), $query, 'dnszone-'.self::cacheKey($domain));
656 }
657
658 /**
659 * DNSゾーンの更新
660 *
661 * @param string $domain ドメイン名
662 * @param array $payload records(add、remove、replace、update)等
663 * @return Result 結果
664 */
665 public function updateDnsZone(string $domain, array $payload = []): Result {
666 return $this->send('DNSゾーンの更新', 'PUT', '/dns/zones/'.self::seg($domain), ['name' => $domain] + $payload, [], ['dnszone-', 'dnsrecords-'.self::cacheKey($domain)]);
667 }
668
669 /**
670 * DNSゾーンの削除
671 *
672 * @param string $domain ドメイン名
673 * @param array $query id、provider等
674 * @return Result 結果
675 */
676 public function deleteDnsZone(string $domain, array $query = []): Result {
677 return $this->send('DNSゾーンの削除', 'DELETE', '/dns/zones/'.self::seg($domain), null, $query, ['dnszone-', 'dnsrecords-'.self::cacheKey($domain)]);
678 }
679
680 //// ゾーンレコード
681
682 /**
683 * DNSレコード一覧
684 *
685 * @param string $domain ドメイン名
686 * @param array $query 検索クエリー(type、limit、order_by.*等)
687 * @return Result 結果。data['data']:results、total
688 */
689 public function listZoneRecords(string $domain, array $query = []): Result {
690 return $this->get('DNSレコード一覧の受け取り', '/dns/zones/'.self::seg($domain).'/records', $query, 'dnsrecords-'.self::cacheKey($domain));
691 }
692
693 // EasyDMARC
694
695 /**
696 * ドメインのEasyDMARCの表示
697 *
698 * @param string $domain ドメイン名(例:076.moe)
699 * @return Result 結果。data['data']:id、status、record_host、record_value等
700 */
701 public function getEasydmarc(string $domain): Result {
702 $d = self::splitDomain($domain);
703 if ($d === null) return Result::Error("不正なドメイン形: {$domain}");
704 // 以前は「/dns/easydmarcs」でクエリーも「name」「extension」だった
705 return $this->get('EasyDMARCの受け取り', '/easydmarcs', ['domain.name' => $d['name'], 'domain.extension' => $d['extension']], 'easydmarc-'.self::cacheKey($domain));
706 }
707
708 /**
709 * EasyDMARCの注文
710 *
711 * @param string $domain ドメイン名
712 * @param string $ownerHandle 所有者のハンドル(省略するとconfig.phpのOPENPROVIDERの「owner_handle」)
713 * @return Result 結果。data['data']:注文
714 */
715 public function createEasydmarc(string $domain, string $ownerHandle = ''): Result {
716 $d = self::splitDomain($domain);
717 if ($d === null) return Result::Error("不正なドメイン形: {$domain}");
718 $payload = ['domain' => $d, 'owner_handle' => $ownerHandle !== '' ? $ownerHandle : (string)(OPENPROVIDER['owner_handle'] ?? '')];
719 return $this->send('EasyDMARCの注文', 'POST', '/easydmarcs', $payload, [], ['easydmarc-']);
720 }
721
722 /**
723 * EasyDMARC一覧
724 *
725 * @param array $query 検索クエリー(status、domain.name等)。「domain」(例:076.moe)も使える
726 * @return Result 結果。data['data']:results、total
727 */
728 public function listEasydmarcs(array $query = []): Result {
729 if (isset($query['domain'])) {
730 $d = self::splitDomain((string)$query['domain']);
731 if ($d === null) return Result::Error("不正なドメイン形: {$query['domain']}");
732 unset($query['domain']);
733 $query += ['domain.name' => $d['name'], 'domain.extension' => $d['extension']];
734 }
735 if (empty($query)) $query = ['limit' => 100, 'offset' => 0, 'order_by.id' => 'asc'];
736 return $this->get('EasyDMARC一覧の受け取り', '/easydmarcs/list', $query, 'easydmarc-list');
737 }
738
739 /**
740 * EasyDMARCの削除
741 *
742 * @param int $orderId 注文ID
743 * @return Result 結果。data['data']:status
744 */
745 public function deleteEasydmarc(int $orderId): Result {
746 // 以前は未定義の$domainを使っていた
747 return $this->send('EasyDMARCの削除', 'DELETE', "/easydmarcs/{$orderId}", null, [], ['easydmarc-']);
748 }
749
750 /**
751 * 失敗したEasyDMARCの注文の再試行
752 *
753 * @param int $orderId 注文ID
754 * @return Result 結果。data['data']:注文
755 */
756 public function retryEasydmarc(int $orderId): Result {
757 return $this->send('EasyDMARCの注文の再試行', 'POST', "/easydmarcs/{$orderId}/retry", ['id' => $orderId], [], ['easydmarc-']);
758 }
759
760 /**
761 * EasyDMARCの管理画面にログインするURL(使い捨てなのでキャッシュしない)
762 *
763 * @param int $orderId 注文ID
764 * @param string $type 画面の種類(省略可)
765 * @return Result 結果。data['data']:url
766 */
767 public function getEasydmarcSso(int $orderId, string $type = ''): Result {
768 return $this->get('EasyDMARCのSSOの受け取り', "/easydmarcs/{$orderId}/sso", $type !== '' ? ['type' => $type] : []);
769 }
770
771 // メールテンプレート
772
773 //// メール
774
775 /**
776 * メールテンプレート一覧
777 *
778 * @param array $query 検索クエリー(id、name、group、is_system等)
779 * @return Result 結果。data['data']:results、total
780 */
781 public function listEmails(array $query = []): Result {
782 return $this->get('メールテンプレート一覧の受け取り', '/emails', $query, 'email-list');
783 }
784
785 /**
786 * メールテンプレートの作成
787 *
788 * @param array $payload name、group、fields、locale、tags、is_active、is_default
789 * @return Result 結果。data['data']:id
790 */
791 public function createEmail(array $payload = []): Result {
792 return $this->send('メールテンプレートの追加', 'POST', '/emails', $payload, [], ['email-']);
793 }
794
795 /**
796 * メールテンプレートの更新
797 *
798 * @param int $id メールテンプレートID
799 * @param array $payload 更新する項目
800 * @return Result 結果
801 */
802 public function updateEmail(int $id, array $payload = []): Result {
803 return $this->send('メールテンプレートの更新', 'PUT', "/emails/{$id}", ['id' => $id] + $payload, [], ['email-']);
804 }
805
806 /**
807 * メールテンプレートの削除
808 *
809 * @param int $id メールテンプレートID
810 * @return Result 結果
811 */
812 public function deleteEmail(int $id): Result {
813 return $this->send('メールテンプレートの削除', 'DELETE', "/emails/{$id}", null, [], ['email-']);
814 }
815
816 // ライセンス
817
818 /**
819 * ライセンス一覧(値段付き)
820 *
821 * @param array $query 検索クエリー(product、status、key_number等)
822 * @return Result 結果。data['data']:results、total
823 */
824 public function listLicenses(array $query = []): Result {
825 return $this->get('ライセンス一覧の受け取り', '/licenses', $query, 'license-list');
826 }
827
828 /**
829 * ライセンスのハードウェアIDの再設定
830 *
831 * @param string $product 商品(例:plesk、virtuozzo)
832 * @param int $keyId キーID
833 * @return Result 結果
834 */
835 public function resetLicenseHwid(string $product, int $keyId): Result {
836 return $this->send('ハードウェアIDの再設定', 'POST', '/licenses/hwids/reset/'.self::seg($product)."/{$keyId}", ['key_id' => $keyId, 'product' => $product], [], ['license-']);
837 }
838
839 /**
840 * ライセンスの商品一覧
841 *
842 * @param array $query 検索クエリー(product等)
843 * @return Result 結果。data['data']:results、total
844 */
845 public function listLicenseItems(array $query = []): Result {
846 return $this->get('ライセンスの商品一覧の受け取り', '/licenses/items', $query, 'license-items');
847 }
848
849 //// Plesk
850
851 /**
852 * Pleskのライセンス一覧
853 *
854 * @param array $query 検索クエリー
855 * @return Result 結果。data['data']:results、total
856 */
857 public function listPleskLicenses(array $query = []): Result {
858 return $this->get('Pleskのライセンス一覧の受け取り', '/licenses/plesk', $query, 'license-plesk');
859 }
860
861 /**
862 * Pleskのライセンスの注文
863 *
864 * @param array $payload items、period、title、ip_address_binding等
865 * @return Result 結果。data['data']:key_id、attached_keys
866 */
867 public function createPleskLicense(array $payload): Result {
868 return $this->send('Pleskのライセンスの注文', 'POST', '/licenses/plesk', $payload, [], ['license-']);
869 }
870
871 /**
872 * Pleskのライセンスキー(キャッシュしない)
873 *
874 * @param int $keyId キーID
875 * @return Result 結果。data['data']:key
876 */
877 public function getPleskKey(int $keyId): Result {
878 return $this->get('Pleskのライセンスキーの受け取り', "/licenses/plesk/key/{$keyId}");
879 }
880
881 /**
882 * Pleskのライセンスの表示
883 *
884 * @param int $keyId キーID
885 * @return Result 結果。data['data']:ライセンス
886 */
887 public function getPleskLicense(int $keyId): Result {
888 return $this->get('Pleskのライセンスの受け取り', "/licenses/plesk/{$keyId}", [], "license-plesk-{$keyId}");
889 }
890
891 /**
892 * Pleskのライセンスの更新
893 *
894 * @param int $keyId キーID
895 * @param array $payload 更新する項目(items、title、suspended等)
896 * @return Result 結果
897 */
898 public function updatePleskLicense(int $keyId, array $payload = []): Result {
899 return $this->send('Pleskのライセンスの更新', 'PUT', "/licenses/plesk/{$keyId}", ['key_id' => $keyId] + $payload, [], ['license-']);
900 }
901
902 /**
903 * Pleskのライセンスの削除
904 *
905 * @param int $keyId キーID
906 * @return Result 結果
907 */
908 public function deletePleskLicense(int $keyId): Result {
909 return $this->send('Pleskのライセンスの削除', 'DELETE', "/licenses/plesk/{$keyId}", null, [], ['license-']);
910 }
911
912 //// Virtuozzo
913
914 /**
915 * Virtuozzoのライセンス一覧
916 *
917 * @param array $query 検索クエリー
918 * @return Result 結果。data['data']:results、total
919 */
920 public function listVirtuozzoLicenses(array $query = []): Result {
921 return $this->get('Virtuozzoのライセンス一覧の受け取り', '/licenses/virtuozzo', $query, 'license-virtuozzo');
922 }
923
924 /**
925 * Virtuozzoのライセンスの注文
926 *
927 * @param array $payload items、period、pcs_capacity、title等
928 * @return Result 結果。data['data']:key_id、attached_keys
929 */
930 public function createVirtuozzoLicense(array $payload): Result {
931 return $this->send('Virtuozzoのライセンスの注文', 'POST', '/licenses/virtuozzo', $payload, [], ['license-']);
932 }
933
934 /**
935 * Virtuozzoのライセンスの表示
936 *
937 * @param int $keyId キーID
938 * @return Result 結果。data['data']:ライセンス
939 */
940 public function getVirtuozzoLicense(int $keyId): Result {
941 return $this->get('Virtuozzoのライセンスの受け取り', "/licenses/virtuozzo/{$keyId}", [], "license-virtuozzo-{$keyId}");
942 }
943
944 /**
945 * Virtuozzoのライセンスの更新
946 *
947 * @param int $keyId キーID
948 * @param array $payload 更新する項目
949 * @return Result 結果
950 */
951 public function updateVirtuozzoLicense(int $keyId, array $payload = []): Result {
952 return $this->send('Virtuozzoのライセンスの更新', 'PUT', "/licenses/virtuozzo/{$keyId}", ['key_id' => $keyId] + $payload, [], ['license-']);
953 }
954
955 /**
956 * Virtuozzoのライセンスの削除
957 *
958 * @param int $keyId キーID
959 * @return Result 結果
960 */
961 public function deleteVirtuozzoLicense(int $keyId): Result {
962 return $this->send('Virtuozzoのライセンスの削除', 'DELETE', "/licenses/virtuozzo/{$keyId}", null, [], ['license-']);
963 }
964
965 /**
966 * Virtuozzoのライセンスキー(キャッシュしない)
967 *
968 * @param int $keyId キーID
969 * @return Result 結果。data['data']:key
970 */
971 public function getVirtuozzoKey(int $keyId): Result {
972 return $this->get('Virtuozzoのライセンスキーの受け取り', "/licenses/virtuozzo/{$keyId}/key");
973 }
974
975 // リセラー・顧客様
976
977 //// 連絡先
978
979 /**
980 * 連絡先の一覧
981 *
982 * @param array $query 検索クエリー
983 * @return Result 結果。data['data']:results、total
984 */
985 public function listContacts(array $query = []): Result {
986 return $this->get('連絡先一覧の受け取り', '/contacts', $query, 'contact-list');
987 }
988
989 /**
990 * 連絡先の作成
991 *
992 * @param array $payload name、email、phone、address、role、username、password等
993 * @return Result 結果。data['data']:id
994 */
995 public function createContact(array $payload): Result {
996 return $this->send('連絡先の追加', 'POST', '/contacts', $payload, [], ['contact-']);
997 }
998
999 /**
1000 * 連絡先の表示
1001 *
1002 * @param int $id 連絡先ID
1003 * @param bool $withAdditionalData 追加データも受け取る
1004 * @return Result 結果。data['data']:連絡先
1005 */
1006 public function getContact(int $id, bool $withAdditionalData = false): Result {
1007 return $this->get('連絡先の受け取り', "/contacts/{$id}", ['with_additional_data' => $withAdditionalData], "contact-{$id}");
1008 }
1009
1010 /**
1011 * 連絡先の更新
1012 *
1013 * @param int $id 連絡先ID
1014 * @param array $payload 更新する項目
1015 * @return Result 結果
1016 */
1017 public function updateContact(int $id, array $payload = []): Result {
1018 return $this->send('連絡先の更新', 'PUT', "/contacts/{$id}", ['id' => $id] + $payload, [], ['contact-']);
1019 }
1020
1021 /**
1022 * 連絡先の削除
1023 *
1024 * @param int $id 連絡先ID
1025 * @return Result 結果
1026 */
1027 public function deleteContact(int $id): Result {
1028 return $this->send('連絡先の削除', 'DELETE', "/contacts/{$id}", null, [], ['contact-']);
1029 }
1030
1031 /**
1032 * 連絡先の通知の設定(ログイン中の連絡先)
1033 *
1034 * @return Result 結果。data['data']:operational_notification、marketing_promotion等
1035 */
1036 public function getContactNotificationSetting(): Result {
1037 return $this->get('通知の設定の受け取り', '/contact/notification-setting', [], 'contact-notification');
1038 }
1039
1040 /**
1041 * 連絡先の通知の設定の更新
1042 *
1043 * @param array $payload educational_content、marketing_promotion、operational_notification、platform_update_news(真偽値)
1044 * @return Result 結果
1045 */
1046 public function updateContactNotificationSetting(array $payload): Result {
1047 return $this->send('通知の設定の更新', 'POST', '/contact/notification-setting', $payload, [], ['contact-notification']);
1048 }
1049
1050 //// 顧客様
1051
1052 /**
1053 * 顧客様一覧
1054 *
1055 * @param array $query 検索クエリー
1056 * @return Result 結果。data['data']:results、total
1057 */
1058 public function listCustomers(array $query = []): Result {
1059 return $this->get('顧客様一覧の受け取り', '/customers', $query, 'customer-list');
1060 }
1061
1062 /**
1063 * 顧客様の作成
1064 *
1065 * @param array $payload name、email、phone、address、company_name等
1066 * @return Result 結果。data['data']:handle
1067 */
1068 public function createCustomer(array $payload): Result {
1069 return $this->send('顧客様の追加', 'POST', '/customers', $payload, [], ['customer-']);
1070 }
1071
1072 /**
1073 * 顧客様の表示
1074 *
1075 * @param string $handle 顧客様のハンドル
1076 * @param bool $withAdditionalData 追加データも受け取る
1077 * @return Result 結果。data['data']:顧客様
1078 */
1079 public function getCustomer(string $handle, bool $withAdditionalData = false): Result {
1080 return $this->get('顧客様の受け取り', '/customers/'.self::seg($handle), ['with_additional_data' => $withAdditionalData], 'customer-'.self::cacheKey($handle));
1081 }
1082
1083 /**
1084 * 顧客様の更新
1085 *
1086 * @param string $handle 顧客様のハンドル
1087 * @param array $payload 更新する項目
1088 * @return Result 結果
1089 */
1090 public function updateCustomer(string $handle, array $payload = []): Result {
1091 return $this->send('顧客様の更新', 'PUT', '/customers/'.self::seg($handle), ['handle' => $handle] + $payload, [], ['customer-']);
1092 }
1093
1094 /**
1095 * 顧客様の削除
1096 *
1097 * @param string $handle 顧客様のハンドル
1098 * @return Result 結果
1099 */
1100 public function deleteCustomer(string $handle): Result {
1101 return $this->send('顧客様の削除', 'DELETE', '/customers/'.self::seg($handle), null, [], ['customer-']);
1102 }
1103
1104 //// メール認証
1105
1106 /**
1107 * ドメインのメール認証一覧
1108 *
1109 * @param array $query 検索クエリー(domain、email、handle、status等)
1110 * @return Result 結果。data['data']:results、total
1111 */
1112 public function listEmailVerifications(array $query = []): Result {
1113 return $this->get('メール認証一覧の受け取り', '/customers/verifications/emails/domains', $query, 'verification-list');
1114 }
1115
1116 /**
1117 * メール認証の再送信
1118 *
1119 * @param string $email メールアドレス
1120 * @param string $language 言語(省略可)
1121 * @param string $tag タグ(省略可)
1122 * @return Result 結果
1123 */
1124 public function restartEmailVerifications(string $email, string $language = '', string $tag = ''): Result {
1125 // 以前は空の項目も送っていた
1126 $payload = array_filter(['email' => $email, 'language' => $language, 'tag' => $tag], fn($v) => $v !== '');
1127 return $this->send('メール認証の再送信', 'POST', '/customers/verifications/emails/restart', $payload, [], ['verification-']);
1128 }
1129
1130 /**
1131 * メール認証の開始
1132 *
1133 * @param string $email メールアドレス
1134 * @param string $handle 顧客様のハンドル(省略可)
1135 * @param string $language 言語(省略可)
1136 * @param string $tag タグ(省略可)
1137 * @return Result 結果。data['data']:id
1138 */
1139 public function startEmailVerifications(string $email, string $handle = '', string $language = '', string $tag = ''): Result {
1140 $payload = array_filter(['email' => $email, 'handle' => $handle, 'language' => $language, 'tag' => $tag], fn($v) => $v !== '');
1141 return $this->send('メール認証の開始', 'POST', '/customers/verifications/emails/start', $payload, [], ['verification-']);
1142 }
1143
1144 //// リセラー
1145
1146 /**
1147 * リセラー(自分)の表示
1148 *
1149 * @param array $query with_statistics、with_settings、with_contacts、with_additional_data
1150 * @return Result 結果。data['data']:リセラー(balance等)
1151 */
1152 public function getReseller(array $query = []): Result {
1153 return $this->get('リセラーの受け取り', '/resellers', $query, 'reseller');
1154 }
1155
1156 /**
1157 * リセラーの更新
1158 *
1159 * @param int $id リセラーID(getResellerId())
1160 * @param array $payload 更新する項目
1161 * @return Result 結果
1162 */
1163 public function updateReseller(int $id, array $payload = []): Result {
1164 return $this->send('リセラーの更新', 'PUT', "/resellers/{$id}", ['id' => $id] + $payload, [], ['reseller']);
1165 }
1166
1167 //// 設定
1168
1169 /**
1170 * リセラーの設定
1171 *
1172 * @return Result 結果。data['data']:設定
1173 */
1174 public function listSettings(): Result {
1175 return $this->get('設定の受け取り', '/resellers/settings', [], 'reseller-settings');
1176 }
1177
1178 //// 統計
1179
1180 /**
1181 * リセラーの統計
1182 *
1183 * @return Result 結果。data['data']:domain、dns、ssl等の統計
1184 */
1185 public function listStatistics(): Result {
1186 return $this->get('統計の受け取り', '/resellers/statistics', [], 'reseller-statistics');
1187 }
1188
1189 //// タグ
1190
1191 /**
1192 * タグ一覧
1193 *
1194 * @param array $query key、value
1195 * @return Result 結果。data['data']:results、total
1196 */
1197 public function listTags(array $query = []): Result {
1198 return $this->get('タグ一覧の受け取り', '/tags', $query, 'tag-list');
1199 }
1200
1201 /**
1202 * タグの作成
1203 *
1204 * @param array $payload key、value
1205 * @return Result 結果
1206 */
1207 public function createTag(array $payload = []): Result {
1208 return $this->send('タグの追加', 'POST', '/tags', $payload, [], ['tag-']);
1209 }
1210
1211 /**
1212 * タグの削除
1213 *
1214 * @param array $query key、value(削除するタグ)
1215 * @return Result 結果
1216 */
1217 public function deleteTag(array $query = []): Result {
1218 // 以前は未定義の$idで「/tags/{id}」に送っていた(APIは「/tags?key=…&value=…」)
1219 return $this->send('タグの削除', 'DELETE', '/tags', null, $query, ['tag-']);
1220 }
1221
1222 // SpamExpert
1223
1224 /**
1225 * SpamExpertの管理画面にログインするURL(使い捨てなのでキャッシュしない)
1226 *
1227 * @param array $payload domain_or_email、bundle
1228 * @return Result 結果。data['data']:url
1229 */
1230 public function generateLoginUrlSpamExpert(array $payload = []): Result {
1231 // 以前はURLが「/spam-expert-generate-login-url」で、メッセージがDNSテンプレートの物だった
1232 return $this->send('SpamExpertのログインURLの作成', 'POST', '/spam-expert/generate-login-url', $payload);
1233 }
1234
1235 //// SEDomain
1236
1237 /**
1238 * SpamExpertにドメインを追加する
1239 *
1240 * @param array $payload domain_name、destinations、aliases、products、bundle
1241 * @return Result 結果
1242 */
1243 public function createSeDomain(array $payload): Result {
1244 return $this->send('SpamExpertのドメインの追加', 'POST', '/spam-expert/domains', $payload, [], ['spamexpert-']);
1245 }
1246
1247 /**
1248 * SpamExpertのドメインの表示
1249 *
1250 * @param string $domain ドメイン名
1251 * @param array $query with_records、bundle
1252 * @return Result 結果。data['data']:ドメイン
1253 */
1254 public function getSeDomain(string $domain, array $query = []): Result {
1255 return $this->get('SpamExpertのドメインの受け取り', '/spam-expert/domains/'.self::seg($domain), $query, 'spamexpert-'.self::cacheKey($domain));
1256 }
1257
1258 /**
1259 * SpamExpertのドメインの更新
1260 *
1261 * @param string $domain ドメイン名
1262 * @param array $payload 更新する項目
1263 * @return Result 結果
1264 */
1265 public function updateSeDomain(string $domain, array $payload = []): Result {
1266 return $this->send('SpamExpertのドメインの更新', 'PUT', '/spam-expert/domains/'.self::seg($domain), ['domain_name' => $domain] + $payload, [], ['spamexpert-']);
1267 }
1268
1269 /**
1270 * SpamExpertのドメインの削除
1271 *
1272 * @param string $domain ドメイン名
1273 * @param array $query bundle
1274 * @return Result 結果
1275 */
1276 public function deleteSeDomain(string $domain, array $query = []): Result {
1277 return $this->send('SpamExpertのドメインの削除', 'DELETE', '/spam-expert/domains/'.self::seg($domain), null, $query, ['spamexpert-']);
1278 }
1279
1280 // SSL証明書
1281
1282 //// 確認担当者メール
1283
1284 /**
1285 * 確認担当者のメールアドレス一覧(ドメインの所有を確認するメールの宛先)
1286 *
1287 * @param array $query product_id、domain
1288 * @return Result 結果。data['data']['results']:メールアドレス
1289 */
1290 public function listApproverEmails(array $query = []): Result {
1291 return $this->get('確認担当者メール一覧の受け取り', '/ssl/approver-emails', $query, 'ssl-approver-'.self::cacheKey($query));
1292 }
1293
1294 //// CSR
1295
1296 /**
1297 * CSRと秘密鍵の作成
1298 *
1299 * @param array $payload common_name、country、organization、email、bits、subject_alternative_name等
1300 * @return Result 結果。data['data']:csr、key等
1301 */
1302 public function createCsr(array $payload): Result {
1303 // 以前はメッセージが確認担当者メールの物だった
1304 return $this->send('CSRの作成', 'POST', '/ssl/csr', $payload);
1305 }
1306
1307 /**
1308 * CSRの解析
1309 *
1310 * @param string $csr CSR(PEM)
1311 * @return Result 結果。data['data']:subject、subject_alternative_name等
1312 */
1313 public function decodeCsr(string $csr): Result {
1314 return $this->send('CSRの解析', 'POST', '/ssl/csr/decode', ['csr' => $csr]);
1315 }
1316
1317 //// 注文
1318
1319 /**
1320 * SSL証明書の注文一覧
1321 *
1322 * @param array $query 検索クエリー(status、common_name_pattern等)
1323 * @return Result 結果。data['data']:results、total
1324 */
1325 public function listSslOrders(array $query = []): Result {
1326 return $this->get('SSL証明書の注文一覧の受け取り', '/ssl/orders', $query, 'ssl-order-list');
1327 }
1328
1329 /**
1330 * SSL証明書の注文
1331 *
1332 * @param array $payload product_id、period、csr、host_names、approver_email、organization_handle、technical_handle等
1333 * @return Result 結果。data['data']:id
1334 */
1335 public function createSslOrder(array $payload): Result {
1336 return $this->send('SSL証明書の注文', 'POST', '/ssl/orders', $payload, [], ['ssl-order-']);
1337 }
1338
1339 /**
1340 * SSL証明書の注文の表示
1341 *
1342 * @param int $id 注文ID
1343 * @return Result 結果。data['data']:注文(certificate、status等)
1344 */
1345 public function getSslOrder(int $id): Result {
1346 return $this->get('SSL証明書の注文の受け取り', "/ssl/orders/{$id}", [], "ssl-order-{$id}");
1347 }
1348
1349 /**
1350 * SSL証明書の注文の更新
1351 *
1352 * @param int $id 注文ID
1353 * @param array $payload 更新する項目
1354 * @return Result 結果。data['data']:id
1355 */
1356 public function updateSslOrder(int $id, array $payload = []): Result {
1357 return $this->send('SSL証明書の注文の更新', 'PUT', "/ssl/orders/{$id}", ['id' => $id] + $payload, [], ['ssl-order-']);
1358 }
1359
1360 /**
1361 * SSL証明書の注文の取消
1362 *
1363 * @param int $id 注文ID
1364 * @return Result 結果。data['data']:id
1365 */
1366 public function cancelSslOrder(int $id): Result {
1367 return $this->send('SSL証明書の注文の取消', 'POST', "/ssl/orders/{$id}/cancel", ['id' => $id], [], ['ssl-order-']);
1368 }
1369
1370 /**
1371 * SSL証明書の再発行
1372 *
1373 * @param int $id 注文ID
1374 * @param array $payload csr、host_names、approver_email等
1375 * @return Result 結果。data['data']:id
1376 */
1377 public function reissueSslOrder(int $id, array $payload = []): Result {
1378 return $this->send('SSL証明書の再発行', 'POST', "/ssl/orders/{$id}/reissue", ['id' => $id] + $payload, [], ['ssl-order-']);
1379 }
1380
1381 /**
1382 * SSL証明書の更新(期間の延長)
1383 *
1384 * @param int $id 注文ID
1385 * @param array $payload enable_dns_automation
1386 * @return Result 結果。data['data']:id
1387 */
1388 public function renewSslOrder(int $id, array $payload = []): Result {
1389 return $this->send('SSL証明書の更新', 'POST', "/ssl/orders/{$id}/renew", ['id' => $id] + $payload, [], ['ssl-order-']);
1390 }
1391
1392 //// 注文確認担当者メール
1393
1394 /**
1395 * 注文の確認担当者のメールアドレスの変更
1396 *
1397 * @param int $id 注文ID
1398 * @param string $approverEmail メールアドレス
1399 * @return Result 結果。data['data']:id
1400 */
1401 public function updateSslApproverEmail(int $id, string $approverEmail): Result {
1402 return $this->send('確認担当者メールの変更', 'PUT', "/ssl/orders/{$id}/approver-email", ['id' => $id, 'approver_email' => $approverEmail], [], ["ssl-order-{$id}"]);
1403 }
1404
1405 /**
1406 * 確認担当者メールの再送信
1407 *
1408 * @param int $id 注文ID
1409 * @return Result 結果。data['data']:id
1410 */
1411 public function resendSslApproverEmail(int $id): Result {
1412 return $this->send('確認担当者メールの再送信', 'POST', "/ssl/orders/{$id}/approver-email/resend", ['id' => $id]);
1413 }
1414
1415 //// OTPトークン
1416
1417 /**
1418 * SSL証明書の管理画面に入るOTPトークンの作成
1419 *
1420 * @param int $id 注文ID
1421 * @return Result 結果。data['data']:token、uri、expire_at
1422 */
1423 public function createSslOtpToken(int $id): Result {
1424 return $this->send('OTPトークンの作成', 'POST', "/ssl/orders/{$id}/otp-tokens", ['id' => $id]);
1425 }
1426
1427 //// 商品
1428
1429 /**
1430 * SSL証明書の商品一覧
1431 *
1432 * @param array $query 検索クエリー(with_price、with_description等)
1433 * @return Result 結果。data['data']:results、total
1434 */
1435 public function listSslProducts(array $query = []): Result {
1436 return $this->get('SSL証明書の商品一覧の受け取り', '/ssl/products', $query, 'ssl-product-list');
1437 }
1438
1439 /**
1440 * SSL証明書の商品の表示
1441 *
1442 * @param int $id 商品ID
1443 * @return Result 結果。data['data']:商品
1444 */
1445 public function getSslProduct(int $id): Result {
1446 // 以前は未定義の$uriを使っていた
1447 return $this->get('SSL証明書の商品の受け取り', "/ssl/products/{$id}", [], "ssl-product-{$id}");
1448 }
1449
1450 ////////
1451
1452 /**
1453 * トークンの受け取り。
1454 *
1455 * @return string トークン
1456 */
1457 public function getToken(): string {
1458 if (!OPENPROVIDER_ENABLED) return '';
1459 return $this->token;
1460 }
1461
1462 /**
1463 * リセラーIDの受け取り(login()の後)。
1464 *
1465 * @return int リセラーID
1466 */
1467 public function getResellerId(): int {
1468 if (!OPENPROVIDER_ENABLED) return 0;
1469 return $this->resellerId;
1470 }
1471
1472 //////////
1473
1474 protected function setupCurl(string $uri, string $resType = 'GET', array $payload = []): Curl {
1475 $curl = new Curl("{$this->BASEURL}{$uri}");
1476
1477 $curl->setMethod($resType);
1478 if (!empty($payload)) $curl->setPostRaw(json_encode($payload));
1479 if ($this->token !== '') $curl->setBearerAuth($this->token);
1480 $curl->addHeader('Content-Type', 'application/json');
1481 $curl->addHeader('Accept', 'application/json');
1482
1483 return $curl;
1484 }
1485
1486 private function curlResult(Curl &$curl): Result {
1487 $res = $curl->execute();
1488 if (!$res->isSuccess) {
1489 $err = "CURL実行に失敗: {$curl->getError()}";
1490 assert_unless_success($res, $err);
1491 return Result::Error($err);
1492 }
1493
1494 $body = $curl->getResponseBody();
1495 if ($curl->getResponseCode() != 200) {
1496 $err = self::errorDesc($body, $curl->getResponseCode());
1497 assert($curl->getResponseCode() == 200, $err);
1498 return Result::Error($err);
1499 }
1500 assert_not_null($body, "返事ボディーは空です。");
1501
1502 $data = json_decode($body, true);
1503 if (!is_array($data)) return Result::Error('レスポンスのJSONの解析に失敗。');
1504 return Result::Success('', $data);
1505 }
1506
1507 /**
1508 * GETのAPIを呼ぶ(キャッシュ名があれば、同じクエリーの結果を30分キャッシュする)
1509 *
1510 * @param string $what 操作の説明(メッセージ用)
1511 * @param string $uri パス(/v1betaの後)
1512 * @param array $query クエリー
1513 * @param string|null $cacheName キャッシュ名(nullならキャッシュしない)
1514 * @return Result data:レスポンス(code、data等)
1515 */
1516 private function get(string $what, string $uri, array $query = [], ?string $cacheName = null): Result {
1517 if (!OPENPROVIDER_ENABLED) return Result::Error('エラー:OpenProviderは無効です。');
1518 $query = self::normalizeQuery($query);
1519 if ($cacheName !== null) {
1520 $cache = $this->cache->get($cacheName);
1521 if ($this->cache->resurrect($cache, $query)) {
1522 unset($cache['query'], $cache['lifespan']);
1523 return Result::Success('', $cache);
1524 }
1525 }
1526
1527 $res = $this->call($what, 'GET', $uri, $query);
1528 if ($res->isSuccess && $cacheName !== null) $this->cache->set($cacheName, $res->data + ['query' => $query]);
1529 return $res;
1530 }
1531
1532 /**
1533 * POST・PUT・DELETEのAPIを呼び、成功したら関係するキャッシュを消す
1534 *
1535 * @param string $what 操作の説明(メッセージ用)
1536 * @param string $method POST、PUT又はDELETE
1537 * @param string $uri パス(/v1betaの後)
1538 * @param array|null $payload 本文(JSON)
1539 * @param array $query クエリー
1540 * @param array $forget 消すキャッシュの名前の始まり
1541 * @return Result data:レスポンス(code、data等)
1542 */
1543 private function send(string $what, string $method, string $uri, ?array $payload = null, array $query = [], array $forget = []): Result {
1544 if (!OPENPROVIDER_ENABLED) return Result::Error('エラー:OpenProviderは無効です。');
1545 $res = $this->call($what, $method, $uri, self::normalizeQuery($query), $payload);
1546 if ($res->isSuccess) foreach ($forget as $prefix) $this->cache->murderPrefix($prefix);
1547 return $res;
1548 }
1549
1550 /**
1551 * APIを呼ぶ。HTTPのエラー、「code」が0以外、「success」がfalseの場合は失敗
1552 */
1553 private function call(string $what, string $method, string $uri, array $query, ?array $payload = null): Result {
1554 $qs = self::buildQuery($query);
1555 $curl = $this->setupCurl($uri.($qs !== '' ? "?{$qs}" : ''), $method, $payload ?? []);
1556 // 本文が空のPOST(再試行等)でも「{}」を送る(JSONを期待するAPIがある)
1557 if ($payload !== null && empty($payload)) $curl->setPostRaw('{}');
1558 $res = $this->curlResult($curl);
1559 if (!$res->isSuccess) return Result::Error("{$what}に失敗: {$res->message}");
1560
1561 if ((int)($res->data['code'] ?? 0) !== 0) {
1562 return Result::Error("{$what}に失敗: ".((string)($res->data['desc'] ?? '') ?: "code {$res->data['code']}"));
1563 }
1564 if (($res->data['data']['success'] ?? true) === false) return Result::Error("{$what}に失敗。");
1565 return Result::Success("{$what}に成功。", $res->data);
1566 }
1567
1568 /**
1569 * クエリーの真偽値を「true」「false」にする
1570 */
1571 private static function normalizeQuery(array $query): array {
1572 foreach ($query as $k => $v) {
1573 if (is_bool($v)) $query[$k] = $v ? 'true' : 'false';
1574 else if (is_array($v)) $query[$k] = array_map(fn($x) => is_bool($x) ? ($x ? 'true' : 'false') : $x, $v);
1575 }
1576 return $query;
1577 }
1578
1579 /**
1580 * クエリー文字列。配列は同じキーを繰り返す(status=ACT&status=REQ。APIの「multi」形式)
1581 */
1582 private static function buildQuery(array $query): string {
1583 $parts = [];
1584 foreach ($query as $k => $v) {
1585 foreach (is_array($v) ? $v : [$v] as $x) {
1586 if ($x === null) continue;
1587 $parts[] = rawurlencode((string)$k).'='.rawurlencode((string)$x);
1588 }
1589 }
1590 return implode('&', $parts);
1591 }
1592
1593 /**
1594 * パスの一部(ドメイン名・ハンドル等)を安全にする
1595 */
1596 private static function seg(string $s): string {
1597 return rawurlencode($s);
1598 }
1599
1600 /**
1601 * キャッシュ名に使える文字列(ドメイン名等はそのまま、他は短いハッシュ)
1602 */
1603 private static function cacheKey(mixed $v): string {
1604 if (is_string($v) && preg_match('/^[A-Za-z0-9._-]{1,100}$/', $v) && !str_contains($v, '..')) return $v;
1605 return substr(sha1(json_encode($v)), 0, 16);
1606 }
1607
1608 /**
1609 * 「076.co.jp」を['name' => '076', 'extension' => 'co.jp']にする(最初の「.」で分ける)
1610 */
1611 private static function splitDomain(string $domain): ?array {
1612 $parts = explode('.', $domain, 2);
1613 if (count($parts) !== 2 || $parts[0] === '' || $parts[1] === '') return null;
1614 return ['name' => $parts[0], 'extension' => $parts[1]];
1615 }
1616
1617 /**
1618 * エラーレスポンスの説明(JSONでない場合もある)
1619 */
1620 private static function errorDesc(string $body, int $code): string {
1621 $err = json_decode($body, true);
1622 $desc = is_array($err) ? (string)($err['desc'] ?? '') : '';
1623 return $desc !== '' ? $desc : "HTTP {$code}";
1624 }
1625}